Convert RequireCapability checks to PermissionBackend
Replace CapabilityUtils with support in PermissionBackend to check if
the caller has at least one of the specified permissions parsed from
class annotation.
This enables hiding canPerform(String) from CapabilityControl, which
makes it much harder to bypass the PermissionBackend.
Assume anyone with ADMINISTRATE_SERVER also has any PluginPermission.
This is carried over from CapabilityUtils, which skip any further
checks when the user has canAdministrateServer.
Update the error message in GarbageCollectionIT to now be the generic
"maintain server not permitted".
Change-Id: I9458bd55fa1c9709557ae1ad95a57a1d968c52a3
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
index b1efb4a..19fcff9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
@@ -83,10 +83,7 @@
assertThat(userSshSession.hasError()).isTrue();
String error = userSshSession.getError();
assertThat(error).isNotNull();
- assertError(
- "One of the following capabilities is required to access this"
- + " resource: [runGC, maintainServer]",
- error);
+ assertError("maintain server not permitted", error);
}
@Test
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index abf5323..3385bf2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -95,8 +95,10 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OptionUtil;
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.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.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
@@ -188,6 +190,7 @@
final Provider<CurrentUser> currentUser;
final DynamicItem<WebSession> webSession;
final Provider<ParameterParser> paramParser;
+ final PermissionBackend permissionBackend;
final AuditService auditService;
final RestApiMetrics metrics;
final Pattern allowOrigin;
@@ -197,12 +200,14 @@
Provider<CurrentUser> currentUser,
DynamicItem<WebSession> webSession,
Provider<ParameterParser> paramParser,
+ PermissionBackend permissionBackend,
AuditService auditService,
RestApiMetrics metrics,
@GerritServerConfig Config cfg) {
this.currentUser = currentUser;
this.webSession = webSession;
this.paramParser = paramParser;
+ this.permissionBackend = permissionBackend;
this.auditService = auditService;
this.metrics = metrics;
allowOrigin = makeAllowOrigin(cfg);
@@ -263,7 +268,10 @@
List<IdString> path = splitPath(req);
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);
@@ -1106,9 +1114,12 @@
return "GET".equals(req.getMethod()) || "HEAD".equals(req.getMethod());
}
- private void checkRequiresCapability(ViewData viewData) throws AuthException {
- CapabilityUtils.checkRequiresCapability(
- globals.currentUser, viewData.pluginName, viewData.view.getClass());
+ private void checkRequiresCapability(ViewData d)
+ throws AuthException, PermissionBackendException {
+ globals
+ .permissionBackend
+ .user(globals.currentUser)
+ .checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
}
private static long handleException(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index 7e3217b..f38d019 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -142,11 +142,8 @@
return QueueProvider.QueueType.INTERACTIVE;
}
- /** True if the user has this permission. Works only for non labels. */
- public boolean canPerform(String permissionName) {
- if (GlobalCapability.ADMINISTRATE_SERVER.equals(permissionName)) {
- return canAdministrateServer();
- }
+ /** @return true if the user has this permission. */
+ private boolean canPerform(String permissionName) {
return !access(permissionName).isEmpty();
}
@@ -223,7 +220,7 @@
if (perm instanceof GlobalPermission) {
return can((GlobalPermission) perm);
} else if (perm instanceof PluginPermission) {
- return canPerform(perm.permissionName());
+ return canPerform(perm.permissionName()) || canAdministrateServer();
}
throw new PermissionBackendException(perm + " unsupported");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
deleted file mode 100644
index 21399f4..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
index 498b720..bade8ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.api.accounts;
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.AccountInput;
@@ -32,6 +31,9 @@
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.CreateAccount;
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.inject.Inject;
import com.google.inject.Provider;
@@ -44,6 +46,7 @@
public class AccountsImpl implements Accounts {
private final AccountsCollection accounts;
private final AccountApiImpl.Factory api;
+ private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> self;
private final CreateAccount.Factory createAccount;
private final Provider<QueryAccounts> queryAccountsProvider;
@@ -52,11 +55,13 @@
AccountsImpl(
AccountsCollection accounts,
AccountApiImpl.Factory api,
+ PermissionBackend permissionBackend,
Provider<CurrentUser> self,
CreateAccount.Factory createAccount,
Provider<QueryAccounts> queryAccountsProvider) {
this.accounts = accounts;
this.api = api;
+ this.permissionBackend = permissionBackend;
this.self = self;
this.createAccount = createAccount;
this.queryAccountsProvider = queryAccountsProvider;
@@ -96,12 +101,12 @@
if (checkNotNull(in, "AccountInput").username == null) {
throw new BadRequestException("AccountInput must specify username");
}
- checkRequiresCapability(self, null, CreateAccount.class);
try {
- AccountInfo info =
- createAccount.create(in.username).apply(TopLevelResource.INSTANCE, in).value();
+ CreateAccount impl = createAccount.create(in.username);
+ permissionBackend.user(self).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
+ AccountInfo info = impl.apply(TopLevelResource.INSTANCE, in).value();
return id(info._accountId);
- } catch (OrmException | IOException | ConfigInvalidException e) {
+ } catch (OrmException | IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot create account " + in.username, e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
index 1d725a8..6eef5e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.api.groups;
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.GroupInput;
@@ -32,6 +31,9 @@
import com.google.gerrit.server.group.GroupsCollection;
import com.google.gerrit.server.group.ListGroups;
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.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -49,6 +51,7 @@
private final Provider<ListGroups> listGroups;
private final Provider<QueryGroups> queryGroups;
private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
private final CreateGroup.Factory createGroup;
private final GroupApiImpl.Factory api;
@@ -60,6 +63,7 @@
Provider<ListGroups> listGroups,
Provider<QueryGroups> queryGroups,
Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
CreateGroup.Factory createGroup,
GroupApiImpl.Factory api) {
this.accounts = accounts;
@@ -68,6 +72,7 @@
this.listGroups = listGroups;
this.queryGroups = queryGroups;
this.user = user;
+ this.permissionBackend = permissionBackend;
this.createGroup = createGroup;
this.api = api;
}
@@ -89,11 +94,12 @@
if (checkNotNull(in, "GroupInput").name == null) {
throw new BadRequestException("GroupInput must specify name");
}
- checkRequiresCapability(user, null, CreateGroup.class);
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);
- } catch (OrmException | IOException e) {
+ } catch (OrmException | IOException | PermissionBackendException e) {
throw new RestApiException("Cannot create group " + in.name, e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 025b62a..65673de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -14,8 +14,6 @@
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.ProjectAccessInput;
import com.google.gerrit.extensions.api.projects.BranchApi;
@@ -39,6 +37,9 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
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.CommitsCollection;
import com.google.gerrit.server.project.CreateProject;
@@ -71,6 +72,7 @@
}
private final CurrentUser user;
+ private final PermissionBackend permissionBackend;
private final CreateProject.Factory createProjectFactory;
private final ProjectApiImpl.Factory projectApi;
private final ProjectsCollection projects;
@@ -97,6 +99,7 @@
@AssistedInject
ProjectApiImpl(
CurrentUser user,
+ PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
@@ -120,6 +123,7 @@
@Assisted ProjectResource project) {
this(
user,
+ permissionBackend,
createProjectFactory,
projectApi,
projects,
@@ -147,6 +151,7 @@
@AssistedInject
ProjectApiImpl(
CurrentUser user,
+ PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
@@ -170,6 +175,7 @@
@Assisted String name) {
this(
user,
+ permissionBackend,
createProjectFactory,
projectApi,
projects,
@@ -196,6 +202,7 @@
private ProjectApiImpl(
CurrentUser user,
+ PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
@@ -219,6 +226,7 @@
CommitApiImpl.Factory commitApi,
String name) {
this.user = user;
+ this.permissionBackend = permissionBackend;
this.createProjectFactory = createProjectFactory;
this.projectApi = projectApi;
this.projects = projects;
@@ -257,10 +265,11 @@
if (in.name != null && !name.equals(in.name)) {
throw new BadRequestException("name must match input.name");
}
- checkRequiresCapability(user, null, CreateProject.class);
- createProjectFactory.create(name).apply(TopLevelResource.INSTANCE, in);
+ CreateProject impl = createProjectFactory.create(name);
+ permissionBackend.user(user).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
+ impl.apply(TopLevelResource.INSTANCE, in);
return projectApi.create(projects.parse(name));
- } catch (IOException | ConfigInvalidException e) {
+ } catch (IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot create project: " + e.getMessage(), e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
index af619d7..19fdcfb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -30,14 +30,11 @@
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
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.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
-import com.google.inject.util.Providers;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -48,6 +45,7 @@
private final Revisions revisions;
private final ChangeJson.Factory changeJsonFactory;
private final ChangeResource.Factory changeResourceFactory;
+ private final UiActions uiActions;
private final DynamicMap<RestView<ChangeResource>> changeViews;
private final DynamicSet<ActionVisitor> visitorSet;
@@ -56,11 +54,13 @@
Revisions revisions,
ChangeJson.Factory changeJsonFactory,
ChangeResource.Factory changeResourceFactory,
+ UiActions uiActions,
DynamicMap<RestView<ChangeResource>> changeViews,
DynamicSet<ActionVisitor> visitorSet) {
this.revisions = revisions;
this.changeJsonFactory = changeJsonFactory;
this.changeResourceFactory = changeResourceFactory;
+ this.uiActions = uiActions;
this.changeViews = changeViews;
this.visitorSet = visitorSet;
}
@@ -162,9 +162,9 @@
return out;
}
- Provider<CurrentUser> userProvider = Providers.of(ctl.getUser());
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
// have a server side handler. It must be manually registered into the
// resulting action map.
@@ -198,10 +198,10 @@
if (!rsrc.getControl().getUser().isIdentifiedUser()) {
return ImmutableMap.of();
}
+
Map<String, ActionInfo> out = new LinkedHashMap<>();
- Provider<CurrentUser> userProvider = Providers.of(rsrc.getControl().getUser());
ACTION:
- for (UiAction.Description d : UiActions.from(revisions, rsrc, userProvider)) {
+ for (UiAction.Description d : uiActions.from(revisions, rsrc)) {
ActionInfo actionInfo = new ActionInfo(d);
for (ActionVisitor visitor : visitors) {
if (!visitor.visit(d.getId(), actionInfo, changeInfo, revisionInfo)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index b7f872b..9754a3a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -107,6 +107,7 @@
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.events.EventsMetrics;
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.ChangeMessageModifier;
import com.google.gerrit.server.git.EmailMerge;
@@ -293,6 +294,7 @@
bind(AccountControl.Factory.class);
install(new AuditModule());
+ bind(UiActions.class);
install(new com.google.gerrit.server.access.Module());
install(new com.google.gerrit.server.account.Module());
install(new com.google.gerrit.server.api.Module());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
index 85ee4f9..bd5d6a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -16,20 +16,27 @@
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
+import com.google.gerrit.common.Nullable;
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.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
import com.google.gerrit.extensions.webui.UiAction;
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.Singleton;
import java.util.Objects;
+import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+@Singleton
public class UiActions {
private static final Logger log = LoggerFactory.getLogger(UiActions.class);
@@ -37,57 +44,70 @@
return UiAction.Description::isEnabled;
}
- public static <R extends RestResource> FluentIterable<UiAction.Description> from(
- RestCollection<?, R> collection, R resource, Provider<CurrentUser> userProvider) {
- return from(collection.views(), resource, userProvider);
+ private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> userProvider;
+
+ @Inject
+ UiActions(PermissionBackend permissionBackend, Provider<CurrentUser> userProvider) {
+ this.permissionBackend = permissionBackend;
+ this.userProvider = userProvider;
}
- public static <R extends RestResource> FluentIterable<UiAction.Description> from(
- DynamicMap<RestView<R>> views, R resource, Provider<CurrentUser> userProvider) {
+ public <R extends RestResource> FluentIterable<UiAction.Description> from(
+ 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)
- .transform(
- (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;
- })
+ .transform((e) -> describe(e, resource))
.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;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalPermission.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalPermission.java
index 89dd67c..4111253 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalPermission.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalPermission.java
@@ -17,7 +17,16 @@
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.common.Nullable;
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.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/** Global server permissions built into Gerrit. */
public enum GlobalPermission implements GlobalOrPluginPermission {
@@ -40,6 +49,7 @@
VIEW_PLUGINS(GlobalCapability.VIEW_PLUGINS),
VIEW_QUEUE(GlobalCapability.VIEW_QUEUE);
+ private static final Logger log = LoggerFactory.getLogger(GlobalPermission.class);
private static final ImmutableMap<String, GlobalPermission> BY_NAME;
static {
@@ -55,6 +65,47 @@
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;
GlobalPermission(String name) {
@@ -71,4 +122,45 @@
public String describeForException() {
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;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
index 76acce7..83b9182 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -31,6 +31,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
+import java.util.Iterator;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -137,6 +138,35 @@
public abstract void check(GlobalOrPluginPermission perm)
throws AuthException, PermissionBackendException;
+ /**
+ * Verify scoped user can perform at least one listed permission.
+ *
+ * <p>If {@code any} is empty, the method completes normally and allows the caller to continue.
+ * Since no permissions were supplied to check, its assumed no permissions are necessary to
+ * 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;
+ }
+ }
+ }
+ }
+
/** 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;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
index 0dcb5f8..4f83d5f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
@@ -31,7 +31,6 @@
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.inject.util.Providers;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -45,6 +44,7 @@
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
+ UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) {
ProjectState projectState = control.getProjectState();
Project p = control.getProject();
@@ -126,8 +126,7 @@
getPluginConfig(control.getProjectState(), pluginConfigEntries, cfgFactory, allProjects);
actions = new TreeMap<>();
- for (UiAction.Description d :
- UiActions.from(views, new ProjectResource(control), Providers.of(control.getUser()))) {
+ for (UiAction.Description d : uiActions.from(views, new ProjectResource(control))) {
actions.put(d.getId(), new ActionInfo(d));
}
this.theme = projectState.getTheme();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
index 8192e29..b1ba281 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
@@ -22,6 +22,7 @@
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfigFactory;
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.inject.Inject;
import com.google.inject.Singleton;
@@ -33,6 +34,7 @@
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final PluginConfigFactory cfgFactory;
private final AllProjectsName allProjects;
+ private final UiActions uiActions;
private final DynamicMap<RestView<ProjectResource>> views;
@Inject
@@ -42,12 +44,14 @@
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
+ UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) {
this.serverEnableSignedPush = serverEnableSignedPush;
this.config = config;
this.pluginConfigEntries = pluginConfigEntries;
this.allProjects = allProjects;
this.cfgFactory = cfgFactory;
+ this.uiActions = uiActions;
this.views = views;
}
@@ -60,6 +64,7 @@
pluginConfigEntries,
cfgFactory,
allProjects,
+ uiActions,
views);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index a5b6458..09a6b86 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -30,7 +30,6 @@
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
-import com.google.inject.util.Providers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -48,6 +47,7 @@
public class ListBranches implements RestReadView<ProjectResource> {
private final GitRepositoryManager repoManager;
private final DynamicMap<RestView<BranchResource>> branchViews;
+ private final UiActions uiActions;
private final WebLinks webLinks;
@Option(
@@ -99,9 +99,11 @@
public ListBranches(
GitRepositoryManager repoManager,
DynamicMap<RestView<BranchResource>> branchViews,
+ UiActions uiActions,
WebLinks webLinks) {
this.repoManager = repoManager;
this.branchViews = branchViews;
+ this.uiActions = uiActions;
this.webLinks = webLinks;
}
@@ -197,16 +199,15 @@
info.ref = ref.getName();
info.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
info.canDelete = !targets.contains(ref.getName()) && refControl.canDelete() ? true : null;
- for (UiAction.Description d :
- UiActions.from(
- branchViews,
- new BranchResource(refControl.getProjectControl(), info),
- Providers.of(refControl.getUser()))) {
+
+ BranchResource rsrc = new BranchResource(refControl.getProjectControl(), info);
+ for (UiAction.Description d : uiActions.from(branchViews, rsrc)) {
if (info.actions == null) {
info.actions = new TreeMap<>();
}
info.actions.put(d.getId(), new ActionInfo(d));
}
+
List<WebLinkInfo> links =
webLinks.getBranchLinks(
refControl.getProjectControl().getProject().getName(), ref.getName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 8705f3b..806c01a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -36,6 +36,7 @@
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
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.ProjectConfig;
import com.google.gerrit.server.git.TransferConfig;
@@ -64,6 +65,7 @@
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final PluginConfigFactory cfgFactory;
private final AllProjectsName allProjects;
+ private final UiActions uiActions;
private final DynamicMap<RestView<ProjectResource>> views;
private final Provider<CurrentUser> user;
@@ -77,6 +79,7 @@
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
+ UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views,
Provider<CurrentUser> user) {
this.serverEnableSignedPush = serverEnableSignedPush;
@@ -87,6 +90,7 @@
this.pluginConfigEntries = pluginConfigEntries;
this.cfgFactory = cfgFactory;
this.allProjects = allProjects;
+ this.uiActions = uiActions;
this.views = views;
this.user = user;
}
@@ -185,6 +189,7 @@
pluginConfigEntries,
cfgFactory,
allProjects,
+ uiActions,
views);
} catch (RepositoryNotFoundException notFound) {
throw new ResourceNotFoundException(projectName.get());
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
index 45835d9..7b7893a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
@@ -16,12 +16,16 @@
import com.google.common.base.Throwables;
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.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.util.LinkedList;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
@@ -30,14 +34,17 @@
public class AliasCommand extends BaseCommand {
private final DispatchCommandProvider root;
private final CurrentUser currentUser;
+ private final PermissionBackend permissionBackend;
private final CommandName command;
private final AtomicReference<Command> atomicCmd;
AliasCommand(
@CommandName(Commands.ROOT) DispatchCommandProvider root,
+ PermissionBackend permissionBackend,
CurrentUser currentUser,
CommandName command) {
this.root = root;
+ this.permissionBackend = permissionBackend;
this.currentUser = currentUser;
this.command = command;
this.atomicCmd = Atomics.newReference();
@@ -47,7 +54,7 @@
public void start(Environment env) throws IOException {
try {
begin(env);
- } catch (UnloggedFailure e) {
+ } catch (Failure e) {
String msg = e.getMessage();
if (!msg.endsWith("\n")) {
msg += "\n";
@@ -58,7 +65,7 @@
}
}
- private void begin(Environment env) throws UnloggedFailure, IOException {
+ private void begin(Environment env) throws IOException, Failure {
Map<String, CommandProvider> map = root.getMap();
for (String name : chain(command)) {
CommandProvider p = map.get(name);
@@ -103,17 +110,16 @@
}
}
- private void checkRequiresCapability(Command cmd) throws UnloggedFailure {
- RequiresCapability rc = cmd.getClass().getAnnotation(RequiresCapability.class);
- if (rc != null) {
- CapabilityControl ctl = currentUser.getCapabilities();
- if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) {
- String msg =
- String.format(
- "fatal: %s does not have \"%s\" capability.",
- currentUser.getUserName(), rc.value());
- throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ private void checkRequiresCapability(Command cmd) throws Failure {
+ try {
+ Set<GlobalOrPluginPermission> check = GlobalPermission.fromAnnotation(cmd.getClass());
+ try {
+ permissionBackend.user(currentUser).checkAny(check);
+ } catch (AuthException err) {
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, "fatal: " + err.getMessage());
}
+ } catch (PermissionBackendException err) {
+ throw new Failure(1, "fatal: permissions unavailable", err);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommandProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommandProvider.java
index 10beb40..0ef0473 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommandProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommandProvider.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.sshd.server.Command;
@@ -27,6 +28,7 @@
@CommandName(Commands.ROOT)
private DispatchCommandProvider root;
+ @Inject private PermissionBackend permissionBackend;
@Inject private CurrentUser currentUser;
public AliasCommandProvider(CommandName command) {
@@ -35,6 +37,6 @@
@Override
public Command get() {
- return new AliasCommand(root, currentUser, command);
+ return new AliasCommand(root, permissionBackend, currentUser, command);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
index 2f3d10f6..87e90f4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -20,8 +20,10 @@
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.extensions.restapi.AuthException;
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.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.assistedinject.Assisted;
import java.io.IOException;
@@ -41,6 +43,7 @@
}
private final CurrentUser currentUser;
+ private final PermissionBackend permissionBackend;
private final Map<String, CommandProvider> commands;
private final AtomicReference<Command> atomicCmd;
@@ -51,8 +54,12 @@
private List<String> args = new ArrayList<>();
@Inject
- DispatchCommand(CurrentUser cu, @Assisted final Map<String, CommandProvider> all) {
- currentUser = cu;
+ DispatchCommand(
+ CurrentUser user,
+ PermissionBackend permissionBackend,
+ @Assisted Map<String, CommandProvider> all) {
+ this.currentUser = user;
+ this.permissionBackend = permissionBackend;
commands = all;
atomicCmd = Atomics.newReference();
}
@@ -117,9 +124,13 @@
pluginName = ((BaseCommand) cmd).getPluginName();
}
try {
- CapabilityUtils.checkRequiresCapability(currentUser, pluginName, cmd.getClass());
+ permissionBackend
+ .user(currentUser)
+ .checkAny(GlobalPermission.fromAnnotation(pluginName, cmd.getClass()));
} catch (AuthException e) {
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, e.getMessage());
+ } catch (PermissionBackendException e) {
+ throw new UnloggedFailure(1, "fatal: permission check unavailable", e);
}
}