Line data Source code
1 : // Copyright (C) 2009 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.permissions; 16 : 17 : import static com.google.gerrit.server.permissions.DefaultPermissionMappings.labelPermissionName; 18 : import static com.google.gerrit.server.permissions.LabelPermission.ForUser.ON_BEHALF_OF; 19 : 20 : import com.google.common.collect.Maps; 21 : import com.google.common.collect.Sets; 22 : import com.google.gerrit.entities.Account; 23 : import com.google.gerrit.entities.Change; 24 : import com.google.gerrit.entities.Permission; 25 : import com.google.gerrit.entities.PermissionRange; 26 : import com.google.gerrit.exceptions.StorageException; 27 : import com.google.gerrit.extensions.conditions.BooleanCondition; 28 : import com.google.gerrit.extensions.restapi.AuthException; 29 : import com.google.gerrit.server.CurrentUser; 30 : import com.google.gerrit.server.permissions.PermissionBackend.ForChange; 31 : import com.google.gerrit.server.query.change.ChangeData; 32 : import java.util.Collection; 33 : import java.util.EnumSet; 34 : import java.util.Map; 35 : import java.util.Set; 36 : 37 : /** Access control management for a user accessing a single change. */ 38 : class ChangeControl { 39 : private final RefControl refControl; 40 : private final ChangeData changeData; 41 : 42 103 : ChangeControl(RefControl refControl, ChangeData changeData) { 43 103 : this.refControl = refControl; 44 103 : this.changeData = changeData; 45 103 : } 46 : 47 : ForChange asForChange() { 48 103 : return new ForChangeImpl(); 49 : } 50 : 51 : private CurrentUser getUser() { 52 103 : return refControl.getUser(); 53 : } 54 : 55 : private ProjectControl getProjectControl() { 56 60 : return refControl.getProjectControl(); 57 : } 58 : 59 : private Change getChange() { 60 103 : return changeData.change(); 61 : } 62 : 63 : /** Can this user see this change? */ 64 : boolean isVisible() { 65 103 : if (getChange().isPrivate() && !isPrivateVisible(changeData)) { 66 14 : return false; 67 : } 68 : // Does the user have READ permission on the destination? 69 103 : return refControl.asForRef().testOrFalse(RefPermission.READ); 70 : } 71 : 72 : /** Can this user abandon this change? */ 73 : private boolean canAbandon() { 74 56 : return isOwner() // owner (aka creator) of the change can abandon 75 16 : || refControl.isOwner() // branch owner can abandon 76 16 : || getProjectControl().isOwner() // project owner can abandon 77 16 : || refControl.canPerform(Permission.ABANDON) // user can abandon a specific ref 78 56 : || getProjectControl().isAdmin(); 79 : } 80 : 81 : /** Can this user rebase this change? */ 82 : private boolean canRebase() { 83 54 : return (isOwner() || refControl.canSubmit(isOwner()) || refControl.canRebase()) 84 54 : && refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE); 85 : } 86 : 87 : /** Can this user restore this change? */ 88 : private boolean canRestore() { 89 : // Anyone who can abandon the change can restore it, as long as they can create changes. 90 14 : return canAbandon() && refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE); 91 : } 92 : 93 : /** Can this user revert this change? */ 94 : private boolean canRevert() { 95 27 : return refControl.canRevert() && refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE); 96 : } 97 : 98 : /** The range of permitted values associated with a label permission. */ 99 : private PermissionRange getRange(String permission) { 100 103 : return refControl.getRange(permission, isOwner()); 101 : } 102 : 103 : /** Can this user add a patch set to this change? */ 104 : private boolean canAddPatchSet() { 105 54 : if (!refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE)) { 106 0 : return false; 107 : } 108 54 : if (isOwner()) { 109 51 : return true; 110 : } 111 22 : return refControl.canAddPatchSet(); 112 : } 113 : 114 : /** Is this user the owner of the change? */ 115 : private boolean isOwner() { 116 103 : if (getUser().isIdentifiedUser()) { 117 103 : Account.Id id = getUser().asIdentifiedUser().getAccountId(); 118 103 : return id.equals(getChange().getOwner()); 119 : } 120 6 : return false; 121 : } 122 : 123 : /** Is this user assigned to this change? */ 124 : private boolean isAssignee() { 125 14 : Account.Id currentAssignee = getChange().getAssignee(); 126 14 : if (currentAssignee != null && getUser().isIdentifiedUser()) { 127 0 : Account.Id id = getUser().getAccountId(); 128 0 : return id.equals(currentAssignee); 129 : } 130 14 : return false; 131 : } 132 : 133 : /** Is this user a reviewer for the change? */ 134 : private boolean isReviewer(ChangeData cd) { 135 17 : if (getUser().isIdentifiedUser()) { 136 17 : Collection<Account.Id> results = cd.reviewers().all(); 137 17 : return results.contains(getUser().getAccountId()); 138 : } 139 5 : return false; 140 : } 141 : 142 : /** Can this user edit the topic name? */ 143 : private boolean canEditTopicName() { 144 59 : if (getChange().isNew()) { 145 55 : return isOwner() // owner (aka creator) of the change can edit topic 146 14 : || refControl.isOwner() // branch owner can edit topic 147 14 : || getProjectControl().isOwner() // project owner can edit topic 148 14 : || refControl.canPerform( 149 : Permission.EDIT_TOPIC_NAME) // user can edit topic on a specific ref 150 55 : || getProjectControl().isAdmin(); 151 : } 152 25 : return refControl.canForceEditTopicName(); 153 : } 154 : 155 : /** Can this user toggle WorkInProgress state? */ 156 : private boolean canToggleWorkInProgressState() { 157 55 : return isOwner() 158 17 : || getProjectControl().isOwner() 159 17 : || refControl.canPerform(Permission.TOGGLE_WORK_IN_PROGRESS_STATE) 160 55 : || getProjectControl().isAdmin(); 161 : } 162 : 163 : /** Can this user edit the description? */ 164 : private boolean canEditDescription() { 165 57 : if (getChange().isNew()) { 166 52 : return isOwner() // owner (aka creator) of the change can edit desc 167 15 : || refControl.isOwner() // branch owner can edit desc 168 15 : || getProjectControl().isOwner() // project owner can edit desc 169 52 : || getProjectControl().isAdmin(); 170 : } 171 24 : return false; 172 : } 173 : 174 : private boolean canEditAssignee() { 175 57 : return isOwner() 176 15 : || getProjectControl().isOwner() 177 14 : || refControl.canPerform(Permission.EDIT_ASSIGNEE) 178 57 : || isAssignee(); 179 : } 180 : 181 : /** Can this user edit the hashtag name? */ 182 : private boolean canEditHashtags() { 183 57 : return isOwner() // owner (aka creator) of the change can edit hashtags 184 14 : || refControl.isOwner() // branch owner can edit hashtags 185 14 : || getProjectControl().isOwner() // project owner can edit hashtags 186 14 : || refControl.canPerform( 187 : Permission.EDIT_HASHTAGS) // user can edit hashtag on a specific ref 188 57 : || getProjectControl().isAdmin(); 189 : } 190 : 191 : private boolean isPrivateVisible(ChangeData cd) { 192 22 : return isOwner() 193 17 : || isReviewer(cd) 194 14 : || refControl.canPerform(Permission.VIEW_PRIVATE_CHANGES) 195 22 : || getUser().isInternalUser(); 196 : } 197 : 198 : private class ForChangeImpl extends ForChange { 199 : private Map<String, PermissionRange> labels; 200 : private String resourcePath; 201 : 202 103 : private ForChangeImpl() {} 203 : 204 : @Override 205 : public String resourcePath() { 206 58 : if (resourcePath == null) { 207 58 : resourcePath = 208 58 : String.format( 209 : "/projects/%s/+changes/%s", 210 58 : getProjectControl().getProjectState().getName(), changeData.getId().get()); 211 : } 212 58 : return resourcePath; 213 : } 214 : 215 : @Override 216 : public void check(ChangePermissionOrLabel perm) 217 : throws AuthException, PermissionBackendException { 218 80 : if (!can(perm)) { 219 15 : throw new AuthException(perm.describeForException() + " not permitted"); 220 : } 221 80 : } 222 : 223 : @Override 224 : public <T extends ChangePermissionOrLabel> Set<T> test(Collection<T> permSet) 225 : throws PermissionBackendException { 226 103 : Set<T> ok = newSet(permSet); 227 103 : for (T perm : permSet) { 228 103 : if (can(perm)) { 229 103 : ok.add(perm); 230 : } 231 103 : } 232 103 : return ok; 233 : } 234 : 235 : @Override 236 : public BooleanCondition testCond(ChangePermissionOrLabel perm) { 237 58 : return new PermissionBackendCondition.ForChange(this, perm, getUser()); 238 : } 239 : 240 : private boolean can(ChangePermissionOrLabel perm) throws PermissionBackendException { 241 103 : if (perm instanceof ChangePermission) { 242 103 : return can((ChangePermission) perm); 243 103 : } else if (perm instanceof LabelPermission) { 244 39 : return can((LabelPermission) perm); 245 103 : } else if (perm instanceof LabelPermission.WithValue) { 246 103 : return can((LabelPermission.WithValue) perm); 247 : } 248 0 : throw new PermissionBackendException(perm + " unsupported"); 249 : } 250 : 251 : private boolean can(ChangePermission perm) throws PermissionBackendException { 252 : try { 253 103 : switch (perm) { 254 : case READ: 255 103 : return isVisible(); 256 : case ABANDON: 257 56 : return canAbandon(); 258 : case DELETE: 259 54 : return getProjectControl().isAdmin() || refControl.canDeleteChanges(isOwner()); 260 : case ADD_PATCH_SET: 261 54 : return canAddPatchSet(); 262 : case EDIT_ASSIGNEE: 263 57 : return canEditAssignee(); 264 : case EDIT_DESCRIPTION: 265 57 : return canEditDescription(); 266 : case EDIT_HASHTAGS: 267 57 : return canEditHashtags(); 268 : case EDIT_TOPIC_NAME: 269 59 : return canEditTopicName(); 270 : case REBASE: 271 54 : return canRebase(); 272 : case RESTORE: 273 14 : return canRestore(); 274 : case REVERT: 275 27 : return canRevert(); 276 : case SUBMIT: 277 46 : return refControl.canSubmit(isOwner()); 278 : case TOGGLE_WORK_IN_PROGRESS_STATE: 279 55 : return canToggleWorkInProgressState(); 280 : 281 : case REMOVE_REVIEWER: 282 : case SUBMIT_AS: 283 103 : return refControl.canPerform(changePermissionName(perm)); 284 : } 285 0 : } catch (StorageException e) { 286 0 : throw new PermissionBackendException("unavailable", e); 287 0 : } 288 0 : throw new PermissionBackendException(perm + " unsupported"); 289 : } 290 : 291 : private boolean can(LabelPermission perm) { 292 39 : return !label(labelPermissionName(perm)).isEmpty(); 293 : } 294 : 295 : private boolean can(LabelPermission.WithValue perm) { 296 103 : PermissionRange r = label(labelPermissionName(perm)); 297 103 : if (perm.forUser() == ON_BEHALF_OF && r.isEmpty()) { 298 1 : return false; 299 : } 300 103 : return r.contains(perm.value()); 301 : } 302 : 303 : private PermissionRange label(String permission) { 304 103 : if (labels == null) { 305 103 : labels = Maps.newHashMapWithExpectedSize(4); 306 : } 307 103 : PermissionRange r = labels.get(permission); 308 103 : if (r == null) { 309 103 : r = getRange(permission); 310 103 : labels.put(permission, r); 311 : } 312 103 : return r; 313 : } 314 : } 315 : 316 : private static <T extends ChangePermissionOrLabel> Set<T> newSet(Collection<T> permSet) { 317 103 : if (permSet instanceof EnumSet) { 318 : @SuppressWarnings({"unchecked", "rawtypes"}) 319 18 : Set<T> s = ((EnumSet) permSet).clone(); 320 18 : s.clear(); 321 18 : return s; 322 : } 323 103 : return Sets.newHashSetWithExpectedSize(permSet.size()); 324 : } 325 : 326 : private static String changePermissionName(ChangePermission changePermission) { 327 : // Within this class, it's programmer error to call this method on a 328 : // ChangePermission that isn't associated with a permission name. 329 103 : return DefaultPermissionMappings.changePermissionName(changePermission) 330 103 : .orElseThrow(() -> new IllegalStateException("no name for " + changePermission)); 331 : } 332 : }