Line data Source code
1 : // Copyright (C) 2020 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.util; 16 : 17 : import com.google.common.base.Strings; 18 : import com.google.common.collect.ImmutableSet; 19 : import com.google.common.collect.Iterables; 20 : import com.google.gerrit.entities.Account; 21 : import com.google.gerrit.entities.AttentionSetUpdate; 22 : import com.google.gerrit.entities.AttentionSetUpdate.Operation; 23 : import com.google.gerrit.extensions.api.changes.AttentionSetInput; 24 : import com.google.gerrit.extensions.common.AccountInfo; 25 : import com.google.gerrit.extensions.common.AttentionSetInfo; 26 : import com.google.gerrit.extensions.restapi.BadRequestException; 27 : import com.google.gerrit.server.account.AccountLoader; 28 : import com.google.gerrit.server.account.AccountResolver; 29 : import com.google.gerrit.server.notedb.ChangeNotes; 30 : import java.io.IOException; 31 : import java.util.Collection; 32 : import org.eclipse.jgit.errors.ConfigInvalidException; 33 : 34 : /** Common helpers for dealing with attention set data structures. */ 35 : public class AttentionSetUtil { 36 : 37 : /** Returns only updates where the user was added. */ 38 : public static ImmutableSet<AttentionSetUpdate> additionsOnly( 39 : Collection<AttentionSetUpdate> updates) { 40 103 : return updates.stream() 41 103 : .filter(u -> u.operation() == Operation.ADD) 42 103 : .collect(ImmutableSet.toImmutableSet()); 43 : } 44 : 45 : /** Returns only updates where the user was removed. */ 46 : public static ImmutableSet<AttentionSetUpdate> removalsOnly( 47 : Collection<AttentionSetUpdate> updates) { 48 49 : return updates.stream() 49 49 : .filter(u -> u.operation() == Operation.REMOVE) 50 49 : .collect(ImmutableSet.toImmutableSet()); 51 : } 52 : 53 : /** 54 : * Validates the input for AttentionSetInput. This must be called for all inputs that relate to 55 : * adding or removing attention set entries, except for {@link 56 : * com.google.gerrit.server.restapi.change.RemoveFromAttentionSet}. 57 : */ 58 : public static void validateInput(AttentionSetInput input) throws BadRequestException { 59 13 : input.user = Strings.nullToEmpty(input.user).trim(); 60 13 : if (input.user.isEmpty()) { 61 2 : throw new BadRequestException("missing field: user"); 62 : } 63 12 : input.reason = Strings.nullToEmpty(input.reason).trim(); 64 12 : if (input.reason.isEmpty()) { 65 1 : throw new BadRequestException("missing field: reason"); 66 : } 67 12 : } 68 : 69 : /** 70 : * Returns the {@code Account.Id} of {@code user} if the user is active on the change, and exists. 71 : * If the user doesn't exist or is not active on the change, the same exception is thrown to 72 : * disallow probing for account existence based on exception type. 73 : */ 74 : public static Account.Id resolveAccount( 75 : AccountResolver accountResolver, ChangeNotes changeNotes, String user) 76 : throws ConfigInvalidException, IOException, BadRequestException { 77 : // We will throw this exception if the account doesn't exist, or if the account is not active. 78 : // This is purposely the same exception so that users can't probe for account existence based on 79 : // the thrown exception. 80 14 : BadRequestException possibleExceptionForNotFoundOrInactiveAccount = 81 : new BadRequestException( 82 14 : String.format( 83 : "%s doesn't exist or is not active on the change as an owner, uploader, " 84 : + "reviewer, or cc so they can't be added to the attention set", 85 : user)); 86 : Account.Id attentionUserId; 87 : try { 88 14 : attentionUserId = accountResolver.resolveIgnoreVisibility(user).asUnique().account().id(); 89 1 : } catch (AccountResolver.UnresolvableAccountException ex) { 90 1 : possibleExceptionForNotFoundOrInactiveAccount.initCause(ex); 91 1 : throw possibleExceptionForNotFoundOrInactiveAccount; 92 14 : } 93 14 : if (!isActiveOnTheChange(changeNotes, attentionUserId)) { 94 2 : throw possibleExceptionForNotFoundOrInactiveAccount; 95 : } 96 13 : return attentionUserId; 97 : } 98 : 99 : /** 100 : * Returns whether {@code attentionUserId} is active on a change. Activity is defined as being a 101 : * part of the reviewers, an uploader, or an owner of a change. 102 : */ 103 : private static boolean isActiveOnTheChange(ChangeNotes changeNotes, Account.Id attentionUserId) { 104 14 : return changeNotes.getChange().getOwner().equals(attentionUserId) 105 8 : || changeNotes.getCurrentPatchSet().uploader().equals(attentionUserId) 106 14 : || changeNotes.getReviewers().all().stream().anyMatch(id -> id.equals(attentionUserId)); 107 : } 108 : 109 : /** 110 : * Returns {@link AttentionSetInfo} from {@link AttentionSetUpdate} with {@link AccountInfo} 111 : * fields filled by {@code accountLoader}. 112 : */ 113 : public static AttentionSetInfo createAttentionSetInfo( 114 : AttentionSetUpdate attentionSetUpdate, AccountLoader accountLoader) { 115 : // Only one account is expected in attention set reason. If there are multiple, do not return 116 : // anything instead of failing the request. 117 49 : ImmutableSet<Account.Id> accountsInTemplate = 118 49 : AccountTemplateUtil.parseTemplates(attentionSetUpdate.reason()); 119 : AccountInfo reasonAccount = 120 49 : accountsInTemplate.size() == 1 121 1 : ? accountLoader.get(Iterables.getOnlyElement(accountsInTemplate)) 122 49 : : null; 123 49 : return new AttentionSetInfo( 124 49 : accountLoader.get(attentionSetUpdate.account()), 125 49 : attentionSetUpdate.timestamp(), 126 49 : attentionSetUpdate.reason(), 127 : reasonAccount); 128 : } 129 : 130 : private AttentionSetUtil() {} 131 : }