Line data Source code
1 : // Copyright (C) 2017 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.group.db; 16 : 17 : import static com.google.common.base.Preconditions.checkState; 18 : import static java.util.Objects.requireNonNull; 19 : 20 : import com.google.common.collect.ImmutableSet; 21 : import com.google.gerrit.common.Nullable; 22 : import com.google.gerrit.entities.Account; 23 : import com.google.gerrit.entities.AccountGroup; 24 : import com.google.gerrit.entities.GroupDescription; 25 : import com.google.gerrit.server.account.AccountCache; 26 : import com.google.gerrit.server.account.AccountState; 27 : import com.google.gerrit.server.account.GroupBackend; 28 : import java.util.Optional; 29 : import java.util.function.Function; 30 : import org.eclipse.jgit.lib.PersonIdent; 31 : 32 : /** 33 : * A formatter for entities used in an audit log which is typically represented by NoteDb commits. 34 : * 35 : * <p>The formatted representation of those entities must be parsable so that we can read them later 36 : * on and map them back to their original entities. {@link AuditLogFormatter} and {@link 37 : * com.google.gerrit.server.notedb.NoteDbUtil NoteDbUtil} contain some of those parsing/mapping 38 : * methods. 39 : */ 40 : public class AuditLogFormatter { 41 : private final Function<Account.Id, Optional<Account>> accountRetriever; 42 : private final Function<AccountGroup.UUID, Optional<GroupDescription.Basic>> groupRetriever; 43 : @Nullable private final String serverId; 44 : 45 : public static AuditLogFormatter createBackedBy( 46 : AccountCache accountCache, GroupBackend groupBackend, String serverId) { 47 151 : return create( 48 151 : accountId -> getAccount(accountCache, accountId), 49 7 : groupUuid -> getGroup(groupBackend, groupUuid), 50 : serverId); 51 : } 52 : 53 : private static Optional<Account> getAccount(AccountCache accountCache, Account.Id accountId) { 54 151 : return accountCache.get(accountId).map(AccountState::account); 55 : } 56 : 57 : private static Optional<GroupDescription.Basic> getGroup( 58 : GroupBackend groupBackend, AccountGroup.UUID groupUuid) { 59 7 : return Optional.ofNullable(groupBackend.get(groupUuid)); 60 : } 61 : 62 : public static AuditLogFormatter createBackedBy( 63 : ImmutableSet<Account> allAccounts, 64 : ImmutableSet<GroupDescription.Basic> allGroups, 65 : String serverId) { 66 152 : return create(id -> getAccount(allAccounts, id), uuid -> getGroup(allGroups, uuid), serverId); 67 : } 68 : 69 : private static Optional<GroupDescription.Basic> getGroup( 70 : ImmutableSet<GroupDescription.Basic> groups, AccountGroup.UUID uuid) { 71 1 : return groups.stream().filter(group -> group.getGroupUUID().equals(uuid)).findAny(); 72 : } 73 : 74 : private static Optional<Account> getAccount(ImmutableSet<Account> accounts, Account.Id id) { 75 1 : return accounts.stream().filter(account -> account.id().equals(id)).findAny(); 76 : } 77 : 78 : public static AuditLogFormatter createPartiallyWorkingFallBack() { 79 152 : return new AuditLogFormatter(id -> Optional.empty(), uuid -> Optional.empty()); 80 : } 81 : 82 : public static AuditLogFormatter create( 83 : Function<Account.Id, Optional<Account>> accountRetriever, 84 : Function<AccountGroup.UUID, Optional<GroupDescription.Basic>> groupRetriever, 85 : String serverId) { 86 152 : return new AuditLogFormatter(accountRetriever, groupRetriever, serverId); 87 : } 88 : 89 : private AuditLogFormatter( 90 : Function<Account.Id, Optional<Account>> accountRetriever, 91 : Function<AccountGroup.UUID, Optional<GroupDescription.Basic>> groupRetriever, 92 152 : String serverId) { 93 152 : this.accountRetriever = requireNonNull(accountRetriever); 94 152 : this.groupRetriever = requireNonNull(groupRetriever); 95 152 : this.serverId = requireNonNull(serverId); 96 152 : } 97 : 98 : private AuditLogFormatter( 99 : Function<Account.Id, Optional<Account>> accountRetriever, 100 152 : Function<AccountGroup.UUID, Optional<GroupDescription.Basic>> groupRetriever) { 101 152 : this.accountRetriever = requireNonNull(accountRetriever); 102 152 : this.groupRetriever = requireNonNull(groupRetriever); 103 152 : serverId = null; 104 152 : } 105 : 106 : /** 107 : * Creates a parsable {@code PersonIdent} for commits which are used as an audit log. 108 : * 109 : * <p><em>Parsable</em> means that we can unambiguously identify the original account when being 110 : * presented with a {@code PersonIdent} of a commit. 111 : * 112 : * <p>We typically use the initiator of an action as the author of the commit when using those 113 : * commits as an audit log. That's something which has to be specified by a caller of this method 114 : * as this class doesn't create any commits itself. 115 : * 116 : * @param account the {@code Account} of the user who should be represented 117 : * @param personIdent a {@code PersonIdent} which provides the timestamp for the created {@code 118 : * PersonIdent} 119 : * @return a {@code PersonIdent} which can be used for the author of a commit 120 : */ 121 : public PersonIdent getParsableAuthorIdent(Account account, PersonIdent personIdent) { 122 35 : return getParsableAuthorIdent(account.getName(), account.id(), personIdent); 123 : } 124 : 125 : /** 126 : * Creates a parsable {@code PersonIdent} for commits which are used as an audit log. 127 : * 128 : * <p>See {@link #getParsableAuthorIdent(Account, PersonIdent)} for further details. 129 : * 130 : * @param accountId the ID of the account of the user who should be represented 131 : * @param personIdent a {@code PersonIdent} which provides the timestamp for the created {@code 132 : * PersonIdent} 133 : * @return a {@code PersonIdent} which can be used for the author of a commit 134 : */ 135 : public PersonIdent getParsableAuthorIdent(Account.Id accountId, PersonIdent personIdent) { 136 0 : String accountName = getAccountName(accountId); 137 0 : return getParsableAuthorIdent(accountName, accountId, personIdent); 138 : } 139 : 140 : /** 141 : * Provides a parsable representation of an account for use in e.g. commit messages. 142 : * 143 : * @param accountId the ID of the account of the user who should be represented 144 : * @return the {@code String} representation of the account 145 : */ 146 : public String getParsableAccount(Account.Id accountId) { 147 152 : String accountName = getAccountName(accountId); 148 152 : return formatNameEmail(accountName, getEmailForAuditLog(accountId)); 149 : } 150 : 151 : /** 152 : * Provides a parsable representation of a group for use in e.g. commit messages. 153 : * 154 : * @param groupUuid the UUID of the group 155 : * @return the {@code String} representation of the group 156 : */ 157 : public String getParsableGroup(AccountGroup.UUID groupUuid) { 158 8 : String uuid = groupUuid.get(); 159 8 : Optional<GroupDescription.Basic> group = groupRetriever.apply(groupUuid); 160 8 : String name = group.map(GroupDescription.Basic::getName).orElse(uuid); 161 8 : return formatNameEmail(name, uuid); 162 : } 163 : 164 : private String getAccountName(Account.Id accountId) { 165 152 : Optional<Account> account = accountRetriever.apply(accountId); 166 152 : return account 167 152 : .map(Account::getName) 168 : // Historically, the database did not enforce relational integrity, so it is 169 : // possible for groups to have non-existing members. 170 152 : .orElse("No Account for Id #" + accountId); 171 : } 172 : 173 : private PersonIdent getParsableAuthorIdent( 174 : String accountname, Account.Id accountId, PersonIdent personIdent) { 175 35 : return new PersonIdent( 176 : accountname, 177 35 : getEmailForAuditLog(accountId), 178 35 : personIdent.getWhen(), 179 35 : personIdent.getTimeZone()); 180 : } 181 : 182 : private String getEmailForAuditLog(Account.Id accountId) { 183 : // If we ever switch to UUIDs for accounts, consider to remove the serverId and to use a similar 184 : // approach as for group UUIDs. 185 152 : checkState( 186 : serverId != null, "serverId must be defined; fall-back AuditLogFormatter isn't sufficient"); 187 152 : return accountId.get() + "@" + serverId; 188 : } 189 : 190 : private static String formatNameEmail(String name, String email) { 191 152 : StringBuilder formattedResult = new StringBuilder(); 192 152 : PersonIdent.appendSanitized(formattedResult, name); 193 152 : formattedResult.append(" <"); 194 152 : PersonIdent.appendSanitized(formattedResult, email); 195 152 : formattedResult.append(">"); 196 152 : return formattedResult.toString(); 197 : } 198 : }