From 4e84166db5c5538e3984d9d2d6bb1f9902e65ee0 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 04 Nov 2014 17:38:17 -0500
Subject: [PATCH] Merged #217 "Exclude SSLv3 from Gitblit GO https protocols"

---
 src/main/java/com/gitblit/manager/GitblitManager.java | 1428 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 1,150 insertions(+), 278 deletions(-)

diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java
index 9947382..9692e65 100644
--- a/src/main/java/com/gitblit/manager/GitblitManager.java
+++ b/src/main/java/com/gitblit/manager/GitblitManager.java
@@ -27,59 +27,135 @@
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
 
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.FetchCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RefSpec;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import ro.fortsoft.pf4j.PluginState;
+import ro.fortsoft.pf4j.PluginWrapper;
+import ro.fortsoft.pf4j.Version;
+
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.Role;
 import com.gitblit.GitBlitException;
 import com.gitblit.IStoredSettings;
-import com.gitblit.Keys;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.FederationSet;
+import com.gitblit.models.ForkModel;
 import com.gitblit.models.GitClientApplication;
+import com.gitblit.models.Mailing;
+import com.gitblit.models.Metric;
+import com.gitblit.models.PluginRegistry.InstallState;
+import com.gitblit.models.PluginRegistry.PluginRegistration;
+import com.gitblit.models.PluginRegistry.PluginRelease;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.RepositoryUrl;
+import com.gitblit.models.SearchResult;
 import com.gitblit.models.ServerSettings;
+import com.gitblit.models.ServerStatus;
 import com.gitblit.models.SettingModel;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.transport.ssh.IPublicKeyManager;
+import com.gitblit.transport.ssh.SshKey;
 import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.HttpUtils;
-import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JsonUtils;
 import com.gitblit.utils.ObjectCache;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.XssFilter;
 import com.google.gson.Gson;
 import com.google.gson.JsonIOException;
 import com.google.gson.JsonSyntaxException;
 import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
-public class GitblitManager implements IGitblitManager {
+/**
+ * GitblitManager is an aggregate interface delegate.  It implements all the manager
+ * interfaces and delegates most methods calls to the proper manager implementation.
+ * It's primary purpose is to provide complete management control to the git
+ * upload and receive pack functions.
+ *
+ * GitblitManager also implements several integration methods when it is required
+ * to manipulate several manages for one operation.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class GitblitManager implements IGitblit {
 
-	private final Logger logger = LoggerFactory.getLogger(getClass());
+	protected final Logger logger = LoggerFactory.getLogger(getClass());
 
-	private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
+	protected final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
 
-	private final IStoredSettings settings;
+	protected final Provider<IPublicKeyManager> publicKeyManagerProvider;
 
-	private final IRuntimeManager runtimeManager;
+	protected final Provider<ITicketService> ticketServiceProvider;
 
-	private final IUserManager userManager;
+	protected final IStoredSettings settings;
 
-	private final IRepositoryManager repositoryManager;
+	protected final IRuntimeManager runtimeManager;
 
+	protected final IPluginManager pluginManager;
+
+	protected final INotificationManager notificationManager;
+
+	protected final IUserManager userManager;
+
+	protected final IAuthenticationManager authenticationManager;
+
+	protected final IRepositoryManager repositoryManager;
+
+	protected final IProjectManager projectManager;
+
+	protected final IFederationManager federationManager;
+
+	@Inject
 	public GitblitManager(
+			Provider<IPublicKeyManager> publicKeyManagerProvider,
+			Provider<ITicketService> ticketServiceProvider,
 			IRuntimeManager runtimeManager,
+			IPluginManager pluginManager,
+			INotificationManager notificationManager,
 			IUserManager userManager,
-			IRepositoryManager repositoryManager) {
+			IAuthenticationManager authenticationManager,
+			IRepositoryManager repositoryManager,
+			IProjectManager projectManager,
+			IFederationManager federationManager) {
+
+		this.publicKeyManagerProvider = publicKeyManagerProvider;
+		this.ticketServiceProvider = ticketServiceProvider;
 
 		this.settings = runtimeManager.getSettings();
 		this.runtimeManager = runtimeManager;
+		this.pluginManager = pluginManager;
+		this.notificationManager = notificationManager;
 		this.userManager = userManager;
+		this.authenticationManager = authenticationManager;
 		this.repositoryManager = repositoryManager;
+		this.projectManager = projectManager;
+		this.federationManager = federationManager;
 	}
 
 	@Override
@@ -91,6 +167,265 @@
 	@Override
 	public GitblitManager stop() {
 		return this;
+	}
+
+	/*
+	 * IGITBLIT
+	 */
+
+	/**
+	 * Creates a personal fork of the specified repository. The clone is view
+	 * restricted by default and the owner of the source repository is given
+	 * access to the clone.
+	 *
+	 * @param repository
+	 * @param user
+	 * @return the repository model of the fork, if successful
+	 * @throws GitBlitException
+	 */
+	@Override
+	public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
+		String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
+		String fromUrl = MessageFormat.format("file://{0}/{1}", repositoryManager.getRepositoriesFolder().getAbsolutePath(), repository.name);
+
+		// clone the repository
+		try {
+			Repository canonical = getRepository(repository.name);
+			File folder = new File(repositoryManager.getRepositoriesFolder(), cloneName);
+			CloneCommand clone = new CloneCommand();
+			clone.setBare(true);
+
+			// fetch branches with exclusions
+			Collection<Ref> branches = canonical.getRefDatabase().getRefs(Constants.R_HEADS).values();
+			List<String> branchesToClone = new ArrayList<String>();
+			for (Ref branch : branches) {
+				String name = branch.getName();
+				if (name.startsWith(Constants.R_TICKET)) {
+					// exclude ticket branches
+					continue;
+				}
+				branchesToClone.add(name);
+			}
+			clone.setBranchesToClone(branchesToClone);
+			clone.setURI(fromUrl);
+			clone.setDirectory(folder);
+			Git git = clone.call();
+
+			// fetch tags
+			FetchCommand fetch  = git.fetch();
+			fetch.setRefSpecs(new RefSpec("+refs/tags/*:refs/tags/*"));
+			fetch.call();
+
+			git.getRepository().close();
+		} catch (Exception e) {
+			throw new GitBlitException(e);
+		}
+
+		// create a Gitblit repository model for the clone
+		RepositoryModel cloneModel = repository.cloneAs(cloneName);
+		// owner has REWIND/RW+ permissions
+		cloneModel.addOwner(user.username);
+
+		// ensure initial access restriction of the fork
+		// is not lower than the source repository  (issue-495/ticket-167)
+		if (repository.accessRestriction.exceeds(cloneModel.accessRestriction)) {
+			cloneModel.accessRestriction = repository.accessRestriction;
+		}
+
+		repositoryManager.updateRepositoryModel(cloneName, cloneModel, false);
+
+		// add the owner of the source repository to the clone's access list
+		if (!ArrayUtils.isEmpty(repository.owners)) {
+			for (String owner : repository.owners) {
+				UserModel originOwner = userManager.getUserModel(owner);
+				if (originOwner != null && !originOwner.canClone(cloneModel)) {
+					// origin owner can't yet clone fork, grant explicit clone access
+					originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+					reviseUser(originOwner.username, originOwner);
+				}
+			}
+		}
+
+		// grant origin's user list clone permission to fork
+		List<String> users = repositoryManager.getRepositoryUsers(repository);
+		List<UserModel> cloneUsers = new ArrayList<UserModel>();
+		for (String name : users) {
+			if (!name.equalsIgnoreCase(user.username)) {
+				UserModel cloneUser = userManager.getUserModel(name);
+				if (cloneUser.canClone(repository) && !cloneUser.canClone(cloneModel)) {
+					// origin user can't yet clone fork, grant explicit clone access
+					cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+				}
+				cloneUsers.add(cloneUser);
+			}
+		}
+		userManager.updateUserModels(cloneUsers);
+
+		// grant origin's team list clone permission to fork
+		List<String> teams = repositoryManager.getRepositoryTeams(repository);
+		List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
+		for (String name : teams) {
+			TeamModel cloneTeam = userManager.getTeamModel(name);
+			if (cloneTeam.canClone(repository) && !cloneTeam.canClone(cloneModel)) {
+				// origin team can't yet clone fork, grant explicit clone access
+				cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+			}
+			cloneTeams.add(cloneTeam);
+		}
+		userManager.updateTeamModels(cloneTeams);
+
+		// add this clone to the cached model
+		repositoryManager.addToCachedRepositoryList(cloneModel);
+		return cloneModel;
+	}
+
+	/**
+	 * Adds a TeamModel object.
+	 *
+	 * @param team
+	 */
+	@Override
+	public void addTeam(TeamModel team) throws GitBlitException {
+		if (!userManager.updateTeamModel(team)) {
+			throw new GitBlitException("Failed to add team!");
+		}
+	}
+
+	/**
+	 * Updates the TeamModel object for the specified name.
+	 *
+	 * @param teamname
+	 * @param team
+	 */
+	@Override
+	public void reviseTeam(String teamname, TeamModel team) throws GitBlitException {
+		if (!teamname.equalsIgnoreCase(team.name)) {
+			if (userManager.getTeamModel(team.name) != null) {
+				throw new GitBlitException(MessageFormat.format(
+						"Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
+						team.name));
+			}
+		}
+		if (!userManager.updateTeamModel(teamname, team)) {
+			throw new GitBlitException("Failed to update team!");
+		}
+	}
+
+	/**
+	 * Adds a user object.
+	 *
+	 * @param user
+	 * @throws GitBlitException
+	 */
+	@Override
+	public void addUser(UserModel user) throws GitBlitException {
+		if (!userManager.updateUserModel(user)) {
+			throw new GitBlitException("Failed to add user!");
+		}
+	}
+
+	/**
+	 * Updates a user object keyed by username. This method allows
+	 * for renaming a user.
+	 *
+	 * @param username
+	 * @param user
+	 * @throws GitBlitException
+	 */
+	@Override
+	public void reviseUser(String username, UserModel user) throws GitBlitException {
+		if (!username.equalsIgnoreCase(user.username)) {
+			if (userManager.getUserModel(user.username) != null) {
+				throw new GitBlitException(MessageFormat.format(
+						"Failed to rename ''{0}'' because ''{1}'' already exists.", username,
+						user.username));
+			}
+
+			// rename repositories and owner fields for all repositories
+			for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) {
+				if (model.isUsersPersonalRepository(username)) {
+					// personal repository
+					model.addOwner(user.username);
+					String oldRepositoryName = model.name;
+					model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length());
+					model.projectPath = user.getPersonalPath();
+					repositoryManager.updateRepositoryModel(oldRepositoryName, model, false);
+				} else if (model.isOwner(username)) {
+					// common/shared repo
+					model.addOwner(user.username);
+					repositoryManager.updateRepositoryModel(model.name, model, false);
+				}
+			}
+
+			// rename the user's ssh public keystore
+			getPublicKeyManager().renameUser(username, user.username);
+		}
+		if (!userManager.updateUserModel(username, user)) {
+			throw new GitBlitException("Failed to update user!");
+		}
+	}
+
+	/**
+	 * Returns the list of custom client applications to be used for the
+	 * repository url panel;
+	 *
+	 * @return a collection of client applications
+	 */
+	@Override
+	public Collection<GitClientApplication> getClientApplications() {
+		// prefer user definitions, if they exist
+		File userDefs = new File(runtimeManager.getBaseFolder(), "clientapps.json");
+		if (userDefs.exists()) {
+			Date lastModified = new Date(userDefs.lastModified());
+			if (clientApplications.hasCurrent("user", lastModified)) {
+				return clientApplications.getObject("user");
+			} else {
+				// (re)load user definitions
+				try {
+					InputStream is = new FileInputStream(userDefs);
+					Collection<GitClientApplication> clients = readClientApplications(is);
+					is.close();
+					if (clients != null) {
+						clientApplications.updateObject("user", lastModified, clients);
+						return clients;
+					}
+				} catch (IOException e) {
+					logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
+				}
+			}
+		}
+
+		// no user definitions, use system definitions
+		if (!clientApplications.hasCurrent("system", new Date(0))) {
+			try {
+				InputStream is = GitblitManager.class.getResourceAsStream("/clientapps.json");
+				Collection<GitClientApplication> clients = readClientApplications(is);
+				is.close();
+				if (clients != null) {
+					clientApplications.updateObject("system", new Date(0), clients);
+				}
+			} catch (IOException e) {
+				logger.error("Failed to deserialize clientapps.json resource!", e);
+			}
+		}
+
+		return clientApplications.getObject("system");
+	}
+
+	private Collection<GitClientApplication> readClientApplications(InputStream is) {
+		try {
+			Type type = new TypeToken<Collection<GitClientApplication>>() {
+			}.getType();
+			InputStreamReader reader = new InputStreamReader(is);
+			Gson gson = JsonUtils.gson();
+			Collection<GitClientApplication> links = gson.fromJson(reader, type);
+			return links;
+		} catch (JsonIOException e) {
+			logger.error("Error deserializing client applications!", e);
+		} catch (JsonSyntaxException e) {
+			logger.error("Error deserializing client applications!", e);
+		}
+		return null;
 	}
 
 	/**
@@ -105,7 +440,7 @@
 			// Read bundled Gitblit properties to extract setting descriptions.
 			// This copy is pristine and only used for populating the setting
 			// models map.
-			InputStream is = getClass().getResourceAsStream("/reference.properties");
+			InputStream is = GitblitManager.class.getResourceAsStream("/defaults.properties");
 			BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
 			StringBuilder description = new StringBuilder();
 			SettingModel setting = new SettingModel();
@@ -150,307 +485,844 @@
 			}
 			propertiesReader.close();
 		} catch (NullPointerException e) {
-			logger.error("Failed to find resource copy of gitblit.properties");
+			logger.error("Failed to find classpath resource 'defaults.properties'");
 		} catch (IOException e) {
-			logger.error("Failed to load resource copy of gitblit.properties");
+			logger.error("Failed to load classpath resource 'defaults.properties'");
 		}
 	}
 
-	/**
-	 * Returns a list of repository URLs and the user access permission.
-	 *
-	 * @param request
-	 * @param user
-	 * @param repository
-	 * @return a list of repository urls
-	 */
 	@Override
-	public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+	public ITicketService getTicketService() {
+		return ticketServiceProvider.get();
+	}
+
+	@Override
+	public IPublicKeyManager getPublicKeyManager() {
+		return publicKeyManagerProvider.get();
+	}
+
+	/*
+	 * ISTOREDSETTINGS
+	 *
+	 * these methods are necessary for (nearly) seamless Groovy hook operation
+	 * after the massive refactor.
+	 */
+
+	public boolean getBoolean(String key, boolean defaultValue) {
+		return runtimeManager.getSettings().getBoolean(key, defaultValue);
+	}
+
+	public String getString(String key, String defaultValue) {
+		return runtimeManager.getSettings().getString(key, defaultValue);
+	}
+
+	public int getInteger(String key, int defaultValue) {
+		return runtimeManager.getSettings().getInteger(key, defaultValue);
+	}
+
+	public List<String> getStrings(String key) {
+		return runtimeManager.getSettings().getStrings(key);
+	}
+
+	/*
+	 * RUNTIME MANAGER
+	 */
+
+	@Override
+	public File getBaseFolder() {
+		return runtimeManager.getBaseFolder();
+	}
+
+	@Override
+	public void setBaseFolder(File folder) {
+		runtimeManager.setBaseFolder(folder);
+	}
+
+	@Override
+	public Date getBootDate() {
+		return runtimeManager.getBootDate();
+	}
+
+	@Override
+	public ServerSettings getSettingsModel() {
+		return runtimeManager.getSettingsModel();
+	}
+
+	@Override
+	public TimeZone getTimezone() {
+		return runtimeManager.getTimezone();
+	}
+
+	@Override
+	public Locale getLocale() {
+		return runtimeManager.getLocale();
+	}
+
+	@Override
+	public boolean isDebugMode() {
+		return runtimeManager.isDebugMode();
+	}
+
+	@Override
+	public File getFileOrFolder(String key, String defaultFileOrFolder) {
+		return runtimeManager.getFileOrFolder(key, defaultFileOrFolder);
+	}
+
+	@Override
+	public File getFileOrFolder(String fileOrFolder) {
+		return runtimeManager.getFileOrFolder(fileOrFolder);
+	}
+
+	@Override
+	public IStoredSettings getSettings() {
+		return runtimeManager.getSettings();
+	}
+
+	@Override
+	public boolean updateSettings(Map<String, String> updatedSettings) {
+		return runtimeManager.updateSettings(updatedSettings);
+	}
+
+	@Override
+	public ServerStatus getStatus() {
+		return runtimeManager.getStatus();
+	}
+
+	@Override
+	public Injector getInjector() {
+		return runtimeManager.getInjector();
+	}
+
+	@Override
+	public XssFilter getXssFilter() {
+		return runtimeManager.getXssFilter();
+	}
+
+	/*
+	 * NOTIFICATION MANAGER
+	 */
+
+	@Override
+	public boolean isSendingMail() {
+		return notificationManager.isSendingMail();
+	}
+
+	@Override
+	public void sendMailToAdministrators(String subject, String message) {
+		notificationManager.sendMailToAdministrators(subject, message);
+	}
+
+	@Override
+	public void sendMail(String subject, String message, Collection<String> toAddresses) {
+		notificationManager.sendMail(subject, message, toAddresses);
+	}
+
+	@Override
+	public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
+		notificationManager.sendHtmlMail(subject, message, toAddresses);
+	}
+
+	@Override
+	public void send(Mailing mail) {
+		notificationManager.send(mail);
+	}
+
+	/*
+	 * SESSION MANAGER
+	 */
+
+	@Override
+	public UserModel authenticate(String username, char[] password) {
+		return authenticationManager.authenticate(username, password);
+	}
+
+	@Override
+	public UserModel authenticate(HttpServletRequest httpRequest) {
+		UserModel user = authenticationManager.authenticate(httpRequest, false);
 		if (user == null) {
-			user = UserModel.ANONYMOUS;
+			user = federationManager.authenticate(httpRequest);
 		}
-		String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
-
-		List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
-		// http/https url
-		if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
-			AccessPermission permission = user.getRepositoryPermission(repository).permission;
-			if (permission.exceeds(AccessPermission.NONE)) {
-				list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
-			}
-		}
-
-		// git daemon url
-		String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
-		if (!StringUtils.isEmpty(gitDaemonUrl)) {
-			AccessPermission permission = getGitDaemonAccessPermission(user, repository);
-			if (permission.exceeds(AccessPermission.NONE)) {
-				list.add(new RepositoryUrl(gitDaemonUrl, permission));
-			}
-		}
-
-		// add all other urls
-		// {0} = repository
-		// {1} = username
-		for (String url : settings.getStrings(Keys.web.otherUrls)) {
-			if (url.contains("{1}")) {
-				// external url requires username, only add url IF we have one
-				if (!StringUtils.isEmpty(username)) {
-					list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
-				}
-			} else {
-				// external url does not require username
-				list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
-			}
-		}
-		return list;
+		return user;
 	}
 
-	protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
-		StringBuilder sb = new StringBuilder();
-		sb.append(HttpUtils.getGitblitURL(request));
-		sb.append(Constants.GIT_PATH);
-		sb.append(repository.name);
+	@Override
+	public UserModel authenticate(String username, SshKey key) {
+		return authenticationManager.authenticate(username, key);
+	}
 
-		// inject username into repository url if authentication is required
-		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
-				&& !StringUtils.isEmpty(username)) {
-			sb.insert(sb.indexOf("://") + 3, username + "@");
+	@Override
+	public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
+		UserModel user = authenticationManager.authenticate(httpRequest, requiresCertificate);
+		if (user == null) {
+			user = federationManager.authenticate(httpRequest);
 		}
-		return sb.toString();
+		return user;
 	}
 
-	protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
-//		if (gitDaemon != null) {
-//			String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
-//			if (bindInterface.equals("localhost")
-//					&& (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
-//				// git daemon is bound to localhost and the request is from elsewhere
-//				return null;
-//			}
-//			if (user.canClone(repository)) {
-//				String servername = request.getServerName();
-//				String url = gitDaemon.formatUrl(servername, repository.name);
-//				return url;
-//			}
-//		}
-		return null;
+	@Override
+	public String getCookie(HttpServletRequest request) {
+		return authenticationManager.getCookie(request);
 	}
 
-	protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
-//		if (gitDaemon != null && user.canClone(repository)) {
-//			AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
-//			if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
-//				if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
-//					// can not authenticate clone via anonymous git protocol
-//					gitDaemonPermission = AccessPermission.NONE;
-//				} else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
-//					// can not authenticate push via anonymous git protocol
-//					gitDaemonPermission = AccessPermission.CLONE;
-//				} else {
-//					// normal user permission
-//				}
-//			}
-//			return gitDaemonPermission;
-//		}
-		return AccessPermission.NONE;
+	@Override
+	@Deprecated
+	public void setCookie(HttpServletResponse response, UserModel user) {
+		authenticationManager.setCookie(response, user);
+	}
+
+	@Override
+	public void setCookie(HttpServletRequest request, HttpServletResponse response, UserModel user) {
+		authenticationManager.setCookie(request, response, user);
+	}
+
+	@Override
+	@Deprecated
+	public void logout(HttpServletResponse response, UserModel user) {
+		authenticationManager.logout(response, user);
+	}
+
+	@Override
+	public void logout(HttpServletRequest request, HttpServletResponse response, UserModel user) {
+		authenticationManager.logout(request, response, user);
+	}
+
+	@Override
+	public boolean supportsCredentialChanges(UserModel user) {
+		return authenticationManager.supportsCredentialChanges(user);
+	}
+
+	@Override
+	public boolean supportsDisplayNameChanges(UserModel user) {
+		return authenticationManager.supportsDisplayNameChanges(user);
+	}
+
+	@Override
+	public boolean supportsEmailAddressChanges(UserModel user) {
+		return authenticationManager.supportsEmailAddressChanges(user);
+	}
+
+	@Override
+	public boolean supportsTeamMembershipChanges(UserModel user) {
+		return authenticationManager.supportsTeamMembershipChanges(user);
+	}
+
+	@Override
+	public boolean supportsTeamMembershipChanges(TeamModel team) {
+		return authenticationManager.supportsTeamMembershipChanges(team);
+	}
+
+	@Override
+	public boolean supportsRoleChanges(UserModel user, Role role) {
+		return authenticationManager.supportsRoleChanges(user, role);
+	}
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		return authenticationManager.supportsRoleChanges(team, role);
+	}
+
+	/*
+	 * USER MANAGER
+	 */
+
+	@Override
+	public void setup(IRuntimeManager runtimeManager) {
+	}
+
+	@Override
+	public boolean isInternalAccount(String username) {
+		return userManager.isInternalAccount(username);
+	}
+
+	@Override
+	public List<String> getAllUsernames() {
+		return userManager.getAllUsernames();
+	}
+
+	@Override
+	public List<UserModel> getAllUsers() {
+		return userManager.getAllUsers();
+	}
+
+	@Override
+	public UserModel getUserModel(String username) {
+		return userManager.getUserModel(username);
+	}
+
+	@Override
+	public List<TeamModel> getAllTeams() {
+		return userManager.getAllTeams();
+	}
+
+	@Override
+	public TeamModel getTeamModel(String teamname) {
+		return userManager.getTeamModel(teamname);
+	}
+
+	@Override
+	public String getCookie(UserModel model) {
+		return userManager.getCookie(model);
+	}
+
+	@Override
+	public UserModel getUserModel(char[] cookie) {
+		return userManager.getUserModel(cookie);
+	}
+
+	@Override
+	public boolean updateUserModel(UserModel model) {
+		return userManager.updateUserModel(model);
+	}
+
+	@Override
+	public boolean updateUserModels(Collection<UserModel> models) {
+		return userManager.updateUserModels(models);
+	}
+
+	@Override
+	public boolean updateUserModel(String username, UserModel model) {
+		return userManager.updateUserModel(username, model);
+	}
+
+	@Override
+	public boolean deleteUser(String username) {
+		// delegate to deleteUserModel() to delete public ssh keys
+		UserModel user = userManager.getUserModel(username);
+		return deleteUserModel(user);
 	}
 
 	/**
-	 * Returns the list of custom client applications to be used for the
-	 * repository url panel;
-	 *
-	 * @return a collection of client applications
+	 * Delete the user and all associated public ssh keys.
 	 */
 	@Override
-	public Collection<GitClientApplication> getClientApplications() {
-		// prefer user definitions, if they exist
-		File userDefs = new File(runtimeManager.getBaseFolder(), "clientapps.json");
-		if (userDefs.exists()) {
-			Date lastModified = new Date(userDefs.lastModified());
-			if (clientApplications.hasCurrent("user", lastModified)) {
-				return clientApplications.getObject("user");
-			} else {
-				// (re)load user definitions
-				try {
-					InputStream is = new FileInputStream(userDefs);
-					Collection<GitClientApplication> clients = readClientApplications(is);
-					is.close();
-					if (clients != null) {
-						clientApplications.updateObject("user", lastModified, clients);
-						return clients;
-					}
-				} catch (IOException e) {
-					logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
-				}
-			}
+	public boolean deleteUserModel(UserModel model) {
+		boolean success = userManager.deleteUserModel(model);
+		if (success) {
+			getPublicKeyManager().removeAllKeys(model.username);
 		}
-
-		// no user definitions, use system definitions
-		if (!clientApplications.hasCurrent("system", new Date(0))) {
-			try {
-				InputStream is = getClass().getResourceAsStream("/clientapps.json");
-				Collection<GitClientApplication> clients = readClientApplications(is);
-				is.close();
-				if (clients != null) {
-					clientApplications.updateObject("system", new Date(0), clients);
-				}
-			} catch (IOException e) {
-				logger.error("Failed to deserialize clientapps.json resource!", e);
-			}
-		}
-
-		return clientApplications.getObject("system");
+		return success;
 	}
 
-	private Collection<GitClientApplication> readClientApplications(InputStream is) {
-		try {
-			Type type = new TypeToken<Collection<GitClientApplication>>() {
-			}.getType();
-			InputStreamReader reader = new InputStreamReader(is);
-			Gson gson = JsonUtils.gson();
-			Collection<GitClientApplication> links = gson.fromJson(reader, type);
-			return links;
-		} catch (JsonIOException e) {
-			logger.error("Error deserializing client applications!", e);
-		} catch (JsonSyntaxException e) {
-			logger.error("Error deserializing client applications!", e);
-		}
-		return null;
+	@Override
+	public List<String> getAllTeamNames() {
+		return userManager.getAllTeamNames();
+	}
+
+	@Override
+	public List<String> getTeamNamesForRepositoryRole(String role) {
+		return userManager.getTeamNamesForRepositoryRole(role);
+	}
+
+	@Override
+	public boolean updateTeamModel(TeamModel model) {
+		return userManager.updateTeamModel(model);
+	}
+
+	@Override
+	public boolean updateTeamModels(Collection<TeamModel> models) {
+		return userManager.updateTeamModels(models);
+	}
+
+	@Override
+	public boolean updateTeamModel(String teamname, TeamModel model) {
+		return userManager.updateTeamModel(teamname, model);
+	}
+
+	@Override
+	public boolean deleteTeamModel(TeamModel model) {
+		return userManager.deleteTeamModel(model);
+	}
+
+	@Override
+	public List<String> getUsernamesForRepositoryRole(String role) {
+		return userManager.getUsernamesForRepositoryRole(role);
+	}
+
+	@Override
+	public boolean renameRepositoryRole(String oldRole, String newRole) {
+		return userManager.renameRepositoryRole(oldRole, newRole);
+	}
+
+	@Override
+	public boolean deleteRepositoryRole(String role) {
+		return userManager.deleteRepositoryRole(role);
+	}
+
+	@Override
+	public boolean deleteTeam(String teamname) {
+		return userManager.deleteTeam(teamname);
+	}
+
+	/*
+	 * REPOSITORY MANAGER
+	 */
+
+	@Override
+	public Date getLastActivityDate() {
+		return repositoryManager.getLastActivityDate();
+	}
+
+	@Override
+	public File getRepositoriesFolder() {
+		return repositoryManager.getRepositoriesFolder();
+	}
+
+	@Override
+	public File getHooksFolder() {
+		return repositoryManager.getHooksFolder();
+	}
+
+	@Override
+	public File getGrapesFolder() {
+		return repositoryManager.getGrapesFolder();
+	}
+
+	@Override
+	public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
+		return repositoryManager.getUserAccessPermissions(user);
+	}
+
+	@Override
+	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
+		return repositoryManager.getUserAccessPermissions(repository);
+	}
+
+	@Override
+	public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
+		return repositoryManager.setUserAccessPermissions(repository, permissions);
+	}
+
+	@Override
+	public List<String> getRepositoryUsers(RepositoryModel repository) {
+		return repositoryManager.getRepositoryUsers(repository);
+	}
+
+	@Override
+	public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
+		return repositoryManager.getTeamAccessPermissions(repository);
+	}
+
+	@Override
+	public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
+		return repositoryManager.setTeamAccessPermissions(repository, permissions);
+	}
+
+	@Override
+	public List<String> getRepositoryTeams(RepositoryModel repository) {
+		return repositoryManager.getRepositoryTeams(repository);
+	}
+	@Override
+	public void addToCachedRepositoryList(RepositoryModel model) {
+		repositoryManager.addToCachedRepositoryList(model);
+	}
+
+	@Override
+	public void resetRepositoryListCache() {
+		repositoryManager.resetRepositoryListCache();
+	}
+
+	@Override
+	public void resetRepositoryCache(String repositoryName) {
+		repositoryManager.resetRepositoryCache(repositoryName);
+	}
+
+	@Override
+	public List<String> getRepositoryList() {
+		return repositoryManager.getRepositoryList();
+	}
+
+	@Override
+	public Repository getRepository(String repositoryName) {
+		return repositoryManager.getRepository(repositoryName);
+	}
+
+	@Override
+	public Repository getRepository(String repositoryName, boolean logError) {
+		return repositoryManager.getRepository(repositoryName, logError);
+	}
+
+	@Override
+	public List<RepositoryModel> getRepositoryModels() {
+		return repositoryManager.getRepositoryModels();
+	}
+
+	@Override
+	public List<RepositoryModel> getRepositoryModels(UserModel user) {
+		return repositoryManager.getRepositoryModels(user);
+	}
+
+	@Override
+	public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
+		return repositoryManager.getRepositoryModel(repositoryName);
+	}
+
+	@Override
+	public RepositoryModel getRepositoryModel(String repositoryName) {
+		return repositoryManager.getRepositoryModel(repositoryName);
+	}
+
+	@Override
+	public long getStarCount(RepositoryModel repository) {
+		return repositoryManager.getStarCount(repository);
+	}
+
+	@Override
+	public boolean hasRepository(String repositoryName) {
+		return repositoryManager.hasRepository(repositoryName);
+	}
+
+	@Override
+	public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
+		return repositoryManager.hasRepository(repositoryName, caseSensitiveCheck);
+	}
+
+	@Override
+	public boolean hasFork(String username, String origin) {
+		return repositoryManager.hasFork(username, origin);
+	}
+
+	@Override
+	public String getFork(String username, String origin) {
+		return repositoryManager.getFork(username, origin);
+	}
+
+	@Override
+	public ForkModel getForkNetwork(String repository) {
+		return repositoryManager.getForkNetwork(repository);
+	}
+
+	@Override
+	public long updateLastChangeFields(Repository r, RepositoryModel model) {
+		return repositoryManager.updateLastChangeFields(r, model);
+	}
+
+	@Override
+	public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
+		return repositoryManager.getRepositoryDefaultMetrics(model, repository);
 	}
 
 	/**
-	 * Creates a personal fork of the specified repository. The clone is view
-	 * restricted by default and the owner of the source repository is given
-	 * access to the clone.
-	 *
-	 * @param repository
-	 * @param user
-	 * @return the repository model of the fork, if successful
-	 * @throws GitBlitException
+	 * Detect renames and reindex as appropriate.
 	 */
 	@Override
-	public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
-		String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
-		String fromUrl = MessageFormat.format("file://{0}/{1}", repositoryManager.getRepositoriesFolder().getAbsolutePath(), repository.name);
-
-		// clone the repository
-		try {
-			JGitUtils.cloneRepository(repositoryManager.getRepositoriesFolder(), cloneName, fromUrl, true, null);
-		} catch (Exception e) {
-			throw new GitBlitException(e);
+	public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
+			boolean isCreate) throws GitBlitException {
+		RepositoryModel oldModel = null;
+		boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name);
+		if (isRename) {
+			oldModel = repositoryManager.getRepositoryModel(repositoryName);
 		}
 
-		// create a Gitblit repository model for the clone
-		RepositoryModel cloneModel = repository.cloneAs(cloneName);
-		// owner has REWIND/RW+ permissions
-		cloneModel.addOwner(user.username);
-		repositoryManager.updateRepositoryModel(cloneName, cloneModel, false);
+		repositoryManager.updateRepositoryModel(repositoryName, repository, isCreate);
 
-		// add the owner of the source repository to the clone's access list
-		if (!ArrayUtils.isEmpty(repository.owners)) {
-			for (String owner : repository.owners) {
-				UserModel originOwner = userManager.getUserModel(owner);
-				if (originOwner != null) {
-					originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-					updateUserModel(originOwner.username, originOwner, false);
-				}
-			}
-		}
-
-		// grant origin's user list clone permission to fork
-		List<String> users = repositoryManager.getRepositoryUsers(repository);
-		List<UserModel> cloneUsers = new ArrayList<UserModel>();
-		for (String name : users) {
-			if (!name.equalsIgnoreCase(user.username)) {
-				UserModel cloneUser = userManager.getUserModel(name);
-				if (cloneUser.canClone(repository)) {
-					// origin user can clone origin, grant clone access to fork
-					cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-				}
-				cloneUsers.add(cloneUser);
-			}
-		}
-		userManager.updateUserModels(cloneUsers);
-
-		// grant origin's team list clone permission to fork
-		List<String> teams = repositoryManager.getRepositoryTeams(repository);
-		List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
-		for (String name : teams) {
-			TeamModel cloneTeam = userManager.getTeamModel(name);
-			if (cloneTeam.canClone(repository)) {
-				// origin team can clone origin, grant clone access to fork
-				cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-			}
-			cloneTeams.add(cloneTeam);
-		}
-		userManager.updateTeamModels(cloneTeams);
-
-		// add this clone to the cached model
-		repositoryManager.addToCachedRepositoryList(cloneModel);
-		return cloneModel;
-	}
-
-	/**
-	 * Adds/updates a complete user object keyed by username. This method allows
-	 * for renaming a user.
-	 *
-	 * @see IUserService.updateUserModel(String, UserModel)
-	 * @param username
-	 * @param user
-	 * @param isCreate
-	 * @throws GitBlitException
-	 */
-	@Override
-	public void updateUserModel(String username, UserModel user, boolean isCreate)
-			throws GitBlitException {
-		if (!username.equalsIgnoreCase(user.username)) {
-			if (userManager.getUserModel(user.username) != null) {
-				throw new GitBlitException(MessageFormat.format(
-						"Failed to rename ''{0}'' because ''{1}'' already exists.", username,
-						user.username));
-			}
-
-			// rename repositories and owner fields for all repositories
-			for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) {
-				if (model.isUsersPersonalRepository(username)) {
-					// personal repository
-					model.addOwner(user.username);
-					String oldRepositoryName = model.name;
-					model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length());
-					model.projectPath = user.getPersonalPath();
-					repositoryManager.updateRepositoryModel(oldRepositoryName, model, false);
-				} else if (model.isOwner(username)) {
-					// common/shared repo
-					model.addOwner(user.username);
-					repositoryManager.updateRepositoryModel(model.name, model, false);
-				}
-			}
-		}
-		if (!userManager.updateUserModel(username, user)) {
-			throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
+		if (isRename && ticketServiceProvider.get() != null) {
+			ticketServiceProvider.get().rename(oldModel, repository);
 		}
 	}
 
+	@Override
+	public void updateConfiguration(Repository r, RepositoryModel repository) {
+		repositoryManager.updateConfiguration(r, repository);
+	}
+
+	@Override
+	public boolean canDelete(RepositoryModel model) {
+		return repositoryManager.canDelete(model);
+	}
+
 	/**
-	 * Updates the TeamModel object for the specified name.
-	 *
-	 * @param teamname
-	 * @param team
-	 * @param isCreate
+	 * Delete the repository and all associated tickets.
 	 */
 	@Override
-	public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
-			throws GitBlitException {
-		if (!teamname.equalsIgnoreCase(team.name)) {
-			if (userManager.getTeamModel(team.name) != null) {
-				throw new GitBlitException(MessageFormat.format(
-						"Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
-						team.name));
-			}
+	public boolean deleteRepositoryModel(RepositoryModel model) {
+		boolean success = repositoryManager.deleteRepositoryModel(model);
+		if (success && ticketServiceProvider.get() != null) {
+			ticketServiceProvider.get().deleteAll(model);
 		}
-		if (!userManager.updateTeamModel(teamname, team)) {
-			throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
-		}
+		return success;
+	}
+
+	@Override
+	public boolean deleteRepository(String repositoryName) {
+		// delegate to deleteRepositoryModel() to destroy indexed tickets
+		RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName);
+		return deleteRepositoryModel(repository);
+	}
+
+	@Override
+	public List<String> getAllScripts() {
+		return repositoryManager.getAllScripts();
+	}
+
+	@Override
+	public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
+		return repositoryManager.getPreReceiveScriptsInherited(repository);
+	}
+
+	@Override
+	public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
+		return repositoryManager.getPreReceiveScriptsUnused(repository);
+	}
+
+	@Override
+	public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
+		return repositoryManager.getPostReceiveScriptsInherited(repository);
+	}
+
+	@Override
+	public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
+		return repositoryManager.getPostReceiveScriptsUnused(repository);
+	}
+
+	@Override
+	public List<SearchResult> search(String query, int page, int pageSize, List<String> repositories) {
+		return repositoryManager.search(query, page, pageSize, repositories);
+	}
+
+	@Override
+	public boolean isCollectingGarbage() {
+		return repositoryManager.isCollectingGarbage();
+	}
+
+	@Override
+	public boolean isCollectingGarbage(String repositoryName) {
+		return repositoryManager.isCollectingGarbage(repositoryName);
+	}
+
+	/*
+	 * PROJECT MANAGER
+	 */
+
+	@Override
+	public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
+		return projectManager.getProjectModels(user, includeUsers);
+	}
+
+	@Override
+	public ProjectModel getProjectModel(String name, UserModel user) {
+		return projectManager.getProjectModel(name, user);
+	}
+
+	@Override
+	public ProjectModel getProjectModel(String name) {
+		return projectManager.getProjectModel(name);
+	}
+
+	@Override
+	public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
+		return projectManager.getProjectModels(repositoryModels, includeUsers);
+	}
+
+	/*
+	 * FEDERATION MANAGER
+	 */
+
+	@Override
+	public File getProposalsFolder() {
+		return federationManager.getProposalsFolder();
+	}
+
+	@Override
+	public boolean canFederate() {
+		return federationManager.canFederate();
+	}
+
+	@Override
+	public UserModel getFederationUser() {
+		return federationManager.getFederationUser();
+	}
+
+	@Override
+	public List<FederationModel> getFederationRegistrations() {
+		return federationManager.getFederationRegistrations();
+	}
+
+	@Override
+	public FederationModel getFederationRegistration(String url, String name) {
+		return federationManager.getFederationRegistration(url, name);
+	}
+
+	@Override
+	public List<FederationSet> getFederationSets(String gitblitUrl) {
+		return federationManager.getFederationSets(gitblitUrl);
+	}
+
+	@Override
+	public List<String> getFederationTokens() {
+		return federationManager.getFederationTokens();
+	}
+
+	@Override
+	public String getFederationToken(FederationToken type) {
+		return federationManager.getFederationToken(type);
+	}
+
+	@Override
+	public String getFederationToken(String value) {
+		return federationManager.getFederationToken(value);
+	}
+
+	@Override
+	public boolean validateFederationRequest(FederationRequest req, String token) {
+		return federationManager.validateFederationRequest(req, token);
+	}
+
+	@Override
+	public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
+		return federationManager.acknowledgeFederationStatus(identification, registration);
+	}
+
+	@Override
+	public List<FederationModel> getFederationResultRegistrations() {
+		return federationManager.getFederationResultRegistrations();
+	}
+
+	@Override
+	public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
+		return federationManager.submitFederationProposal(proposal, gitblitUrl);
+	}
+
+	@Override
+	public List<FederationProposal> getPendingFederationProposals() {
+		return federationManager.getPendingFederationProposals();
+	}
+
+	@Override
+	public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
+		return federationManager.getRepositories(gitblitUrl, token);
+	}
+
+	@Override
+	public FederationProposal createFederationProposal(String gitblitUrl, String token) {
+		return federationManager.createFederationProposal(gitblitUrl, token);
+	}
+
+	@Override
+	public FederationProposal getPendingFederationProposal(String token) {
+		return federationManager.getPendingFederationProposal(token);
+	}
+
+	@Override
+	public boolean deletePendingFederationProposal(FederationProposal proposal) {
+		return federationManager.deletePendingFederationProposal(proposal);
+	}
+
+	@Override
+	public void closeAll() {
+		repositoryManager.closeAll();
+	}
+
+	@Override
+	public void close(String repository) {
+		repositoryManager.close(repository);
+	}
+
+	@Override
+	public boolean isIdle(Repository repository) {
+		return repositoryManager.isIdle(repository);
+	}
+
+	/*
+	 * PLUGIN MANAGER
+	 */
+
+	@Override
+	public Version getSystemVersion() {
+		return pluginManager.getSystemVersion();
+	}
+
+	@Override
+	public void startPlugins() {
+		pluginManager.startPlugins();
+	}
+
+	@Override
+	public void stopPlugins() {
+		pluginManager.stopPlugins();
+	}
+
+	@Override
+	public List<PluginWrapper> getPlugins() {
+		return pluginManager.getPlugins();
+	}
+
+	@Override
+	public PluginWrapper getPlugin(String pluginId) {
+		return pluginManager.getPlugin(pluginId);
+	}
+
+	@Override
+	public List<Class<?>> getExtensionClasses(String pluginId) {
+		return pluginManager.getExtensionClasses(pluginId);
+	}
+
+	@Override
+	public <T> List<T> getExtensions(Class<T> clazz) {
+		return pluginManager.getExtensions(clazz);
+	}
+
+	@Override
+	public PluginWrapper whichPlugin(Class<?> clazz) {
+		return pluginManager.whichPlugin(clazz);
+	}
+
+	@Override
+	public PluginState startPlugin(String pluginId) {
+		return pluginManager.startPlugin(pluginId);
+	}
+
+	@Override
+	public PluginState stopPlugin(String pluginId) {
+		return pluginManager.stopPlugin(pluginId);
+	}
+
+	@Override
+	public boolean disablePlugin(String pluginId) {
+		return pluginManager.disablePlugin(pluginId);
+	}
+
+	@Override
+	public boolean enablePlugin(String pluginId) {
+		return pluginManager.enablePlugin(pluginId);
+	}
+
+	@Override
+	public boolean uninstallPlugin(String pluginId) {
+		return pluginManager.uninstallPlugin(pluginId);
+	}
+
+	@Override
+	public boolean refreshRegistry(boolean verifyChecksum) {
+		return pluginManager.refreshRegistry(verifyChecksum);
+	}
+
+	@Override
+	public boolean installPlugin(String url, boolean verifyChecksum) throws IOException {
+		return pluginManager.installPlugin(url, verifyChecksum);
+	}
+
+	@Override
+	public boolean upgradePlugin(String pluginId, String url, boolean verifyChecksum) throws IOException {
+		return pluginManager.upgradePlugin(pluginId, url, verifyChecksum);
+	}
+
+	@Override
+	public List<PluginRegistration> getRegisteredPlugins() {
+		return pluginManager.getRegisteredPlugins();
+	}
+
+	@Override
+	public List<PluginRegistration> getRegisteredPlugins(InstallState state) {
+		return pluginManager.getRegisteredPlugins(state);
+	}
+
+	@Override
+	public PluginRegistration lookupPlugin(String pluginId) {
+		return pluginManager.lookupPlugin(pluginId);
+	}
+
+	@Override
+	public PluginRelease lookupRelease(String pluginId, String version) {
+		return pluginManager.lookupRelease(pluginId, version);
 	}
 }

--
Gitblit v1.9.1