Merge changes from topic 'permission-backend'

* changes:
  Convert RequireCapability checks to PermissionBackend
  Change capabilities collection to parse using PermissionBackend
This commit is contained in:
David Pursehouse
2017-04-24 05:18:49 +00:00
committed by Gerrit Code Review
24 changed files with 511 additions and 293 deletions

View File

@@ -83,10 +83,7 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
assertThat(userSshSession.hasError()).isTrue(); assertThat(userSshSession.hasError()).isTrue();
String error = userSshSession.getError(); String error = userSshSession.getError();
assertThat(error).isNotNull(); assertThat(error).isNotNull();
assertError( assertError("maintain server not permitted", error);
"One of the following capabilities is required to access this"
+ " resource: [runGC, maintainServer]",
error);
} }
@Test @Test

View File

@@ -95,8 +95,10 @@ import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OptionUtil; import com.google.gerrit.server.OptionUtil;
import com.google.gerrit.server.OutputFormat; import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.account.CapabilityUtils;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
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.util.http.RequestUtil; import com.google.gerrit.util.http.RequestUtil;
import com.google.gson.ExclusionStrategy; import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes; import com.google.gson.FieldAttributes;
@@ -188,6 +190,7 @@ public class RestApiServlet extends HttpServlet {
final Provider<CurrentUser> currentUser; final Provider<CurrentUser> currentUser;
final DynamicItem<WebSession> webSession; final DynamicItem<WebSession> webSession;
final Provider<ParameterParser> paramParser; final Provider<ParameterParser> paramParser;
final PermissionBackend permissionBackend;
final AuditService auditService; final AuditService auditService;
final RestApiMetrics metrics; final RestApiMetrics metrics;
final Pattern allowOrigin; final Pattern allowOrigin;
@@ -197,12 +200,14 @@ public class RestApiServlet extends HttpServlet {
Provider<CurrentUser> currentUser, Provider<CurrentUser> currentUser,
DynamicItem<WebSession> webSession, DynamicItem<WebSession> webSession,
Provider<ParameterParser> paramParser, Provider<ParameterParser> paramParser,
PermissionBackend permissionBackend,
AuditService auditService, AuditService auditService,
RestApiMetrics metrics, RestApiMetrics metrics,
@GerritServerConfig Config cfg) { @GerritServerConfig Config cfg) {
this.currentUser = currentUser; this.currentUser = currentUser;
this.webSession = webSession; this.webSession = webSession;
this.paramParser = paramParser; this.paramParser = paramParser;
this.permissionBackend = permissionBackend;
this.auditService = auditService; this.auditService = auditService;
this.metrics = metrics; this.metrics = metrics;
allowOrigin = makeAllowOrigin(cfg); allowOrigin = makeAllowOrigin(cfg);
@@ -263,7 +268,10 @@ public class RestApiServlet extends HttpServlet {
List<IdString> path = splitPath(req); List<IdString> path = splitPath(req);
RestCollection<RestResource, RestResource> rc = members.get(); RestCollection<RestResource, RestResource> rc = members.get();
CapabilityUtils.checkRequiresCapability(globals.currentUser, null, rc.getClass()); globals
.permissionBackend
.user(globals.currentUser)
.checkAny(GlobalPermission.fromAnnotation(rc.getClass()));
viewData = new ViewData(null, null); viewData = new ViewData(null, null);
@@ -1106,9 +1114,12 @@ public class RestApiServlet extends HttpServlet {
return "GET".equals(req.getMethod()) || "HEAD".equals(req.getMethod()); return "GET".equals(req.getMethod()) || "HEAD".equals(req.getMethod());
} }
private void checkRequiresCapability(ViewData viewData) throws AuthException { private void checkRequiresCapability(ViewData d)
CapabilityUtils.checkRequiresCapability( throws AuthException, PermissionBackendException {
globals.currentUser, viewData.pluginName, viewData.view.getClass()); globals
.permissionBackend
.user(globals.currentUser)
.checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
} }
private static long handleException( private static long handleException(

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.server.account; 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.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection; 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.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.CurrentUser; 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.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.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@@ -30,15 +35,18 @@ import com.google.inject.Singleton;
@Singleton @Singleton
class Capabilities implements ChildCollection<AccountResource, AccountResource.Capability> { class Capabilities implements ChildCollection<AccountResource, AccountResource.Capability> {
private final Provider<CurrentUser> self; private final Provider<CurrentUser> self;
private final PermissionBackend permissionBackend;
private final DynamicMap<RestView<AccountResource.Capability>> views; private final DynamicMap<RestView<AccountResource.Capability>> views;
private final Provider<GetCapabilities> get; private final Provider<GetCapabilities> get;
@Inject @Inject
Capabilities( Capabilities(
Provider<CurrentUser> self, Provider<CurrentUser> self,
PermissionBackend permissionBackend,
DynamicMap<RestView<AccountResource.Capability>> views, DynamicMap<RestView<AccountResource.Capability>> views,
Provider<GetCapabilities> get) { Provider<GetCapabilities> get) {
this.self = self; this.self = self;
this.permissionBackend = permissionBackend;
this.views = views; this.views = views;
this.get = get; this.get = get;
} }
@@ -50,20 +58,39 @@ class Capabilities implements ChildCollection<AccountResource, AccountResource.C
@Override @Override
public Capability parse(AccountResource parent, IdString id) public Capability parse(AccountResource parent, IdString id)
throws ResourceNotFoundException, AuthException { throws ResourceNotFoundException, AuthException, PermissionBackendException {
if (self.get() != parent.getUser() && !self.get().getCapabilities().canAdministrateServer()) { IdentifiedUser target = parent.getUser();
throw new AuthException("restricted to administrator"); if (self.get() != target) {
permissionBackend.user(self).check(GlobalPermission.ADMINISTRATE_SERVER);
} }
String name = id.get(); GlobalOrPluginPermission perm = parse(id);
CapabilityControl cap = parent.getUser().getCapabilities(); if (permissionBackend.user(target).test(perm)) {
if (cap.canPerform(name) return new AccountResource.Capability(target, perm.permissionName());
|| (cap.canAdministrateServer() && GlobalCapability.isCapability(name))) {
return new AccountResource.Capability(parent.getUser(), name);
} }
throw new ResourceNotFoundException(id); 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 @Override
public DynamicMap<RestView<Capability>> views() { public DynamicMap<RestView<Capability>> views() {
return views; return views;

View File

@@ -26,8 +26,10 @@ import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PeerDaemonUser; import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.git.QueueProvider; import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.group.SystemGroupBackend; 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.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.PluginPermission;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
@@ -140,11 +142,8 @@ public class CapabilityControl {
return QueueProvider.QueueType.INTERACTIVE; return QueueProvider.QueueType.INTERACTIVE;
} }
/** True if the user has this permission. Works only for non labels. */ /** @return true if the user has this permission. */
public boolean canPerform(String permissionName) { private boolean canPerform(String permissionName) {
if (GlobalCapability.ADMINISTRATE_SERVER.equals(permissionName)) {
return canAdministrateServer();
}
return !access(permissionName).isEmpty(); return !access(permissionName).isEmpty();
} }
@@ -216,8 +215,17 @@ public class CapabilityControl {
} }
/** Do not use unless inside DefaultPermissionBackend. */ /** Do not use unless inside DefaultPermissionBackend. */
public boolean doCanForDefaultPermissionBackend(GlobalPermission perm) public boolean doCanForDefaultPermissionBackend(GlobalOrPluginPermission perm)
throws PermissionBackendException { throws PermissionBackendException {
if (perm instanceof GlobalPermission) {
return can((GlobalPermission) perm);
} else if (perm instanceof PluginPermission) {
return canPerform(perm.permissionName()) || canAdministrateServer();
}
throw new PermissionBackendException(perm + " unsupported");
}
private boolean can(GlobalPermission perm) throws PermissionBackendException {
switch (perm) { switch (perm) {
case ADMINISTRATE_SERVER: case ADMINISTRATE_SERVER:
return canAdministrateServer(); return canAdministrateServer();

View File

@@ -1,132 +0,0 @@
// Copyright (C) 2013 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 com.google.gerrit.extensions.annotations.CapabilityScope;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Provider;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CapabilityUtils {
private static final Logger log = LoggerFactory.getLogger(CapabilityUtils.class);
public static void checkRequiresCapability(
Provider<CurrentUser> userProvider, String pluginName, Class<?> clazz) throws AuthException {
checkRequiresCapability(userProvider.get(), pluginName, clazz);
}
public static void checkRequiresCapability(CurrentUser user, String pluginName, Class<?> clazz)
throws AuthException {
RequiresCapability rc = getClassAnnotation(clazz, RequiresCapability.class);
RequiresAnyCapability rac = getClassAnnotation(clazz, RequiresAnyCapability.class);
if (rc != null && rac != null) {
log.error(
String.format(
"Class %s uses both @%s and @%s",
clazz.getName(),
RequiresCapability.class.getSimpleName(),
RequiresAnyCapability.class.getSimpleName()));
throw new AuthException("cannot check capability");
}
CapabilityControl ctl = user.getCapabilities();
if (ctl.canAdministrateServer()) {
return;
}
checkRequiresCapability(ctl, pluginName, clazz, rc);
checkRequiresAnyCapability(ctl, pluginName, clazz, rac);
}
private static void checkRequiresCapability(
CapabilityControl ctl, String pluginName, Class<?> clazz, RequiresCapability rc)
throws AuthException {
if (rc == null) {
return;
}
String capability = resolveCapability(pluginName, rc.value(), rc.scope(), clazz);
if (!ctl.canPerform(capability)) {
throw new AuthException(
String.format("Capability %s is required to access this resource", capability));
}
}
private static void checkRequiresAnyCapability(
CapabilityControl ctl, String pluginName, Class<?> clazz, RequiresAnyCapability rac)
throws AuthException {
if (rac == null) {
return;
}
if (rac.value().length == 0) {
log.error(
String.format(
"Class %s uses @%s with no capabilities listed",
clazz.getName(), RequiresAnyCapability.class.getSimpleName()));
throw new AuthException("cannot check capability");
}
for (String capability : rac.value()) {
capability = resolveCapability(pluginName, capability, rac.scope(), clazz);
if (ctl.canPerform(capability)) {
return;
}
}
throw new AuthException(
"One of the following capabilities is required to access this"
+ " resource: "
+ Arrays.asList(rac.value()));
}
private static String resolveCapability(
String pluginName, String capability, CapabilityScope scope, Class<?> clazz)
throws AuthException {
if (pluginName != null
&& !"gerrit".equals(pluginName)
&& (scope == CapabilityScope.PLUGIN || scope == CapabilityScope.CONTEXT)) {
capability = String.format("%s-%s", pluginName, capability);
} else if (scope == CapabilityScope.PLUGIN) {
log.error(
String.format(
"Class %s uses @%s(scope=%s), but is not within a plugin",
clazz.getName(),
RequiresCapability.class.getSimpleName(),
CapabilityScope.PLUGIN.name()));
throw new AuthException("cannot check capability");
}
return capability;
}
/**
* Find an instance of the specified annotation, walking up the inheritance tree if necessary.
*
* @param <T> Annotation type to search for
* @param clazz root class to search, may be null
* @param annotationClass class object of Annotation subclass to search for
* @return the requested annotation or null if none
*/
private static <T extends Annotation> T getClassAnnotation(
Class<?> clazz, Class<T> annotationClass) {
for (; clazz != null; clazz = clazz.getSuperclass()) {
T t = clazz.getAnnotation(annotationClass);
if (t != null) {
return t;
}
}
return null;
}
}

View File

@@ -29,14 +29,15 @@ import com.google.gerrit.server.OptionUtil;
import com.google.gerrit.server.OutputFormat; import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.account.AccountResource.Capability; import com.google.gerrit.server.account.AccountResource.Capability;
import com.google.gerrit.server.git.QueueProvider; 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.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.PluginPermission;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@@ -77,11 +78,10 @@ class GetCapabilities implements RestReadView<AccountResource> {
} }
Map<String, Object> have = new LinkedHashMap<>(); Map<String, Object> have = new LinkedHashMap<>();
for (GlobalPermission p : testGlobalPermissions(perm)) { for (GlobalOrPluginPermission p : perm.test(permissionsToTest())) {
have.put(p.permissionName(), true); have.put(p.permissionName(), true);
} }
addRanges(have, rsrc); addRanges(have, rsrc);
addPluginCapabilities(have, rsrc);
addPriority(have, rsrc); addPriority(have, rsrc);
return OutputFormat.JSON return OutputFormat.JSON
@@ -89,20 +89,23 @@ class GetCapabilities implements RestReadView<AccountResource> {
.toJsonTree(have, new TypeToken<Map<String, Object>>() {}.getType()); .toJsonTree(have, new TypeToken<Map<String, Object>>() {}.getType());
} }
private Set<GlobalPermission> testGlobalPermissions(PermissionBackend.WithUser perm) private Set<GlobalOrPluginPermission> permissionsToTest() {
throws PermissionBackendException { Set<GlobalOrPluginPermission> toTest = new HashSet<>();
EnumSet<GlobalPermission> toTest; for (GlobalPermission p : GlobalPermission.values()) {
if (query != null) { if (want(p.permissionName())) {
toTest = EnumSet.noneOf(GlobalPermission.class); toTest.add(p);
for (GlobalPermission p : GlobalPermission.values()) { }
}
for (String pluginName : pluginCapabilities.plugins()) {
for (String capability : pluginCapabilities.byPlugin(pluginName).keySet()) {
PluginPermission p = new PluginPermission(pluginName, capability);
if (want(p.permissionName())) { if (want(p.permissionName())) {
toTest.add(p); toTest.add(p);
} }
} }
} else {
toTest = EnumSet.allOf(GlobalPermission.class);
} }
return perm.test(toTest); return toTest;
} }
private boolean want(String name) { 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) { private void addPriority(Map<String, Object> have, AccountResource rsrc) {
QueueProvider.QueueType queue = rsrc.getUser().getCapabilities().getQueueType(); QueueProvider.QueueType queue = rsrc.getUser().getCapabilities().getQueueType();
if (queue != QueueProvider.QueueType.INTERACTIVE if (queue != QueueProvider.QueueType.INTERACTIVE

View File

@@ -15,7 +15,6 @@
package com.google.gerrit.server.api.accounts; package com.google.gerrit.server.api.accounts;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
import com.google.gerrit.extensions.api.accounts.AccountApi; import com.google.gerrit.extensions.api.accounts.AccountApi;
import com.google.gerrit.extensions.api.accounts.AccountInput; import com.google.gerrit.extensions.api.accounts.AccountInput;
@@ -32,6 +31,9 @@ import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountsCollection; import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.CreateAccount; import com.google.gerrit.server.account.CreateAccount;
import com.google.gerrit.server.account.QueryAccounts; import com.google.gerrit.server.account.QueryAccounts;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -44,6 +46,7 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
public class AccountsImpl implements Accounts { public class AccountsImpl implements Accounts {
private final AccountsCollection accounts; private final AccountsCollection accounts;
private final AccountApiImpl.Factory api; private final AccountApiImpl.Factory api;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> self; private final Provider<CurrentUser> self;
private final CreateAccount.Factory createAccount; private final CreateAccount.Factory createAccount;
private final Provider<QueryAccounts> queryAccountsProvider; private final Provider<QueryAccounts> queryAccountsProvider;
@@ -52,11 +55,13 @@ public class AccountsImpl implements Accounts {
AccountsImpl( AccountsImpl(
AccountsCollection accounts, AccountsCollection accounts,
AccountApiImpl.Factory api, AccountApiImpl.Factory api,
PermissionBackend permissionBackend,
Provider<CurrentUser> self, Provider<CurrentUser> self,
CreateAccount.Factory createAccount, CreateAccount.Factory createAccount,
Provider<QueryAccounts> queryAccountsProvider) { Provider<QueryAccounts> queryAccountsProvider) {
this.accounts = accounts; this.accounts = accounts;
this.api = api; this.api = api;
this.permissionBackend = permissionBackend;
this.self = self; this.self = self;
this.createAccount = createAccount; this.createAccount = createAccount;
this.queryAccountsProvider = queryAccountsProvider; this.queryAccountsProvider = queryAccountsProvider;
@@ -96,12 +101,12 @@ public class AccountsImpl implements Accounts {
if (checkNotNull(in, "AccountInput").username == null) { if (checkNotNull(in, "AccountInput").username == null) {
throw new BadRequestException("AccountInput must specify username"); throw new BadRequestException("AccountInput must specify username");
} }
checkRequiresCapability(self, null, CreateAccount.class);
try { try {
AccountInfo info = CreateAccount impl = createAccount.create(in.username);
createAccount.create(in.username).apply(TopLevelResource.INSTANCE, in).value(); permissionBackend.user(self).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
AccountInfo info = impl.apply(TopLevelResource.INSTANCE, in).value();
return id(info._accountId); return id(info._accountId);
} catch (OrmException | IOException | ConfigInvalidException e) { } catch (OrmException | IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot create account " + in.username, e); throw new RestApiException("Cannot create account " + in.username, e);
} }
} }

View File

@@ -15,7 +15,6 @@
package com.google.gerrit.server.api.groups; package com.google.gerrit.server.api.groups;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
import com.google.gerrit.extensions.api.groups.GroupApi; import com.google.gerrit.extensions.api.groups.GroupApi;
import com.google.gerrit.extensions.api.groups.GroupInput; import com.google.gerrit.extensions.api.groups.GroupInput;
@@ -32,6 +31,9 @@ import com.google.gerrit.server.group.CreateGroup;
import com.google.gerrit.server.group.GroupsCollection; import com.google.gerrit.server.group.GroupsCollection;
import com.google.gerrit.server.group.ListGroups; import com.google.gerrit.server.group.ListGroups;
import com.google.gerrit.server.group.QueryGroups; import com.google.gerrit.server.group.QueryGroups;
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.project.ProjectsCollection; import com.google.gerrit.server.project.ProjectsCollection;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -49,6 +51,7 @@ class GroupsImpl implements Groups {
private final Provider<ListGroups> listGroups; private final Provider<ListGroups> listGroups;
private final Provider<QueryGroups> queryGroups; private final Provider<QueryGroups> queryGroups;
private final Provider<CurrentUser> user; private final Provider<CurrentUser> user;
private final PermissionBackend permissionBackend;
private final CreateGroup.Factory createGroup; private final CreateGroup.Factory createGroup;
private final GroupApiImpl.Factory api; private final GroupApiImpl.Factory api;
@@ -60,6 +63,7 @@ class GroupsImpl implements Groups {
Provider<ListGroups> listGroups, Provider<ListGroups> listGroups,
Provider<QueryGroups> queryGroups, Provider<QueryGroups> queryGroups,
Provider<CurrentUser> user, Provider<CurrentUser> user,
PermissionBackend permissionBackend,
CreateGroup.Factory createGroup, CreateGroup.Factory createGroup,
GroupApiImpl.Factory api) { GroupApiImpl.Factory api) {
this.accounts = accounts; this.accounts = accounts;
@@ -68,6 +72,7 @@ class GroupsImpl implements Groups {
this.listGroups = listGroups; this.listGroups = listGroups;
this.queryGroups = queryGroups; this.queryGroups = queryGroups;
this.user = user; this.user = user;
this.permissionBackend = permissionBackend;
this.createGroup = createGroup; this.createGroup = createGroup;
this.api = api; this.api = api;
} }
@@ -89,11 +94,12 @@ class GroupsImpl implements Groups {
if (checkNotNull(in, "GroupInput").name == null) { if (checkNotNull(in, "GroupInput").name == null) {
throw new BadRequestException("GroupInput must specify name"); throw new BadRequestException("GroupInput must specify name");
} }
checkRequiresCapability(user, null, CreateGroup.class);
try { try {
GroupInfo info = createGroup.create(in.name).apply(TopLevelResource.INSTANCE, in); CreateGroup impl = createGroup.create(in.name);
permissionBackend.user(user).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
GroupInfo info = impl.apply(TopLevelResource.INSTANCE, in);
return id(info.id); return id(info.id);
} catch (OrmException | IOException e) { } catch (OrmException | IOException | PermissionBackendException e) {
throw new RestApiException("Cannot create group " + in.name, e); throw new RestApiException("Cannot create group " + in.name, e);
} }
} }

View File

@@ -14,8 +14,6 @@
package com.google.gerrit.server.api.projects; package com.google.gerrit.server.api.projects;
import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo; import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInput; import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.api.projects.BranchApi; import com.google.gerrit.extensions.api.projects.BranchApi;
@@ -39,6 +37,9 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
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.project.ChildProjectsCollection; import com.google.gerrit.server.project.ChildProjectsCollection;
import com.google.gerrit.server.project.CommitsCollection; import com.google.gerrit.server.project.CommitsCollection;
import com.google.gerrit.server.project.CreateProject; import com.google.gerrit.server.project.CreateProject;
@@ -71,6 +72,7 @@ public class ProjectApiImpl implements ProjectApi {
} }
private final CurrentUser user; private final CurrentUser user;
private final PermissionBackend permissionBackend;
private final CreateProject.Factory createProjectFactory; private final CreateProject.Factory createProjectFactory;
private final ProjectApiImpl.Factory projectApi; private final ProjectApiImpl.Factory projectApi;
private final ProjectsCollection projects; private final ProjectsCollection projects;
@@ -97,6 +99,7 @@ public class ProjectApiImpl implements ProjectApi {
@AssistedInject @AssistedInject
ProjectApiImpl( ProjectApiImpl(
CurrentUser user, CurrentUser user,
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory, CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi, ProjectApiImpl.Factory projectApi,
ProjectsCollection projects, ProjectsCollection projects,
@@ -120,6 +123,7 @@ public class ProjectApiImpl implements ProjectApi {
@Assisted ProjectResource project) { @Assisted ProjectResource project) {
this( this(
user, user,
permissionBackend,
createProjectFactory, createProjectFactory,
projectApi, projectApi,
projects, projects,
@@ -147,6 +151,7 @@ public class ProjectApiImpl implements ProjectApi {
@AssistedInject @AssistedInject
ProjectApiImpl( ProjectApiImpl(
CurrentUser user, CurrentUser user,
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory, CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi, ProjectApiImpl.Factory projectApi,
ProjectsCollection projects, ProjectsCollection projects,
@@ -170,6 +175,7 @@ public class ProjectApiImpl implements ProjectApi {
@Assisted String name) { @Assisted String name) {
this( this(
user, user,
permissionBackend,
createProjectFactory, createProjectFactory,
projectApi, projectApi,
projects, projects,
@@ -196,6 +202,7 @@ public class ProjectApiImpl implements ProjectApi {
private ProjectApiImpl( private ProjectApiImpl(
CurrentUser user, CurrentUser user,
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory, CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi, ProjectApiImpl.Factory projectApi,
ProjectsCollection projects, ProjectsCollection projects,
@@ -219,6 +226,7 @@ public class ProjectApiImpl implements ProjectApi {
CommitApiImpl.Factory commitApi, CommitApiImpl.Factory commitApi,
String name) { String name) {
this.user = user; this.user = user;
this.permissionBackend = permissionBackend;
this.createProjectFactory = createProjectFactory; this.createProjectFactory = createProjectFactory;
this.projectApi = projectApi; this.projectApi = projectApi;
this.projects = projects; this.projects = projects;
@@ -257,10 +265,11 @@ public class ProjectApiImpl implements ProjectApi {
if (in.name != null && !name.equals(in.name)) { if (in.name != null && !name.equals(in.name)) {
throw new BadRequestException("name must match input.name"); throw new BadRequestException("name must match input.name");
} }
checkRequiresCapability(user, null, CreateProject.class); CreateProject impl = createProjectFactory.create(name);
createProjectFactory.create(name).apply(TopLevelResource.INSTANCE, in); permissionBackend.user(user).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
impl.apply(TopLevelResource.INSTANCE, in);
return projectApi.create(projects.parse(name)); return projectApi.create(projects.parse(name));
} catch (IOException | ConfigInvalidException e) { } catch (IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot create project: " + e.getMessage(), e); throw new RestApiException("Cannot create project: " + e.getMessage(), e);
} }
} }

View File

@@ -30,14 +30,11 @@ import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription; import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
import com.google.gerrit.extensions.webui.UiAction; import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.extensions.webui.UiActions; import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.util.Providers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -48,6 +45,7 @@ public class ActionJson {
private final Revisions revisions; private final Revisions revisions;
private final ChangeJson.Factory changeJsonFactory; private final ChangeJson.Factory changeJsonFactory;
private final ChangeResource.Factory changeResourceFactory; private final ChangeResource.Factory changeResourceFactory;
private final UiActions uiActions;
private final DynamicMap<RestView<ChangeResource>> changeViews; private final DynamicMap<RestView<ChangeResource>> changeViews;
private final DynamicSet<ActionVisitor> visitorSet; private final DynamicSet<ActionVisitor> visitorSet;
@@ -56,11 +54,13 @@ public class ActionJson {
Revisions revisions, Revisions revisions,
ChangeJson.Factory changeJsonFactory, ChangeJson.Factory changeJsonFactory,
ChangeResource.Factory changeResourceFactory, ChangeResource.Factory changeResourceFactory,
UiActions uiActions,
DynamicMap<RestView<ChangeResource>> changeViews, DynamicMap<RestView<ChangeResource>> changeViews,
DynamicSet<ActionVisitor> visitorSet) { DynamicSet<ActionVisitor> visitorSet) {
this.revisions = revisions; this.revisions = revisions;
this.changeJsonFactory = changeJsonFactory; this.changeJsonFactory = changeJsonFactory;
this.changeResourceFactory = changeResourceFactory; this.changeResourceFactory = changeResourceFactory;
this.uiActions = uiActions;
this.changeViews = changeViews; this.changeViews = changeViews;
this.visitorSet = visitorSet; this.visitorSet = visitorSet;
} }
@@ -162,9 +162,9 @@ public class ActionJson {
return out; return out;
} }
Provider<CurrentUser> userProvider = Providers.of(ctl.getUser());
FluentIterable<UiAction.Description> descs = FluentIterable<UiAction.Description> descs =
UiActions.from(changeViews, changeResourceFactory.create(ctl), userProvider); uiActions.from(changeViews, changeResourceFactory.create(ctl));
// The followup action is a client-side only operation that does not // The followup action is a client-side only operation that does not
// have a server side handler. It must be manually registered into the // have a server side handler. It must be manually registered into the
// resulting action map. // resulting action map.
@@ -198,10 +198,10 @@ public class ActionJson {
if (!rsrc.getControl().getUser().isIdentifiedUser()) { if (!rsrc.getControl().getUser().isIdentifiedUser()) {
return ImmutableMap.of(); return ImmutableMap.of();
} }
Map<String, ActionInfo> out = new LinkedHashMap<>(); Map<String, ActionInfo> out = new LinkedHashMap<>();
Provider<CurrentUser> userProvider = Providers.of(rsrc.getControl().getUser());
ACTION: ACTION:
for (UiAction.Description d : UiActions.from(revisions, rsrc, userProvider)) { for (UiAction.Description d : uiActions.from(revisions, rsrc)) {
ActionInfo actionInfo = new ActionInfo(d); ActionInfo actionInfo = new ActionInfo(d);
for (ActionVisitor visitor : visitors) { for (ActionVisitor visitor : visitors) {
if (!visitor.visit(d.getId(), actionInfo, changeInfo, revisionInfo)) { if (!visitor.visit(d.getId(), actionInfo, changeInfo, revisionInfo)) {

View File

@@ -107,6 +107,7 @@ import com.google.gerrit.server.change.ReviewerSuggestion;
import com.google.gerrit.server.events.EventFactory; import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.events.EventsMetrics; import com.google.gerrit.server.events.EventsMetrics;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.AbandonOp; import com.google.gerrit.server.git.AbandonOp;
import com.google.gerrit.server.git.ChangeMessageModifier; import com.google.gerrit.server.git.ChangeMessageModifier;
import com.google.gerrit.server.git.EmailMerge; import com.google.gerrit.server.git.EmailMerge;
@@ -293,6 +294,7 @@ public class GerritGlobalModule extends FactoryModule {
bind(AccountControl.Factory.class); bind(AccountControl.Factory.class);
install(new AuditModule()); install(new AuditModule());
bind(UiActions.class);
install(new com.google.gerrit.server.access.Module()); install(new com.google.gerrit.server.access.Module());
install(new com.google.gerrit.server.account.Module()); install(new com.google.gerrit.server.account.Module());
install(new com.google.gerrit.server.api.Module()); install(new com.google.gerrit.server.api.Module());

View File

@@ -16,20 +16,27 @@ package com.google.gerrit.server.extensions.webui;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestCollection; import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestResource; import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription; import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
import com.google.gerrit.extensions.webui.UiAction; import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.CapabilityUtils; 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.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@Singleton
public class UiActions { public class UiActions {
private static final Logger log = LoggerFactory.getLogger(UiActions.class); private static final Logger log = LoggerFactory.getLogger(UiActions.class);
@@ -37,57 +44,70 @@ public class UiActions {
return UiAction.Description::isEnabled; return UiAction.Description::isEnabled;
} }
public static <R extends RestResource> FluentIterable<UiAction.Description> from( private final PermissionBackend permissionBackend;
RestCollection<?, R> collection, R resource, Provider<CurrentUser> userProvider) { private final Provider<CurrentUser> userProvider;
return from(collection.views(), resource, userProvider);
@Inject
UiActions(PermissionBackend permissionBackend, Provider<CurrentUser> userProvider) {
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
} }
public static <R extends RestResource> FluentIterable<UiAction.Description> from( public <R extends RestResource> FluentIterable<UiAction.Description> from(
DynamicMap<RestView<R>> views, R resource, Provider<CurrentUser> userProvider) { RestCollection<?, R> collection, R resource) {
return from(collection.views(), resource);
}
public <R extends RestResource> FluentIterable<UiAction.Description> from(
DynamicMap<RestView<R>> views, R resource) {
return FluentIterable.from(views) return FluentIterable.from(views)
.transform( .transform((e) -> describe(e, resource))
(DynamicMap.Entry<RestView<R>> e) -> {
int d = e.getExportName().indexOf('.');
if (d < 0) {
return null;
}
RestView<R> view;
try {
view = e.getProvider().get();
} catch (RuntimeException err) {
log.error(
String.format(
"error creating view %s.%s", e.getPluginName(), e.getExportName()),
err);
return null;
}
if (!(view instanceof UiAction)) {
return null;
}
try {
CapabilityUtils.checkRequiresCapability(
userProvider, e.getPluginName(), view.getClass());
} catch (AuthException exc) {
return null;
}
UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
if (dsc == null || !dsc.isVisible()) {
return null;
}
String name = e.getExportName().substring(d + 1);
PrivateInternals_UiActionDescription.setMethod(
dsc, e.getExportName().substring(0, d));
PrivateInternals_UiActionDescription.setId(
dsc, "gerrit".equals(e.getPluginName()) ? name : e.getPluginName() + '~' + name);
return dsc;
})
.filter(Objects::nonNull); .filter(Objects::nonNull);
} }
private UiActions() {} @Nullable
private <R extends RestResource> UiAction.Description describe(
DynamicMap.Entry<RestView<R>> e, R resource) {
int d = e.getExportName().indexOf('.');
if (d < 0) {
return null;
}
RestView<R> view;
try {
view = e.getProvider().get();
} catch (RuntimeException err) {
log.error(
String.format("error creating view %s.%s", e.getPluginName(), e.getExportName()), err);
return null;
}
if (!(view instanceof UiAction)) {
return null;
}
try {
Set<GlobalOrPluginPermission> need =
GlobalPermission.fromAnnotation(e.getPluginName(), view.getClass());
if (!need.isEmpty() && permissionBackend.user(userProvider).test(need).isEmpty()) {
// A permission is required, but test returned no candidates.
return null;
}
} catch (PermissionBackendException err) {
log.error(
String.format("exception testing view %s.%s", e.getPluginName(), e.getExportName()), err);
return null;
}
UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
if (dsc == null || !dsc.isVisible()) {
return null;
}
String name = e.getExportName().substring(d + 1);
PrivateInternals_UiActionDescription.setMethod(dsc, e.getExportName().substring(0, d));
PrivateInternals_UiActionDescription.setId(
dsc, "gerrit".equals(e.getPluginName()) ? name : e.getPluginName() + '~' + name);
return dsc;
}
} }

View File

@@ -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();
}

View File

@@ -14,10 +14,22 @@
package com.google.gerrit.server.permissions; 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 com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.CapabilityScope;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public enum GlobalPermission { /** Global server permissions built into Gerrit. */
public enum GlobalPermission implements GlobalOrPluginPermission {
ACCESS_DATABASE(GlobalCapability.ACCESS_DATABASE), ACCESS_DATABASE(GlobalCapability.ACCESS_DATABASE),
ADMINISTRATE_SERVER(GlobalCapability.ADMINISTRATE_SERVER), ADMINISTRATE_SERVER(GlobalCapability.ADMINISTRATE_SERVER),
CREATE_ACCOUNT(GlobalCapability.CREATE_ACCOUNT), CREATE_ACCOUNT(GlobalCapability.CREATE_ACCOUNT),
@@ -37,6 +49,63 @@ public enum GlobalPermission {
VIEW_PLUGINS(GlobalCapability.VIEW_PLUGINS), VIEW_PLUGINS(GlobalCapability.VIEW_PLUGINS),
VIEW_QUEUE(GlobalCapability.VIEW_QUEUE); VIEW_QUEUE(GlobalCapability.VIEW_QUEUE);
private static final Logger log = LoggerFactory.getLogger(GlobalPermission.class);
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);
}
/**
* Extracts the {@code @RequiresCapability} or {@code @RequiresAnyCapability} annotation.
*
* @param pluginName name of the declaring plugin. May be {@code null} or {@code "gerrit"} for
* classes originating from the core server.
* @param clazz target class to extract annotation from.
* @return empty set if no annotations were found, or a collection of permissions, any of which
* are suitable to enable access.
* @throws PermissionBackendException the annotation could not be parsed.
*/
public static Set<GlobalOrPluginPermission> fromAnnotation(
@Nullable String pluginName, Class<?> clazz) throws PermissionBackendException {
RequiresCapability rc = findAnnotation(clazz, RequiresCapability.class);
RequiresAnyCapability rac = findAnnotation(clazz, RequiresAnyCapability.class);
if (rc != null && rac != null) {
log.error(
String.format(
"Class %s uses both @%s and @%s",
clazz.getName(),
RequiresCapability.class.getSimpleName(),
RequiresAnyCapability.class.getSimpleName()));
throw new PermissionBackendException("cannot extract permission");
} else if (rc != null) {
return Collections.singleton(
resolve(pluginName, rc.value(), rc.scope(), clazz, RequiresCapability.class));
} else if (rac != null) {
Set<GlobalOrPluginPermission> r = new LinkedHashSet<>();
for (String capability : rac.value()) {
r.add(resolve(pluginName, capability, rac.scope(), clazz, RequiresAnyCapability.class));
}
return Collections.unmodifiableSet(r);
} else {
return Collections.emptySet();
}
}
public static Set<GlobalOrPluginPermission> fromAnnotation(Class<?> clazz)
throws PermissionBackendException {
return fromAnnotation(null, clazz);
}
private final String name; private final String name;
GlobalPermission(String name) { GlobalPermission(String name) {
@@ -44,11 +113,54 @@ public enum GlobalPermission {
} }
/** @return name used in {@code project.config} permissions. */ /** @return name used in {@code project.config} permissions. */
@Override
public String permissionName() { public String permissionName() {
return name; return name;
} }
@Override
public String describeForException() { public String describeForException() {
return toString().toLowerCase(Locale.US).replace('_', ' '); return toString().toLowerCase(Locale.US).replace('_', ' ');
} }
private static GlobalOrPluginPermission resolve(
@Nullable String pluginName,
String capability,
CapabilityScope scope,
Class<?> clazz,
Class<?> annotationClass)
throws PermissionBackendException {
if (pluginName != null
&& !"gerrit".equals(pluginName)
&& (scope == CapabilityScope.PLUGIN || scope == CapabilityScope.CONTEXT)) {
return new PluginPermission(pluginName, capability);
}
if (scope == CapabilityScope.PLUGIN) {
log.error(
String.format(
"Class %s uses @%s(scope=%s), but is not within a plugin",
clazz.getName(), annotationClass.getSimpleName(), scope.name()));
throw new PermissionBackendException("cannot extract permission");
}
GlobalPermission perm = byName(capability);
if (perm == null) {
log.error(
String.format("Class %s requires unknown capability %s", clazz.getName(), capability));
throw new PermissionBackendException("cannot extract permission");
}
return perm;
}
@Nullable
private static <T extends Annotation> T findAnnotation(Class<?> clazz, Class<T> annotation) {
for (; clazz != null; clazz = clazz.getSuperclass()) {
T t = clazz.getAnnotation(annotation);
if (t != null) {
return t;
}
}
return null;
}
} }

View File

@@ -31,6 +31,7 @@ import com.google.inject.util.Providers;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Iterator;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -134,18 +135,47 @@ public abstract class PermissionBackend {
} }
/** Verify scoped user can {@code perm}, throwing if denied. */ /** Verify scoped user can {@code perm}, throwing if denied. */
public abstract void check(GlobalPermission perm) public abstract void check(GlobalOrPluginPermission perm)
throws AuthException, PermissionBackendException; throws AuthException, PermissionBackendException;
/** Filter {@code permSet} to permissions scoped user might be able to perform. */ /**
public abstract Set<GlobalPermission> test(Collection<GlobalPermission> permSet) * Verify scoped user can perform at least one listed permission.
throws PermissionBackendException; *
* <p>If {@code any} is empty, the method completes normally and allows the caller to continue.
public boolean test(GlobalPermission perm) throws PermissionBackendException { * Since no permissions were supplied to check, its assumed no permissions are necessary to
return test(EnumSet.of(perm)).contains(perm); * continue with the caller's operation.
*
* <p>If the user has at least one of the permissions in {@code any}, the method completes
* normally, possibly without checking all listed permissions.
*
* <p>If {@code any} is non-empty and the user has none, {@link AuthException} is thrown for one
* of the failed permissions.
*
* @param any set of permissions to check.
*/
public void checkAny(Set<GlobalOrPluginPermission> any)
throws PermissionBackendException, AuthException {
for (Iterator<GlobalOrPluginPermission> itr = any.iterator(); itr.hasNext(); ) {
try {
check(itr.next());
return;
} catch (AuthException err) {
if (!itr.hasNext()) {
throw err;
}
}
}
} }
public boolean testOrFalse(GlobalPermission perm) { /** Filter {@code permSet} to permissions scoped user might be able to perform. */
public abstract <T extends GlobalOrPluginPermission> Set<T> test(Collection<T> permSet)
throws PermissionBackendException;
public boolean test(GlobalOrPluginPermission perm) throws PermissionBackendException {
return test(Collections.singleton(perm)).contains(perm);
}
public boolean testOrFalse(GlobalOrPluginPermission perm) {
try { try {
return test(perm); return test(perm);
} catch (PermissionBackendException e) { } catch (PermissionBackendException e) {

View File

@@ -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 + ']';
}
}

View File

@@ -31,7 +31,6 @@ import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry; import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.extensions.webui.UiActions; import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.TransferConfig; import com.google.gerrit.server.git.TransferConfig;
import com.google.inject.util.Providers;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@@ -45,6 +44,7 @@ public class ConfigInfoImpl extends ConfigInfo {
DynamicMap<ProjectConfigEntry> pluginConfigEntries, DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory, PluginConfigFactory cfgFactory,
AllProjectsName allProjects, AllProjectsName allProjects,
UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) { DynamicMap<RestView<ProjectResource>> views) {
ProjectState projectState = control.getProjectState(); ProjectState projectState = control.getProjectState();
Project p = control.getProject(); Project p = control.getProject();
@@ -126,8 +126,7 @@ public class ConfigInfoImpl extends ConfigInfo {
getPluginConfig(control.getProjectState(), pluginConfigEntries, cfgFactory, allProjects); getPluginConfig(control.getProjectState(), pluginConfigEntries, cfgFactory, allProjects);
actions = new TreeMap<>(); actions = new TreeMap<>();
for (UiAction.Description d : for (UiAction.Description d : uiActions.from(views, new ProjectResource(control))) {
UiActions.from(views, new ProjectResource(control), Providers.of(control.getUser()))) {
actions.put(d.getId(), new ActionInfo(d)); actions.put(d.getId(), new ActionInfo(d));
} }
this.theme = projectState.getTheme(); this.theme = projectState.getTheme();

View File

@@ -16,11 +16,12 @@ package com.google.gerrit.server.project;
import static com.google.common.base.Preconditions.checkNotNull; 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.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.FailedPermissionBackend; 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.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -65,17 +66,18 @@ class DefaultPermissionBackend extends PermissionBackend {
} }
@Override @Override
public void check(GlobalPermission perm) throws AuthException, PermissionBackendException { public void check(GlobalOrPluginPermission perm)
throws AuthException, PermissionBackendException {
if (!can(perm)) { if (!can(perm)) {
throw new AuthException(perm.describeForException() + " not permitted"); throw new AuthException(perm.describeForException() + " not permitted");
} }
} }
@Override @Override
public Set<GlobalPermission> test(Collection<GlobalPermission> permSet) public <T extends GlobalOrPluginPermission> Set<T> test(Collection<T> permSet)
throws PermissionBackendException { throws PermissionBackendException {
EnumSet<GlobalPermission> ok = EnumSet.noneOf(GlobalPermission.class); Set<T> ok = newSet(permSet);
for (GlobalPermission perm : permSet) { for (T perm : permSet) {
if (can(perm)) { if (can(perm)) {
ok.add(perm); ok.add(perm);
} }
@@ -83,8 +85,18 @@ class DefaultPermissionBackend extends PermissionBackend {
return ok; return ok;
} }
private boolean can(GlobalPermission perm) throws PermissionBackendException { private boolean can(GlobalOrPluginPermission perm) throws PermissionBackendException {
return user.getCapabilities().doCanForDefaultPermissionBackend(perm); 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());
}
} }

View File

@@ -22,6 +22,7 @@ import com.google.gerrit.server.EnableSignedPush;
import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry; import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.TransferConfig; import com.google.gerrit.server.git.TransferConfig;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@@ -33,6 +34,7 @@ public class GetConfig implements RestReadView<ProjectResource> {
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries; private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final PluginConfigFactory cfgFactory; private final PluginConfigFactory cfgFactory;
private final AllProjectsName allProjects; private final AllProjectsName allProjects;
private final UiActions uiActions;
private final DynamicMap<RestView<ProjectResource>> views; private final DynamicMap<RestView<ProjectResource>> views;
@Inject @Inject
@@ -42,12 +44,14 @@ public class GetConfig implements RestReadView<ProjectResource> {
DynamicMap<ProjectConfigEntry> pluginConfigEntries, DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory, PluginConfigFactory cfgFactory,
AllProjectsName allProjects, AllProjectsName allProjects,
UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) { DynamicMap<RestView<ProjectResource>> views) {
this.serverEnableSignedPush = serverEnableSignedPush; this.serverEnableSignedPush = serverEnableSignedPush;
this.config = config; this.config = config;
this.pluginConfigEntries = pluginConfigEntries; this.pluginConfigEntries = pluginConfigEntries;
this.allProjects = allProjects; this.allProjects = allProjects;
this.cfgFactory = cfgFactory; this.cfgFactory = cfgFactory;
this.uiActions = uiActions;
this.views = views; this.views = views;
} }
@@ -60,6 +64,7 @@ public class GetConfig implements RestReadView<ProjectResource> {
pluginConfigEntries, pluginConfigEntries,
cfgFactory, cfgFactory,
allProjects, allProjects,
uiActions,
views); views);
} }
} }

View File

@@ -30,7 +30,6 @@ import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.extensions.webui.UiActions; import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.util.Providers;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -48,6 +47,7 @@ import org.kohsuke.args4j.Option;
public class ListBranches implements RestReadView<ProjectResource> { public class ListBranches implements RestReadView<ProjectResource> {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final DynamicMap<RestView<BranchResource>> branchViews; private final DynamicMap<RestView<BranchResource>> branchViews;
private final UiActions uiActions;
private final WebLinks webLinks; private final WebLinks webLinks;
@Option( @Option(
@@ -99,9 +99,11 @@ public class ListBranches implements RestReadView<ProjectResource> {
public ListBranches( public ListBranches(
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
DynamicMap<RestView<BranchResource>> branchViews, DynamicMap<RestView<BranchResource>> branchViews,
UiActions uiActions,
WebLinks webLinks) { WebLinks webLinks) {
this.repoManager = repoManager; this.repoManager = repoManager;
this.branchViews = branchViews; this.branchViews = branchViews;
this.uiActions = uiActions;
this.webLinks = webLinks; this.webLinks = webLinks;
} }
@@ -197,16 +199,15 @@ public class ListBranches implements RestReadView<ProjectResource> {
info.ref = ref.getName(); info.ref = ref.getName();
info.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null; info.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
info.canDelete = !targets.contains(ref.getName()) && refControl.canDelete() ? true : null; info.canDelete = !targets.contains(ref.getName()) && refControl.canDelete() ? true : null;
for (UiAction.Description d :
UiActions.from( BranchResource rsrc = new BranchResource(refControl.getProjectControl(), info);
branchViews, for (UiAction.Description d : uiActions.from(branchViews, rsrc)) {
new BranchResource(refControl.getProjectControl(), info),
Providers.of(refControl.getUser()))) {
if (info.actions == null) { if (info.actions == null) {
info.actions = new TreeMap<>(); info.actions = new TreeMap<>();
} }
info.actions.put(d.getId(), new ActionInfo(d)); info.actions.put(d.getId(), new ActionInfo(d));
} }
List<WebLinkInfo> links = List<WebLinkInfo> links =
webLinks.getBranchLinks( webLinks.getBranchLinks(
refControl.getProjectControl().getProject().getName(), ref.getName()); refControl.getProjectControl().getProject().getName(), ref.getName());

View File

@@ -36,6 +36,7 @@ import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfig; import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry; import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.TransferConfig; import com.google.gerrit.server.git.TransferConfig;
@@ -64,6 +65,7 @@ public class PutConfig implements RestModifyView<ProjectResource, ConfigInput> {
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries; private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final PluginConfigFactory cfgFactory; private final PluginConfigFactory cfgFactory;
private final AllProjectsName allProjects; private final AllProjectsName allProjects;
private final UiActions uiActions;
private final DynamicMap<RestView<ProjectResource>> views; private final DynamicMap<RestView<ProjectResource>> views;
private final Provider<CurrentUser> user; private final Provider<CurrentUser> user;
@@ -77,6 +79,7 @@ public class PutConfig implements RestModifyView<ProjectResource, ConfigInput> {
DynamicMap<ProjectConfigEntry> pluginConfigEntries, DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory, PluginConfigFactory cfgFactory,
AllProjectsName allProjects, AllProjectsName allProjects,
UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views, DynamicMap<RestView<ProjectResource>> views,
Provider<CurrentUser> user) { Provider<CurrentUser> user) {
this.serverEnableSignedPush = serverEnableSignedPush; this.serverEnableSignedPush = serverEnableSignedPush;
@@ -87,6 +90,7 @@ public class PutConfig implements RestModifyView<ProjectResource, ConfigInput> {
this.pluginConfigEntries = pluginConfigEntries; this.pluginConfigEntries = pluginConfigEntries;
this.cfgFactory = cfgFactory; this.cfgFactory = cfgFactory;
this.allProjects = allProjects; this.allProjects = allProjects;
this.uiActions = uiActions;
this.views = views; this.views = views;
this.user = user; this.user = user;
} }
@@ -185,6 +189,7 @@ public class PutConfig implements RestModifyView<ProjectResource, ConfigInput> {
pluginConfigEntries, pluginConfigEntries,
cfgFactory, cfgFactory,
allProjects, allProjects,
uiActions,
views); views);
} catch (RepositoryNotFoundException notFound) { } catch (RepositoryNotFoundException notFound) {
throw new ResourceNotFoundException(projectName.get()); throw new ResourceNotFoundException(projectName.get());

View File

@@ -16,12 +16,16 @@ package com.google.gerrit.sshd;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.util.concurrent.Atomics; import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.CapabilityControl; 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 java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.server.Command; import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment; import org.apache.sshd.server.Environment;
@@ -30,14 +34,17 @@ import org.apache.sshd.server.Environment;
public class AliasCommand extends BaseCommand { public class AliasCommand extends BaseCommand {
private final DispatchCommandProvider root; private final DispatchCommandProvider root;
private final CurrentUser currentUser; private final CurrentUser currentUser;
private final PermissionBackend permissionBackend;
private final CommandName command; private final CommandName command;
private final AtomicReference<Command> atomicCmd; private final AtomicReference<Command> atomicCmd;
AliasCommand( AliasCommand(
@CommandName(Commands.ROOT) DispatchCommandProvider root, @CommandName(Commands.ROOT) DispatchCommandProvider root,
PermissionBackend permissionBackend,
CurrentUser currentUser, CurrentUser currentUser,
CommandName command) { CommandName command) {
this.root = root; this.root = root;
this.permissionBackend = permissionBackend;
this.currentUser = currentUser; this.currentUser = currentUser;
this.command = command; this.command = command;
this.atomicCmd = Atomics.newReference(); this.atomicCmd = Atomics.newReference();
@@ -47,7 +54,7 @@ public class AliasCommand extends BaseCommand {
public void start(Environment env) throws IOException { public void start(Environment env) throws IOException {
try { try {
begin(env); begin(env);
} catch (UnloggedFailure e) { } catch (Failure e) {
String msg = e.getMessage(); String msg = e.getMessage();
if (!msg.endsWith("\n")) { if (!msg.endsWith("\n")) {
msg += "\n"; msg += "\n";
@@ -58,7 +65,7 @@ public class AliasCommand extends BaseCommand {
} }
} }
private void begin(Environment env) throws UnloggedFailure, IOException { private void begin(Environment env) throws IOException, Failure {
Map<String, CommandProvider> map = root.getMap(); Map<String, CommandProvider> map = root.getMap();
for (String name : chain(command)) { for (String name : chain(command)) {
CommandProvider p = map.get(name); CommandProvider p = map.get(name);
@@ -103,17 +110,16 @@ public class AliasCommand extends BaseCommand {
} }
} }
private void checkRequiresCapability(Command cmd) throws UnloggedFailure { private void checkRequiresCapability(Command cmd) throws Failure {
RequiresCapability rc = cmd.getClass().getAnnotation(RequiresCapability.class); try {
if (rc != null) { Set<GlobalOrPluginPermission> check = GlobalPermission.fromAnnotation(cmd.getClass());
CapabilityControl ctl = currentUser.getCapabilities(); try {
if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) { permissionBackend.user(currentUser).checkAny(check);
String msg = } catch (AuthException err) {
String.format( throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, "fatal: " + err.getMessage());
"fatal: %s does not have \"%s\" capability.",
currentUser.getUserName(), rc.value());
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
} }
} catch (PermissionBackendException err) {
throw new Failure(1, "fatal: permissions unavailable", err);
} }
} }

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.sshd; package com.google.gerrit.sshd;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import org.apache.sshd.server.Command; import org.apache.sshd.server.Command;
@@ -27,6 +28,7 @@ public class AliasCommandProvider implements Provider<Command> {
@CommandName(Commands.ROOT) @CommandName(Commands.ROOT)
private DispatchCommandProvider root; private DispatchCommandProvider root;
@Inject private PermissionBackend permissionBackend;
@Inject private CurrentUser currentUser; @Inject private CurrentUser currentUser;
public AliasCommandProvider(CommandName command) { public AliasCommandProvider(CommandName command) {
@@ -35,6 +37,6 @@ public class AliasCommandProvider implements Provider<Command> {
@Override @Override
public Command get() { public Command get() {
return new AliasCommand(root, currentUser, command); return new AliasCommand(root, permissionBackend, currentUser, command);
} }
} }

View File

@@ -20,8 +20,10 @@ import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Atomics; import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.CapabilityUtils;
import com.google.gerrit.server.args4j.SubcommandHandler; import com.google.gerrit.server.args4j.SubcommandHandler;
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; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import java.io.IOException; import java.io.IOException;
@@ -41,6 +43,7 @@ final class DispatchCommand extends BaseCommand {
} }
private final CurrentUser currentUser; private final CurrentUser currentUser;
private final PermissionBackend permissionBackend;
private final Map<String, CommandProvider> commands; private final Map<String, CommandProvider> commands;
private final AtomicReference<Command> atomicCmd; private final AtomicReference<Command> atomicCmd;
@@ -51,8 +54,12 @@ final class DispatchCommand extends BaseCommand {
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Inject @Inject
DispatchCommand(CurrentUser cu, @Assisted final Map<String, CommandProvider> all) { DispatchCommand(
currentUser = cu; CurrentUser user,
PermissionBackend permissionBackend,
@Assisted Map<String, CommandProvider> all) {
this.currentUser = user;
this.permissionBackend = permissionBackend;
commands = all; commands = all;
atomicCmd = Atomics.newReference(); atomicCmd = Atomics.newReference();
} }
@@ -117,9 +124,13 @@ final class DispatchCommand extends BaseCommand {
pluginName = ((BaseCommand) cmd).getPluginName(); pluginName = ((BaseCommand) cmd).getPluginName();
} }
try { try {
CapabilityUtils.checkRequiresCapability(currentUser, pluginName, cmd.getClass()); permissionBackend
.user(currentUser)
.checkAny(GlobalPermission.fromAnnotation(pluginName, cmd.getClass()));
} catch (AuthException e) { } catch (AuthException e) {
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, e.getMessage()); throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, e.getMessage());
} catch (PermissionBackendException e) {
throw new UnloggedFailure(1, "fatal: permission check unavailable", e);
} }
} }