blob: 01e16d71a6099bdca9e0fc4931c2227237c74051 [file] [log] [blame]
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import static com.google.common.base.Predicates.not;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Access control management for server-wide capabilities. */
public class CapabilityControl {
public interface Factory {
CapabilityControl create(CurrentUser user);
}
private final CapabilityCollection capabilities;
private final CurrentUser user;
private final Map<String, List<PermissionRule>> effective;
private Boolean canAdministrateServer;
private Boolean canEmailReviewers;
@Inject
CapabilityControl(ProjectCache projectCache, @Assisted CurrentUser currentUser) {
capabilities = projectCache.getAllProjects().getCapabilityCollection();
user = currentUser;
effective = new HashMap<>();
}
/** Identity of the user the control will compute for. */
public CurrentUser getUser() {
return user;
}
/**
* <b>Do not use.</b> Determine if the user can administer this server.
*
* <p>This method is visible only for the benefit of the following transitional classes:
*
* <ul>
* <li>{@link ProjectControl}
* <li>{@link RefControl}
* <li>{@link ChangeControl}
* <li>{@link GroupControl}
* </ul>
*
* Other callers should not use this method, as it is slated to go away.
*
* @return true if the user can administer this server.
*/
public boolean isAdmin_DoNotUse() {
if (canAdministrateServer == null) {
if (user.getRealUser() != user) {
canAdministrateServer = false;
} else {
canAdministrateServer =
user instanceof PeerDaemonUser
|| matchAny(capabilities.administrateServer, ALLOWED_RULE);
}
}
return canAdministrateServer;
}
/** @return true if the user can email reviewers. */
public boolean canEmailReviewers() {
if (canEmailReviewers == null) {
canEmailReviewers =
matchAny(capabilities.emailReviewers, ALLOWED_RULE)
|| !matchAny(capabilities.emailReviewers, not(ALLOWED_RULE));
}
return canEmailReviewers;
}
/** @return true if the user can view all accounts. */
public boolean canViewAllAccounts() {
return canPerform(GlobalCapability.VIEW_ALL_ACCOUNTS) || isAdmin_DoNotUse();
}
/** @return true if the user can access the database (with gsql). */
public boolean canAccessDatabase() {
try {
return doCanForDefaultPermissionBackend(GlobalPermission.ACCESS_DATABASE);
} catch (PermissionBackendException e) {
return false;
}
}
/** @return which priority queue the user's tasks should be submitted to. */
public QueueProvider.QueueType getQueueType() {
// If a non-generic group (that is not Anonymous Users or Registered Users)
// grants us INTERACTIVE permission, use the INTERACTIVE queue even if
// BATCH was otherwise granted. This allows site administrators to grant
// INTERACTIVE to Registered Users, and BATCH to 'CI Servers' and have
// the 'CI Servers' actually use the BATCH queue while everyone else gets
// to use the INTERACTIVE queue without additional grants.
//
GroupMembership groups = user.getEffectiveGroups();
boolean batch = false;
for (PermissionRule r : capabilities.priority) {
if (match(groups, r)) {
switch (r.getAction()) {
case INTERACTIVE:
if (!SystemGroupBackend.isAnonymousOrRegistered(r.getGroup())) {
return QueueProvider.QueueType.INTERACTIVE;
}
break;
case BATCH:
batch = true;
break;
case ALLOW:
case BLOCK:
case DENY:
break;
}
}
}
if (batch) {
// If any of our groups matched to the BATCH queue, use it.
return QueueProvider.QueueType.BATCH;
}
return QueueProvider.QueueType.INTERACTIVE;
}
/** @return true if the user has this permission. */
private boolean canPerform(String permissionName) {
return !access(permissionName).isEmpty();
}
/** @return true if the user has a permission rule specifying the range. */
public boolean hasExplicitRange(String permission) {
return GlobalCapability.hasRange(permission) && !access(permission).isEmpty();
}
/** The range of permitted values associated with a label permission. */
public PermissionRange getRange(String permission) {
if (GlobalCapability.hasRange(permission)) {
return toRange(permission, access(permission));
}
return null;
}
private static PermissionRange toRange(String permissionName, List<PermissionRule> ruleList) {
int min = 0;
int max = 0;
if (ruleList.isEmpty()) {
PermissionRange.WithDefaults defaultRange = GlobalCapability.getRange(permissionName);
if (defaultRange != null) {
min = defaultRange.getDefaultMin();
max = defaultRange.getDefaultMax();
}
} else {
for (PermissionRule rule : ruleList) {
min = Math.min(min, rule.getMin());
max = Math.max(max, rule.getMax());
}
}
return new PermissionRange(permissionName, min, max);
}
/** Rules for the given permission, or the empty list. */
private List<PermissionRule> access(String permissionName) {
List<PermissionRule> rules = effective.get(permissionName);
if (rules != null) {
return rules;
}
rules = capabilities.getPermission(permissionName);
GroupMembership groups = user.getEffectiveGroups();
List<PermissionRule> mine = new ArrayList<>(rules.size());
for (PermissionRule rule : rules) {
if (match(groups, rule)) {
mine.add(rule);
}
}
if (mine.isEmpty()) {
mine = Collections.emptyList();
}
effective.put(permissionName, mine);
return mine;
}
private static final Predicate<PermissionRule> ALLOWED_RULE = r -> r.getAction() == Action.ALLOW;
private boolean matchAny(Collection<PermissionRule> rules, Predicate<PermissionRule> predicate) {
return user.getEffectiveGroups()
.containsAnyOf(
FluentIterable.from(rules).filter(predicate).transform(r -> r.getGroup().getUUID()));
}
private static boolean match(GroupMembership groups, PermissionRule rule) {
return groups.contains(rule.getGroup().getUUID());
}
/** Do not use unless inside DefaultPermissionBackend. */
public boolean doCanForDefaultPermissionBackend(GlobalOrPluginPermission perm)
throws PermissionBackendException {
if (perm instanceof GlobalPermission) {
return can((GlobalPermission) perm);
} else if (perm instanceof PluginPermission) {
return canPerform(perm.permissionName()) || isAdmin_DoNotUse();
}
throw new PermissionBackendException(perm + " unsupported");
}
private boolean can(GlobalPermission perm) throws PermissionBackendException {
switch (perm) {
case ADMINISTRATE_SERVER:
return isAdmin_DoNotUse();
case EMAIL_REVIEWERS:
return canEmailReviewers();
case VIEW_ALL_ACCOUNTS:
return canViewAllAccounts();
case FLUSH_CACHES:
case KILL_TASK:
case RUN_GC:
case VIEW_CACHES:
case VIEW_QUEUE:
return canPerform(perm.permissionName())
|| canPerform(GlobalCapability.MAINTAIN_SERVER)
|| isAdmin_DoNotUse();
case CREATE_ACCOUNT:
case CREATE_GROUP:
case CREATE_PROJECT:
case MAINTAIN_SERVER:
case MODIFY_ACCOUNT:
case STREAM_EVENTS:
case VIEW_CONNECTIONS:
case VIEW_PLUGINS:
return canPerform(perm.permissionName()) || isAdmin_DoNotUse();
case ACCESS_DATABASE:
case RUN_AS:
return canPerform(perm.permissionName());
}
throw new PermissionBackendException(perm + " unsupported");
}
}