Merge "Add cache for external ids"
This commit is contained in:
		@@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.client.AccountGroupMember;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.account.AccountByEmailCache;
 | 
			
		||||
import com.google.gerrit.server.account.AccountCache;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCache;
 | 
			
		||||
import com.google.gerrit.server.account.GroupCache;
 | 
			
		||||
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 | 
			
		||||
import com.google.gerrit.server.index.account.AccountIndexer;
 | 
			
		||||
@@ -56,6 +57,7 @@ public class AccountCreator {
 | 
			
		||||
  private final AccountCache accountCache;
 | 
			
		||||
  private final AccountByEmailCache byEmailCache;
 | 
			
		||||
  private final AccountIndexer indexer;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  AccountCreator(SchemaFactory<ReviewDb> schema,
 | 
			
		||||
@@ -64,7 +66,8 @@ public class AccountCreator {
 | 
			
		||||
      SshKeyCache sshKeyCache,
 | 
			
		||||
      AccountCache accountCache,
 | 
			
		||||
      AccountByEmailCache byEmailCache,
 | 
			
		||||
      AccountIndexer indexer) {
 | 
			
		||||
      AccountIndexer indexer,
 | 
			
		||||
      ExternalIdCache externalIdCache) {
 | 
			
		||||
    accounts = new HashMap<>();
 | 
			
		||||
    reviewDbProvider = schema;
 | 
			
		||||
    this.authorizedKeys = authorizedKeys;
 | 
			
		||||
@@ -73,6 +76,7 @@ public class AccountCreator {
 | 
			
		||||
    this.accountCache = accountCache;
 | 
			
		||||
    this.byEmailCache = byEmailCache;
 | 
			
		||||
    this.indexer = indexer;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public synchronized TestAccount create(String username, String email,
 | 
			
		||||
@@ -90,11 +94,13 @@ public class AccountCreator {
 | 
			
		||||
      String httpPass = "http-pass";
 | 
			
		||||
      extUser.setPassword(httpPass);
 | 
			
		||||
      db.accountExternalIds().insert(Collections.singleton(extUser));
 | 
			
		||||
      externalIdCache.onCreate(extUser);
 | 
			
		||||
 | 
			
		||||
      if (email != null) {
 | 
			
		||||
        AccountExternalId extMailto = new AccountExternalId(id, getEmailKey(email));
 | 
			
		||||
        extMailto.setEmailAddress(email);
 | 
			
		||||
        db.accountExternalIds().insert(Collections.singleton(extMailto));
 | 
			
		||||
        externalIdCache.onCreate(extMailto);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      Account a = new Account(id, TimeUtil.nowTs());
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,7 @@ import com.google.gerrit.gpg.testutil.TestKey;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.RefNames;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCache;
 | 
			
		||||
import com.google.gerrit.server.account.WatchConfig;
 | 
			
		||||
import com.google.gerrit.server.account.WatchConfig.NotifyType;
 | 
			
		||||
import com.google.gerrit.server.config.AllUsersName;
 | 
			
		||||
@@ -112,6 +113,9 @@ public class AccountIT extends AbstractDaemonTest {
 | 
			
		||||
  @Inject
 | 
			
		||||
  private AllUsersName allUsers;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  private ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  private List<AccountExternalId> savedExternalIds;
 | 
			
		||||
 | 
			
		||||
  @Before
 | 
			
		||||
@@ -127,10 +131,18 @@ public class AccountIT extends AbstractDaemonTest {
 | 
			
		||||
      // savedExternalIds is null when we don't run SSH tests and the assume in
 | 
			
		||||
      // @Before in AbstractDaemonTest prevents this class' @Before method from
 | 
			
		||||
      // being executed.
 | 
			
		||||
      db.accountExternalIds().delete(getExternalIds(admin));
 | 
			
		||||
      db.accountExternalIds().delete(getExternalIds(user));
 | 
			
		||||
      Collection<AccountExternalId> adminExtIds = getExternalIds(admin);
 | 
			
		||||
      db.accountExternalIds().delete(adminExtIds);
 | 
			
		||||
      externalIdCache.onRemove(adminExtIds);
 | 
			
		||||
 | 
			
		||||
      Collection<AccountExternalId> userExtIds = getExternalIds(user);
 | 
			
		||||
      db.accountExternalIds().delete(userExtIds);
 | 
			
		||||
      externalIdCache.onRemove(userExtIds);
 | 
			
		||||
 | 
			
		||||
      db.accountExternalIds().insert(savedExternalIds);
 | 
			
		||||
      externalIdCache.onCreate(savedExternalIds);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    accountCache.evict(admin.getId());
 | 
			
		||||
    accountCache.evict(user.getId());
 | 
			
		||||
  }
 | 
			
		||||
@@ -595,6 +607,7 @@ public class AccountIT extends AbstractDaemonTest {
 | 
			
		||||
        user.getId(), new AccountExternalId.Key("foo:myId"));
 | 
			
		||||
 | 
			
		||||
    db.accountExternalIds().insert(Collections.singleton(extId));
 | 
			
		||||
    externalIdCache.onCreate(extId);
 | 
			
		||||
    accountCache.evict(user.getId());
 | 
			
		||||
 | 
			
		||||
    TestKey key = validKeyWithSecondUserId();
 | 
			
		||||
@@ -791,7 +804,8 @@ public class AccountIT extends AbstractDaemonTest {
 | 
			
		||||
    Account.Id currAccountId = atrScope.get().getUser().getAccountId();
 | 
			
		||||
    Iterable<String> expectedFps = expected.transform(
 | 
			
		||||
        k -> BaseEncoding.base16().encode(k.getPublicKey().getFingerprint()));
 | 
			
		||||
    Iterable<String> actualFps = GpgKeys.getGpgExtIds(db, currAccountId)
 | 
			
		||||
    Iterable<String> actualFps =
 | 
			
		||||
        GpgKeys.getGpgExtIds(externalIdCache, currAccountId)
 | 
			
		||||
            .transform(AccountExternalId::getSchemeRest);
 | 
			
		||||
    assertThat(actualFps)
 | 
			
		||||
        .named("external IDs in database")
 | 
			
		||||
@@ -825,6 +839,7 @@ public class AccountIT extends AbstractDaemonTest {
 | 
			
		||||
        account.getId(), new AccountExternalId.Key(name("test"), email));
 | 
			
		||||
    extId.setEmailAddress(email);
 | 
			
		||||
    db.accountExternalIds().insert(Collections.singleton(extId));
 | 
			
		||||
    externalIdCache.onCreate(extId);
 | 
			
		||||
    // Clear saved AccountState and AccountExternalIds.
 | 
			
		||||
    accountCache.evict(account.getId());
 | 
			
		||||
    setApiUser(account);
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.client.AccountExternalId;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.GerritPersonIdent;
 | 
			
		||||
import com.google.gerrit.server.account.AccountCache;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCache;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
@@ -47,16 +48,19 @@ public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
 | 
			
		||||
  private final Provider<ReviewDb> db;
 | 
			
		||||
  private final Provider<PublicKeyStore> storeProvider;
 | 
			
		||||
  private final AccountCache accountCache;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  DeleteGpgKey(@GerritPersonIdent Provider<PersonIdent> serverIdent,
 | 
			
		||||
      Provider<ReviewDb> db,
 | 
			
		||||
      Provider<PublicKeyStore> storeProvider,
 | 
			
		||||
      AccountCache accountCache) {
 | 
			
		||||
      AccountCache accountCache,
 | 
			
		||||
      ExternalIdCache externalIdCache) {
 | 
			
		||||
    this.serverIdent = serverIdent;
 | 
			
		||||
    this.db = db;
 | 
			
		||||
    this.storeProvider = storeProvider;
 | 
			
		||||
    this.accountCache = accountCache;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
@@ -68,6 +72,7 @@ public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
 | 
			
		||||
        AccountExternalId.SCHEME_GPGKEY,
 | 
			
		||||
        BaseEncoding.base16().encode(key.getFingerprint()));
 | 
			
		||||
    db.get().accountExternalIds().deleteKeys(Collections.singleton(extIdKey));
 | 
			
		||||
    externalIdCache.onRemove(rsrc.getUser().getAccountId(), extIdKey);
 | 
			
		||||
    accountCache.evict(rsrc.getUser().getAccountId());
 | 
			
		||||
 | 
			
		||||
    try (PublicKeyStore store = storeProvider.get()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,9 +38,9 @@ import com.google.gerrit.gpg.PublicKeyChecker;
 | 
			
		||||
import com.google.gerrit.gpg.PublicKeyStore;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.CurrentUser;
 | 
			
		||||
import com.google.gerrit.server.account.AccountResource;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCache;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
@@ -69,22 +69,22 @@ public class GpgKeys implements
 | 
			
		||||
  public static String MIME_TYPE = "application/pgp-keys";
 | 
			
		||||
 | 
			
		||||
  private final DynamicMap<RestView<GpgKey>> views;
 | 
			
		||||
  private final Provider<ReviewDb> db;
 | 
			
		||||
  private final Provider<CurrentUser> self;
 | 
			
		||||
  private final Provider<PublicKeyStore> storeProvider;
 | 
			
		||||
  private final GerritPublicKeyChecker.Factory checkerFactory;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  GpgKeys(DynamicMap<RestView<GpgKey>> views,
 | 
			
		||||
      Provider<ReviewDb> db,
 | 
			
		||||
      Provider<CurrentUser> self,
 | 
			
		||||
      Provider<PublicKeyStore> storeProvider,
 | 
			
		||||
      GerritPublicKeyChecker.Factory checkerFactory) {
 | 
			
		||||
      GerritPublicKeyChecker.Factory checkerFactory,
 | 
			
		||||
      ExternalIdCache externalIdCache) {
 | 
			
		||||
    this.views = views;
 | 
			
		||||
    this.db = db;
 | 
			
		||||
    this.self = self;
 | 
			
		||||
    this.storeProvider = storeProvider;
 | 
			
		||||
    this.checkerFactory = checkerFactory;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
@@ -208,15 +208,14 @@ public class GpgKeys implements
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @VisibleForTesting
 | 
			
		||||
  public static FluentIterable<AccountExternalId> getGpgExtIds(ReviewDb db,
 | 
			
		||||
      Account.Id accountId) throws OrmException {
 | 
			
		||||
    return FluentIterable.from(db.accountExternalIds().byAccount(accountId))
 | 
			
		||||
  public static FluentIterable<AccountExternalId> getGpgExtIds(
 | 
			
		||||
      ExternalIdCache externalIdCache, Account.Id accountId) {
 | 
			
		||||
    return FluentIterable.from(externalIdCache.byAccount(accountId))
 | 
			
		||||
        .filter(in -> in.isScheme(SCHEME_GPGKEY));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Iterable<AccountExternalId> getGpgExtIds(AccountResource rsrc)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    return getGpgExtIds(db.get(), rsrc.getUser().getAccountId());
 | 
			
		||||
  private Iterable<AccountExternalId> getGpgExtIds(AccountResource rsrc) {
 | 
			
		||||
    return getGpgExtIds(externalIdCache, rsrc.getUser().getAccountId());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static long keyId(byte[] fp) {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,11 +17,11 @@ package com.google.gerrit.gpg.server;
 | 
			
		||||
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
 | 
			
		||||
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
 | 
			
		||||
import static java.nio.charset.StandardCharsets.UTF_8;
 | 
			
		||||
import static java.util.stream.Collectors.toList;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.Joiner;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.collect.ImmutableSet;
 | 
			
		||||
import com.google.common.collect.Iterables;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.common.collect.Maps;
 | 
			
		||||
import com.google.common.collect.Sets;
 | 
			
		||||
@@ -47,6 +47,7 @@ import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.account.AccountCache;
 | 
			
		||||
import com.google.gerrit.server.account.AccountResource;
 | 
			
		||||
import com.google.gerrit.server.account.AccountState;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCache;
 | 
			
		||||
import com.google.gerrit.server.mail.send.AddKeySender;
 | 
			
		||||
import com.google.gerrit.server.query.account.InternalAccountQuery;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
@@ -90,6 +91,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
 | 
			
		||||
  private final AddKeySender.Factory addKeyFactory;
 | 
			
		||||
  private final AccountCache accountCache;
 | 
			
		||||
  private final Provider<InternalAccountQuery> accountQueryProvider;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  PostGpgKeys(@GerritPersonIdent Provider<PersonIdent> serverIdent,
 | 
			
		||||
@@ -99,7 +101,8 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
 | 
			
		||||
      GerritPublicKeyChecker.Factory checkerFactory,
 | 
			
		||||
      AddKeySender.Factory addKeyFactory,
 | 
			
		||||
      AccountCache accountCache,
 | 
			
		||||
      Provider<InternalAccountQuery> accountQueryProvider) {
 | 
			
		||||
      Provider<InternalAccountQuery> accountQueryProvider,
 | 
			
		||||
      ExternalIdCache externalIdCache) {
 | 
			
		||||
    this.serverIdent = serverIdent;
 | 
			
		||||
    this.db = db;
 | 
			
		||||
    this.self = self;
 | 
			
		||||
@@ -108,6 +111,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
 | 
			
		||||
    this.addKeyFactory = addKeyFactory;
 | 
			
		||||
    this.accountCache = accountCache;
 | 
			
		||||
    this.accountQueryProvider = accountQueryProvider;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
@@ -117,7 +121,8 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
 | 
			
		||||
    GpgKeys.checkVisible(self, rsrc);
 | 
			
		||||
 | 
			
		||||
    List<AccountExternalId> existingExtIds =
 | 
			
		||||
        GpgKeys.getGpgExtIds(db.get(), rsrc.getUser().getAccountId()).toList();
 | 
			
		||||
        GpgKeys.getGpgExtIds(externalIdCache,
 | 
			
		||||
            rsrc.getUser().getAccountId()).toList();
 | 
			
		||||
 | 
			
		||||
    try (PublicKeyStore store = storeProvider.get()) {
 | 
			
		||||
      Set<Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
 | 
			
		||||
@@ -142,9 +147,13 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
 | 
			
		||||
      storeKeys(rsrc, newKeys, toRemove);
 | 
			
		||||
      if (!newExtIds.isEmpty()) {
 | 
			
		||||
        db.get().accountExternalIds().insert(newExtIds);
 | 
			
		||||
        externalIdCache.onCreate(newExtIds);
 | 
			
		||||
      }
 | 
			
		||||
      db.get().accountExternalIds().deleteKeys(
 | 
			
		||||
          Iterables.transform(toRemove, fp -> toExtIdKey(fp.get())));
 | 
			
		||||
 | 
			
		||||
      List<AccountExternalId.Key> extIdKeysToRemove =
 | 
			
		||||
          toRemove.stream().map(fp -> toExtIdKey(fp.get())).collect(toList());
 | 
			
		||||
      db.get().accountExternalIds().deleteKeys(extIdKeysToRemove);
 | 
			
		||||
      externalIdCache.onRemove(rsrc.getUser().getAccountId(), extIdKeysToRemove);
 | 
			
		||||
      accountCache.evict(rsrc.getUser().getAccountId());
 | 
			
		||||
      return toJson(newKeys, toRemove, store, rsrc.getUser());
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.account.AccountCache;
 | 
			
		||||
import com.google.gerrit.server.account.AccountManager;
 | 
			
		||||
import com.google.gerrit.server.account.AuthRequest;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCache;
 | 
			
		||||
import com.google.gerrit.server.schema.SchemaCreator;
 | 
			
		||||
import com.google.gerrit.server.util.RequestContext;
 | 
			
		||||
import com.google.gerrit.server.util.ThreadLocalRequestContext;
 | 
			
		||||
@@ -69,6 +70,7 @@ import org.junit.Test;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@@ -95,6 +97,9 @@ public class GerritPublicKeyCheckerTest {
 | 
			
		||||
  @Inject
 | 
			
		||||
  private ThreadLocalRequestContext requestContext;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  private ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  private LifecycleManager lifecycle;
 | 
			
		||||
  private ReviewDb db;
 | 
			
		||||
  private Account.Id userId;
 | 
			
		||||
@@ -229,8 +234,10 @@ public class GerritPublicKeyCheckerTest {
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void noExternalIds() throws Exception {
 | 
			
		||||
    db.accountExternalIds().delete(
 | 
			
		||||
        db.accountExternalIds().byAccount(user.getAccountId()));
 | 
			
		||||
    Collection<AccountExternalId> extIds =
 | 
			
		||||
        externalIdCache.byAccount(user.getAccountId());
 | 
			
		||||
    db.accountExternalIds().delete(extIds);
 | 
			
		||||
    externalIdCache.onRemove(extIds);
 | 
			
		||||
    reloadUser();
 | 
			
		||||
 | 
			
		||||
    TestKey key = validKeyWithSecondUserId();
 | 
			
		||||
@@ -248,9 +255,10 @@ public class GerritPublicKeyCheckerTest {
 | 
			
		||||
        checker.check(key.getPublicKey()), Status.BAD,
 | 
			
		||||
        "Key is not associated with any users");
 | 
			
		||||
 | 
			
		||||
    db.accountExternalIds().insert(Collections.singleton(
 | 
			
		||||
        new AccountExternalId(
 | 
			
		||||
            user.getAccountId(), toExtIdKey(key.getPublicKey()))));
 | 
			
		||||
    AccountExternalId extId = new AccountExternalId(user.getAccountId(),
 | 
			
		||||
        toExtIdKey(key.getPublicKey()));
 | 
			
		||||
    db.accountExternalIds().insert(Collections.singleton(extId));
 | 
			
		||||
    externalIdCache.onCreate(extId);
 | 
			
		||||
    reloadUser();
 | 
			
		||||
    assertProblems(
 | 
			
		||||
        checker.check(key.getPublicKey()), Status.BAD,
 | 
			
		||||
@@ -427,6 +435,7 @@ public class GerritPublicKeyCheckerTest {
 | 
			
		||||
    assertThat(store.save(cb)).isAnyOf(NEW, FAST_FORWARD, FORCED);
 | 
			
		||||
 | 
			
		||||
    db.accountExternalIds().insert(newExtIds);
 | 
			
		||||
    externalIdCache.onCreate(newExtIds);
 | 
			
		||||
    accountCache.evict(user.getAccountId());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -459,6 +468,7 @@ public class GerritPublicKeyCheckerTest {
 | 
			
		||||
      extId.setEmailAddress(email);
 | 
			
		||||
    }
 | 
			
		||||
    db.accountExternalIds().insert(Collections.singleton(extId));
 | 
			
		||||
    externalIdCache.onCreate(extId);
 | 
			
		||||
    reloadUser();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.account.AccountByEmailCache;
 | 
			
		||||
import com.google.gerrit.server.account.AccountCache;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCache;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.assistedinject.Assisted;
 | 
			
		||||
@@ -42,21 +43,24 @@ class DeleteExternalIds extends Handler<Set<AccountExternalId.Key>> {
 | 
			
		||||
  private final ExternalIdDetailFactory detailFactory;
 | 
			
		||||
  private final AccountByEmailCache byEmailCache;
 | 
			
		||||
  private final AccountCache accountCache;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  private final Set<AccountExternalId.Key> keys;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  DeleteExternalIds(final ReviewDb db, final IdentifiedUser user,
 | 
			
		||||
      final ExternalIdDetailFactory detailFactory,
 | 
			
		||||
      final AccountByEmailCache byEmailCache, final AccountCache accountCache,
 | 
			
		||||
 | 
			
		||||
      @Assisted final Set<AccountExternalId.Key> keys) {
 | 
			
		||||
  DeleteExternalIds(ReviewDb db,
 | 
			
		||||
      IdentifiedUser user,
 | 
			
		||||
      ExternalIdDetailFactory detailFactory,
 | 
			
		||||
      AccountByEmailCache byEmailCache,
 | 
			
		||||
      AccountCache accountCache,
 | 
			
		||||
      ExternalIdCache externalIdCache,
 | 
			
		||||
      @Assisted Set<AccountExternalId.Key> keys) {
 | 
			
		||||
    this.db = db;
 | 
			
		||||
    this.user = user;
 | 
			
		||||
    this.detailFactory = detailFactory;
 | 
			
		||||
    this.byEmailCache = byEmailCache;
 | 
			
		||||
    this.accountCache = accountCache;
 | 
			
		||||
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
    this.keys = keys;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -74,6 +78,7 @@ class DeleteExternalIds extends Handler<Set<AccountExternalId.Key>> {
 | 
			
		||||
 | 
			
		||||
    if (!toDelete.isEmpty()) {
 | 
			
		||||
      db.accountExternalIds().delete(toDelete);
 | 
			
		||||
      externalIdCache.onRemove(toDelete);
 | 
			
		||||
      accountCache.evict(user.getAccountId());
 | 
			
		||||
      for (AccountExternalId e : toDelete) {
 | 
			
		||||
        byEmailCache.evict(e.getEmailAddress());
 | 
			
		||||
 
 | 
			
		||||
@@ -20,12 +20,13 @@ import com.google.gerrit.extensions.registration.DynamicItem;
 | 
			
		||||
import com.google.gerrit.httpd.WebSession;
 | 
			
		||||
import com.google.gerrit.httpd.rpc.Handler;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCache;
 | 
			
		||||
import com.google.gerrit.server.config.AuthConfig;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@@ -34,25 +35,27 @@ class ExternalIdDetailFactory extends Handler<List<AccountExternalId>> {
 | 
			
		||||
    ExternalIdDetailFactory create();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final ReviewDb db;
 | 
			
		||||
  private final IdentifiedUser user;
 | 
			
		||||
  private final AuthConfig authConfig;
 | 
			
		||||
  private final DynamicItem<WebSession> session;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  ExternalIdDetailFactory(final ReviewDb db, final IdentifiedUser user,
 | 
			
		||||
      final AuthConfig authConfig, final DynamicItem<WebSession> session) {
 | 
			
		||||
    this.db = db;
 | 
			
		||||
  ExternalIdDetailFactory(IdentifiedUser user,
 | 
			
		||||
      AuthConfig authConfig,
 | 
			
		||||
      DynamicItem<WebSession> session,
 | 
			
		||||
      ExternalIdCache externalIdCache) {
 | 
			
		||||
    this.user = user;
 | 
			
		||||
    this.authConfig = authConfig;
 | 
			
		||||
    this.session = session;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public List<AccountExternalId> call() throws OrmException {
 | 
			
		||||
    final AccountExternalId.Key last = session.get().getLastLoginExternalId();
 | 
			
		||||
    final List<AccountExternalId> ids =
 | 
			
		||||
        db.accountExternalIds().byAccount(user.getAccountId()).toList();
 | 
			
		||||
    AccountExternalId.Key last = session.get().getLastLoginExternalId();
 | 
			
		||||
    List<AccountExternalId> ids =
 | 
			
		||||
        new ArrayList<>(externalIdCache.byAccount(user.getAccountId()));
 | 
			
		||||
 | 
			
		||||
    for (final AccountExternalId e : ids) {
 | 
			
		||||
      e.setTrusted(authConfig.isIdentityTrustable(Collections.singleton(e)));
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ import com.google.gerrit.server.account.AccountVisibility;
 | 
			
		||||
import com.google.gerrit.server.account.AccountVisibilityProvider;
 | 
			
		||||
import com.google.gerrit.server.account.CapabilityCollection;
 | 
			
		||||
import com.google.gerrit.server.account.CapabilityControl;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCacheImpl;
 | 
			
		||||
import com.google.gerrit.server.account.FakeRealm;
 | 
			
		||||
import com.google.gerrit.server.account.GroupCacheImpl;
 | 
			
		||||
import com.google.gerrit.server.account.GroupIncludeCacheImpl;
 | 
			
		||||
@@ -153,6 +154,7 @@ public class BatchProgramModule extends FactoryModule {
 | 
			
		||||
    install(new PrologModule());
 | 
			
		||||
    install(AccountByEmailCacheImpl.module());
 | 
			
		||||
    install(AccountCacheImpl.module());
 | 
			
		||||
    install(ExternalIdCacheImpl.module());
 | 
			
		||||
    install(GroupCacheImpl.module());
 | 
			
		||||
    install(GroupIncludeCacheImpl.module());
 | 
			
		||||
    install(ProjectCacheImpl.module());
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,8 @@ import com.google.gerrit.extensions.client.AuthType;
 | 
			
		||||
import com.google.gwtorm.client.Column;
 | 
			
		||||
import com.google.gwtorm.client.StringKey;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/** Association of an external account identifier to a local {@link Account}. */
 | 
			
		||||
public final class AccountExternalId {
 | 
			
		||||
  /**
 | 
			
		||||
@@ -167,4 +169,21 @@ public final class AccountExternalId {
 | 
			
		||||
  public void setCanDelete(final boolean t) {
 | 
			
		||||
    canDelete = t;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean equals(Object o) {
 | 
			
		||||
    if (o instanceof AccountExternalId) {
 | 
			
		||||
      AccountExternalId extId = (AccountExternalId) o;
 | 
			
		||||
      return Objects.equals(key, extId.key)
 | 
			
		||||
          && Objects.equals(accountId, extId.accountId)
 | 
			
		||||
          && Objects.equals(emailAddress, extId.emailAddress)
 | 
			
		||||
          && Objects.equals(password, extId.password);
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public int hashCode() {
 | 
			
		||||
    return Objects.hash(key, accountId, emailAddress, password);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.reviewdb.server;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
 | 
			
		||||
import com.google.gwtorm.server.Access;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
@@ -28,9 +27,6 @@ public interface AccountExternalIdAccess extends
 | 
			
		||||
  @PrimaryKey("key")
 | 
			
		||||
  AccountExternalId get(AccountExternalId.Key key) throws OrmException;
 | 
			
		||||
 | 
			
		||||
  @Query("WHERE accountId = ?")
 | 
			
		||||
  ResultSet<AccountExternalId> byAccount(Account.Id id) throws OrmException;
 | 
			
		||||
 | 
			
		||||
  @Query
 | 
			
		||||
  ResultSet<AccountExternalId> all() throws OrmException;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -155,6 +155,7 @@ public class AccountCacheImpl implements AccountCache {
 | 
			
		||||
    private final GeneralPreferencesLoader loader;
 | 
			
		||||
    private final LoadingCache<String, Optional<Account.Id>> byName;
 | 
			
		||||
    private final Provider<WatchConfig.Accessor> watchConfig;
 | 
			
		||||
    private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
    @Inject
 | 
			
		||||
    ByIdLoader(SchemaFactory<ReviewDb> sf,
 | 
			
		||||
@@ -162,12 +163,14 @@ public class AccountCacheImpl implements AccountCache {
 | 
			
		||||
        GeneralPreferencesLoader loader,
 | 
			
		||||
        @Named(BYUSER_NAME) LoadingCache<String,
 | 
			
		||||
            Optional<Account.Id>> byUsername,
 | 
			
		||||
        Provider<WatchConfig.Accessor> watchConfig) {
 | 
			
		||||
        Provider<WatchConfig.Accessor> watchConfig,
 | 
			
		||||
        ExternalIdCache externalIdCache) {
 | 
			
		||||
      this.schema = sf;
 | 
			
		||||
      this.groupCache = groupCache;
 | 
			
		||||
      this.loader = loader;
 | 
			
		||||
      this.byName = byUsername;
 | 
			
		||||
      this.watchConfig = watchConfig;
 | 
			
		||||
      this.externalIdCache = externalIdCache;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -191,8 +194,7 @@ public class AccountCacheImpl implements AccountCache {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      Collection<AccountExternalId> externalIds =
 | 
			
		||||
          Collections.unmodifiableCollection(
 | 
			
		||||
              db.accountExternalIds().byAccount(who).toList());
 | 
			
		||||
          externalIdCache.byAccount(who);
 | 
			
		||||
 | 
			
		||||
      Set<AccountGroup.UUID> internalGroups = new HashSet<>();
 | 
			
		||||
      for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@ import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.project.ProjectCache;
 | 
			
		||||
import com.google.gerrit.server.query.account.InternalAccountQuery;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.gwtorm.server.ResultSet;
 | 
			
		||||
import com.google.gwtorm.server.SchemaFactory;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
@@ -42,6 +41,7 @@ import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
@@ -63,6 +63,7 @@ public class AccountManager {
 | 
			
		||||
  private final AtomicBoolean awaitsFirstAccountCheck;
 | 
			
		||||
  private final AuditService auditService;
 | 
			
		||||
  private final Provider<InternalAccountQuery> accountQueryProvider;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  AccountManager(SchemaFactory<ReviewDb> schema,
 | 
			
		||||
@@ -73,7 +74,8 @@ public class AccountManager {
 | 
			
		||||
      ChangeUserName.Factory changeUserNameFactory,
 | 
			
		||||
      ProjectCache projectCache,
 | 
			
		||||
      AuditService auditService,
 | 
			
		||||
      Provider<InternalAccountQuery> accountQueryProvider) {
 | 
			
		||||
      Provider<InternalAccountQuery> accountQueryProvider,
 | 
			
		||||
      ExternalIdCache externalIdCache) {
 | 
			
		||||
    this.schema = schema;
 | 
			
		||||
    this.byIdCache = byIdCache;
 | 
			
		||||
    this.byEmailCache = byEmailCache;
 | 
			
		||||
@@ -84,6 +86,7 @@ public class AccountManager {
 | 
			
		||||
    this.awaitsFirstAccountCheck = new AtomicBoolean(true);
 | 
			
		||||
    this.auditService = auditService;
 | 
			
		||||
    this.accountQueryProvider = accountQueryProvider;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -171,6 +174,7 @@ public class AccountManager {
 | 
			
		||||
 | 
			
		||||
      extId.setEmailAddress(newEmail);
 | 
			
		||||
      db.accountExternalIds().update(Collections.singleton(extId));
 | 
			
		||||
      externalIdCache.onUpdate(extId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!realm.allowsEdit(AccountFieldName.FULL_NAME)
 | 
			
		||||
@@ -242,6 +246,7 @@ public class AccountManager {
 | 
			
		||||
                + "\" to account " + newId + "; external ID already in use.");
 | 
			
		||||
      }
 | 
			
		||||
      db.accountExternalIds().upsert(Collections.singleton(extId));
 | 
			
		||||
      externalIdCache.onUpdate(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
 | 
			
		||||
@@ -335,6 +340,7 @@ public class AccountManager {
 | 
			
		||||
      // the database
 | 
			
		||||
      db.accounts().delete(Collections.singleton(account));
 | 
			
		||||
      db.accountExternalIds().delete(Collections.singleton(extId));
 | 
			
		||||
      externalIdCache.onRemove(extId);
 | 
			
		||||
      throw new AccountUserNameException(errorMessage, e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -367,6 +373,7 @@ public class AccountManager {
 | 
			
		||||
        extId = createId(to, who);
 | 
			
		||||
        extId.setEmailAddress(who.getEmailAddress());
 | 
			
		||||
        db.accountExternalIds().insert(Collections.singleton(extId));
 | 
			
		||||
        externalIdCache.onCreate(extId);
 | 
			
		||||
 | 
			
		||||
        if (who.getEmailAddress() != null) {
 | 
			
		||||
          Account a = db.accounts().get(to);
 | 
			
		||||
@@ -405,12 +412,12 @@ public class AccountManager {
 | 
			
		||||
    try (ReviewDb db = schema.open()) {
 | 
			
		||||
      AccountExternalId.Key key = id(who);
 | 
			
		||||
      List<AccountExternalId.Key> filteredKeysByScheme =
 | 
			
		||||
          filterKeysByScheme(key.getScheme(), db.accountExternalIds()
 | 
			
		||||
              .byAccount(to));
 | 
			
		||||
          filterKeysByScheme(key.getScheme(), externalIdCache.byAccount(to));
 | 
			
		||||
      if (!filteredKeysByScheme.isEmpty()
 | 
			
		||||
          && (filteredKeysByScheme.size() > 1 || !filteredKeysByScheme
 | 
			
		||||
              .contains(key))) {
 | 
			
		||||
        db.accountExternalIds().deleteKeys(filteredKeysByScheme);
 | 
			
		||||
        externalIdCache.onRemove(to, filteredKeysByScheme);
 | 
			
		||||
      }
 | 
			
		||||
      byIdCache.evict(to);
 | 
			
		||||
      return link(to, who);
 | 
			
		||||
@@ -418,7 +425,7 @@ public class AccountManager {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private List<AccountExternalId.Key> filterKeysByScheme(
 | 
			
		||||
      String keyScheme, ResultSet<AccountExternalId> externalIds) {
 | 
			
		||||
      String keyScheme, Collection<AccountExternalId> externalIds) {
 | 
			
		||||
    List<AccountExternalId.Key> filteredExternalIds = new ArrayList<>();
 | 
			
		||||
    for (AccountExternalId accountExternalId : externalIds) {
 | 
			
		||||
      if (accountExternalId.isScheme(keyScheme)) {
 | 
			
		||||
@@ -448,6 +455,7 @@ public class AccountManager {
 | 
			
		||||
              "Identity '" + key.get() + "' in use by another account");
 | 
			
		||||
        }
 | 
			
		||||
        db.accountExternalIds().delete(Collections.singleton(extId));
 | 
			
		||||
        externalIdCache.onRemove(extId);
 | 
			
		||||
 | 
			
		||||
        if (who.getEmailAddress() != null) {
 | 
			
		||||
          Account a = db.accounts().get(from);
 | 
			
		||||
 
 | 
			
		||||
@@ -51,19 +51,22 @@ public class ChangeUserName implements Callable<VoidResult> {
 | 
			
		||||
 | 
			
		||||
  private final AccountCache accountCache;
 | 
			
		||||
  private final SshKeyCache sshKeyCache;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  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) {
 | 
			
		||||
  ChangeUserName(AccountCache accountCache,
 | 
			
		||||
      SshKeyCache sshKeyCache,
 | 
			
		||||
      ExternalIdCache externalIdCache,
 | 
			
		||||
      @Assisted ReviewDb db,
 | 
			
		||||
      @Assisted IdentifiedUser user,
 | 
			
		||||
      @Nullable @Assisted String newUsername) {
 | 
			
		||||
    this.accountCache = accountCache;
 | 
			
		||||
    this.sshKeyCache = sshKeyCache;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
 | 
			
		||||
    this.db = db;
 | 
			
		||||
    this.user = user;
 | 
			
		||||
@@ -96,6 +99,7 @@ public class ChangeUserName implements Callable<VoidResult> {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        db.accountExternalIds().insert(Collections.singleton(id));
 | 
			
		||||
        externalIdCache.onCreate(id);
 | 
			
		||||
      } catch (OrmDuplicateKeyException dupeErr) {
 | 
			
		||||
        // If we are using this identity, don't report the exception.
 | 
			
		||||
        //
 | 
			
		||||
@@ -113,6 +117,7 @@ public class ChangeUserName implements Callable<VoidResult> {
 | 
			
		||||
    // If we have any older user names, remove them.
 | 
			
		||||
    //
 | 
			
		||||
    db.accountExternalIds().delete(old);
 | 
			
		||||
    externalIdCache.onRemove(old);
 | 
			
		||||
    for (AccountExternalId i : old) {
 | 
			
		||||
      sshKeyCache.evict(i.getSchemeRest());
 | 
			
		||||
      accountCache.evictByUsername(i.getSchemeRest());
 | 
			
		||||
@@ -124,9 +129,9 @@ public class ChangeUserName implements Callable<VoidResult> {
 | 
			
		||||
    return VoidResult.INSTANCE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Collection<AccountExternalId> old() throws OrmException {
 | 
			
		||||
  private Collection<AccountExternalId> old() {
 | 
			
		||||
    final Collection<AccountExternalId> r = new ArrayList<>(1);
 | 
			
		||||
    for (AccountExternalId i : db.accountExternalIds().byAccount(
 | 
			
		||||
    for (AccountExternalId i : externalIdCache.byAccount(
 | 
			
		||||
        user.getAccountId())) {
 | 
			
		||||
      if (i.isScheme(SCHEME_USERNAME)) {
 | 
			
		||||
        r.add(i);
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,7 @@ public class CreateAccount
 | 
			
		||||
  private final AccountLoader.Factory infoLoader;
 | 
			
		||||
  private final DynamicSet<AccountExternalIdCreator> externalIdCreators;
 | 
			
		||||
  private final AuditService auditService;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
  private final String username;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
@@ -87,6 +88,7 @@ public class CreateAccount
 | 
			
		||||
      AccountLoader.Factory infoLoader,
 | 
			
		||||
      DynamicSet<AccountExternalIdCreator> externalIdCreators,
 | 
			
		||||
      AuditService auditService,
 | 
			
		||||
      ExternalIdCache externalIdCache,
 | 
			
		||||
      @Assisted String username) {
 | 
			
		||||
    this.db = db;
 | 
			
		||||
    this.currentUser = currentUser;
 | 
			
		||||
@@ -99,6 +101,7 @@ public class CreateAccount
 | 
			
		||||
    this.infoLoader = infoLoader;
 | 
			
		||||
    this.externalIdCreators = externalIdCreators;
 | 
			
		||||
    this.auditService = auditService;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
    this.username = username;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -153,6 +156,7 @@ public class CreateAccount
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      db.accountExternalIds().insert(externalIds);
 | 
			
		||||
      externalIdCache.onCreate(externalIds);
 | 
			
		||||
    } catch (OrmDuplicateKeyException duplicateKey) {
 | 
			
		||||
      throw new ResourceConflictException(
 | 
			
		||||
          "username '" + username + "' already exists");
 | 
			
		||||
@@ -164,9 +168,11 @@ public class CreateAccount
 | 
			
		||||
      extMailto.setEmailAddress(input.email);
 | 
			
		||||
      try {
 | 
			
		||||
        db.accountExternalIds().insert(Collections.singleton(extMailto));
 | 
			
		||||
        externalIdCache.onCreate(extMailto);
 | 
			
		||||
      } catch (OrmDuplicateKeyException duplicateKey) {
 | 
			
		||||
        try {
 | 
			
		||||
          db.accountExternalIds().delete(Collections.singleton(extUser));
 | 
			
		||||
          externalIdCache.onRemove(extUser);
 | 
			
		||||
        } catch (OrmException cleanupError) {
 | 
			
		||||
          // Ignored
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
// Copyright (C) 2016 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.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
/** Caches external ids of all accounts */
 | 
			
		||||
public interface ExternalIdCache {
 | 
			
		||||
  void onCreate(Iterable<AccountExternalId> extId);
 | 
			
		||||
  void onRemove(Iterable<AccountExternalId> extId);
 | 
			
		||||
  void onRemove(Account.Id accountId,
 | 
			
		||||
      Iterable<AccountExternalId.Key> extIdKeys);
 | 
			
		||||
  void onUpdate(AccountExternalId extId);
 | 
			
		||||
  Collection<AccountExternalId> byAccount(Account.Id accountId);
 | 
			
		||||
 | 
			
		||||
  default void onCreate(AccountExternalId extId) {
 | 
			
		||||
    onCreate(Collections.singleton(extId));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  default void onRemove(AccountExternalId extId) {
 | 
			
		||||
    onRemove(Collections.singleton(extId));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  default void onRemove(Account.Id accountId, AccountExternalId.Key extIdKey) {
 | 
			
		||||
    onRemove(accountId, Collections.singleton(extIdKey));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,193 @@
 | 
			
		||||
// Copyright (C) 2016 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.cache.CacheLoader;
 | 
			
		||||
import com.google.common.cache.LoadingCache;
 | 
			
		||||
import com.google.common.collect.ImmutableSetMultimap;
 | 
			
		||||
import com.google.common.collect.Multimap;
 | 
			
		||||
import com.google.common.collect.MultimapBuilder;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.cache.CacheModule;
 | 
			
		||||
import com.google.gwtorm.server.SchemaFactory;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Module;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
import com.google.inject.TypeLiteral;
 | 
			
		||||
import com.google.inject.name.Named;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.locks.Lock;
 | 
			
		||||
import java.util.concurrent.locks.ReentrantLock;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
public class ExternalIdCacheImpl implements ExternalIdCache {
 | 
			
		||||
  private static final Logger log =
 | 
			
		||||
      LoggerFactory.getLogger(ExternalIdCacheImpl.class);
 | 
			
		||||
 | 
			
		||||
  private static final String CACHE_NAME = "external_ids_map";
 | 
			
		||||
 | 
			
		||||
  public static Module module() {
 | 
			
		||||
    return new CacheModule() {
 | 
			
		||||
      @Override
 | 
			
		||||
      protected void configure() {
 | 
			
		||||
        cache(CACHE_NAME, AllKey.class,
 | 
			
		||||
            new TypeLiteral<ImmutableSetMultimap<Account.Id, AccountExternalId>>() {})
 | 
			
		||||
                .maximumWeight(1).loader(Loader.class);
 | 
			
		||||
 | 
			
		||||
        bind(ExternalIdCacheImpl.class);
 | 
			
		||||
        bind(ExternalIdCache.class).to(ExternalIdCacheImpl.class);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final LoadingCache<AllKey,
 | 
			
		||||
      ImmutableSetMultimap<Account.Id, AccountExternalId>> extIdsByAccount;
 | 
			
		||||
  private final Lock lock;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  ExternalIdCacheImpl(
 | 
			
		||||
      @Named(CACHE_NAME) LoadingCache<AllKey,
 | 
			
		||||
          ImmutableSetMultimap<Account.Id, AccountExternalId>> extIdsByAccount) {
 | 
			
		||||
    this.extIdsByAccount = extIdsByAccount;
 | 
			
		||||
    this.lock = new ReentrantLock(true /* fair */);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onCreate(Iterable<AccountExternalId> extIds) {
 | 
			
		||||
    lock.lock();
 | 
			
		||||
    try {
 | 
			
		||||
      Multimap<Account.Id, AccountExternalId> n = MultimapBuilder.hashKeys()
 | 
			
		||||
          .arrayListValues().build(extIdsByAccount.get(AllKey.ALL));
 | 
			
		||||
      for (AccountExternalId extId : extIds) {
 | 
			
		||||
        n.put(extId.getAccountId(), extId);
 | 
			
		||||
      }
 | 
			
		||||
      extIdsByAccount.put(AllKey.ALL, ImmutableSetMultimap.copyOf(n));
 | 
			
		||||
    } catch (ExecutionException e) {
 | 
			
		||||
      log.warn("Cannot list external IDs", e);
 | 
			
		||||
    } finally {
 | 
			
		||||
      lock.unlock();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onRemove(Iterable<AccountExternalId> extIds) {
 | 
			
		||||
    lock.lock();
 | 
			
		||||
    try {
 | 
			
		||||
      Multimap<Account.Id, AccountExternalId> n = MultimapBuilder.hashKeys()
 | 
			
		||||
          .arrayListValues().build(extIdsByAccount.get(AllKey.ALL));
 | 
			
		||||
      for (AccountExternalId extId : extIds) {
 | 
			
		||||
        n.remove(extId.getAccountId(), extId);
 | 
			
		||||
      }
 | 
			
		||||
      extIdsByAccount.put(AllKey.ALL, ImmutableSetMultimap.copyOf(n));
 | 
			
		||||
    } catch (ExecutionException e) {
 | 
			
		||||
      log.warn("Cannot list external IDs", e);
 | 
			
		||||
    } finally {
 | 
			
		||||
      lock.unlock();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onRemove(Account.Id accountId,
 | 
			
		||||
      Iterable<AccountExternalId.Key> extIdKeys) {
 | 
			
		||||
    lock.lock();
 | 
			
		||||
    try {
 | 
			
		||||
      Multimap<Account.Id, AccountExternalId> n = MultimapBuilder.hashKeys()
 | 
			
		||||
          .arrayListValues().build(extIdsByAccount.get(AllKey.ALL));
 | 
			
		||||
      for (AccountExternalId extId : byAccount(accountId)) {
 | 
			
		||||
        for (AccountExternalId.Key extIdKey : extIdKeys) {
 | 
			
		||||
          if (extIdKey.equals(extId.getKey())) {
 | 
			
		||||
            n.remove(accountId, extId);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      extIdsByAccount.put(AllKey.ALL, ImmutableSetMultimap.copyOf(n));
 | 
			
		||||
    } catch (ExecutionException e) {
 | 
			
		||||
      log.warn("Cannot list external IDs", e);
 | 
			
		||||
    } finally {
 | 
			
		||||
      lock.unlock();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onUpdate(AccountExternalId updatedExtId) {
 | 
			
		||||
    lock.lock();
 | 
			
		||||
    try {
 | 
			
		||||
      Multimap<Account.Id, AccountExternalId> n = MultimapBuilder.hashKeys()
 | 
			
		||||
          .arrayListValues().build(extIdsByAccount.get(AllKey.ALL));
 | 
			
		||||
      for (AccountExternalId extId : byAccount(updatedExtId.getAccountId())) {
 | 
			
		||||
        if (updatedExtId.getKey().equals(extId.getKey())) {
 | 
			
		||||
          n.remove(updatedExtId.getAccountId(), extId);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      n.put(updatedExtId.getAccountId(), updatedExtId);
 | 
			
		||||
      extIdsByAccount.put(AllKey.ALL, ImmutableSetMultimap.copyOf(n));
 | 
			
		||||
    } catch (ExecutionException e) {
 | 
			
		||||
      log.warn("Cannot list external IDs", e);
 | 
			
		||||
    } finally {
 | 
			
		||||
      lock.unlock();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Collection<AccountExternalId> byAccount(Account.Id accountId) {
 | 
			
		||||
    try {
 | 
			
		||||
      return extIdsByAccount.get(AllKey.ALL).get(accountId);
 | 
			
		||||
    } catch (ExecutionException e) {
 | 
			
		||||
      log.warn("Cannot list external ids", e);
 | 
			
		||||
      return Collections.emptySet();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static class AllKey {
 | 
			
		||||
    static final AllKey ALL = new AllKey();
 | 
			
		||||
 | 
			
		||||
    private AllKey() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static class Loader
 | 
			
		||||
      extends CacheLoader<AllKey,
 | 
			
		||||
          ImmutableSetMultimap<Account.Id, AccountExternalId>> {
 | 
			
		||||
    private final SchemaFactory<ReviewDb> schema;
 | 
			
		||||
 | 
			
		||||
    @Inject
 | 
			
		||||
    Loader(SchemaFactory<ReviewDb> schema) {
 | 
			
		||||
      this.schema = schema;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ImmutableSetMultimap<Account.Id, AccountExternalId> load(AllKey key)
 | 
			
		||||
        throws Exception {
 | 
			
		||||
      try (ReviewDb db = schema.open()) {
 | 
			
		||||
        Multimap<Account.Id, AccountExternalId> extIdsByAccount =
 | 
			
		||||
            MultimapBuilder.hashKeys().arrayListValues().build();
 | 
			
		||||
        for (AccountExternalId extId : db.accountExternalIds().all()) {
 | 
			
		||||
          extIdsByAccount.put(extId.getAccountId(), extId);
 | 
			
		||||
        }
 | 
			
		||||
        return ImmutableSetMultimap.copyOf(extIdsByAccount);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -60,13 +60,17 @@ public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
 | 
			
		||||
  private final Provider<CurrentUser> self;
 | 
			
		||||
  private final Provider<ReviewDb> dbProvider;
 | 
			
		||||
  private final AccountCache accountCache;
 | 
			
		||||
  private final ExternalIdCache externalIdCache;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  PutHttpPassword(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
 | 
			
		||||
      AccountCache accountCache) {
 | 
			
		||||
  PutHttpPassword(Provider<CurrentUser> self,
 | 
			
		||||
      Provider<ReviewDb> dbProvider,
 | 
			
		||||
      AccountCache accountCache,
 | 
			
		||||
      ExternalIdCache externalIdCache) {
 | 
			
		||||
    this.self = self;
 | 
			
		||||
    this.dbProvider = dbProvider;
 | 
			
		||||
    this.accountCache = accountCache;
 | 
			
		||||
    this.externalIdCache = externalIdCache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
@@ -117,6 +121,7 @@ public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
 | 
			
		||||
    }
 | 
			
		||||
    id.setPassword(newPassword);
 | 
			
		||||
    dbProvider.get().accountExternalIds().update(Collections.singleton(id));
 | 
			
		||||
    externalIdCache.onUpdate(id);
 | 
			
		||||
    accountCache.evict(user.getAccountId());
 | 
			
		||||
 | 
			
		||||
    return Strings.isNullOrEmpty(newPassword)
 | 
			
		||||
 
 | 
			
		||||
@@ -86,6 +86,7 @@ import com.google.gerrit.server.account.CapabilityCollection;
 | 
			
		||||
import com.google.gerrit.server.account.CapabilityControl;
 | 
			
		||||
import com.google.gerrit.server.account.ChangeUserName;
 | 
			
		||||
import com.google.gerrit.server.account.EmailExpander;
 | 
			
		||||
import com.google.gerrit.server.account.ExternalIdCacheImpl;
 | 
			
		||||
import com.google.gerrit.server.account.GroupCacheImpl;
 | 
			
		||||
import com.google.gerrit.server.account.GroupControl;
 | 
			
		||||
import com.google.gerrit.server.account.GroupDetailFactory;
 | 
			
		||||
@@ -217,6 +218,7 @@ public class GerritGlobalModule extends FactoryModule {
 | 
			
		||||
    install(AccountCacheImpl.module());
 | 
			
		||||
    install(ChangeKindCacheImpl.module());
 | 
			
		||||
    install(ConflictsCacheImpl.module());
 | 
			
		||||
    install(ExternalIdCacheImpl.module());
 | 
			
		||||
    install(GroupCacheImpl.module());
 | 
			
		||||
    install(GroupIncludeCacheImpl.module());
 | 
			
		||||
    install(MergeabilityCacheImpl.module());
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user