Fix the missing DB entry in Gerrit DB

Due to an unknown reason (still under investigation), a few of the new
LDAP users were not able to login after first time because the Gerrit DB
was missing one of the two entries needed to authenticate the user (the
second entry is only needed for the users with non-null usernames).
The entry with external Id - "gerrit:abc" was never inserted to DB
in such cases whereas the entry with external id - "username:abc" was
created resulting in an account creation for the user.

This causes inconsistency in the DB. An account gets created in first login
attempt and second attempt fails to create missing external id because the
"username:abc" entry already exists causing the DB action to rollback.

As this bug is very infrequent and hard to replicate, this change
provides an after-fix for the issue i.e. fixes the problem when an entry
is missing in Gerrit DB. It checks if there exists an account already
for the given username and links the newly created external ID to that
account. It prevents the need to manually enter the missing entries in
DB to solve the issue.

Bug: Issue 7652
Change-Id: Icd2fbf77971f00783277f88fa085ea1bc761878f
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
index cb06c66..38ffeb8 100644
--- 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.account;
 
+import static com.google.gerrit.server.account.ExternalId.SCHEME_USERNAME;
 import static java.util.stream.Collectors.toSet;
 
 import com.google.common.base.Strings;
@@ -107,8 +108,22 @@
       try (ReviewDb db = schema.open()) {
         ExternalId id = findExternalId(db, who.getExternalIdKey());
         if (id == null) {
+          if (who.getUserName() != null) {
+            ExternalId.Key key = ExternalId.Key.create(SCHEME_USERNAME, who.getUserName());
+            ExternalId existingId = findExternalId(db, key);
+            if (existingId != null) {
+              // An inconsistency is detected in the database, having a record for scheme "username:"
+              // but no record for scheme "gerrit:". Try to recover by linking
+              // "gerrit:" identity to the existing account.
+              log.warn(
+                  "User {} already has an account; link new identity to the existing account.",
+                  who.getUserName());
+              return link(existingId.accountId(), who);
+            }
+          }
           // New account, automatically create and return.
           //
+          log.info("External ID not found. Attempting to create new account.");
           return create(db, who);
         }
 
@@ -348,13 +363,16 @@
   public AuthResult link(Account.Id to, AuthRequest who)
       throws AccountException, OrmException, IOException {
     try (ReviewDb db = schema.open()) {
+      log.info("Link another authentication identity to an existing account");
       ExternalId extId = findExternalId(db, who.getExternalIdKey());
       if (extId != null) {
         if (!extId.accountId().equals(to)) {
           throw new AccountException("Identity in use by another account");
         }
+        log.info("Updating existing external ID data");
         update(db, who, extId);
       } else {
+        log.info("Linking new external ID to the existing account");
         externalIdsUpdateFactory
             .create()
             .insert(