Fetch ssh keys for users involved in the project migration.

Status before this change:
When a user does not already exist in the target system  it is created
without transfering the ssh keys from the source system into the
target system.

Status after this change:
The user is created and the ssh keys are transferred.

The ssh keys are only transferred when the user does not already exist
in the target system. For already existing users in the target system
the ssh keys are not transferred.

Change-Id: I00654105583c556b7440f29d2cc086d7e95a01de
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/AccountUtil.java b/src/main/java/com/googlesource/gerrit/plugins/importer/AccountUtil.java
index 341081f..b996657 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/AccountUtil.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/AccountUtil.java
@@ -16,17 +16,27 @@
 
 import com.google.gerrit.common.errors.NoSuchAccountException;
 import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
 import com.google.gerrit.server.config.AuthConfig;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 
 @Singleton
@@ -35,24 +45,30 @@
   private final AccountCache accountCache;
   private final AccountManager accountManager;
   private final AuthType authType;
+  private final Provider<ReviewDb> db;
 
   @Inject
   public AccountUtil(AccountCache accountCache,
       AccountManager accountManager,
-      AuthConfig authConfig) {
+      AuthConfig authConfig,
+      Provider<ReviewDb> db) {
     this.accountCache = accountCache;
     this.accountManager = accountManager;
     this.authType = authConfig.getAuthType();
+    this.db = db;
   }
 
-  Account.Id resolveUser(AccountInfo acc) throws NoSuchAccountException {
+  Account.Id resolveUser(RemoteApi api, AccountInfo acc)
+      throws NoSuchAccountException, BadRequestException, IOException,
+      OrmException {
     AccountState a = accountCache.getByUsername(acc.username);
+
     if (a == null) {
       switch (authType) {
         case HTTP_LDAP:
         case CLIENT_SSL_CERT_LDAP:
         case LDAP:
-          return createAccountByLdap(acc.username);
+          return createAccountByLdapAndAddSshKeys(api, acc, a);
         default:
           throw new NoSuchAccountException(String.format("User %s not found",
               acc.username));
@@ -63,21 +79,46 @@
           "User %s not found: Email mismatch, expected %s but found %s",
           acc.username, acc.email, a.getAccount().getPreferredEmail()));
     }
+
     return a.getAccount().getId();
   }
 
-  private Account.Id createAccountByLdap(String user)
-      throws NoSuchAccountException {
-    if (!user.matches(Account.USER_NAME_PATTERN)) {
-      throw new NoSuchAccountException(String.format("User %s not found", user));
+  private Account.Id createAccountByLdapAndAddSshKeys(RemoteApi api,
+      AccountInfo acc, AccountState a)
+      throws NoSuchAccountException, BadRequestException, IOException,
+      OrmException {
+    if (!acc.username.matches(Account.USER_NAME_PATTERN)) {
+      throw new NoSuchAccountException(String.format("User %s not found",
+          acc.username));
     }
 
     try {
-      AuthRequest req = AuthRequest.forUser(user);
+      AuthRequest req = AuthRequest.forUser(acc.username);
       req.setSkipAuthentication(true);
-      return accountManager.authenticate(req).getAccountId();
+      Account.Id id = accountManager.authenticate(req).getAccountId();
+      addSshKeys(api, acc, a);
+      return id;
     } catch (AccountException e) {
-      throw new NoSuchAccountException(String.format("User %s not found", user));
+      throw new NoSuchAccountException(
+          String.format("User %s not found", acc.username));
     }
   }
+
+  private void addSshKeys(RemoteApi api, AccountInfo acc, AccountState a) throws
+  BadRequestException, IOException, OrmException {
+    List<SshKeyInfo> sshKeys = api.getSshKeys(acc.username);
+    db.get().accountSshKeys().upsert(toAccountSshKey(a, sshKeys));
+  }
+
+  private static Collection<AccountSshKey> toAccountSshKey(AccountState a,
+      List<SshKeyInfo> sshKeys) {
+    Collection<AccountSshKey> result = new HashSet<>();
+    int index = 1;
+    for (SshKeyInfo sshKeyInfo : sshKeys) {
+      result.add(new AccountSshKey(
+          new AccountSshKey.Id(a.getAccount().getId(), index++),
+          sshKeyInfo.sshPublicKey));
+    }
+    return result;
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/AddApprovalsStep.java b/src/main/java/com/googlesource/gerrit/plugins/importer/AddApprovalsStep.java
index f5afd95..cf08a37 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/AddApprovalsStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/AddApprovalsStep.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.common.ApprovalInfo;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
@@ -71,8 +72,8 @@
     this.resume = resume;
   }
 
-  void add() throws OrmException, NoSuchChangeException, IOException,
-      NoSuchAccountException {
+  void add(RemoteApi api) throws OrmException, NoSuchChangeException, IOException,
+      NoSuchAccountException, BadRequestException {
     if (resume) {
       db.patchSetApprovals().delete(
           db.patchSetApprovals().byChange(change.getId()));
@@ -84,7 +85,7 @@
       LabelInfo label = e.getValue();
       if (label.all != null) {
         for (ApprovalInfo a : label.all) {
-          Account.Id user = accountUtil.resolveUser(a);
+          Account.Id user = accountUtil.resolveUser(api, a);
           ChangeControl ctrl = control(change, a);
           LabelType labelType = ctrl.getLabelTypes().byLabel(labelName);
           approvals.add(new PatchSetApproval(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java b/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java
index 7d8beac..80fcf5a 100755
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 
@@ -34,6 +35,7 @@
 import java.util.Map;
 
 public class RemoteApi {
+
   private final RestSession restSession;
 
   RemoteApi(String url, String user, String pass) {
@@ -102,6 +104,14 @@
     return Iterables.concat(result.values());
   }
 
+  public List<SshKeyInfo> getSshKeys(String userId) throws BadRequestException, IOException {
+    String endPoint = "/accounts/" + userId + "/sshkeys/";
+    try (RestResponse r = checkedGet(endPoint)) {
+      return newGson().fromJson(r.getReader(),
+          new TypeToken<List<SshKeyInfo>>() {}.getType());
+    }
+  }
+
   private static Gson newGson() {
     return OutputFormat.JSON_COMPACT.newGson();
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java
index a961e69..e32d1ec 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.common.errors.NoSuchAccountException;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
@@ -151,12 +152,12 @@
         return;
       }
     }
-    replayRevisionsFactory.create(repo, rw, change, c).replay();
+    replayRevisionsFactory.create(repo, rw, change, c).replay(api);
     upsertChange(resumeChange, change, c);
 
     replayInlineCommentsFactory.create(change, c, api, resumeChange).replay();
-    replayMessagesFactory.create(change, c, resumeChange).replay();
-    addApprovalsFactory.create(change, c, resume).add();
+    replayMessagesFactory.create(change, c, resumeChange).replay(api);
+    addApprovalsFactory.create(change, c, resume).add(api);
     addHashtagsFactory.create(change, c, resumeChange).add();
 
     insertLinkToOriginalFactory.create(fromGerrit,change, c, resumeChange).insert();
@@ -178,11 +179,11 @@
   }
 
   private Change createChange(ChangeInfo c) throws OrmException,
-      NoSuchAccountException {
+      NoSuchAccountException, BadRequestException, IOException {
     Change.Id changeId = new Change.Id(db.nextChangeId());
 
     Change change =
-        new Change(new Change.Key(c.changeId), changeId, accountUtil.resolveUser(c.owner),
+        new Change(new Change.Key(c.changeId), changeId, accountUtil.resolveUser(api, c.owner),
             new Branch.NameKey(targetProject, fullName(c.branch)), c.created);
     change.setStatus(Change.Status.forChangeStatus(c.status));
     change.setTopic(c.topic);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayInlineCommentsStep.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayInlineCommentsStep.java
index 1e2fa12..15e4d51 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayInlineCommentsStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayInlineCommentsStep.java
@@ -107,7 +107,7 @@
 
       Multimap<Account.Id, CommentInfo> commentsByAuthor = ArrayListMultimap.create();
       for (CommentInfo comment : comments) {
-        Account.Id id  = accountUtil.resolveUser(comment.author);
+        Account.Id id  = accountUtil.resolveUser(api, comment.author);
         commentsByAuthor.put(id, comment);
       }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayMessagesStep.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayMessagesStep.java
index 1b2d5e8..6502b3e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayMessagesStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayMessagesStep.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.errors.NoSuchAccountException;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -73,8 +74,8 @@
     this.resume = resume;
   }
 
-  void replay() throws NoSuchAccountException, NoSuchChangeException,
-      OrmException, IOException {
+  void replay(RemoteApi api) throws NoSuchAccountException, NoSuchChangeException,
+      OrmException, IOException, BadRequestException {
     for (ChangeMessageInfo msg : changeInfo.messages) {
       ChangeMessage.Key msgKey = new ChangeMessage.Key(change.getId(), msg.id);
       if (resume && db.changeMessages().get(msgKey) != null) {
@@ -84,7 +85,7 @@
 
       Timestamp ts = msg.date;
       if (msg.author != null) {
-        Account.Id userId = accountUtil.resolveUser(msg.author);
+        Account.Id userId = accountUtil.resolveUser(api, msg.author);
         ChangeUpdate update = updateFactory.create(control(change, userId), ts);
         ChangeMessage cmsg =
             new ChangeMessage(msgKey, userId, ts, new PatchSet.Id(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayRevisionsStep.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayRevisionsStep.java
index 8d5b152..6572c1e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayRevisionsStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayRevisionsStep.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.errors.NoSuchAccountException;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.RevisionInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
@@ -73,7 +74,8 @@
     this.changeInfo = changeInfo;
   }
 
-  void replay() throws IOException, OrmException, NoSuchAccountException {
+  void replay(RemoteApi api) throws IOException, OrmException, NoSuchAccountException,
+      BadRequestException {
     List<RevisionInfo> revisions = new ArrayList<>(changeInfo.revisions.values());
     sortRevisionInfoByNumber(revisions);
     List<PatchSet> patchSets = new ArrayList<>();
@@ -101,7 +103,7 @@
 
         patchSets.add(ps);
 
-        ps.setUploader(accountUtil.resolveUser(r.uploader));
+        ps.setUploader(accountUtil.resolveUser(api, r.uploader));
         ps.setCreatedOn(r.created);
         ps.setRevision(new RevId(commit.name()));
         ps.setDraft(r.draft != null && r.draft);