Merge "Move capability evaluation to DefaultPermissionBackend"
This commit is contained in:
commit
8800928410
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user