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