// 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 static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME; import com.google.gerrit.common.errors.InvalidUserNameException; import com.google.gerrit.common.errors.NameAlreadyUsedException; import com.google.gerrit.reviewdb.Account; import com.google.gerrit.reviewdb.AccountExternalId; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.ssh.SshKeyCache; import com.google.gwtjsonrpc.client.VoidResult; import com.google.gwtorm.client.OrmDuplicateKeyException; import com.google.gwtorm.client.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.assistedinject.Assisted; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.concurrent.Callable; import java.util.regex.Pattern; import javax.annotation.Nullable; /** Operation to change the username of an account. */ public class ChangeUserName implements Callable<VoidResult> { private static final Pattern USER_NAME_PATTERN = Pattern.compile(Account.USER_NAME_PATTERN); /** Factory to change the username for the current user. */ public static class CurrentUser { private final Factory factory; private final Provider<ReviewDb> db; private final Provider<IdentifiedUser> user; @Inject CurrentUser(Factory factory, Provider<ReviewDb> db, Provider<IdentifiedUser> user) { this.factory = factory; this.db = db; this.user = user; } public ChangeUserName create(String newUsername) { return factory.create(db.get(), user.get(), newUsername); } } /** Generic factory to change any user's username. */ public interface Factory { ChangeUserName create(ReviewDb db, IdentifiedUser user, String newUsername); } private final AccountCache accountCache; private final SshKeyCache sshKeyCache; private final ReviewDb db; private final IdentifiedUser user; private final String newUsername; @Inject ChangeUserName(final AccountCache accountCache, final SshKeyCache sshKeyCache, @Assisted final ReviewDb db, @Assisted final IdentifiedUser user, @Nullable @Assisted final String newUsername) { this.accountCache = accountCache; this.sshKeyCache = sshKeyCache; this.db = db; this.user = user; this.newUsername = newUsername; } public VoidResult call() throws OrmException, NameAlreadyUsedException, InvalidUserNameException { final Collection<AccountExternalId> old = old(); if (!old.isEmpty()) { throw new IllegalStateException("Username cannot be changed."); } if (newUsername != null && !newUsername.isEmpty()) { if (!USER_NAME_PATTERN.matcher(newUsername).matches()) { throw new InvalidUserNameException(); } final AccountExternalId.Key key = new AccountExternalId.Key(SCHEME_USERNAME, newUsername); try { final AccountExternalId id = new AccountExternalId(user.getAccountId(), key); for (AccountExternalId i : old) { if (i.getPassword() != null) { id.setPassword(i.getPassword()); } } db.accountExternalIds().insert(Collections.singleton(id)); } catch (OrmDuplicateKeyException dupeErr) { // If we are using this identity, don't report the exception. // AccountExternalId other = db.accountExternalIds().get(key); if (other != null && other.getAccountId().equals(user.getAccountId())) { return VoidResult.INSTANCE; } // Otherwise, someone else has this identity. // throw new NameAlreadyUsedException(); } } // If we have any older user names, remove them. // db.accountExternalIds().delete(old); for (AccountExternalId i : old) { sshKeyCache.evict(i.getSchemeRest()); accountCache.evictByUsername(i.getSchemeRest()); } accountCache.evict(user.getAccountId()); accountCache.evictByUsername(newUsername); sshKeyCache.evict(newUsername); return VoidResult.INSTANCE; } private Collection<AccountExternalId> old() throws OrmException { final Collection<AccountExternalId> r = new ArrayList<AccountExternalId>(1); for (AccountExternalId i : db.accountExternalIds().byAccount( user.getAccountId())) { if (i.isScheme(SCHEME_USERNAME)) { r.add(i); } } return r; } }