LCOV - code coverage report
Current view: top level - server/permissions - ProjectControl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 151 164 92.1 %
Date: 2022-11-19 15:00:39 Functions: 40 40 100.0 %

          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.common.base.Preconditions.checkArgument;
      18             : import static com.google.gerrit.entities.AccessSection.ALL;
      19             : import static com.google.gerrit.entities.AccessSection.REGEX_PREFIX;
      20             : import static com.google.gerrit.entities.RefNames.REFS_TAGS;
      21             : import static com.google.gerrit.server.util.MagicBranch.NEW_CHANGE;
      22             : 
      23             : import com.google.common.collect.Sets;
      24             : import com.google.gerrit.common.Nullable;
      25             : import com.google.gerrit.entities.AccessSection;
      26             : import com.google.gerrit.entities.AccountGroup;
      27             : import com.google.gerrit.entities.BranchNameKey;
      28             : import com.google.gerrit.entities.Change;
      29             : import com.google.gerrit.entities.Permission;
      30             : import com.google.gerrit.entities.PermissionRule;
      31             : import com.google.gerrit.entities.Project;
      32             : import com.google.gerrit.entities.RefNames;
      33             : import com.google.gerrit.exceptions.StorageException;
      34             : import com.google.gerrit.extensions.api.access.CoreOrPluginProjectPermission;
      35             : import com.google.gerrit.extensions.api.access.PluginProjectPermission;
      36             : import com.google.gerrit.extensions.conditions.BooleanCondition;
      37             : import com.google.gerrit.extensions.restapi.AuthException;
      38             : import com.google.gerrit.server.CurrentUser;
      39             : import com.google.gerrit.server.account.GroupMembership;
      40             : import com.google.gerrit.server.config.AllUsersName;
      41             : import com.google.gerrit.server.config.GitReceivePackGroups;
      42             : import com.google.gerrit.server.config.GitUploadPackGroups;
      43             : import com.google.gerrit.server.git.GitRepositoryManager;
      44             : import com.google.gerrit.server.group.SystemGroupBackend;
      45             : import com.google.gerrit.server.notedb.ChangeNotes;
      46             : import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
      47             : import com.google.gerrit.server.permissions.PermissionBackend.ForProject;
      48             : import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
      49             : import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
      50             : import com.google.gerrit.server.project.ProjectState;
      51             : import com.google.gerrit.server.project.SectionMatcher;
      52             : import com.google.gerrit.server.query.change.ChangeData;
      53             : import com.google.inject.Inject;
      54             : import com.google.inject.assistedinject.Assisted;
      55             : import java.util.Collection;
      56             : import java.util.Collections;
      57             : import java.util.HashMap;
      58             : import java.util.HashSet;
      59             : import java.util.List;
      60             : import java.util.Map;
      61             : import java.util.Set;
      62             : import org.eclipse.jgit.lib.Ref;
      63             : import org.eclipse.jgit.lib.Repository;
      64             : 
      65             : /** Access control management for a user accessing a project's data. */
      66             : class ProjectControl {
      67             :   interface Factory {
      68             :     ProjectControl create(CurrentUser who, ProjectState ps);
      69             :   }
      70             : 
      71             :   private final Set<AccountGroup.UUID> uploadGroups;
      72             :   private final Set<AccountGroup.UUID> receiveGroups;
      73             :   private final PermissionBackend permissionBackend;
      74             :   private final RefVisibilityControl refVisibilityControl;
      75             :   private final GitRepositoryManager repositoryManager;
      76             :   private final CurrentUser user;
      77             :   private final ProjectState state;
      78             :   private final PermissionCollection.Factory permissionFilter;
      79             :   private final DefaultRefFilter.Factory refFilterFactory;
      80             :   private final ChangeData.Factory changeDataFactory;
      81             :   private final AllUsersName allUsersName;
      82             : 
      83             :   private List<SectionMatcher> allSections;
      84             :   private Map<String, RefControl> refControls;
      85             :   private Boolean declaredOwner;
      86             : 
      87             :   @Inject
      88             :   ProjectControl(
      89             :       @GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
      90             :       @GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
      91             :       PermissionCollection.Factory permissionFilter,
      92             :       PermissionBackend permissionBackend,
      93             :       RefVisibilityControl refVisibilityControl,
      94             :       GitRepositoryManager repositoryManager,
      95             :       DefaultRefFilter.Factory refFilterFactory,
      96             :       ChangeData.Factory changeDataFactory,
      97             :       AllUsersName allUsersName,
      98             :       @Assisted CurrentUser who,
      99         145 :       @Assisted ProjectState ps) {
     100         145 :     this.uploadGroups = uploadGroups;
     101         145 :     this.receiveGroups = receiveGroups;
     102         145 :     this.permissionFilter = permissionFilter;
     103         145 :     this.permissionBackend = permissionBackend;
     104         145 :     this.refVisibilityControl = refVisibilityControl;
     105         145 :     this.repositoryManager = repositoryManager;
     106         145 :     this.refFilterFactory = refFilterFactory;
     107         145 :     this.changeDataFactory = changeDataFactory;
     108         145 :     this.allUsersName = allUsersName;
     109         145 :     user = who;
     110         145 :     state = ps;
     111         145 :   }
     112             : 
     113             :   ForProject asForProject() {
     114         145 :     return new ForProjectImpl();
     115             :   }
     116             : 
     117             :   ChangeControl controlFor(ChangeData cd) {
     118         103 :     return new ChangeControl(controlForRef(cd.change().getDest()), cd);
     119             :   }
     120             : 
     121             :   RefControl controlForRef(BranchNameKey ref) {
     122         103 :     return controlForRef(ref.branch());
     123             :   }
     124             : 
     125             :   public RefControl controlForRef(String refName) {
     126         145 :     if (refControls == null) {
     127         145 :       refControls = new HashMap<>();
     128             :     }
     129         145 :     RefControl ctl = refControls.get(refName);
     130         145 :     if (ctl == null) {
     131         145 :       PermissionCollection relevant = permissionFilter.filter(access(), refName, user);
     132         145 :       ctl =
     133             :           new RefControl(
     134             :               changeDataFactory, refVisibilityControl, this, repositoryManager, refName, relevant);
     135         145 :       refControls.put(refName, ctl);
     136             :     }
     137         145 :     return ctl;
     138             :   }
     139             : 
     140             :   CurrentUser getUser() {
     141         145 :     return user;
     142             :   }
     143             : 
     144             :   ProjectState getProjectState() {
     145         141 :     return state;
     146             :   }
     147             : 
     148             :   Project getProject() {
     149         145 :     return state.getProject();
     150             :   }
     151             : 
     152             :   /** Is this user a project owner? */
     153             :   boolean isOwner() {
     154         144 :     return (isDeclaredOwner() && controlForRef(ALL).canPerform(Permission.OWNER)) || isAdmin();
     155             :   }
     156             : 
     157             :   /**
     158             :    * Returns {@code Capable.OK} if the user can upload to at least one reference. Does not check
     159             :    * Contributor Agreements.
     160             :    */
     161             :   boolean canPushToAtLeastOneRef() {
     162          98 :     return canPerformOnAnyRef(Permission.PUSH)
     163           1 :         || canPerformOnAnyRef(Permission.CREATE_TAG)
     164          98 :         || isOwner();
     165             :   }
     166             : 
     167             :   boolean isAdmin() {
     168             :     try {
     169         144 :       return permissionBackend.user(user).test(GlobalPermission.ADMINISTRATE_SERVER);
     170           0 :     } catch (PermissionBackendException e) {
     171           0 :       return false;
     172             :     }
     173             :   }
     174             : 
     175             :   boolean match(PermissionRule rule, boolean isChangeOwner) {
     176         145 :     return match(rule.getGroup().getUUID(), isChangeOwner);
     177             :   }
     178             : 
     179             :   boolean allRefsAreVisible(Set<String> ignore) {
     180         136 :     return user.isInternalUser()
     181         136 :         || (!getProject().getNameKey().equals(allUsersName)
     182         136 :             && canPerformOnAllRefs(Permission.READ, ignore));
     183             :   }
     184             : 
     185             :   /** Can the user run upload pack? */
     186             :   private boolean canRunUploadPack() {
     187         134 :     for (AccountGroup.UUID group : uploadGroups) {
     188         134 :       if (match(group)) {
     189         134 :         return true;
     190             :       }
     191           3 :     }
     192           0 :     return false;
     193             :   }
     194             : 
     195             :   /** Can the user run receive pack? */
     196             :   private boolean canRunReceivePack() {
     197          97 :     for (AccountGroup.UUID group : receiveGroups) {
     198          97 :       if (match(group)) {
     199          97 :         return true;
     200             :       }
     201           0 :     }
     202           0 :     return false;
     203             :   }
     204             : 
     205             :   private boolean canAddRefs() {
     206           9 :     return (canPerformOnAnyRef(Permission.CREATE) || isAdmin());
     207             :   }
     208             : 
     209             :   private boolean canAddTagRefs() {
     210           9 :     return (canPerformOnTagRef(Permission.CREATE) || isAdmin());
     211             :   }
     212             : 
     213             :   private boolean canCreateChanges() {
     214          57 :     for (SectionMatcher matcher : access()) {
     215          57 :       AccessSection section = matcher.getSection();
     216          57 :       if (section.getName().startsWith(NEW_CHANGE)
     217          56 :           || section.getName().startsWith(REGEX_PREFIX + NEW_CHANGE)) {
     218          57 :         Permission permission = section.getPermission(Permission.PUSH);
     219          57 :         if (permission != null && controlForRef(section.getName()).canPerform(Permission.PUSH)) {
     220          57 :           return true;
     221             :         }
     222             :       }
     223          56 :     }
     224           0 :     return false;
     225             :   }
     226             : 
     227             :   private boolean isDeclaredOwner() {
     228         145 :     if (declaredOwner == null) {
     229         145 :       GroupMembership effectiveGroups = user.getEffectiveGroups();
     230         145 :       declaredOwner = effectiveGroups.containsAnyOf(state.getAllOwners());
     231             :     }
     232         145 :     return declaredOwner;
     233             :   }
     234             : 
     235             :   private boolean canPerformOnTagRef(String permissionName) {
     236           9 :     for (SectionMatcher matcher : access()) {
     237           9 :       AccessSection section = matcher.getSection();
     238             : 
     239           9 :       if (section.getName().startsWith(REFS_TAGS)
     240           9 :           || section.getName().startsWith(REGEX_PREFIX + REFS_TAGS)) {
     241           9 :         Permission permission = section.getPermission(permissionName);
     242           9 :         if (permission == null) {
     243           0 :           continue;
     244             :         }
     245             : 
     246           9 :         Boolean can = canPerform(permissionName, section, permission);
     247           9 :         if (can != null) {
     248           9 :           return can;
     249             :         }
     250             :       }
     251           9 :     }
     252             : 
     253           1 :     return false;
     254             :   }
     255             : 
     256             :   private boolean canPerformOnAnyRef(String permissionName) {
     257         118 :     for (SectionMatcher matcher : access()) {
     258         118 :       AccessSection section = matcher.getSection();
     259         118 :       Permission permission = section.getPermission(permissionName);
     260         118 :       if (permission == null) {
     261         118 :         continue;
     262             :       }
     263             : 
     264         118 :       Boolean can = canPerform(permissionName, section, permission);
     265         118 :       if (can != null) {
     266         118 :         return can;
     267             :       }
     268          44 :     }
     269             : 
     270          12 :     return false;
     271             :   }
     272             : 
     273             :   @Nullable
     274             :   private Boolean canPerform(String permissionName, AccessSection section, Permission permission) {
     275         118 :     for (PermissionRule rule : permission.getRules()) {
     276         118 :       if (rule.isBlock() || rule.isDeny() || !match(rule)) {
     277          44 :         continue;
     278             :       }
     279             : 
     280             :       // Being in a group that was granted this permission is only an
     281             :       // approximation.  There might be overrides and doNotInherit
     282             :       // that would render this to be false.
     283             :       //
     284         118 :       if (controlForRef(section.getName()).canPerform(permissionName)) {
     285         118 :         return true;
     286             :       }
     287             :       break;
     288             :     }
     289          44 :     return null;
     290             :   }
     291             : 
     292             :   private boolean canPerformOnAllRefs(String permission, Set<String> ignore) {
     293         135 :     boolean canPerform = false;
     294         135 :     Set<String> patterns = allRefPatterns(permission);
     295         135 :     if (patterns.contains(ALL)) {
     296             :       // Only possible if granted on the pattern that
     297             :       // matches every possible reference.  Check all
     298             :       // patterns also have the permission.
     299             :       //
     300         135 :       for (String pattern : patterns) {
     301         135 :         if (controlForRef(pattern).canPerform(permission)) {
     302         135 :           canPerform = true;
     303          31 :         } else if (ignore.contains(pattern)) {
     304          28 :           continue;
     305             :         } else {
     306          31 :           return false;
     307             :         }
     308         135 :       }
     309             :     }
     310         135 :     return canPerform;
     311             :   }
     312             : 
     313             :   private Set<String> allRefPatterns(String permissionName) {
     314         135 :     Set<String> all = new HashSet<>();
     315         135 :     for (SectionMatcher matcher : access()) {
     316         135 :       AccessSection section = matcher.getSection();
     317         135 :       Permission permission = section.getPermission(permissionName);
     318         135 :       if (permission != null) {
     319         135 :         all.add(section.getName());
     320             :       }
     321         135 :     }
     322         135 :     return all;
     323             :   }
     324             : 
     325             :   private List<SectionMatcher> access() {
     326         145 :     if (allSections == null) {
     327         145 :       allSections = state.getAllSections();
     328             :     }
     329         145 :     return allSections;
     330             :   }
     331             : 
     332             :   private boolean match(PermissionRule rule) {
     333         118 :     return match(rule.getGroup().getUUID());
     334             :   }
     335             : 
     336             :   private boolean match(AccountGroup.UUID uuid) {
     337         139 :     return match(uuid, false);
     338             :   }
     339             : 
     340             :   private boolean match(AccountGroup.UUID uuid, boolean isChangeOwner) {
     341         145 :     if (SystemGroupBackend.PROJECT_OWNERS.equals(uuid)) {
     342         114 :       return isDeclaredOwner();
     343         145 :     } else if (SystemGroupBackend.CHANGE_OWNER.equals(uuid)) {
     344           9 :       return isChangeOwner;
     345             :     } else {
     346         145 :       return user.getEffectiveGroups().contains(uuid);
     347             :     }
     348             :   }
     349             : 
     350         145 :   private class ForProjectImpl extends ForProject {
     351             :     private String resourcePath;
     352             : 
     353             :     @Override
     354             :     public String resourcePath() {
     355          58 :       if (resourcePath == null) {
     356          58 :         resourcePath = "/projects/" + getProjectState().getName();
     357             :       }
     358          58 :       return resourcePath;
     359             :     }
     360             : 
     361             :     @Override
     362             :     public ForRef ref(String ref) {
     363         132 :       return controlForRef(ref).asForRef();
     364             :     }
     365             : 
     366             :     @Override
     367             :     public ForChange change(ChangeData cd) {
     368             :       try {
     369          19 :         checkProject(cd.change());
     370          19 :         return super.change(cd);
     371           0 :       } catch (StorageException e) {
     372           0 :         return FailedPermissionBackend.change("unavailable", e);
     373             :       }
     374             :     }
     375             : 
     376             :     @Override
     377             :     public ForChange change(ChangeNotes notes) {
     378          38 :       checkProject(notes.getChange());
     379          38 :       return super.change(notes);
     380             :     }
     381             : 
     382             :     private void checkProject(Change change) {
     383          47 :       Project.NameKey project = getProject().getNameKey();
     384          47 :       checkArgument(
     385          47 :           project.equals(change.getProject()),
     386             :           "expected change in project %s, not %s",
     387             :           project,
     388          47 :           change.getProject());
     389          47 :     }
     390             : 
     391             :     @Override
     392             :     public void check(CoreOrPluginProjectPermission perm)
     393             :         throws AuthException, PermissionBackendException {
     394         144 :       if (!can(perm)) {
     395          15 :         throw new AuthException(perm.describeForException() + " not permitted");
     396             :       }
     397         144 :     }
     398             : 
     399             :     @Override
     400             :     public <T extends CoreOrPluginProjectPermission> Set<T> test(Collection<T> permSet)
     401             :         throws PermissionBackendException {
     402         143 :       Set<T> ok = Sets.newHashSetWithExpectedSize(permSet.size());
     403         143 :       for (T perm : permSet) {
     404         143 :         if (can(perm)) {
     405         143 :           ok.add(perm);
     406             :         }
     407         143 :       }
     408         143 :       return ok;
     409             :     }
     410             : 
     411             :     @Override
     412             :     public BooleanCondition testCond(CoreOrPluginProjectPermission perm) {
     413          58 :       return new PermissionBackendCondition.ForProject(this, perm, getUser());
     414             :     }
     415             : 
     416             :     @Override
     417             :     public Collection<Ref> filter(Collection<Ref> refs, Repository repo, RefFilterOptions opts)
     418             :         throws PermissionBackendException {
     419         135 :       return refFilterFactory.create(ProjectControl.this).filter(refs, repo, opts);
     420             :     }
     421             : 
     422             :     private boolean can(CoreOrPluginProjectPermission perm) throws PermissionBackendException {
     423         144 :       if (perm instanceof ProjectPermission) {
     424         144 :         return can((ProjectPermission) perm);
     425           0 :       } else if (perm instanceof PluginProjectPermission) {
     426             :         // TODO(xchangcheng): implement for plugin defined project permissions.
     427           0 :         return false;
     428             :       }
     429             : 
     430           0 :       throw new PermissionBackendException(perm.describeForException() + " unsupported");
     431             :     }
     432             : 
     433             :     private boolean can(ProjectPermission perm) throws PermissionBackendException {
     434         144 :       switch (perm) {
     435             :         case ACCESS:
     436         144 :           return user.isInternalUser() || isOwner() || canPerformOnAnyRef(Permission.READ);
     437             : 
     438             :         case READ:
     439         136 :           return allRefsAreVisible(Collections.emptySet());
     440             : 
     441             :         case CREATE_REF:
     442           9 :           return canAddRefs();
     443             :         case CREATE_TAG_REF:
     444           9 :           return canAddTagRefs();
     445             :         case CREATE_CHANGE:
     446          57 :           return canCreateChanges();
     447             : 
     448             :         case RUN_RECEIVE_PACK:
     449          97 :           return canRunReceivePack();
     450             :         case RUN_UPLOAD_PACK:
     451         134 :           return canRunUploadPack();
     452             : 
     453             :         case PUSH_AT_LEAST_ONE_REF:
     454          97 :           return canPushToAtLeastOneRef();
     455             : 
     456             :         case READ_CONFIG:
     457          12 :           return controlForRef(RefNames.REFS_CONFIG).hasReadPermissionOnRef(false);
     458             : 
     459             :         case BAN_COMMIT:
     460             :         case READ_REFLOG:
     461             :         case WRITE_CONFIG:
     462          40 :           return isOwner();
     463             :       }
     464           0 :       throw new PermissionBackendException(perm + " unsupported");
     465             :     }
     466             :   }
     467             : }

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