diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java index f13eee9ccc..6f5618bb7b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java @@ -31,6 +31,8 @@ import java.util.Set; * anything it wants, anytime it wants, given the JVM's own direct access to * data. Plugins may use this when they need to have a CurrentUser with read * permission on anything. + * + * @see PluginUser */ public class InternalUser extends CurrentUser { public interface Factory { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PluginUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/PluginUser.java new file mode 100644 index 0000000000..490ab07c26 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/PluginUser.java @@ -0,0 +1,46 @@ +// 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; + +import com.google.gerrit.server.account.CapabilityControl; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +/** User identity for plugin code that needs an identity. */ +public class PluginUser extends InternalUser { + public interface Factory { + PluginUser create(String pluginName); + } + + private final String pluginName; + + @Inject + protected PluginUser( + CapabilityControl.Factory capabilityControlFactory, + @Assisted String pluginName) { + super(capabilityControlFactory); + this.pluginName = pluginName; + } + + @Override + public String getUserName() { + return "plugin " + pluginName; + } + + @Override + public String toString() { + return "PluginUser[" + pluginName + "]"; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java index 5c9f012737..2a8e7c92a3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java @@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.InternalUser; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -127,6 +128,7 @@ public class GroupControl { public boolean isVisible() { AccountGroup accountGroup = GroupDescriptions.toAccountGroup(group); return (accountGroup != null && accountGroup.isVisibleToAll()) + || user instanceof InternalUser || user.getEffectiveGroups().contains(group.getGroupUUID()) || isOwner(); } 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 660344ace8..62c6863743 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 @@ -33,6 +33,7 @@ import com.google.gerrit.server.FileTypeRegistry; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.InternalUser; import com.google.gerrit.server.MimeUtilFileTypeRegistry; +import com.google.gerrit.server.PluginUser; import com.google.gerrit.server.account.AccountByEmailCacheImpl; import com.google.gerrit.server.account.AccountCacheImpl; import com.google.gerrit.server.account.AccountControl; @@ -187,6 +188,7 @@ public class GerritGlobalModule extends FactoryModule { factory(MergeUtil.Factory.class); factory(PerformCreateGroup.Factory.class); factory(PerformRenameGroup.Factory.class); + factory(PluginUser.Factory.class); factory(ProjectNode.Factory.class); factory(ProjectState.Factory.class); factory(RebasedPatchSetSender.Factory.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java index 985272ab1f..8e7192eab3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java @@ -21,6 +21,8 @@ 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.lifecycle.LifecycleManager; +import com.google.gerrit.server.PluginUser; +import com.google.gerrit.server.util.RequestContext; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; @@ -83,6 +85,7 @@ public class Plugin { private final CacheKey cacheKey; private final String name; + private final PluginUser pluginUser; private final File srcJar; private final FileSnapshot snapshot; private final JarFile jarFile; @@ -102,6 +105,7 @@ public class Plugin { private List> reloadableHandles; public Plugin(String name, + PluginUser pluginUser, File srcJar, FileSnapshot snapshot, JarFile jarFile, @@ -113,6 +117,7 @@ public class Plugin { @Nullable Class sshModule, @Nullable Class httpModule) { this.cacheKey = new CacheKey(name); + this.pluginUser = pluginUser; this.name = name; this.srcJar = srcJar; this.snapshot = snapshot; @@ -131,6 +136,10 @@ public class Plugin { return srcJar; } + PluginUser getPluginUser() { + return pluginUser; + } + public CacheKey getCacheKey() { return cacheKey; } @@ -173,6 +182,15 @@ public class Plugin { } void start(PluginGuiceEnvironment env) throws Exception { + RequestContext oldContext = env.enter(this); + try { + startPlugin(env); + } finally { + env.exit(oldContext); + } + } + + private void startPlugin(PluginGuiceEnvironment env) throws Exception { Injector root = newRootInjector(env); manager = new LifecycleManager(); @@ -235,6 +253,7 @@ public class Plugin { modules.add(new AbstractModule() { @Override protected void configure() { + bind(PluginUser.class).toInstance(pluginUser); bind(String.class) .annotatedWith(PluginName.class) .toInstance(name); @@ -264,9 +283,14 @@ public class Plugin { return Guice.createInjector(modules); } - void stop() { + void stop(PluginGuiceEnvironment env) { if (manager != null) { - manager.stop(); + RequestContext oldContext = env.enter(this); + try { + manager.stop(); + } finally { + env.exit(oldContext); + } manager = null; sysInjector = null; sshInjector = null; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java index 664c278a19..387ffa4baf 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java @@ -32,6 +32,9 @@ import com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes; 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.server.util.PluginRequestContext; +import com.google.gerrit.server.util.RequestContext; +import com.google.gerrit.server.util.ThreadLocalRequestContext; import com.google.inject.AbstractModule; import com.google.inject.Binding; import com.google.inject.Guice; @@ -65,6 +68,7 @@ import javax.inject.Inject; public class PluginGuiceEnvironment { private final Injector sysInjector; private final ServerInformation srvInfo; + private final ThreadLocalRequestContext local; private final CopyConfigModule copyConfigModule; private final Set> copyConfigKeys; private final List onStart; @@ -90,10 +94,12 @@ public class PluginGuiceEnvironment { @Inject PluginGuiceEnvironment( Injector sysInjector, + ThreadLocalRequestContext local, ServerInformation srvInfo, CopyConfigModule ccm) { this.sysInjector = sysInjector; this.srvInfo = srvInfo; + this.local = local; this.copyConfigModule = ccm; this.copyConfigKeys = Guice.createInjector(ccm).getAllBindings().keySet(); @@ -187,20 +193,33 @@ public class PluginGuiceEnvironment { return httpGen.get(); } + RequestContext enter(Plugin plugin) { + return local.setContext(new PluginRequestContext(plugin.getPluginUser())); + } + + void exit(RequestContext old) { + local.setContext(old); + } + void onStartPlugin(Plugin plugin) { for (StartPluginListener l : onStart) { l.onStartPlugin(plugin); } - attachItem(sysItems, plugin.getSysInjector(), plugin); + RequestContext oldContext = enter(plugin); + try { + attachItem(sysItems, plugin.getSysInjector(), plugin); - attachSet(sysSets, plugin.getSysInjector(), plugin); - attachSet(sshSets, plugin.getSshInjector(), plugin); - attachSet(httpSets, plugin.getHttpInjector(), plugin); + attachSet(sysSets, plugin.getSysInjector(), plugin); + attachSet(sshSets, plugin.getSshInjector(), plugin); + attachSet(httpSets, plugin.getHttpInjector(), plugin); - attachMap(sysMaps, plugin.getSysInjector(), plugin); - attachMap(sshMaps, plugin.getSshInjector(), plugin); - attachMap(httpMaps, plugin.getHttpInjector(), plugin); + attachMap(sysMaps, plugin.getSysInjector(), plugin); + attachMap(sshMaps, plugin.getSshInjector(), plugin); + attachMap(httpMaps, plugin.getHttpInjector(), plugin); + } finally { + exit(oldContext); + } } private void attachItem(Map, DynamicItem> items, @@ -244,15 +263,20 @@ public class PluginGuiceEnvironment { old.put(h.getKey().getTypeLiteral(), h); } - reattachMap(old, sysMaps, newPlugin.getSysInjector(), newPlugin); - reattachMap(old, sshMaps, newPlugin.getSshInjector(), newPlugin); - reattachMap(old, httpMaps, newPlugin.getHttpInjector(), newPlugin); + RequestContext oldContext = enter(newPlugin); + try { + reattachMap(old, sysMaps, newPlugin.getSysInjector(), newPlugin); + reattachMap(old, sshMaps, newPlugin.getSshInjector(), newPlugin); + reattachMap(old, httpMaps, newPlugin.getHttpInjector(), newPlugin); - reattachSet(old, sysSets, newPlugin.getSysInjector(), newPlugin); - reattachSet(old, sshSets, newPlugin.getSshInjector(), newPlugin); - reattachSet(old, httpSets, newPlugin.getHttpInjector(), newPlugin); + reattachSet(old, sysSets, newPlugin.getSysInjector(), newPlugin); + reattachSet(old, sshSets, newPlugin.getSshInjector(), newPlugin); + reattachSet(old, httpSets, newPlugin.getHttpInjector(), newPlugin); - reattachItem(old, sysItems, newPlugin.getSysInjector(), newPlugin); + reattachItem(old, sysItems, newPlugin.getSysInjector(), newPlugin); + } finally { + exit(oldContext); + } } private void reattachMap( diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java index b07f28cb6e..035592cd48 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java @@ -24,6 +24,7 @@ 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.extensions.webui.JavaScriptPlugin; +import com.google.gerrit.server.PluginUser; import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.SitePaths; @@ -71,6 +72,7 @@ public class PluginLoader implements LifecycleListener { private final File tmpDir; private final PluginGuiceEnvironment env; private final ServerInformationImpl srvInfoImpl; + private final PluginUser.Factory pluginUserFactory; private final ConcurrentMap running; private final ConcurrentMap disabled; private final Map broken; @@ -83,6 +85,7 @@ public class PluginLoader implements LifecycleListener { public PluginLoader(SitePaths sitePaths, PluginGuiceEnvironment pe, ServerInformationImpl sii, + PluginUser.Factory puf, Provider pct, @GerritServerConfig Config cfg) { pluginsDir = sitePaths.plugins_dir; @@ -90,6 +93,7 @@ public class PluginLoader implements LifecycleListener { tmpDir = sitePaths.tmp_dir; env = pe; srvInfoImpl = sii; + pluginUserFactory = puf; running = Maps.newConcurrentMap(); disabled = Maps.newConcurrentMap(); broken = Maps.newHashMap(); @@ -193,7 +197,7 @@ public class PluginLoader implements LifecycleListener { synchronized private void unloadPlugin(Plugin plugin) { String name = plugin.getName(); log.info(String.format("Unloading plugin %s", name)); - plugin.stop(); + plugin.stop(env); running.remove(name); disabled.remove(name); toCleanup.add(plugin); @@ -465,7 +469,7 @@ public class PluginLoader implements LifecycleListener { Class sysModule = load(sysName, pluginLoader); Class sshModule = load(sshName, pluginLoader); Class httpModule = load(httpName, pluginLoader); - Plugin plugin = new Plugin(name, + Plugin plugin = new Plugin(name, pluginUserFactory.create(name), srcJar, snapshot, jarFile, manifest, new File(dataDir, name), type, pluginLoader, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginRequestContext.java new file mode 100644 index 0000000000..a836fd73fd --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginRequestContext.java @@ -0,0 +1,46 @@ +// 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.util; + +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.PluginUser; +import com.google.inject.Provider; +import com.google.inject.ProvisionException; + +/** RequestContext active while plugins load or unload. */ +public class PluginRequestContext implements RequestContext { + private final PluginUser user; + + public PluginRequestContext(PluginUser user) { + this.user = user; + } + + @Override + public CurrentUser getCurrentUser() { + return user; + } + + @Override + public Provider getReviewDbProvider() { + return new Provider() { + @Override + public ReviewDb get() { + throw new ProvisionException( + "Automatic ReviewDb only available in request scope"); + } + }; + } +}