diff options
Diffstat (limited to 'gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java')
-rw-r--r-- | gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java | 296 |
1 files changed, 0 insertions, 296 deletions
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java deleted file mode 100644 index 39e789a614..0000000000 --- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (C) 2015 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.server; - -import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString; -import static com.google.gerrit.gpg.PublicKeyStore.keyToString; -import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY; -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.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.common.io.BaseEncoding; -import com.google.gerrit.common.errors.EmailException; -import com.google.gerrit.extensions.common.GpgKeyInfo; -import com.google.gerrit.extensions.restapi.BadRequestException; -import com.google.gerrit.extensions.restapi.ResourceConflictException; -import com.google.gerrit.extensions.restapi.ResourceNotFoundException; -import com.google.gerrit.extensions.restapi.RestModifyView; -import com.google.gerrit.gpg.CheckResult; -import com.google.gerrit.gpg.Fingerprint; -import com.google.gerrit.gpg.GerritPublicKeyChecker; -import com.google.gerrit.gpg.PublicKeyChecker; -import com.google.gerrit.gpg.PublicKeyStore; -import com.google.gerrit.gpg.server.PostGpgKeys.Input; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.server.CurrentUser; -import com.google.gerrit.server.GerritPersonIdent; -import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.account.AccountResource; -import com.google.gerrit.server.account.AccountState; -import com.google.gerrit.server.account.externalids.ExternalId; -import com.google.gerrit.server.account.externalids.ExternalIds; -import com.google.gerrit.server.account.externalids.ExternalIdsUpdate; -import com.google.gerrit.server.mail.send.AddKeySender; -import com.google.gerrit.server.query.account.InternalAccountQuery; -import com.google.gwtorm.server.OrmException; -import com.google.inject.Inject; -import com.google.inject.Provider; -import com.google.inject.Singleton; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPRuntimeOperationException; -import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.lib.CommitBuilder; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.lib.RefUpdate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Singleton -public class PostGpgKeys implements RestModifyView<AccountResource, Input> { - public static class Input { - public List<String> add; - public List<String> delete; - } - - private final Logger log = LoggerFactory.getLogger(getClass()); - private final Provider<PersonIdent> serverIdent; - private final Provider<CurrentUser> self; - private final Provider<PublicKeyStore> storeProvider; - private final GerritPublicKeyChecker.Factory checkerFactory; - private final AddKeySender.Factory addKeyFactory; - private final Provider<InternalAccountQuery> accountQueryProvider; - private final ExternalIds externalIds; - private final ExternalIdsUpdate.User externalIdsUpdateFactory; - - @Inject - PostGpgKeys( - @GerritPersonIdent Provider<PersonIdent> serverIdent, - Provider<CurrentUser> self, - Provider<PublicKeyStore> storeProvider, - GerritPublicKeyChecker.Factory checkerFactory, - AddKeySender.Factory addKeyFactory, - Provider<InternalAccountQuery> accountQueryProvider, - ExternalIds externalIds, - ExternalIdsUpdate.User externalIdsUpdateFactory) { - this.serverIdent = serverIdent; - this.self = self; - this.storeProvider = storeProvider; - this.checkerFactory = checkerFactory; - this.addKeyFactory = addKeyFactory; - this.accountQueryProvider = accountQueryProvider; - this.externalIds = externalIds; - this.externalIdsUpdateFactory = externalIdsUpdateFactory; - } - - @Override - public Map<String, GpgKeyInfo> apply(AccountResource rsrc, Input input) - throws ResourceNotFoundException, BadRequestException, ResourceConflictException, - PGPException, OrmException, IOException, ConfigInvalidException { - GpgKeys.checkVisible(self, rsrc); - - Collection<ExternalId> existingExtIds = - externalIds.byAccount(rsrc.getUser().getAccountId(), SCHEME_GPGKEY); - try (PublicKeyStore store = storeProvider.get()) { - Set<Fingerprint> toRemove = readKeysToRemove(input, existingExtIds); - List<PGPPublicKeyRing> newKeys = readKeysToAdd(input, toRemove); - List<ExternalId> newExtIds = new ArrayList<>(existingExtIds.size()); - - for (PGPPublicKeyRing keyRing : newKeys) { - PGPPublicKey key = keyRing.getPublicKey(); - ExternalId.Key extIdKey = toExtIdKey(key.getFingerprint()); - Account account = getAccountByExternalId(extIdKey); - if (account != null) { - if (!account.getId().equals(rsrc.getUser().getAccountId())) { - throw new ResourceConflictException("GPG key already associated with another account"); - } - } else { - newExtIds.add(ExternalId.create(extIdKey, rsrc.getUser().getAccountId())); - } - } - - storeKeys(rsrc, newKeys, toRemove); - - List<ExternalId.Key> extIdKeysToRemove = - toRemove.stream().map(fp -> toExtIdKey(fp.get())).collect(toList()); - externalIdsUpdateFactory - .create() - .replace(rsrc.getUser().getAccountId(), extIdKeysToRemove, newExtIds); - return toJson(newKeys, toRemove, store, rsrc.getUser()); - } - } - - private Set<Fingerprint> readKeysToRemove(Input input, Collection<ExternalId> existingExtIds) { - if (input.delete == null || input.delete.isEmpty()) { - return ImmutableSet.of(); - } - Set<Fingerprint> fingerprints = Sets.newHashSetWithExpectedSize(input.delete.size()); - for (String id : input.delete) { - try { - fingerprints.add(new Fingerprint(GpgKeys.parseFingerprint(id, existingExtIds))); - } catch (ResourceNotFoundException e) { - // Skip removal. - } - } - return fingerprints; - } - - private List<PGPPublicKeyRing> readKeysToAdd(Input input, Set<Fingerprint> toRemove) - throws BadRequestException, IOException { - if (input.add == null || input.add.isEmpty()) { - return ImmutableList.of(); - } - List<PGPPublicKeyRing> keyRings = new ArrayList<>(input.add.size()); - for (String armored : input.add) { - try (InputStream in = new ByteArrayInputStream(armored.getBytes(UTF_8)); - ArmoredInputStream ain = new ArmoredInputStream(in)) { - @SuppressWarnings("unchecked") - List<Object> objs = Lists.newArrayList(new BcPGPObjectFactory(ain)); - if (objs.size() != 1 || !(objs.get(0) instanceof PGPPublicKeyRing)) { - throw new BadRequestException("Expected exactly one PUBLIC KEY BLOCK"); - } - PGPPublicKeyRing keyRing = (PGPPublicKeyRing) objs.get(0); - if (toRemove.contains(new Fingerprint(keyRing.getPublicKey().getFingerprint()))) { - throw new BadRequestException( - "Cannot both add and delete key: " + keyToString(keyRing.getPublicKey())); - } - keyRings.add(keyRing); - } catch (PGPRuntimeOperationException e) { - throw new BadRequestException("Failed to parse GPG keys", e); - } - } - return keyRings; - } - - private void storeKeys( - AccountResource rsrc, List<PGPPublicKeyRing> keyRings, Set<Fingerprint> toRemove) - throws BadRequestException, ResourceConflictException, PGPException, IOException { - try (PublicKeyStore store = storeProvider.get()) { - List<String> addedKeys = new ArrayList<>(); - for (PGPPublicKeyRing keyRing : keyRings) { - PGPPublicKey key = keyRing.getPublicKey(); - // Don't check web of trust; admins can fill in certifications later. - CheckResult result = checkerFactory.create(rsrc.getUser(), store).disableTrust().check(key); - if (!result.isOk()) { - throw new BadRequestException( - String.format( - "Problems with public key %s:\n%s", - keyToString(key), Joiner.on('\n').join(result.getProblems()))); - } - addedKeys.add(PublicKeyStore.keyToString(key)); - store.add(keyRing); - } - for (Fingerprint fp : toRemove) { - store.remove(fp.get()); - } - CommitBuilder cb = new CommitBuilder(); - PersonIdent committer = serverIdent.get(); - cb.setAuthor(rsrc.getUser().newCommitterIdent(committer.getWhen(), committer.getTimeZone())); - cb.setCommitter(committer); - - RefUpdate.Result saveResult = store.save(cb); - switch (saveResult) { - case NEW: - case FAST_FORWARD: - case FORCED: - try { - addKeyFactory.create(rsrc.getUser(), addedKeys).send(); - } catch (EmailException e) { - log.error( - "Cannot send GPG key added message to " - + rsrc.getUser().getAccount().getPreferredEmail(), - e); - } - break; - case NO_CHANGE: - break; - case IO_FAILURE: - case LOCK_FAILURE: - case NOT_ATTEMPTED: - case REJECTED: - case REJECTED_CURRENT_BRANCH: - case RENAMED: - case REJECTED_MISSING_OBJECT: - case REJECTED_OTHER_REASON: - default: - // TODO(dborowitz): Backoff and retry on LOCK_FAILURE. - throw new ResourceConflictException("Failed to save public keys: " + saveResult); - } - } - } - - private ExternalId.Key toExtIdKey(byte[] fp) { - return ExternalId.Key.create(SCHEME_GPGKEY, BaseEncoding.base16().encode(fp)); - } - - private Account getAccountByExternalId(ExternalId.Key extIdKey) throws OrmException { - List<AccountState> accountStates = accountQueryProvider.get().byExternalId(extIdKey); - - if (accountStates.isEmpty()) { - return null; - } - - if (accountStates.size() > 1) { - StringBuilder msg = new StringBuilder(); - msg.append("GPG key ") - .append(extIdKey.get()) - .append(" associated with multiple accounts: ") - .append(Lists.transform(accountStates, AccountState.ACCOUNT_ID_FUNCTION)); - throw new IllegalStateException(msg.toString()); - } - - return accountStates.get(0).getAccount(); - } - - private Map<String, GpgKeyInfo> toJson( - Collection<PGPPublicKeyRing> keys, - Set<Fingerprint> deleted, - PublicKeyStore store, - IdentifiedUser user) - throws IOException { - // Unlike when storing keys, include web-of-trust checks when producing - // result JSON, so the user at least knows of any issues. - PublicKeyChecker checker = checkerFactory.create(user, store); - Map<String, GpgKeyInfo> infos = Maps.newHashMapWithExpectedSize(keys.size() + deleted.size()); - for (PGPPublicKeyRing keyRing : keys) { - PGPPublicKey key = keyRing.getPublicKey(); - CheckResult result = checker.check(key); - GpgKeyInfo info = GpgKeys.toJson(key, result); - infos.put(info.id, info); - info.id = null; - } - for (Fingerprint fp : deleted) { - infos.put(keyIdToString(fp.getId()), new GpgKeyInfo()); - } - return infos; - } -} |