Expose only extension-api to extensions

Unless a plugin declares "Gerrit-ApiType: plugin" in its manifest,
assume it is an extension and only make the gerrit-extension-api
available to it through the ClassLoader.

For non-plugins, do not make any Guice bindings available from the
server. This further restricts what an extension can see and do with
the system internals.

Change-Id: Ia38336c42786afb1419d64c06b0d908ae92a64d1
This commit is contained in:
Shawn O. Pearce
2012-05-10 16:54:28 -07:00
parent 6fa1e9121d
commit da4919abf6
8 changed files with 417 additions and 74 deletions

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.extensions.annotations.PluginData;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
@@ -40,6 +41,10 @@ import java.util.jar.Manifest;
import javax.annotation.Nullable;
public class Plugin {
public static enum ApiType {
EXTENSION, PLUGIN;
}
static {
// Guice logs warnings about multiple injectors being created.
// Silence this in case HTTP plugins are used.
@@ -47,12 +52,26 @@ public class Plugin {
.setLevel(java.util.logging.Level.OFF);
}
static ApiType getApiType(Manifest manifest) throws InvalidPluginException {
Attributes main = manifest.getMainAttributes();
String v = main.getValue("Gerrit-ApiType");
if (Strings.isNullOrEmpty(v)
|| ApiType.EXTENSION.name().equalsIgnoreCase(v)) {
return ApiType.EXTENSION;
} else if (ApiType.PLUGIN.name().equalsIgnoreCase(v)) {
return ApiType.PLUGIN;
} else {
throw new InvalidPluginException("Invalid Gerrit-ApiType: " + v);
}
}
private final String name;
private final File srcJar;
private final FileSnapshot snapshot;
private final JarFile jarFile;
private final Manifest manifest;
private final File dataDir;
private final ApiType apiType;
private final ClassLoader classLoader;
private Class<? extends Module> sysModule;
private Class<? extends Module> sshModule;
@@ -70,6 +89,7 @@ public class Plugin {
JarFile jarFile,
Manifest manifest,
File dataDir,
ApiType apiType,
ClassLoader classLoader,
@Nullable Class<? extends Module> sysModule,
@Nullable Class<? extends Module> sshModule,
@@ -80,6 +100,7 @@ public class Plugin {
this.jarFile = jarFile;
this.manifest = manifest;
this.dataDir = dataDir;
this.apiType = apiType;
this.classLoader = classLoader;
this.sysModule = sysModule;
this.sshModule = sshModule;
@@ -94,11 +115,16 @@ public class Plugin {
return name;
}
@Nullable
public String getVersion() {
Attributes main = manifest.getMainAttributes();
return main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
}
public ApiType getApiType() {
return apiType;
}
boolean canReload() {
Attributes main = manifest.getMainAttributes();
String v = main.getValue("Gerrit-ReloadMode");
@@ -139,29 +165,33 @@ public class Plugin {
}
if (env.hasSshModule()) {
List<Module> modules = Lists.newLinkedList();
if (apiType == ApiType.PLUGIN) {
modules.add(env.getSshModule());
}
if (sshModule != null) {
sshInjector = sysInjector.createChildInjector(
env.getSshModule(),
sysInjector.getInstance(sshModule));
modules.add(sysInjector.getInstance(sshModule));
sshInjector = sysInjector.createChildInjector(modules);
manager.add(sshInjector);
} else if (auto != null && auto.sshModule != null) {
sshInjector = sysInjector.createChildInjector(
env.getSshModule(),
auto.sshModule);
modules.add(auto.sshModule);
sshInjector = sysInjector.createChildInjector(modules);
manager.add(sshInjector);
}
}
if (env.hasHttpModule()) {
List<Module> modules = Lists.newLinkedList();
if (apiType == ApiType.PLUGIN) {
modules.add(env.getHttpModule());
}
if (httpModule != null) {
httpInjector = sysInjector.createChildInjector(
env.getHttpModule(),
sysInjector.getInstance(httpModule));
modules.add(sysInjector.getInstance(httpModule));
httpInjector = sysInjector.createChildInjector(modules);
manager.add(httpInjector);
} else if (auto != null && auto.httpModule != null) {
httpInjector = sysInjector.createChildInjector(
env.getHttpModule(),
auto.httpModule);
modules.add(auto.httpModule);
httpInjector = sysInjector.createChildInjector(modules);
manager.add(httpInjector);
}
}
@@ -169,9 +199,19 @@ public class Plugin {
manager.start();
}
private Injector newRootInjector(PluginGuiceEnvironment env) {
private Injector newRootInjector(final PluginGuiceEnvironment env) {
List<Module> modules = Lists.newArrayListWithCapacity(4);
modules.add(env.getSysModule());
if (apiType == ApiType.PLUGIN) {
modules.add(env.getSysModule());
} else {
modules.add(new AbstractModule() {
@Override
protected void configure() {
bind(ServerInformation.class).toInstance(env.getServerInformation());
}
});
}
modules.add(new AbstractModule() {
@Override
protected void configure() {

View File

@@ -19,6 +19,7 @@ import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.server.config.ConfigUtil;
@@ -340,7 +341,7 @@ public class PluginLoader implements LifecycleListener {
}
private Plugin loadPlugin(String name, File srcJar, FileSnapshot snapshot)
throws IOException, ClassNotFoundException {
throws IOException, ClassNotFoundException, InvalidPluginException {
File tmp;
FileInputStream in = new FileInputStream(srcJar);
try {
@@ -353,13 +354,20 @@ public class PluginLoader implements LifecycleListener {
boolean keep = false;
try {
Manifest manifest = jarFile.getManifest();
Plugin.ApiType type = Plugin.getApiType(manifest);
Attributes main = manifest.getMainAttributes();
String sysName = main.getValue("Gerrit-Module");
String sshName = main.getValue("Gerrit-SshModule");
String httpName = main.getValue("Gerrit-HttpModule");
if (!Strings.isNullOrEmpty(sshName) && type != Plugin.ApiType.PLUGIN) {
throw new InvalidPluginException(String.format(
"Using Gerrit-SshModule requires Gerrit-ApiType: %s",
Plugin.ApiType.PLUGIN));
}
URL[] urls = {tmp.toURI().toURL()};
ClassLoader parentLoader = PluginLoader.class.getClassLoader();
ClassLoader parentLoader = parentFor(type);
ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader);
cleanupHandles.put(
new CleanupHandle(tmp, jarFile, pluginLoader, cleanupQueue),
@@ -372,7 +380,7 @@ public class PluginLoader implements LifecycleListener {
return new Plugin(name,
srcJar, snapshot,
jarFile, manifest,
new File(dataDir, name), pluginLoader,
new File(dataDir, name), type, pluginLoader,
sysModule, sshModule, httpModule);
} finally {
if (!keep) {
@@ -381,6 +389,18 @@ public class PluginLoader implements LifecycleListener {
}
}
private static ClassLoader parentFor(Plugin.ApiType type)
throws InvalidPluginException {
switch (type) {
case EXTENSION:
return PluginName.class.getClassLoader();
case PLUGIN:
return PluginLoader.class.getClassLoader();
default:
throw new InvalidPluginException("Unsupported ApiType " + type);
}
}
private static String tempNameFor(String name) {
SimpleDateFormat fmt = new SimpleDateFormat("yyMMdd_HHmm");
return "plugin_" + name + "_" + fmt.format(new Date()) + "_";