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:
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user