LCOV - code coverage report
Current view: top level - server/group/db - AuditLogFormatter.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 43 45 95.6 %
Date: 2022-11-19 15:00:39 Functions: 23 26 88.5 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750