Merge branch 'stable-2.6'

* stable-2.6:
  Run plugin startup and shutdown as PluginUser
  Allow InternalUser (aka plugins) to see any internal group
This commit is contained in:
Shawn Pearce
2013-04-25 12:36:32 -07:00
8 changed files with 168 additions and 18 deletions

View File

@@ -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 {

View File

@@ -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 + "]";
}
}

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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<ReloadableRegistrationHandle<?>> reloadableHandles;
public Plugin(String name,
PluginUser pluginUser,
File srcJar,
FileSnapshot snapshot,
JarFile jarFile,
@@ -113,6 +117,7 @@ public class Plugin {
@Nullable Class<? extends Module> sshModule,
@Nullable Class<? extends Module> 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;

View File

@@ -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<Key<?>> copyConfigKeys;
private final List<StartPluginListener> 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<TypeLiteral<?>, 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(

View File

@@ -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<String, Plugin> running;
private final ConcurrentMap<String, Plugin> disabled;
private final Map<String, FileSnapshot> broken;
@@ -83,6 +85,7 @@ public class PluginLoader implements LifecycleListener {
public PluginLoader(SitePaths sitePaths,
PluginGuiceEnvironment pe,
ServerInformationImpl sii,
PluginUser.Factory puf,
Provider<PluginCleanerTask> 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<? extends Module> sysModule = load(sysName, pluginLoader);
Class<? extends Module> sshModule = load(sshName, pluginLoader);
Class<? extends Module> 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,

View File

@@ -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<ReviewDb> getReviewDbProvider() {
return new Provider<ReviewDb>() {
@Override
public ReviewDb get() {
throw new ProvisionException(
"Automatic ReviewDb only available in request scope");
}
};
}
}