Change capabilities collection to parse using PermissionBackend
Add GlobalOrPluginPermission interface to allow PluginPermission to represent a capability contributed by a plugin. This can now be tested through PermissionBackend instead of using CapabilityControl. Parsing a capability uses test() to determine if the permission exists for the target user. Using test here is more correct than check as the code isn't exercising the permission, its merely testing if the user might be able to exercise the permission. Update the capabilities REST API view to use the new PluginPermission, which allows combining everything into a single test invocation. Change-Id: I23f44ae3398a496fcb13421344ae545ac68cce93
This commit is contained in:
committed by
David Pursehouse
parent
eacabe9154
commit
e9e1af205c
@@ -14,7 +14,6 @@
|
||||
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.ChildCollection;
|
||||
@@ -22,7 +21,13 @@ import com.google.gerrit.extensions.restapi.IdString;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountResource.Capability;
|
||||
import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
|
||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.permissions.PluginPermission;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
@@ -30,15 +35,18 @@ import com.google.inject.Singleton;
|
||||
@Singleton
|
||||
class Capabilities implements ChildCollection<AccountResource, AccountResource.Capability> {
|
||||
private final Provider<CurrentUser> self;
|
||||
private final PermissionBackend permissionBackend;
|
||||
private final DynamicMap<RestView<AccountResource.Capability>> views;
|
||||
private final Provider<GetCapabilities> get;
|
||||
|
||||
@Inject
|
||||
Capabilities(
|
||||
Provider<CurrentUser> self,
|
||||
PermissionBackend permissionBackend,
|
||||
DynamicMap<RestView<AccountResource.Capability>> views,
|
||||
Provider<GetCapabilities> get) {
|
||||
this.self = self;
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.views = views;
|
||||
this.get = get;
|
||||
}
|
||||
@@ -50,20 +58,39 @@ class Capabilities implements ChildCollection<AccountResource, AccountResource.C
|
||||
|
||||
@Override
|
||||
public Capability parse(AccountResource parent, IdString id)
|
||||
throws ResourceNotFoundException, AuthException {
|
||||
if (self.get() != parent.getUser() && !self.get().getCapabilities().canAdministrateServer()) {
|
||||
throw new AuthException("restricted to administrator");
|
||||
throws ResourceNotFoundException, AuthException, PermissionBackendException {
|
||||
IdentifiedUser target = parent.getUser();
|
||||
if (self.get() != target) {
|
||||
permissionBackend.user(self).check(GlobalPermission.ADMINISTRATE_SERVER);
|
||||
}
|
||||
|
||||
String name = id.get();
|
||||
CapabilityControl cap = parent.getUser().getCapabilities();
|
||||
if (cap.canPerform(name)
|
||||
|| (cap.canAdministrateServer() && GlobalCapability.isCapability(name))) {
|
||||
return new AccountResource.Capability(parent.getUser(), name);
|
||||
GlobalOrPluginPermission perm = parse(id);
|
||||
if (permissionBackend.user(target).test(perm)) {
|
||||
return new AccountResource.Capability(target, perm.permissionName());
|
||||
}
|
||||
throw new ResourceNotFoundException(id);
|
||||
}
|
||||
|
||||
private GlobalOrPluginPermission parse(IdString id) throws ResourceNotFoundException {
|
||||
String name = id.get();
|
||||
GlobalOrPluginPermission perm = GlobalPermission.byName(name);
|
||||
if (perm != null) {
|
||||
return perm;
|
||||
}
|
||||
|
||||
int dash = name.lastIndexOf('-');
|
||||
if (dash < 0) {
|
||||
throw new ResourceNotFoundException(id);
|
||||
}
|
||||
|
||||
String pluginName = name.substring(0, dash);
|
||||
String capability = name.substring(dash + 1);
|
||||
if (pluginName.isEmpty() || capability.isEmpty()) {
|
||||
throw new ResourceNotFoundException(id);
|
||||
}
|
||||
return new PluginPermission(pluginName, capability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynamicMap<RestView<Capability>> views() {
|
||||
return views;
|
||||
|
||||
@@ -26,8 +26,10 @@ 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.GlobalOrPluginPermission;
|
||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.permissions.PluginPermission;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
@@ -216,8 +218,17 @@ public class CapabilityControl {
|
||||
}
|
||||
|
||||
/** Do not use unless inside DefaultPermissionBackend. */
|
||||
public boolean doCanForDefaultPermissionBackend(GlobalPermission perm)
|
||||
public boolean doCanForDefaultPermissionBackend(GlobalOrPluginPermission perm)
|
||||
throws PermissionBackendException {
|
||||
if (perm instanceof GlobalPermission) {
|
||||
return can((GlobalPermission) perm);
|
||||
} else if (perm instanceof PluginPermission) {
|
||||
return canPerform(perm.permissionName());
|
||||
}
|
||||
throw new PermissionBackendException(perm + " unsupported");
|
||||
}
|
||||
|
||||
private boolean can(GlobalPermission perm) throws PermissionBackendException {
|
||||
switch (perm) {
|
||||
case ADMINISTRATE_SERVER:
|
||||
return canAdministrateServer();
|
||||
|
||||
@@ -29,14 +29,15 @@ import com.google.gerrit.server.OptionUtil;
|
||||
import com.google.gerrit.server.OutputFormat;
|
||||
import com.google.gerrit.server.account.AccountResource.Capability;
|
||||
import com.google.gerrit.server.git.QueueProvider;
|
||||
import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
|
||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.permissions.PluginPermission;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -77,11 +78,10 @@ class GetCapabilities implements RestReadView<AccountResource> {
|
||||
}
|
||||
|
||||
Map<String, Object> have = new LinkedHashMap<>();
|
||||
for (GlobalPermission p : testGlobalPermissions(perm)) {
|
||||
for (GlobalOrPluginPermission p : perm.test(permissionsToTest())) {
|
||||
have.put(p.permissionName(), true);
|
||||
}
|
||||
addRanges(have, rsrc);
|
||||
addPluginCapabilities(have, rsrc);
|
||||
addPriority(have, rsrc);
|
||||
|
||||
return OutputFormat.JSON
|
||||
@@ -89,20 +89,23 @@ class GetCapabilities implements RestReadView<AccountResource> {
|
||||
.toJsonTree(have, new TypeToken<Map<String, Object>>() {}.getType());
|
||||
}
|
||||
|
||||
private Set<GlobalPermission> testGlobalPermissions(PermissionBackend.WithUser perm)
|
||||
throws PermissionBackendException {
|
||||
EnumSet<GlobalPermission> toTest;
|
||||
if (query != null) {
|
||||
toTest = EnumSet.noneOf(GlobalPermission.class);
|
||||
for (GlobalPermission p : GlobalPermission.values()) {
|
||||
private Set<GlobalOrPluginPermission> permissionsToTest() {
|
||||
Set<GlobalOrPluginPermission> toTest = new HashSet<>();
|
||||
for (GlobalPermission p : GlobalPermission.values()) {
|
||||
if (want(p.permissionName())) {
|
||||
toTest.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
for (String pluginName : pluginCapabilities.plugins()) {
|
||||
for (String capability : pluginCapabilities.byPlugin(pluginName).keySet()) {
|
||||
PluginPermission p = new PluginPermission(pluginName, capability);
|
||||
if (want(p.permissionName())) {
|
||||
toTest.add(p);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
toTest = EnumSet.allOf(GlobalPermission.class);
|
||||
}
|
||||
return perm.test(toTest);
|
||||
return toTest;
|
||||
}
|
||||
|
||||
private boolean want(String name) {
|
||||
@@ -118,18 +121,6 @@ class GetCapabilities implements RestReadView<AccountResource> {
|
||||
}
|
||||
}
|
||||
|
||||
private void addPluginCapabilities(Map<String, Object> have, AccountResource rsrc) {
|
||||
CapabilityControl cc = rsrc.getUser().getCapabilities();
|
||||
for (String pluginName : pluginCapabilities.plugins()) {
|
||||
for (String capability : pluginCapabilities.byPlugin(pluginName).keySet()) {
|
||||
String name = String.format("%s-%s", pluginName, capability);
|
||||
if (want(name) && cc.canPerform(name)) {
|
||||
have.put(name, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addPriority(Map<String, Object> have, AccountResource rsrc) {
|
||||
QueueProvider.QueueType queue = rsrc.getUser().getCapabilities().getQueueType();
|
||||
if (queue != QueueProvider.QueueType.INTERACTIVE
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (C) 2017 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.permissions;
|
||||
|
||||
/** A {@link GlobalPermission} or a {@link PluginPermission}. */
|
||||
public interface GlobalOrPluginPermission {
|
||||
/** @return name used in {@code project.config} permissions. */
|
||||
public String permissionName();
|
||||
|
||||
/** @return readable identifier of this permission for exception message. */
|
||||
public String describeForException();
|
||||
}
|
||||
@@ -14,10 +14,13 @@
|
||||
|
||||
package com.google.gerrit.server.permissions;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import java.util.Locale;
|
||||
|
||||
public enum GlobalPermission {
|
||||
/** Global server permissions built into Gerrit. */
|
||||
public enum GlobalPermission implements GlobalOrPluginPermission {
|
||||
ACCESS_DATABASE(GlobalCapability.ACCESS_DATABASE),
|
||||
ADMINISTRATE_SERVER(GlobalCapability.ADMINISTRATE_SERVER),
|
||||
CREATE_ACCOUNT(GlobalCapability.CREATE_ACCOUNT),
|
||||
@@ -37,6 +40,21 @@ public enum GlobalPermission {
|
||||
VIEW_PLUGINS(GlobalCapability.VIEW_PLUGINS),
|
||||
VIEW_QUEUE(GlobalCapability.VIEW_QUEUE);
|
||||
|
||||
private static final ImmutableMap<String, GlobalPermission> BY_NAME;
|
||||
|
||||
static {
|
||||
ImmutableMap.Builder<String, GlobalPermission> m = ImmutableMap.builder();
|
||||
for (GlobalPermission p : values()) {
|
||||
m.put(p.permissionName(), p);
|
||||
}
|
||||
BY_NAME = m.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static GlobalPermission byName(String name) {
|
||||
return BY_NAME.get(name);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
|
||||
GlobalPermission(String name) {
|
||||
@@ -44,10 +62,12 @@ public enum GlobalPermission {
|
||||
}
|
||||
|
||||
/** @return name used in {@code project.config} permissions. */
|
||||
@Override
|
||||
public String permissionName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeForException() {
|
||||
return toString().toLowerCase(Locale.US).replace('_', ' ');
|
||||
}
|
||||
|
||||
@@ -134,18 +134,18 @@ public abstract class PermissionBackend {
|
||||
}
|
||||
|
||||
/** Verify scoped user can {@code perm}, throwing if denied. */
|
||||
public abstract void check(GlobalPermission perm)
|
||||
public abstract void check(GlobalOrPluginPermission perm)
|
||||
throws AuthException, PermissionBackendException;
|
||||
|
||||
/** Filter {@code permSet} to permissions scoped user might be able to perform. */
|
||||
public abstract Set<GlobalPermission> test(Collection<GlobalPermission> permSet)
|
||||
public abstract <T extends GlobalOrPluginPermission> Set<T> test(Collection<T> permSet)
|
||||
throws PermissionBackendException;
|
||||
|
||||
public boolean test(GlobalPermission perm) throws PermissionBackendException {
|
||||
return test(EnumSet.of(perm)).contains(perm);
|
||||
public boolean test(GlobalOrPluginPermission perm) throws PermissionBackendException {
|
||||
return test(Collections.singleton(perm)).contains(perm);
|
||||
}
|
||||
|
||||
public boolean testOrFalse(GlobalPermission perm) {
|
||||
public boolean testOrFalse(GlobalOrPluginPermission perm) {
|
||||
try {
|
||||
return test(perm);
|
||||
} catch (PermissionBackendException e) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2017 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.permissions;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** A global capability type permission used by a plugin. */
|
||||
public class PluginPermission implements GlobalOrPluginPermission {
|
||||
private final String pluginName;
|
||||
private final String capability;
|
||||
|
||||
public PluginPermission(String pluginName, String capability) {
|
||||
this.pluginName = checkNotNull(pluginName, "pluginName");
|
||||
this.capability = checkNotNull(capability, "capability");
|
||||
}
|
||||
|
||||
public String pluginName() {
|
||||
return pluginName;
|
||||
}
|
||||
|
||||
public String capability() {
|
||||
return capability;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String permissionName() {
|
||||
return pluginName + '-' + capability;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeForException() {
|
||||
return capability + " for plugin " + pluginName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(pluginName, capability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof PluginPermission) {
|
||||
PluginPermission b = (PluginPermission) other;
|
||||
return pluginName.equals(b.pluginName) && capability.equals(b.capability);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PluginPermission[plugin=" + pluginName + ", capability=" + capability + ']';
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,12 @@ package com.google.gerrit.server.project;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.permissions.FailedPermissionBackend;
|
||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||
import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.inject.Inject;
|
||||
@@ -65,17 +66,18 @@ class DefaultPermissionBackend extends PermissionBackend {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(GlobalPermission perm) throws AuthException, PermissionBackendException {
|
||||
public void check(GlobalOrPluginPermission perm)
|
||||
throws AuthException, PermissionBackendException {
|
||||
if (!can(perm)) {
|
||||
throw new AuthException(perm.describeForException() + " not permitted");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GlobalPermission> test(Collection<GlobalPermission> permSet)
|
||||
public <T extends GlobalOrPluginPermission> Set<T> test(Collection<T> permSet)
|
||||
throws PermissionBackendException {
|
||||
EnumSet<GlobalPermission> ok = EnumSet.noneOf(GlobalPermission.class);
|
||||
for (GlobalPermission perm : permSet) {
|
||||
Set<T> ok = newSet(permSet);
|
||||
for (T perm : permSet) {
|
||||
if (can(perm)) {
|
||||
ok.add(perm);
|
||||
}
|
||||
@@ -83,8 +85,18 @@ class DefaultPermissionBackend extends PermissionBackend {
|
||||
return ok;
|
||||
}
|
||||
|
||||
private boolean can(GlobalPermission perm) throws PermissionBackendException {
|
||||
private boolean can(GlobalOrPluginPermission perm) throws PermissionBackendException {
|
||||
return user.getCapabilities().doCanForDefaultPermissionBackend(perm);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends GlobalOrPluginPermission> Set<T> newSet(Collection<T> permSet) {
|
||||
if (permSet instanceof EnumSet) {
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Set<T> s = ((EnumSet) permSet).clone();
|
||||
s.clear();
|
||||
return s;
|
||||
}
|
||||
return Sets.newHashSetWithExpectedSize(permSet.size());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user