Line data Source code
1 : // Copyright (C) 2013 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.query.change; 16 : 17 : import com.google.gerrit.common.Nullable; 18 : import com.google.gerrit.entities.Account; 19 : import com.google.gerrit.entities.AccountGroup; 20 : import com.google.gerrit.entities.Change; 21 : import com.google.gerrit.entities.LabelType; 22 : import com.google.gerrit.entities.LabelTypes; 23 : import com.google.gerrit.entities.PatchSetApproval; 24 : import com.google.gerrit.extensions.restapi.AuthException; 25 : import com.google.gerrit.server.IdentifiedUser; 26 : import com.google.gerrit.server.index.change.ChangeField; 27 : import com.google.gerrit.server.permissions.ChangePermission; 28 : import com.google.gerrit.server.permissions.PermissionBackend; 29 : import com.google.gerrit.server.permissions.PermissionBackendException; 30 : import com.google.gerrit.server.project.ProjectCache; 31 : import com.google.gerrit.server.project.ProjectState; 32 : import com.google.gerrit.server.query.change.ChangeData.StorageConstraint; 33 : import java.util.Optional; 34 : 35 : public class EqualsLabelPredicate extends ChangeIndexPostFilterPredicate { 36 : protected final ProjectCache projectCache; 37 : protected final PermissionBackend permissionBackend; 38 : protected final IdentifiedUser.GenericFactory userFactory; 39 : /** label name to be matched. */ 40 : protected final String label; 41 : 42 : /** Expected vote value for the label. */ 43 : protected final int expVal; 44 : 45 : /** 46 : * Number of times the value {@link #expVal} for label {@link #label} should occur. If null, match 47 : * with any count greater or equal to 1. 48 : */ 49 : @Nullable protected final Integer count; 50 : 51 : /** Account ID that has voted on the label. */ 52 : protected final Account.Id account; 53 : 54 : protected final AccountGroup.UUID group; 55 : 56 : public EqualsLabelPredicate( 57 : LabelPredicate.Args args, 58 : String label, 59 : int expVal, 60 : Account.Id account, 61 : @Nullable Integer count) { 62 13 : super(ChangeField.LABEL, ChangeField.formatLabel(label, expVal, account, count)); 63 13 : this.permissionBackend = args.permissionBackend; 64 13 : this.projectCache = args.projectCache; 65 13 : this.userFactory = args.userFactory; 66 13 : this.count = count; 67 13 : this.group = args.group; 68 13 : this.label = label; 69 13 : this.expVal = expVal; 70 13 : this.account = account; 71 13 : } 72 : 73 : @Override 74 : public boolean match(ChangeData object) { 75 12 : Change c = object.change(); 76 12 : if (c == null) { 77 : // The change has disappeared. 78 : // 79 0 : return false; 80 : } 81 : 82 12 : if (Integer.valueOf(0).equals(count)) { 83 : // We don't match against count=0 so that the computation is identical to the stored values 84 : // in the index. We do that since computing count=0 requires looping on all {label_type, 85 : // vote_value} for the change and storing a {count=0} format for it in the change index which 86 : // is computationally expensive. 87 4 : return false; 88 : } 89 : 90 12 : Optional<ProjectState> project = projectCache.get(c.getDest().project()); 91 12 : if (!project.isPresent()) { 92 : // The project has disappeared. 93 : // 94 0 : return false; 95 : } 96 : 97 12 : LabelType labelType = type(project.get().getLabelTypes(), label); 98 12 : if (labelType == null) { 99 11 : return false; // Label is not defined by this project. 100 : } 101 : 102 9 : boolean hasVote = false; 103 9 : int matchingVotes = 0; 104 9 : StorageConstraint currentStorageConstraint = object.getStorageConstraint(); 105 9 : object.setStorageConstraint(ChangeData.StorageConstraint.INDEX_PRIMARY_NOTEDB_SECONDARY); 106 9 : for (PatchSetApproval p : object.currentApprovals()) { 107 8 : if (labelType.matches(p)) { 108 8 : hasVote = true; 109 8 : if (match(object, p.value(), p.accountId())) { 110 8 : matchingVotes += 1; 111 : } 112 : } 113 8 : } 114 9 : object.setStorageConstraint(currentStorageConstraint); 115 9 : if (!hasVote && expVal == 0) { 116 0 : return true; 117 : } 118 : 119 9 : return count == null ? matchingVotes >= 1 : matchingVotes == count; 120 : } 121 : 122 : @Nullable 123 : protected static LabelType type(LabelTypes types, String toFind) { 124 12 : if (types.byLabel(toFind).isPresent()) { 125 9 : return types.byLabel(toFind).get(); 126 : } 127 : 128 11 : for (LabelType lt : types.getLabelTypes()) { 129 11 : if (toFind.equalsIgnoreCase(lt.getName())) { 130 0 : return lt; 131 : } 132 11 : } 133 11 : return null; 134 : } 135 : 136 : protected boolean match(ChangeData cd, short value, Account.Id approver) { 137 8 : if (value != expVal) { 138 5 : return false; 139 : } 140 : 141 8 : if (account != null) { 142 : // case when account in query is numeric 143 5 : if (!account.equals(approver) && !isMagicUser()) { 144 4 : return false; 145 : } 146 : 147 : // case when account in query = owner 148 5 : if (account.equals(ChangeQueryBuilder.OWNER_ACCOUNT_ID) 149 4 : && !cd.change().getOwner().equals(approver)) { 150 4 : return false; 151 : } 152 : 153 : // case when account in query = non_uploader 154 5 : if (account.equals(ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID) 155 5 : && cd.currentPatchSet().uploader().equals(approver)) { 156 5 : return false; 157 : } 158 : } 159 : 160 8 : IdentifiedUser reviewer = userFactory.create(approver); 161 8 : if (group != null && !reviewer.getEffectiveGroups().contains(group)) { 162 4 : return false; 163 : } 164 : 165 : // Check the user has 'READ' permission. 166 : try { 167 8 : PermissionBackend.ForChange perm = permissionBackend.absentUser(approver).change(cd); 168 8 : if (!projectCache.get(cd.project()).map(ProjectState::statePermitsRead).orElse(false)) { 169 0 : return false; 170 : } 171 : 172 8 : perm.check(ChangePermission.READ); 173 8 : return true; 174 0 : } catch (PermissionBackendException | AuthException e) { 175 0 : return false; 176 : } 177 : } 178 : 179 : private boolean isMagicUser() { 180 5 : return account.equals(ChangeQueryBuilder.OWNER_ACCOUNT_ID) 181 5 : || account.equals(ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID); 182 : } 183 : 184 : @Override 185 : public int getCost() { 186 13 : return 1 + (group == null ? 0 : 1); 187 : } 188 : }