| // 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.server; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| import static java.util.stream.Collectors.joining; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.gerrit.entities.Account; |
| import java.time.Instant; |
| import java.time.ZoneId; |
| import org.eclipse.jgit.lib.PersonIdent; |
| |
| /** |
| * Extension point that allows to control which identity should be recorded in the reflog for ref |
| * updates done by a user or done on behalf of a user. |
| */ |
| public interface RefLogIdentityProvider { |
| /** |
| * Creates a {@link PersonIdent} for the given user that should be used as the user identity in |
| * the reflog for ref updates done by this user or done on behalf of this user. |
| * |
| * <p>The returned {@link PersonIdent} is created with the current timestamp and the system |
| * default timezone. |
| * |
| * @param user the user for which a reflog identity should be created |
| */ |
| default PersonIdent newRefLogIdent(IdentifiedUser user) { |
| return newRefLogIdent(user, Instant.now(), ZoneId.systemDefault()); |
| } |
| |
| /** |
| * Creates a {@link PersonIdent} for the given user that should be used as the user identity in |
| * the reflog for ref updates done by this user or done on behalf of this user. |
| * |
| * @param user the user for which a reflog identity should be created |
| * @param when the timestamp that should be used to create the {@link PersonIdent} |
| * @param zoneId the zone ID identifying the timezone that should be used to create the {@link |
| * PersonIdent} |
| */ |
| PersonIdent newRefLogIdent(IdentifiedUser user, Instant when, ZoneId zoneId); |
| |
| /** |
| * Creates a {@link PersonIdent} for the given users that should be used as the user identity in |
| * the reflog for ref updates done by these users or done on behalf of these users. |
| * |
| * <p>Usually ref updates are done by a single user or on behalf of a single user, but with {@link |
| * com.google.gerrit.server.update.BatchUpdate} it's possible that updates of different users are |
| * batched together into a single ref update. |
| * |
| * <p>If a single user is provided or all provided users reference the same account a reflog |
| * identity for that user/account is created and returned. |
| * |
| * <p>If multiple users (that reference different accounts) are provided a shared reflog identity |
| * is created and returned. The shared reflog identity lists all involved accounts. How the shared |
| * reflog identity looks like doesn't matter much, as long as it's not the reflog identity of a |
| * real user (e.g. if impersonated updates of multiple users are batched together it must not be |
| * the reflog identity of the real user). |
| * |
| * @param users the users for which a reflog identity should be created |
| * @param when the timestamp that should be used to create the {@link PersonIdent} |
| * @param zoneId the zone ID identifying the timezone that should be used to create the {@link |
| * PersonIdent} |
| */ |
| default PersonIdent newRefLogIdent( |
| ImmutableList<IdentifiedUser> users, Instant when, ZoneId zoneId) { |
| checkState(!users.isEmpty(), "expected at least one user"); |
| |
| // If it's a single user create a reflog ident for that user. |
| // Use IdentifiedUser.newReflogIdent(Instant, ZoneId) rather than invoking |
| // #newRefLogIdent(IdentifiedUser, Instant ZoneId) directly, so that we can benefit from the |
| // reflog ident caching in IdentifiedUser. |
| if (users.size() == 1 || users.stream().allMatch(user -> user.hasSameAccountId(users.get(0)))) { |
| return users.get(0).newRefLogIdent(when, zoneId); |
| } |
| |
| // Multiple users (for different accounts) have been provided. Create a shared relog identity |
| // that lists all involved accounts. |
| String accounts = |
| users.stream() |
| .map(IdentifiedUser::getAccountId) |
| .map(Account.Id::get) |
| .distinct() |
| .sorted() |
| .map(id -> "account-" + id) |
| .collect(joining("|")); |
| return new PersonIdent( |
| accounts, String.format("%s@%s", accounts, getDefaultDomain()), when, zoneId); |
| } |
| |
| /** |
| * Returns the default domain for constructing email addresses if guessing the correct host is not |
| * possible. |
| */ |
| default String getDefaultDomain() { |
| return "unknown"; |
| } |
| } |