Merge "Move capability evaluation to DefaultPermissionBackend"

This commit is contained in:
David Pursehouse 2017-06-29 10:55:20 +00:00 committed by Gerrit Code Review
commit 8800928410
2 changed files with 112 additions and 128 deletions

View File

@ -14,37 +14,20 @@
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.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.Singleton;
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 {
private static final CurrentUser.PropertyKey<CapabilityControl> SELF =
CurrentUser.PropertyKey.create();
@Singleton
public static class Factory {
private final ProjectCache projectCache;
@ -55,43 +38,16 @@ public class CapabilityControl {
}
public CapabilityControl create(CurrentUser user) {
CapabilityControl ctl = user.get(SELF);
if (ctl == null) {
ctl = new CapabilityControl(projectCache, user);
user.put(SELF, ctl);
}
return ctl;
return new CapabilityControl(projectCache, user);
}
}
private final CapabilityCollection capabilities;
private final CurrentUser user;
private final Map<String, List<PermissionRule>> effective;
private Boolean canAdministrateServer;
private CapabilityControl(ProjectCache projectCache, CurrentUser currentUser) {
capabilities = projectCache.getAllProjects().getCapabilityCollection();
user = currentUser;
effective = new HashMap<>();
}
private boolean isAdmin() {
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. */
private boolean canEmailReviewers() {
return matchAny(capabilities.emailReviewers, ALLOWED_RULE)
|| !matchAny(capabilities.emailReviewers, not(ALLOWED_RULE));
}
/** @return which priority queue the user's tasks should be submitted to. */
@ -133,20 +89,15 @@ public class CapabilityControl {
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();
return GlobalCapability.hasRange(permission) && !getRules(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 toRange(permission, getRules(permission));
}
return null;
}
@ -169,14 +120,8 @@ public class CapabilityControl {
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);
private List<PermissionRule> getRules(String permissionName) {
List<PermissionRule> rules = capabilities.getPermission(permissionName);
GroupMembership groups = user.getEffectiveGroups();
List<PermissionRule> mine = new ArrayList<>(rules.size());
@ -185,70 +130,10 @@ public class CapabilityControl {
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) {
PluginPermission pluginPermission = (PluginPermission) perm;
return canPerform(pluginPermission.permissionName())
|| (pluginPermission.fallBackToAdmin() && isAdmin());
}
throw new PermissionBackendException(perm + " unsupported");
}
private boolean can(GlobalPermission perm) throws PermissionBackendException {
switch (perm) {
case ADMINISTRATE_SERVER:
return isAdmin();
case EMAIL_REVIEWERS:
return canEmailReviewers();
case FLUSH_CACHES:
case KILL_TASK:
case RUN_GC:
case VIEW_CACHES:
case VIEW_QUEUE:
return canPerform(perm.permissionName())
|| canPerform(GlobalCapability.MAINTAIN_SERVER)
|| isAdmin();
case CREATE_ACCOUNT:
case CREATE_GROUP:
case CREATE_PROJECT:
case MAINTAIN_SERVER:
case MODIFY_ACCOUNT:
case STREAM_EVENTS:
case VIEW_ALL_ACCOUNTS:
case VIEW_CONNECTIONS:
case VIEW_PLUGINS:
return canPerform(perm.permissionName()) || isAdmin();
case ACCESS_DATABASE:
case RUN_AS:
return canPerform(perm.permissionName());
}
throw new PermissionBackendException(perm + " unsupported");
}
}

View File

@ -15,14 +15,21 @@
package com.google.gerrit.server.project;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.Sets;
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.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.account.CapabilityCollection;
import com.google.gerrit.server.permissions.FailedPermissionBackend;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
@ -30,17 +37,22 @@ import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
@Singleton
public class DefaultPermissionBackend extends PermissionBackend {
private static final CurrentUser.PropertyKey<Boolean> IS_ADMIN = CurrentUser.PropertyKey.create();
private final ProjectCache projectCache;
private final CapabilityControl.Factory capabilityFactory;
@Inject
DefaultPermissionBackend(ProjectCache projectCache, CapabilityControl.Factory capabilityFactory) {
DefaultPermissionBackend(ProjectCache projectCache) {
this.projectCache = projectCache;
this.capabilityFactory = capabilityFactory;
}
private CapabilityCollection capabilities() {
return projectCache.getAllProjects().getCapabilityCollection();
}
@Override
@ -50,7 +62,7 @@ public class DefaultPermissionBackend extends PermissionBackend {
class WithUserImpl extends WithUser {
private final CurrentUser user;
private CapabilityControl cap;
private Boolean admin;
WithUserImpl(CurrentUser user) {
this.user = checkNotNull(user, "user");
@ -90,10 +102,97 @@ public class DefaultPermissionBackend extends PermissionBackend {
}
private boolean can(GlobalOrPluginPermission perm) throws PermissionBackendException {
if (cap == null) {
cap = capabilityFactory.create(user);
if (perm instanceof GlobalPermission) {
return can((GlobalPermission) perm);
} else if (perm instanceof PluginPermission) {
PluginPermission pluginPermission = (PluginPermission) perm;
return has(pluginPermission.permissionName())
|| (pluginPermission.fallBackToAdmin() && isAdmin());
}
return cap.doCanForDefaultPermissionBackend(perm);
throw new PermissionBackendException(perm + " unsupported");
}
private boolean can(GlobalPermission perm) throws PermissionBackendException {
switch (perm) {
case ADMINISTRATE_SERVER:
return isAdmin();
case EMAIL_REVIEWERS:
return canEmailReviewers();
case FLUSH_CACHES:
case KILL_TASK:
case RUN_GC:
case VIEW_CACHES:
case VIEW_QUEUE:
return has(perm.permissionName()) || can(GlobalPermission.MAINTAIN_SERVER);
case CREATE_ACCOUNT:
case CREATE_GROUP:
case CREATE_PROJECT:
case MAINTAIN_SERVER:
case MODIFY_ACCOUNT:
case STREAM_EVENTS:
case VIEW_ALL_ACCOUNTS:
case VIEW_CONNECTIONS:
case VIEW_PLUGINS:
return has(perm.permissionName()) || isAdmin();
case ACCESS_DATABASE:
case RUN_AS:
return has(perm.permissionName());
}
throw new PermissionBackendException(perm + " unsupported");
}
private boolean isAdmin() {
if (admin == null) {
admin = computeAdmin();
}
return admin;
}
private Boolean computeAdmin() {
Boolean r = user.get(IS_ADMIN);
if (r == null) {
if (user.getRealUser() != user) {
r = false;
} else if (user instanceof PeerDaemonUser) {
r = true;
} else {
r = allow(capabilities().administrateServer);
}
user.put(IS_ADMIN, r);
}
return r;
}
private boolean canEmailReviewers() {
List<PermissionRule> email = capabilities().emailReviewers;
return allow(email) || notDenied(email);
}
private boolean has(String permissionName) {
return allow(capabilities().getPermission(permissionName));
}
private boolean allow(Collection<PermissionRule> rules) {
return user.getEffectiveGroups()
.containsAnyOf(
rules
.stream()
.filter(r -> r.getAction() == Action.ALLOW)
.map(r -> r.getGroup().getUUID())
.collect(toSet()));
}
private boolean notDenied(Collection<PermissionRule> rules) {
Set<AccountGroup.UUID> denied =
rules
.stream()
.filter(r -> r.getAction() != Action.ALLOW)
.map(r -> r.getGroup().getUUID())
.collect(toSet());
return denied.isEmpty() || !user.getEffectiveGroups().containsAnyOf(denied);
}
}