LCOV - code coverage report
Current view: top level - server/permissions - RefVisibilityControl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 62 65 95.4 %
Date: 2022-11-19 15:00:39 Functions: 5 5 100.0 %

          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.permissions;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : import static com.google.gerrit.entities.RefNames.REFS_CACHE_AUTOMERGE;
      19             : 
      20             : import com.google.common.base.Throwables;
      21             : import com.google.common.flogger.FluentLogger;
      22             : import com.google.gerrit.entities.Account;
      23             : import com.google.gerrit.entities.AccountGroup;
      24             : import com.google.gerrit.entities.Change;
      25             : import com.google.gerrit.entities.RefNames;
      26             : import com.google.gerrit.exceptions.NoSuchGroupException;
      27             : import com.google.gerrit.exceptions.StorageException;
      28             : import com.google.gerrit.server.CurrentUser;
      29             : import com.google.gerrit.server.account.GroupControl;
      30             : import com.google.gerrit.server.project.NoSuchChangeException;
      31             : import com.google.gerrit.server.query.change.ChangeData;
      32             : import javax.inject.Inject;
      33             : import javax.inject.Singleton;
      34             : import org.eclipse.jgit.lib.Constants;
      35             : 
      36             : /**
      37             :  * This class is a component that is internal to {@link DefaultPermissionBackend}. It can
      38             :  * authoritatively tell if a ref is accessible by a user.
      39             :  */
      40             : @Singleton
      41             : class RefVisibilityControl {
      42         148 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      43             : 
      44             :   private final PermissionBackend permissionBackend;
      45             :   private final GroupControl.GenericFactory groupControlFactory;
      46             :   private final ChangeData.Factory changeDataFactory;
      47             : 
      48             :   @Inject
      49             :   RefVisibilityControl(
      50             :       PermissionBackend permissionBackend,
      51             :       GroupControl.GenericFactory groupControlFactory,
      52         148 :       ChangeData.Factory changeDataFactory) {
      53         148 :     this.permissionBackend = permissionBackend;
      54         148 :     this.groupControlFactory = groupControlFactory;
      55         148 :     this.changeDataFactory = changeDataFactory;
      56         148 :   }
      57             : 
      58             :   /**
      59             :    * Returns an authoritative answer if the ref is visible to the user. Does not have support for
      60             :    * tags and will throw a {@link PermissionBackendException} if asked for tags visibility.
      61             :    */
      62             :   boolean isVisible(ProjectControl projectControl, String refName)
      63             :       throws PermissionBackendException {
      64         131 :     if (refName.startsWith(Constants.R_TAGS)) {
      65           0 :       throw new PermissionBackendException(
      66             :           "can't check tags through RefVisibilityControl. Use PermissionBackend#filter instead.");
      67             :     }
      68         131 :     if (!RefNames.isGerritRef(refName)) {
      69             :       // This is not a special Gerrit ref and not a NoteDb ref. Likely, it's just a ref under
      70             :       // refs/heads or another ref the user created. Apply the regular permissions with inheritance.
      71         126 :       return projectControl.controlForRef(refName).hasReadPermissionOnRef(false);
      72             :     }
      73             : 
      74         102 :     if (refName.startsWith(REFS_CACHE_AUTOMERGE)) {
      75             :       // Internal cache state that is accessible to no one.
      76          26 :       return false;
      77             :     }
      78             : 
      79         102 :     boolean hasAccessDatabase =
      80             :         permissionBackend
      81         102 :             .user(projectControl.getUser())
      82         102 :             .testOrFalse(GlobalPermission.ACCESS_DATABASE);
      83         102 :     if (hasAccessDatabase) {
      84           9 :       return true;
      85             :     }
      86             : 
      87             :     // Change and change edit visibility
      88             :     Change.Id changeId;
      89         102 :     if ((changeId = Change.Id.fromRef(refName)) != null) {
      90             :       // Change ref is visible only if the change is visible.
      91             :       ChangeData cd;
      92             :       try {
      93          92 :         cd = changeDataFactory.create(projectControl.getProject().getNameKey(), changeId);
      94          92 :         checkState(cd.change().getId().equals(changeId));
      95           8 :       } catch (StorageException e) {
      96           8 :         if (Throwables.getCausalChain(e).stream()
      97           8 :             .anyMatch(e2 -> e2 instanceof NoSuchChangeException)) {
      98             :           // The change was deleted or is otherwise not accessible anymore.
      99             :           // If the caller can see all refs and is allowed to see private changes on refs/, allow
     100             :           // access. This is an escape hatch for receivers of "ref deleted" events.
     101           8 :           PermissionBackend.ForProject forProject = projectControl.asForProject();
     102           8 :           return forProject.test(ProjectPermission.READ)
     103           8 :               && forProject.ref("refs/").test(RefPermission.READ_PRIVATE_CHANGES);
     104             :         }
     105           0 :         throw new PermissionBackendException(e);
     106          92 :       }
     107          92 :       if (RefNames.isRefsEdit(refName)) {
     108             :         // Edits are visible only to the owning user, if change is visible.
     109          18 :         return visibleEdit(refName, projectControl, cd);
     110             :       }
     111          92 :       return projectControl.controlFor(cd).isVisible();
     112             :     }
     113             : 
     114             :     // Account visibility
     115         102 :     CurrentUser user = projectControl.getUser();
     116         102 :     Account.Id currentUserAccountId = user.isIdentifiedUser() ? user.getAccountId() : null;
     117             :     Account.Id accountId;
     118         102 :     if ((accountId = Account.Id.fromRef(refName)) != null) {
     119             :       // Account ref is visible only to the corresponding account.
     120          45 :       if (accountId.equals(currentUserAccountId)) {
     121             :         // Always allow visibility to refs/draft-comments and refs/starred-changes. For all other
     122             :         // refs, check if the user has read permissions.
     123          19 :         if (RefNames.isRefsDraftsComments(refName)
     124          19 :             || RefNames.isRefsStarredChanges(refName)
     125          19 :             || projectControl.controlForRef(refName).hasReadPermissionOnRef(true)) {
     126          19 :           return true;
     127             :         }
     128             :       }
     129          42 :       return false;
     130             :     }
     131             : 
     132             :     // Group visibility
     133             :     AccountGroup.UUID accountGroupUuid;
     134         102 :     if ((accountGroupUuid = AccountGroup.UUID.fromRef(refName)) != null) {
     135             :       // Group ref is visible only to the corresponding owner group.
     136             :       try {
     137          35 :         return projectControl.controlForRef(refName).hasReadPermissionOnRef(true)
     138          35 :             && groupControlFactory.controlFor(user, accountGroupUuid).isOwner();
     139           1 :       } catch (NoSuchGroupException e) {
     140             :         // The group is broken, but the ref is still around. Pretend the ref is not visible.
     141           1 :         logger.atWarning().withCause(e).log("Found group ref %s but group isn't parsable", refName);
     142           1 :         return false;
     143             :       }
     144             :     }
     145             : 
     146             :     // We are done checking all cases where we would allow access to Gerrit-managed refs. Deny
     147             :     // access in case we got this far.
     148         101 :     logger.atFine().log(
     149             :         "Denying access to %s because user doesn't have access to this Gerrit ref", refName);
     150         101 :     return false;
     151             :   }
     152             : 
     153             :   private boolean visibleEdit(String refName, ProjectControl projectControl, ChangeData cd)
     154             :       throws PermissionBackendException {
     155          18 :     Change.Id id = Change.Id.fromEditRefPart(refName);
     156          18 :     if (id == null) {
     157           0 :       throw new IllegalStateException("unable to parse change id from edit ref " + refName);
     158             :     }
     159             : 
     160          18 :     if (!projectControl.controlFor(cd).isVisible()) {
     161             :       // The user can't see the change so they can't see any edits.
     162           1 :       return false;
     163             :     }
     164             : 
     165          18 :     if (projectControl.getUser().isIdentifiedUser()
     166          18 :         && refName.startsWith(
     167          18 :             RefNames.refsEditPrefix(projectControl.getUser().asIdentifiedUser().getAccountId()))) {
     168          16 :       logger.atFinest().log("Own change edit ref is visible: %s", refName);
     169          16 :       return true;
     170             :     }
     171             : 
     172             :     // Default to READ_PRIVATE_CHANGES as there is no special permission for reading edits.
     173           4 :     boolean canRead =
     174             :         projectControl
     175           4 :             .asForProject()
     176           4 :             .ref(cd.change().getDest().branch())
     177           4 :             .test(RefPermission.READ_PRIVATE_CHANGES);
     178           4 :     logger.atFinest().log(
     179           4 :         "Foreign change edit ref is " + (canRead ? "visible" : "invisible") + ": %s", refName);
     180           4 :     return canRead;
     181             :   }
     182             : }

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