Extract listing of GPG keys into a util class, so it can be reused.

Release-Notes: skip
Bug: Google b/262346110
Change-Id: I93d0f6af11dbd4e13ea6d5b98a6b19de41899c6a
diff --git a/java/com/google/gerrit/gpg/PublicKeyStoreUtil.java b/java/com/google/gerrit/gpg/PublicKeyStoreUtil.java
new file mode 100644
index 0000000..5333826
--- /dev/null
+++ b/java/com/google/gerrit/gpg/PublicKeyStoreUtil.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 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.gpg;
+
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.common.io.BaseEncoding;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIds;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.eclipse.jgit.util.NB;
+
+public class PublicKeyStoreUtil {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private final ExternalIds externalIds;
+  private final Provider<PublicKeyStore> storeProvider;
+
+  @Inject
+  public PublicKeyStoreUtil(ExternalIds externalIds, Provider<PublicKeyStore> storeProvider) {
+    this.externalIds = externalIds;
+    this.storeProvider = storeProvider;
+  }
+
+  public static byte[] parseFingerprint(ExternalId gpgKeyExtId) {
+    return BaseEncoding.base16().decode(gpgKeyExtId.key().id());
+  }
+
+  public static long keyIdFromFingerprint(byte[] fp) {
+    return NB.decodeInt64(fp, fp.length - 8);
+  }
+
+  public List<PGPPublicKey> listGpgKeysForUser(Account.Id id) throws PGPException, IOException {
+    List<PGPPublicKey> keys = new ArrayList<>();
+    try (PublicKeyStore store = storeProvider.get()) {
+      for (ExternalId extId : getGpgExtIds(id)) {
+        byte[] fp = parseFingerprint(extId);
+        boolean found = false;
+        for (PGPPublicKeyRing keyRing : store.get(keyIdFromFingerprint(fp))) {
+          if (Arrays.equals(keyRing.getPublicKey().getFingerprint(), fp)) {
+            found = true;
+            keys.add(keyRing.getPublicKey());
+            break;
+          }
+        }
+        if (!found) {
+          logger.atWarning().log(
+              "No public key stored for fingerprint %s", Fingerprint.toString(fp));
+        }
+      }
+    }
+    return keys;
+  }
+
+  public Iterable<ExternalId> getGpgExtIds(Account.Id id) throws IOException {
+    return externalIds.byAccount(id, SCHEME_GPGKEY);
+  }
+}
diff --git a/java/com/google/gerrit/gpg/server/GpgKeys.java b/java/com/google/gerrit/gpg/server/GpgKeys.java
index 00a0f57..9fb8286 100644
--- a/java/com/google/gerrit/gpg/server/GpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/GpgKeys.java
@@ -14,13 +14,10 @@
 
 package com.google.gerrit.gpg.server;
 
-import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableList;
-import com.google.common.flogger.FluentLogger;
-import com.google.common.io.BaseEncoding;
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -36,10 +33,10 @@
 import com.google.gerrit.gpg.GerritPublicKeyChecker;
 import com.google.gerrit.gpg.PublicKeyChecker;
 import com.google.gerrit.gpg.PublicKeyStore;
+import com.google.gerrit.gpg.PublicKeyStoreUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -48,36 +45,35 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.eclipse.jgit.util.NB;
 
 @Singleton
 public class GpgKeys implements ChildCollection<AccountResource, GpgKey> {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final DynamicMap<RestView<GpgKey>> views;
   private final Provider<CurrentUser> self;
+  private final PublicKeyStoreUtil publicKeyStoreUtil;
   private final Provider<PublicKeyStore> storeProvider;
   private final GerritPublicKeyChecker.Factory checkerFactory;
-  private final ExternalIds externalIds;
 
   @Inject
   GpgKeys(
       DynamicMap<RestView<GpgKey>> views,
       Provider<CurrentUser> self,
+      PublicKeyStoreUtil publicKeyStoreUtil,
       Provider<PublicKeyStore> storeProvider,
-      GerritPublicKeyChecker.Factory checkerFactory,
-      ExternalIds externalIds) {
+      GerritPublicKeyChecker.Factory checkerFactory) {
     this.views = views;
     this.self = self;
+    this.publicKeyStoreUtil = publicKeyStoreUtil;
     this.storeProvider = storeProvider;
     this.checkerFactory = checkerFactory;
-    this.externalIds = externalIds;
   }
 
   @Override
@@ -90,10 +86,11 @@
       throws ResourceNotFoundException, PGPException, IOException {
     checkVisible(self, parent);
 
-    ExternalId gpgKeyExtId = findGpgKey(id.get(), getGpgExtIds(parent));
-    byte[] fp = parseFingerprint(gpgKeyExtId);
+    ExternalId gpgKeyExtId =
+        findGpgKey(id.get(), publicKeyStoreUtil.getGpgExtIds(parent.getUser().getAccountId()));
+    byte[] fp = PublicKeyStoreUtil.parseFingerprint(gpgKeyExtId);
     try (PublicKeyStore store = storeProvider.get()) {
-      long keyId = keyId(fp);
+      long keyId = PublicKeyStoreUtil.keyIdFromFingerprint(fp);
       for (PGPPublicKeyRing keyRing : store.get(keyId)) {
         PGPPublicKey key = keyRing.getPublicKey();
         if (Arrays.equals(key.getFingerprint(), fp)) {
@@ -131,10 +128,6 @@
     return gpgKeyExtId;
   }
 
-  static byte[] parseFingerprint(ExternalId gpgKeyExtId) {
-    return BaseEncoding.base16().decode(gpgKeyExtId.key().id());
-  }
-
   @Override
   public DynamicMap<RestView<GpgKey>> views() {
     return views;
@@ -145,29 +138,17 @@
     public Response<Map<String, GpgKeyInfo>> apply(AccountResource rsrc)
         throws PGPException, IOException, ResourceNotFoundException {
       checkVisible(self, rsrc);
-      Map<String, GpgKeyInfo> keys = new HashMap<>();
+      List<PGPPublicKey> keys =
+          publicKeyStoreUtil.listGpgKeysForUser(rsrc.getUser().getAccountId());
+      Map<String, GpgKeyInfo> res = new HashMap<>();
       try (PublicKeyStore store = storeProvider.get()) {
-        for (ExternalId extId : getGpgExtIds(rsrc)) {
-          byte[] fp = parseFingerprint(extId);
-          boolean found = false;
-          for (PGPPublicKeyRing keyRing : store.get(keyId(fp))) {
-            if (Arrays.equals(keyRing.getPublicKey().getFingerprint(), fp)) {
-              found = true;
-              GpgKeyInfo info =
-                  toJson(
-                      keyRing.getPublicKey(), checkerFactory.create(rsrc.getUser(), store), store);
-              keys.put(info.id, info);
-              info.id = null;
-              break;
-            }
-          }
-          if (!found) {
-            logger.atWarning().log(
-                "No public key stored for fingerprint %s", Fingerprint.toString(fp));
-          }
+        for (PGPPublicKey key : keys) {
+          GpgKeyInfo info = toJson(key, checkerFactory.create(rsrc.getUser(), store), store);
+          res.put(info.id, info);
+          info.id = null;
         }
       }
-      return Response.ok(keys);
+      return Response.ok(res);
     }
   }
 
@@ -194,14 +175,6 @@
     }
   }
 
-  private Iterable<ExternalId> getGpgExtIds(AccountResource rsrc) throws IOException {
-    return externalIds.byAccount(rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
-  }
-
-  private static long keyId(byte[] fp) {
-    return NB.decodeInt64(fp, fp.length - 8);
-  }
-
   static void checkVisible(Provider<CurrentUser> self, AccountResource rsrc)
       throws ResourceNotFoundException {
     if (!BouncyCastleUtil.havePGP()) {
diff --git a/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index d51ee6a..a45c400 100644
--- a/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -46,6 +46,7 @@
 import com.google.gerrit.gpg.GerritPublicKeyChecker;
 import com.google.gerrit.gpg.PublicKeyChecker;
 import com.google.gerrit.gpg.PublicKeyStore;
+import com.google.gerrit.gpg.PublicKeyStoreUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
@@ -175,7 +176,8 @@
     for (String id : input.delete) {
       try {
         ExternalId gpgKeyExtId = GpgKeys.findGpgKey(id, existingExtIds);
-        fingerprints.put(gpgKeyExtId, new Fingerprint(GpgKeys.parseFingerprint(gpgKeyExtId)));
+        fingerprints.put(
+            gpgKeyExtId, new Fingerprint(PublicKeyStoreUtil.parseFingerprint(gpgKeyExtId)));
       } catch (ResourceNotFoundException e) {
         // Skip removal.
       }