All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.account.VersionedAuthorizedKeys Maven / Gradle / Ivy

There is a newer version: 3.11.0-rc3
Show newest version
// 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 static com.google.common.base.Preconditions.checkState;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;

import com.google.common.base.Strings;
import com.google.common.collect.Ordering;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.InvalidSshKeyException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import com.google.gerrit.server.ssh.SshKeyCreator;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Repository;

/**
 * 'authorized_keys' file in the refs/users/CD/ABCD branches of the All-Users repository.
 *
 * 

The `authorized_keys' files stores the public SSH keys of the user. The file format matches * the standard SSH file format, which means that each key is stored on a separate line (see * https://en.wikibooks.org/wiki/OpenSSH/Client_Configuration_Files#.7E.2F.ssh.2Fauthorized_keys). * *

The order of the keys in the file determines the sequence numbers of the keys. The first line * corresponds to sequence number 1. * *

Invalid keys are marked with the prefix # INVALID. * *

To keep the sequence numbers intact when a key is deleted, a # DELETED line is * inserted at the position where the key was deleted. * *

Other comment lines are ignored on read, and are not written back when the file is modified. */ public class VersionedAuthorizedKeys extends VersionedMetaData { /** Read/write SSH keys by user ID. */ @Singleton public static class Accessor { private final GitRepositoryManager repoManager; private final AllUsersName allUsersName; private final VersionedAuthorizedKeys.Factory authorizedKeysFactory; private final Provider metaDataUpdateFactory; private final IdentifiedUser.GenericFactory userFactory; @Inject Accessor( GitRepositoryManager repoManager, AllUsersName allUsersName, VersionedAuthorizedKeys.Factory authorizedKeysFactory, Provider metaDataUpdateFactory, IdentifiedUser.GenericFactory userFactory) { this.repoManager = repoManager; this.allUsersName = allUsersName; this.authorizedKeysFactory = authorizedKeysFactory; this.metaDataUpdateFactory = metaDataUpdateFactory; this.userFactory = userFactory; } public List getKeys(Account.Id accountId) throws IOException, ConfigInvalidException { return read(accountId).getKeys(); } public AccountSshKey getKey(Account.Id accountId, int seq) throws IOException, ConfigInvalidException { return read(accountId).getKey(seq); } @CanIgnoreReturnValue public synchronized AccountSshKey addKey(Account.Id accountId, String pub) throws IOException, ConfigInvalidException, InvalidSshKeyException { VersionedAuthorizedKeys authorizedKeys = read(accountId); AccountSshKey key = authorizedKeys.addKey(pub); commit(authorizedKeys); return key; } public synchronized void deleteKey(Account.Id accountId, int seq) throws IOException, ConfigInvalidException { VersionedAuthorizedKeys authorizedKeys = read(accountId); if (authorizedKeys.deleteKey(seq)) { commit(authorizedKeys); } } public synchronized void markKeyInvalid(Account.Id accountId, int seq) throws IOException, ConfigInvalidException { VersionedAuthorizedKeys authorizedKeys = read(accountId); if (authorizedKeys.markKeyInvalid(seq)) { commit(authorizedKeys); } } private VersionedAuthorizedKeys read(Account.Id accountId) throws IOException, ConfigInvalidException { try (Repository git = repoManager.openRepository(allUsersName)) { VersionedAuthorizedKeys authorizedKeys = authorizedKeysFactory.create(accountId); authorizedKeys.load(allUsersName, git); return authorizedKeys; } } private void commit(VersionedAuthorizedKeys authorizedKeys) throws IOException { try (MetaDataUpdate md = metaDataUpdateFactory .get() .create(allUsersName, userFactory.create(authorizedKeys.accountId))) { authorizedKeys.commit(md); } } } public static class SimpleSshKeyCreator implements SshKeyCreator { @Override public AccountSshKey create(Account.Id accountId, int seq, String encoded) { return AccountSshKey.create(accountId, seq, encoded); } } public interface Factory { VersionedAuthorizedKeys create(Account.Id accountId); } private final SshKeyCreator sshKeyCreator; private final Account.Id accountId; private final String ref; private List> keys; @Inject public VersionedAuthorizedKeys(SshKeyCreator sshKeyCreator, @Assisted Account.Id accountId) { this.sshKeyCreator = sshKeyCreator; this.accountId = accountId; this.ref = RefNames.refsUsers(accountId); } @Override protected String getRefName() { return ref; } @Override protected void onLoad() throws IOException { keys = AuthorizedKeys.parse(accountId, readUTF8(AuthorizedKeys.FILE_NAME)); } @Override protected boolean onSave(CommitBuilder commit) throws IOException { if (Strings.isNullOrEmpty(commit.getMessage())) { commit.setMessage("Updated SSH keys\n"); } saveUTF8(AuthorizedKeys.FILE_NAME, AuthorizedKeys.serialize(keys)); return true; } /** Returns all SSH keys. */ private List getKeys() { checkLoaded(); return keys.stream().filter(Optional::isPresent).map(Optional::get).collect(toList()); } /** * Returns the SSH key with the given sequence number. * * @param seq sequence number * @return the SSH key, null if there is no SSH key with this sequence number, or if * the SSH key with this sequence number has been deleted */ @Nullable private AccountSshKey getKey(int seq) { checkLoaded(); return keys.get(seq - 1).orElse(null); } /** * Adds a new public SSH key. * *

If the specified public key exists already, the existing key is returned. * * @param pub the public SSH key to be added * @return the new SSH key */ private AccountSshKey addKey(String pub) throws InvalidSshKeyException { checkLoaded(); for (Optional key : keys) { if (key.isPresent() && key.get().sshPublicKey().trim().equals(pub.trim())) { return key.get(); } } int seq = keys.size() + 1; AccountSshKey key = sshKeyCreator.create(accountId, seq, pub); keys.add(Optional.of(key)); return key; } /** * Deletes the SSH key with the given sequence number. * * @param seq the sequence number * @return true if a key with this sequence number was found and deleted, false * if no key with the given sequence number exists */ private boolean deleteKey(int seq) { checkLoaded(); if (seq <= keys.size() && keys.get(seq - 1).isPresent()) { keys.set(seq - 1, Optional.empty()); return true; } return false; } /** * Marks the SSH key with the given sequence number as invalid. * * @param seq the sequence number * @return true if a key with this sequence number was found and marked as invalid, * false if no key with the given sequence number exists or if the key was * already marked as invalid */ private boolean markKeyInvalid(int seq) { checkLoaded(); Optional key = keys.get(seq - 1); if (key.isPresent() && key.get().valid()) { keys.set(seq - 1, Optional.of(AccountSshKey.createInvalid(key.get()))); return true; } return false; } /** * Sets new SSH keys. * *

The existing SSH keys are overwritten. * * @param newKeys the new public SSH keys */ public void setKeys(Collection newKeys) { Ordering o = Ordering.from(comparing(AccountSshKey::seq)); keys = new ArrayList<>(Collections.nCopies(o.max(newKeys).seq(), Optional.empty())); for (AccountSshKey key : newKeys) { keys.set(key.seq() - 1, Optional.of(key)); } } private void checkLoaded() { checkState(keys != null, "SSH keys not loaded yet"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy