LCOV - code coverage report
Current view: top level - server/permissions - ChangeControl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 122 130 93.8 %
Date: 2022-11-19 15:00:39 Functions: 33 34 97.1 %

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

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