Add support for plugin-owned capabilities

Plugin contributed SSH commands and UiActions can now be annotated
with plugin-owned capabilities.

Capability scope was introduced to differentiate between plugin-owned
capabilities and core capabilities. Per default the scope of
@RequiresCapability annotation is CapabilityScope.CONTEXT, i. e. when
@RequiresCapability is used within a plugin the scope of the capability is
assumed to be that plugin. If @RequiresCapability is used within the core
Gerrit Code Review server (and thus is outside of a plugin) the scope is the
core server and will use the GlobalCapability known to Gerrit Code Review
server.

If a plugin needs to use a core capability name (e.g. "administrateServer")
this can be specified by setting scope = CapabilityScope.CORE:

  @RequiresCapability(value="administrateServer", scope=CapabilityScope.CORE)

Change-Id: I82f7a6fef2a47613a1fd9c7474ff568db3ca84a2
This commit is contained in:
David Ostrovsky
2013-06-15 14:46:23 +02:00
parent 362963311e
commit 7066cc064e
13 changed files with 339 additions and 46 deletions

View File

@@ -34,6 +34,8 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -68,10 +70,13 @@ class GetCapabilities implements RestReadView<AccountResource> {
private Set<String> query;
private final Provider<CurrentUser> self;
private final DynamicMap<CapabilityDefinition> pluginCapabilities;
@Inject
GetCapabilities(Provider<CurrentUser> self) {
GetCapabilities(Provider<CurrentUser> self,
DynamicMap<CapabilityDefinition> pluginCapabilities) {
this.self = self;
this.pluginCapabilities = pluginCapabilities;
}
@Override
@@ -93,6 +98,14 @@ class GetCapabilities implements RestReadView<AccountResource> {
}
}
}
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);
}
}
}
have.put(CREATE_ACCOUNT, cc.canCreateAccount());
have.put(CREATE_GROUP, cc.canCreateGroup());

View File

@@ -19,6 +19,7 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.common.cache.Cache;
import com.google.gerrit.audit.AuditModule;
import com.google.gerrit.common.ChangeListener;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -246,6 +247,7 @@ public class GerritGlobalModule extends FactoryModule {
bind(GitReferenceUpdated.class);
DynamicMap.mapOf(binder(), new TypeLiteral<Cache<?, ?>>() {});
DynamicSet.setOf(binder(), CacheRemovalListener.class);
DynamicMap.mapOf(binder(), CapabilityDefinition.class);
DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);

View File

@@ -16,21 +16,39 @@ package com.google.gerrit.server.config;
import com.google.common.collect.Maps;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Map;
/** List capabilities visible to the calling user. */
public class ListCapabilities implements RestReadView<ConfigResource> {
private final DynamicMap<CapabilityDefinition> pluginCapabilities;
@Inject
public ListCapabilities(DynamicMap<CapabilityDefinition> pluginCapabilities) {
this.pluginCapabilities = pluginCapabilities;
}
@Override
public Map<String, CapabilityInfo> apply(ConfigResource resource)
throws AuthException, BadRequestException, ResourceConflictException,
IllegalArgumentException, SecurityException, IllegalAccessException,
NoSuchFieldException {
Map<String, CapabilityInfo> output = Maps.newTreeMap();
collectCoreCapabilities(output);
collectPluginCapabilities(output);
return output;
}
private void collectCoreCapabilities(Map<String, CapabilityInfo> output)
throws IllegalAccessException, NoSuchFieldException {
Class<? extends CapabilityConstants> bundleClass =
CapabilityConstants.get().getClass();
CapabilityConstants c = CapabilityConstants.get();
@@ -38,7 +56,18 @@ public class ListCapabilities implements RestReadView<ConfigResource> {
String name = (String) bundleClass.getField(id).get(c);
output.put(id, new CapabilityInfo(id, name));
}
return output;
}
private void collectPluginCapabilities(Map<String, CapabilityInfo> output) {
for (String pluginName : pluginCapabilities.plugins()) {
for (Map.Entry<String, Provider<CapabilityDefinition>> entry :
pluginCapabilities.byPlugin(pluginName).entrySet()) {
String id = String.format("%s-%s", pluginName, entry.getKey());
output.put(id, new CapabilityInfo(
id,
entry.getValue().get().getDescription()));
}
}
}
public static class CapabilityInfo {

View File

@@ -533,15 +533,13 @@ public class ProjectConfig extends VersionedMetaData {
AccessSection capability = null;
for (String varName : rc.getNames(CAPABILITY)) {
if (GlobalCapability.isCapability(varName)) {
if (capability == null) {
capability = new AccessSection(AccessSection.GLOBAL_CAPABILITIES);
accessSections.put(AccessSection.GLOBAL_CAPABILITIES, capability);
}
Permission perm = capability.getPermission(varName, true);
loadPermissionRules(rc, CAPABILITY, null, varName, groupsByName, perm,
GlobalCapability.hasRange(varName));
if (capability == null) {
capability = new AccessSection(AccessSection.GLOBAL_CAPABILITIES);
accessSections.put(AccessSection.GLOBAL_CAPABILITIES, capability);
}
Permission perm = capability.getPermission(varName, true);
loadPermissionRules(rc, CAPABILITY, null, varName, groupsByName, perm,
GlobalCapability.hasRange(varName));
}
}
@@ -879,8 +877,7 @@ public class ProjectConfig extends VersionedMetaData {
rc.setStringList(CAPABILITY, null, permission.getName(), rules);
}
for (String varName : rc.getNames(CAPABILITY)) {
if (GlobalCapability.isCapability(varName)
&& !have.contains(varName.toLowerCase())) {
if (!have.contains(varName.toLowerCase())) {
rc.unset(CAPABILITY, null, varName);
}
}

View File

@@ -19,21 +19,55 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.server.config.ListCapabilities.CapabilityInfo;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.junit.Before;
import org.junit.Test;
import java.util.Map;
public class ListCapabilitiesTest {
private Injector injector;
@Before
public void setUp() throws Exception {
AbstractModule mod = new AbstractModule() {
@Override
protected void configure() {
DynamicMap.mapOf(binder(), CapabilityDefinition.class);
bind(CapabilityDefinition.class)
.annotatedWith(Exports.named("printHello"))
.toInstance(new CapabilityDefinition() {
@Override
public String getDescription() {
return "Print Hello";
}
});
}
};
injector = Guice.createInjector(mod);
}
@Test
public void testList() throws Exception {
Map<String, CapabilityInfo> m =
new ListCapabilities().apply(new ConfigResource());
injector.getInstance(ListCapabilities.class)
.apply(new ConfigResource());
for (String id : GlobalCapability.getAllNames()) {
assertTrue("contains " + id, m.containsKey(id));
assertEquals(id, m.get(id).id);
assertNotNull(id + " has name", m.get(id).name);
}
String pluginCapability = "gerrit-printHello";
assertTrue("contains " + pluginCapability, m.containsKey(pluginCapability));
assertEquals(pluginCapability, m.get(pluginCapability).id);
assertEquals("Print Hello", m.get(pluginCapability).name);
}
}