LCOV - code coverage report
Current view: top level - server/permissions - DefaultPermissionBackend.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 90 102 88.2 %
Date: 2022-11-19 15:00:39 Functions: 24 26 92.3 %

          Line data    Source code
       1             : // Copyright (C) 2017 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.globalPermissionName;
      18             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      19             : import static java.util.Objects.requireNonNull;
      20             : import static java.util.stream.Collectors.toSet;
      21             : 
      22             : import com.google.common.collect.Sets;
      23             : import com.google.common.flogger.FluentLogger;
      24             : import com.google.gerrit.entities.Account;
      25             : import com.google.gerrit.entities.AccountGroup;
      26             : import com.google.gerrit.entities.PermissionRule;
      27             : import com.google.gerrit.entities.PermissionRule.Action;
      28             : import com.google.gerrit.entities.Project;
      29             : import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
      30             : import com.google.gerrit.extensions.api.access.PluginPermission;
      31             : import com.google.gerrit.extensions.conditions.BooleanCondition;
      32             : import com.google.gerrit.extensions.restapi.AuthException;
      33             : import com.google.gerrit.server.CurrentUser;
      34             : import com.google.gerrit.server.IdentifiedUser;
      35             : import com.google.gerrit.server.PeerDaemonUser;
      36             : import com.google.gerrit.server.account.CapabilityCollection;
      37             : import com.google.gerrit.server.cache.PerThreadCache;
      38             : import com.google.gerrit.server.project.ProjectCache;
      39             : import com.google.inject.Inject;
      40             : import com.google.inject.Provider;
      41             : import com.google.inject.Singleton;
      42             : import java.util.Collection;
      43             : import java.util.List;
      44             : import java.util.Optional;
      45             : import java.util.Set;
      46             : 
      47             : @Singleton
      48             : public class DefaultPermissionBackend extends PermissionBackend {
      49         151 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      50             : 
      51             :   private final Provider<CurrentUser> currentUser;
      52             :   private final ProjectCache projectCache;
      53             :   private final ProjectControl.Factory projectControlFactory;
      54             :   private final IdentifiedUser.GenericFactory identifiedUserFactory;
      55             : 
      56             :   @Inject
      57             :   DefaultPermissionBackend(
      58             :       Provider<CurrentUser> currentUser,
      59             :       ProjectCache projectCache,
      60             :       ProjectControl.Factory projectControlFactory,
      61         151 :       IdentifiedUser.GenericFactory identifiedUserFactory) {
      62         151 :     this.currentUser = currentUser;
      63         151 :     this.projectCache = projectCache;
      64         151 :     this.projectControlFactory = projectControlFactory;
      65         151 :     this.identifiedUserFactory = identifiedUserFactory;
      66         151 :   }
      67             : 
      68             :   private CapabilityCollection capabilities() {
      69         150 :     return projectCache.getAllProjects().getCapabilityCollection();
      70             :   }
      71             : 
      72             :   @Override
      73             :   public WithUser currentUser() {
      74         150 :     return new WithUserImpl(currentUser.get());
      75             :   }
      76             : 
      77             :   @Override
      78             :   public WithUser user(CurrentUser user) {
      79         150 :     return new WithUserImpl(requireNonNull(user, "user"));
      80             :   }
      81             : 
      82             :   @Override
      83             :   public WithUser absentUser(Account.Id id) {
      84         111 :     requireNonNull(id, "user");
      85         111 :     Optional<Account.Id> user = getAccountIdOfIdentifiedUser();
      86         111 :     if (user.isPresent() && id.equals(user.get())) {
      87             :       // What looked liked an absent user is actually the current caller. Use the per-request
      88             :       // singleton IdentifiedUser instead of constructing a new object to leverage caching in member
      89             :       // variables of IdentifiedUser.
      90         111 :       return new WithUserImpl(currentUser.get().asIdentifiedUser());
      91             :     }
      92          53 :     return new WithUserImpl(identifiedUserFactory.create(requireNonNull(id, "user")));
      93             :   }
      94             : 
      95             :   @Override
      96             :   public boolean usesDefaultCapabilities() {
      97           3 :     return true;
      98             :   }
      99             : 
     100             :   /**
     101             :    * Returns the {@link com.google.gerrit.entities.Account.Id} of the current user if a user is
     102             :    * signed in. Catches exceptions so that background jobs don't get impacted.
     103             :    */
     104             :   private Optional<Account.Id> getAccountIdOfIdentifiedUser() {
     105             :     try {
     106         111 :       return currentUser.get().isIdentifiedUser()
     107         111 :           ? Optional.of(currentUser.get().getAccountId())
     108           2 :           : Optional.empty();
     109           0 :     } catch (Exception e) {
     110           0 :       logger.atFine().withCause(e).log("Unable to get current user");
     111           0 :       return Optional.empty();
     112             :     }
     113             :   }
     114             : 
     115             :   class WithUserImpl extends WithUser {
     116             :     private final CurrentUser user;
     117             :     private Boolean admin;
     118             : 
     119         150 :     WithUserImpl(CurrentUser user) {
     120         150 :       this.user = requireNonNull(user, "user");
     121         150 :     }
     122             : 
     123             :     @Override
     124             :     public ForProject project(Project.NameKey project) {
     125             :       try {
     126         145 :         ProjectControl control =
     127         145 :             PerThreadCache.getOrCompute(
     128         145 :                 PerThreadCache.Key.create(ProjectControl.class, project, user.getCacheKey()),
     129             :                 () ->
     130         145 :                     projectControlFactory.create(
     131         145 :                         user, projectCache.get(project).orElseThrow(illegalState(project))));
     132         145 :         return control.asForProject();
     133           0 :       } catch (Exception e) {
     134           0 :         Throwable cause = e.getCause() != null ? e.getCause() : e;
     135           0 :         return FailedPermissionBackend.project(
     136           0 :             "project '" + project.get() + "' is unavailable", cause);
     137             :       }
     138             :     }
     139             : 
     140             :     @Override
     141             :     public void check(GlobalOrPluginPermission perm)
     142             :         throws AuthException, PermissionBackendException {
     143         150 :       if (!can(perm)) {
     144          25 :         throw new AuthException(perm.describeForException() + " not permitted");
     145             :       }
     146         150 :     }
     147             : 
     148             :     @Override
     149             :     public <T extends GlobalOrPluginPermission> Set<T> test(Collection<T> permSet)
     150             :         throws PermissionBackendException {
     151         147 :       Set<T> ok = Sets.newHashSetWithExpectedSize(permSet.size());
     152         147 :       for (T perm : permSet) {
     153         147 :         if (can(perm)) {
     154         147 :           ok.add(perm);
     155             :         }
     156         147 :       }
     157         147 :       return ok;
     158             :     }
     159             : 
     160             :     @Override
     161             :     public BooleanCondition testCond(GlobalOrPluginPermission perm) {
     162          63 :       return new PermissionBackendCondition.WithUser(this, perm, user);
     163             :     }
     164             : 
     165             :     private boolean can(GlobalOrPluginPermission perm) throws PermissionBackendException {
     166         150 :       if (perm instanceof GlobalPermission) {
     167         150 :         return can((GlobalPermission) perm);
     168           1 :       } else if (perm instanceof PluginPermission) {
     169           1 :         PluginPermission pluginPermission = (PluginPermission) perm;
     170           1 :         return has(DefaultPermissionMappings.pluginCapabilityName(pluginPermission))
     171           1 :             || (pluginPermission.fallBackToAdmin() && isAdmin());
     172             :       }
     173           0 :       throw new PermissionBackendException(perm + " unsupported");
     174             :     }
     175             : 
     176             :     private boolean can(GlobalPermission perm) throws PermissionBackendException {
     177         150 :       switch (perm) {
     178             :         case ADMINISTRATE_SERVER:
     179         146 :           return isAdmin();
     180             :         case EMAIL_REVIEWERS:
     181         103 :           return canEmailReviewers();
     182             : 
     183             :         case FLUSH_CACHES:
     184             :         case KILL_TASK:
     185             :         case RUN_GC:
     186             :         case VIEW_CACHES:
     187             :         case VIEW_QUEUE:
     188           6 :           return has(globalPermissionName(perm)) || can(GlobalPermission.MAINTAIN_SERVER);
     189             : 
     190             :         case CREATE_ACCOUNT:
     191             :         case CREATE_GROUP:
     192             :         case CREATE_PROJECT:
     193             :         case MAINTAIN_SERVER:
     194             :         case MODIFY_ACCOUNT:
     195             :         case READ_AS:
     196             :         case STREAM_EVENTS:
     197             :         case VIEW_ALL_ACCOUNTS:
     198             :         case VIEW_CONNECTIONS:
     199             :         case VIEW_PLUGINS:
     200             :         case VIEW_ACCESS:
     201         150 :           return has(globalPermissionName(perm)) || isAdmin();
     202             : 
     203             :         case ACCESS_DATABASE:
     204             :         case RUN_AS:
     205         107 :           return has(globalPermissionName(perm));
     206             :       }
     207           0 :       throw new PermissionBackendException(perm + " unsupported");
     208             :     }
     209             : 
     210             :     private boolean isAdmin() {
     211         150 :       if (admin == null) {
     212         150 :         admin = computeAdmin();
     213         150 :         if (admin) {
     214         150 :           logger.atFinest().log(
     215         150 :               "user %s is an administrator of the server", user.getLoggableName());
     216             :         } else {
     217          91 :           logger.atFinest().log(
     218          91 :               "user %s is not an administrator of the server", user.getLoggableName());
     219             :         }
     220             :       }
     221         150 :       return admin;
     222             :     }
     223             : 
     224             :     private Boolean computeAdmin() {
     225         150 :       if (user.isImpersonating()) {
     226           1 :         return false;
     227             :       }
     228         150 :       if (user instanceof PeerDaemonUser) {
     229           0 :         return true;
     230             :       }
     231         150 :       return allow(capabilities().administrateServer);
     232             :     }
     233             : 
     234             :     private boolean canEmailReviewers() {
     235         103 :       List<PermissionRule> email = capabilities().emailReviewers;
     236         103 :       if (allow(email)) {
     237           1 :         logger.atFinest().log(
     238           1 :             "user %s can email reviewers (allowed by %s)", user.getLoggableName(), email);
     239           1 :         return true;
     240             :       }
     241             : 
     242         103 :       if (notDenied(email)) {
     243         103 :         logger.atFinest().log(
     244         103 :             "user %s can email reviewers (not denied by %s)", user.getLoggableName(), email);
     245         103 :         return true;
     246             :       }
     247             : 
     248           0 :       logger.atFinest().log("user %s cannot email reviewers", user.getLoggableName());
     249           0 :       return false;
     250             :     }
     251             : 
     252             :     private boolean has(String permissionName) {
     253         150 :       boolean has = allow(capabilities().getPermission(requireNonNull(permissionName)));
     254         150 :       if (has) {
     255          15 :         logger.atFinest().log(
     256          15 :             "user %s has global capability %s", user.getLoggableName(), permissionName);
     257             :       } else {
     258         150 :         logger.atFinest().log(
     259         150 :             "user %s doesn't have global capability %s", user.getLoggableName(), permissionName);
     260             :       }
     261         150 :       return has;
     262             :     }
     263             : 
     264             :     private boolean allow(Collection<PermissionRule> rules) {
     265         150 :       return user.getEffectiveGroups()
     266         150 :           .containsAnyOf(
     267         150 :               rules.stream()
     268         150 :                   .filter(r -> r.getAction() == Action.ALLOW)
     269         150 :                   .map(r -> r.getGroup().getUUID())
     270         150 :                   .collect(toSet()));
     271             :     }
     272             : 
     273             :     private boolean notDenied(Collection<PermissionRule> rules) {
     274         103 :       Set<AccountGroup.UUID> denied =
     275         103 :           rules.stream()
     276         103 :               .filter(r -> r.getAction() != Action.ALLOW)
     277         103 :               .map(r -> r.getGroup().getUUID())
     278         103 :               .collect(toSet());
     279         103 :       return denied.isEmpty() || !user.getEffectiveGroups().containsAnyOf(denied);
     280             :     }
     281             :   }
     282             : }

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