diff options
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java')
-rw-r--r-- | gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java | 514 |
1 files changed, 0 insertions, 514 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java deleted file mode 100644 index 0f3b48110c..0000000000 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright (C) 2009 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.gerrit.server.account; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.gerrit.common.data.AccessSection; -import com.google.gerrit.common.data.GlobalCapability; -import com.google.gerrit.common.data.Permission; -import com.google.gerrit.common.errors.NameAlreadyUsedException; -import com.google.gerrit.common.errors.NoSuchGroupException; -import com.google.gerrit.extensions.client.AccountFieldName; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.AccountGroup; -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.Sequences; -import com.google.gerrit.server.account.externalids.ExternalId; -import com.google.gerrit.server.account.externalids.ExternalIds; -import com.google.gerrit.server.account.externalids.ExternalIdsUpdate; -import com.google.gerrit.server.auth.NoSuchUserException; -import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.group.GroupsUpdate; -import com.google.gerrit.server.project.ProjectCache; -import com.google.gwtorm.server.OrmException; -import com.google.gwtorm.server.SchemaFactory; -import com.google.inject.Inject; -import com.google.inject.Singleton; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.lib.Config; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** Tracks authentication related details for user accounts. */ -@Singleton -public class AccountManager { - private static final Logger log = LoggerFactory.getLogger(AccountManager.class); - - private final SchemaFactory<ReviewDb> schema; - private final Sequences sequences; - private final Accounts accounts; - private final AccountsUpdate.Server accountsUpdateFactory; - private final AccountCache byIdCache; - private final Realm realm; - private final IdentifiedUser.GenericFactory userFactory; - private final ChangeUserName.Factory changeUserNameFactory; - private final ProjectCache projectCache; - private final AtomicBoolean awaitsFirstAccountCheck; - private final ExternalIds externalIds; - private final ExternalIdsUpdate.Server externalIdsUpdateFactory; - private final GroupsUpdate.Factory groupsUpdateFactory; - private final boolean autoUpdateAccountActiveStatus; - private final SetInactiveFlag setInactiveFlag; - - @Inject - AccountManager( - SchemaFactory<ReviewDb> schema, - Sequences sequences, - @GerritServerConfig Config cfg, - Accounts accounts, - AccountsUpdate.Server accountsUpdateFactory, - AccountCache byIdCache, - Realm accountMapper, - IdentifiedUser.GenericFactory userFactory, - ChangeUserName.Factory changeUserNameFactory, - ProjectCache projectCache, - ExternalIds externalIds, - ExternalIdsUpdate.Server externalIdsUpdateFactory, - GroupsUpdate.Factory groupsUpdateFactory, - SetInactiveFlag setInactiveFlag) { - this.schema = schema; - this.sequences = sequences; - this.accounts = accounts; - this.accountsUpdateFactory = accountsUpdateFactory; - this.byIdCache = byIdCache; - this.realm = accountMapper; - this.userFactory = userFactory; - this.changeUserNameFactory = changeUserNameFactory; - this.projectCache = projectCache; - this.awaitsFirstAccountCheck = - new AtomicBoolean(cfg.getBoolean("capability", "makeFirstUserAdmin", true)); - this.externalIds = externalIds; - this.externalIdsUpdateFactory = externalIdsUpdateFactory; - this.groupsUpdateFactory = groupsUpdateFactory; - this.autoUpdateAccountActiveStatus = - cfg.getBoolean("auth", "autoUpdateAccountActiveStatus", false); - this.setInactiveFlag = setInactiveFlag; - } - - /** @return user identified by this external identity string */ - public Optional<Account.Id> lookup(String externalId) throws AccountException { - try { - ExternalId extId = externalIds.get(ExternalId.Key.parse(externalId)); - return extId != null ? Optional.of(extId.accountId()) : Optional.empty(); - } catch (IOException | ConfigInvalidException e) { - throw new AccountException("Cannot lookup account " + externalId, e); - } - } - - /** - * Authenticate the user, potentially creating a new account if they are new. - * - * @param who identity of the user, with any details we received about them. - * @return the result of authenticating the user. - * @throws AccountException the account does not exist, and cannot be created, or exists, but - * cannot be located, is unable to be activated or deactivated, or is inactive, or cannot be - * added to the admin group (only for the first account). - */ - public AuthResult authenticate(AuthRequest who) throws AccountException, IOException { - try { - who = realm.authenticate(who); - } catch (NoSuchUserException e) { - deactivateAccountIfItExists(who); - throw e; - } - try { - try (ReviewDb db = schema.open()) { - ExternalId id = externalIds.get(who.getExternalIdKey()); - if (id == null) { - // New account, automatically create and return. - // - return create(db, who); - } - - // Account exists - Account act = updateAccountActiveStatus(who, byIdCache.get(id.accountId()).getAccount()); - if (!act.isActive()) { - throw new AccountException("Authentication error, account inactive"); - } - - // return the identity to the caller. - update(who, id); - return new AuthResult(id.accountId(), who.getExternalIdKey(), false); - } - } catch (OrmException | ConfigInvalidException e) { - throw new AccountException("Authentication error", e); - } - } - - private void deactivateAccountIfItExists(AuthRequest authRequest) { - if (!shouldUpdateActiveStatus(authRequest)) { - return; - } - try { - ExternalId id = externalIds.get(authRequest.getExternalIdKey()); - if (id == null) { - return; - } - setInactiveFlag.deactivate(id.accountId()); - } catch (Exception e) { - log.error("Unable to deactivate account " + authRequest.getUserName(), e); - } - } - - private Account updateAccountActiveStatus(AuthRequest authRequest, Account account) - throws AccountException { - if (!shouldUpdateActiveStatus(authRequest) || authRequest.isActive() == account.isActive()) { - return account; - } - - if (authRequest.isActive()) { - try { - setInactiveFlag.activate(account.getId()); - } catch (Exception e) { - throw new AccountException("Unable to activate account " + account.getId(), e); - } - } else { - try { - setInactiveFlag.deactivate(account.getId()); - } catch (Exception e) { - throw new AccountException("Unable to deactivate account " + account.getId(), e); - } - } - return byIdCache.get(account.getId()).getAccount(); - } - - private boolean shouldUpdateActiveStatus(AuthRequest authRequest) { - return autoUpdateAccountActiveStatus && authRequest.authProvidesAccountActiveStatus(); - } - - private void update(AuthRequest who, ExternalId extId) - throws OrmException, IOException, ConfigInvalidException { - IdentifiedUser user = userFactory.create(extId.accountId()); - List<Consumer<Account>> accountUpdates = new ArrayList<>(); - - // If the email address was modified by the authentication provider, - // update our records to match the changed email. - // - String newEmail = who.getEmailAddress(); - String oldEmail = extId.email(); - if (newEmail != null && !newEmail.equals(oldEmail)) { - if (oldEmail != null && oldEmail.equals(user.getAccount().getPreferredEmail())) { - accountUpdates.add(a -> a.setPreferredEmail(newEmail)); - } - - externalIdsUpdateFactory - .create() - .replace( - extId, ExternalId.create(extId.key(), extId.accountId(), newEmail, extId.password())); - } - - if (!Strings.isNullOrEmpty(who.getDisplayName()) - && !eq(user.getAccount().getFullName(), who.getDisplayName())) { - if (realm.allowsEdit(AccountFieldName.FULL_NAME)) { - accountUpdates.add(a -> a.setFullName(who.getDisplayName())); - } else { - log.warn( - "Not changing already set display name '{}' to '{}'", - user.getAccount().getFullName(), - who.getDisplayName()); - } - } - - if (!realm.allowsEdit(AccountFieldName.USER_NAME) - && who.getUserName() != null - && !eq(user.getUserName(), who.getUserName())) { - log.warn("Not changing already set username {} to {}", user.getUserName(), who.getUserName()); - } - - if (!accountUpdates.isEmpty()) { - Account account = accountsUpdateFactory.create().update(user.getAccountId(), accountUpdates); - if (account == null) { - throw new OrmException("Account " + user.getAccountId() + " has been deleted"); - } - } - } - - private static boolean eq(String a, String b) { - return (a == null && b == null) || (a != null && a.equals(b)); - } - - private AuthResult create(ReviewDb db, AuthRequest who) - throws OrmException, AccountException, IOException, ConfigInvalidException { - Account.Id newId = new Account.Id(sequences.nextAccountId()); - log.debug("Assigning new Id {} to account", newId); - - ExternalId extId = - ExternalId.createWithEmail(who.getExternalIdKey(), newId, who.getEmailAddress()); - log.debug("Created external Id: {}", extId); - - boolean isFirstAccount = awaitsFirstAccountCheck.getAndSet(false) && !accounts.hasAnyAccount(); - - Account account; - try { - AccountsUpdate accountsUpdate = accountsUpdateFactory.create(); - account = - accountsUpdate.insert( - newId, - a -> { - a.setFullName(who.getDisplayName()); - a.setPreferredEmail(extId.email()); - }); - - ExternalId existingExtId = externalIds.get(extId.key()); - if (existingExtId != null && !existingExtId.accountId().equals(extId.accountId())) { - // external ID is assigned to another account, do not overwrite - accountsUpdate.delete(account); - throw new AccountException( - "Cannot assign external ID \"" - + extId.key().get() - + "\" to account " - + newId - + "; external ID already in use."); - } - externalIdsUpdateFactory.create().upsert(extId); - } finally { - // If adding the account failed, it may be that it actually was the - // first account. So we reset the 'check for first account'-guard, as - // otherwise the first account would not get administration permissions. - awaitsFirstAccountCheck.set(isFirstAccount); - } - - IdentifiedUser user = userFactory.create(newId); - - if (isFirstAccount) { - // This is the first user account on our site. Assume this user - // is going to be the site's administrator and just make them that - // to bootstrap the authentication database. - // - Permission admin = - projectCache - .getAllProjects() - .getConfig() - .getAccessSection(AccessSection.GLOBAL_CAPABILITIES) - .getPermission(GlobalCapability.ADMINISTRATE_SERVER); - - AccountGroup.UUID uuid = admin.getRules().get(0).getGroup().getUUID(); - // The user initiated this request by logging in. -> Attribute all modifications to that user. - GroupsUpdate groupsUpdate = groupsUpdateFactory.create(user); - try { - groupsUpdate.addGroupMember(db, uuid, newId); - } catch (NoSuchGroupException e) { - throw new AccountException(String.format("Group %s not found", uuid)); - } - } - - log.debug("Username from AuthRequest: {}", who.getUserName()); - if (who.getUserName() != null) { - log.debug("Setting username for: {}", who.getUserName()); - // Only set if the name hasn't been used yet, but was given to us. - // - try { - changeUserNameFactory.create(user, who.getUserName()).call(); - log.debug("Identified user {} was created from {}", user, who.getUserName()); - } catch (NameAlreadyUsedException e) { - String message = - "Cannot assign user name \"" - + who.getUserName() - + "\" to account " - + newId - + "; name already in use."; - handleSettingUserNameFailure(account, extId, message, e, false); - } catch (InvalidUserNameException e) { - String message = - "Cannot assign user name \"" - + who.getUserName() - + "\" to account " - + newId - + "; name does not conform."; - handleSettingUserNameFailure(account, extId, message, e, false); - } catch (OrmException e) { - String message = "Cannot assign user name"; - handleSettingUserNameFailure(account, extId, message, e, true); - } - } - - realm.onCreateAccount(who, account); - return new AuthResult(newId, extId.key(), true); - } - - /** - * This method handles an exception that occurred during the setting of the user name for a newly - * created account. If the realm does not allow the user to set a user name manually this method - * deletes the newly created account and throws an {@link AccountUserNameException}. In any case - * the error message is logged. - * - * @param account the newly created account - * @param extId the newly created external id - * @param errorMessage the error message - * @param e the exception that occurred during the setting of the user name for the new account - * @param logException flag that decides whether the exception should be included into the log - * @throws AccountUserNameException thrown if the realm does not allow the user to manually set - * the user name - * @throws OrmException thrown if cleaning the database failed - */ - private void handleSettingUserNameFailure( - Account account, ExternalId extId, String errorMessage, Exception e, boolean logException) - throws AccountUserNameException, OrmException, IOException, ConfigInvalidException { - if (logException) { - log.error(errorMessage, e); - } else { - log.error(errorMessage); - } - if (!realm.allowsEdit(AccountFieldName.USER_NAME)) { - // setting the given user name has failed, but the realm does not - // allow the user to manually set a user name, - // this means we would end with an account without user name - // (without 'username:<USERNAME>' external ID), - // such an account cannot be used for uploading changes, - // this is why the best we can do here is to fail early and cleanup - // the database - accountsUpdateFactory.create().delete(account); - externalIdsUpdateFactory.create().delete(extId); - throw new AccountUserNameException(errorMessage, e); - } - } - - /** - * Link another authentication identity to an existing account. - * - * @param to account to link the identity onto. - * @param who the additional identity. - * @return the result of linking the identity to the user. - * @throws AccountException the identity belongs to a different account, or it cannot be linked at - * this time. - */ - public AuthResult link(Account.Id to, AuthRequest who) - throws AccountException, OrmException, IOException, ConfigInvalidException { - ExternalId extId = externalIds.get(who.getExternalIdKey()); - if (extId != null) { - if (!extId.accountId().equals(to)) { - throw new AccountException( - "Identity '" + extId.key().get() + "' in use by another account"); - } - update(who, extId); - } else { - externalIdsUpdateFactory - .create() - .insert(ExternalId.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress())); - - if (who.getEmailAddress() != null) { - accountsUpdateFactory - .create() - .update( - to, - a -> { - if (a.getPreferredEmail() == null) { - a.setPreferredEmail(who.getEmailAddress()); - } - }); - } - } - - return new AuthResult(to, who.getExternalIdKey(), false); - } - - /** - * Update the link to another unique authentication identity to an existing account. - * - * <p>Existing external identities with the same scheme will be removed and replaced with the new - * one. - * - * @param to account to link the identity onto. - * @param who the additional identity. - * @return the result of linking the identity to the user. - * @throws OrmException - * @throws AccountException the identity belongs to a different account, or it cannot be linked at - * this time. - */ - public AuthResult updateLink(Account.Id to, AuthRequest who) - throws OrmException, AccountException, IOException, ConfigInvalidException { - Collection<ExternalId> filteredExtIdsByScheme = - externalIds.byAccount(to, who.getExternalIdKey().scheme()); - - if (!filteredExtIdsByScheme.isEmpty() - && (filteredExtIdsByScheme.size() > 1 - || !filteredExtIdsByScheme.stream() - .filter(e -> e.key().equals(who.getExternalIdKey())) - .findAny() - .isPresent())) { - externalIdsUpdateFactory.create().delete(filteredExtIdsByScheme); - } - return link(to, who); - } - - /** - * Unlink an external identity from an existing account. - * - * @param from account to unlink the external identity from - * @param extIdKey the key of the external ID that should be deleted - * @throws AccountException the identity belongs to a different account, or the identity was not - * found - */ - public void unlink(Account.Id from, ExternalId.Key extIdKey) - throws AccountException, OrmException, IOException, ConfigInvalidException { - unlink(from, ImmutableList.of(extIdKey)); - } - - /** - * Unlink an external identities from an existing account. - * - * @param from account to unlink the external identity from - * @param extIdKeys the keys of the external IDs that should be deleted - * @throws AccountException any of the identity belongs to a different account, or any of the - * identity was not found - */ - public void unlink(Account.Id from, Collection<ExternalId.Key> extIdKeys) - throws AccountException, OrmException, IOException, ConfigInvalidException { - if (extIdKeys.isEmpty()) { - return; - } - - List<ExternalId> extIds = new ArrayList<>(extIdKeys.size()); - for (ExternalId.Key extIdKey : extIdKeys) { - ExternalId extId = externalIds.get(extIdKey); - if (extId != null) { - if (!extId.accountId().equals(from)) { - throw new AccountException("Identity '" + extIdKey.get() + "' in use by another account"); - } - extIds.add(extId); - } else { - throw new AccountException("Identity '" + extIdKey.get() + "' not found"); - } - } - - externalIdsUpdateFactory.create().delete(extIds); - - if (extIds.stream().anyMatch(e -> e.email() != null)) { - accountsUpdateFactory - .create() - .update( - from, - a -> { - if (a.getPreferredEmail() != null) { - for (ExternalId extId : extIds) { - if (a.getPreferredEmail().equals(extId.email())) { - a.setPreferredEmail(null); - break; - } - } - } - }); - } - } -} |