From 3f5b8f5d9203aa7ffb7fbe9cdbaf9dba3da6cae6 Mon Sep 17 00:00:00 2001 From: Hybris95 <hybris_95@hotmail.com> Date: Thu, 01 May 2014 16:14:15 -0400 Subject: [PATCH] Fixes sort, page building and search functions on "my tickets" page. --- src/main/java/com/gitblit/GitBlit.java | 667 ++++++++++++++++++++++++++++-------------------------- 1 files changed, 346 insertions(+), 321 deletions(-) diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index ca676ff..3db5f08 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 gitblit.com. + * Copyright 2013 gitblit.com. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,412 +15,437 @@ */ package com.gitblit; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; -import java.util.Map; +import java.util.Set; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.servlet.ServletContext; -import javax.servlet.annotation.WebListener; +import javax.inject.Singleton; +import javax.servlet.http.HttpServletRequest; -import com.gitblit.dagger.DaggerContextListener; -import com.gitblit.git.GitServlet; +import com.gitblit.Constants.AccessPermission; +import com.gitblit.Constants.Transport; +import com.gitblit.manager.GitblitManager; +import com.gitblit.manager.IAuthenticationManager; import com.gitblit.manager.IFederationManager; -import com.gitblit.manager.IGitblitManager; -import com.gitblit.manager.IManager; +import com.gitblit.manager.IGitblit; import com.gitblit.manager.INotificationManager; +import com.gitblit.manager.IPluginManager; import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.IServicesManager; -import com.gitblit.manager.ISessionManager; import com.gitblit.manager.IUserManager; -import com.gitblit.utils.ContainerUtils; +import com.gitblit.manager.ServicesManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.RepositoryUrl; +import com.gitblit.models.UserModel; +import com.gitblit.tickets.BranchTicketService; +import com.gitblit.tickets.FileTicketService; +import com.gitblit.tickets.ITicketService; +import com.gitblit.tickets.NullTicketService; +import com.gitblit.tickets.RedisTicketService; +import com.gitblit.transport.ssh.IPublicKeyManager; import com.gitblit.utils.StringUtils; -import com.gitblit.wicket.GitblitWicketFilter; +import dagger.Module; import dagger.ObjectGraph; +import dagger.Provides; /** - * This class is the main entry point for the entire webapp. It is a singleton - * created manually by Gitblit GO or dynamically by the WAR/Express servlet - * container. This class instantiates and starts all managers followed by - * instantiating and registering all servlets and filters. - * - * Leveraging Servlet 3 and Dagger static dependency injection allows Gitblit to - * be modular and completely code-driven rather then relying on the fragility of - * a web.xml descriptor and the static & monolithic design previously used. + * GitBlit is the aggregate manager for the Gitblit webapp. It provides all + * management functions and also manages some long-running services. * * @author James Moger * */ -@WebListener -public class GitBlit extends DaggerContextListener { +public class GitBlit extends GitblitManager { - private static GitBlit gitblit; + private final ObjectGraph injector; - private final List<IManager> managers = new ArrayList<IManager>(); + private final ServicesManager servicesManager; - private final IStoredSettings goSettings; + private ITicketService ticketService; - private final File goBaseFolder; + public GitBlit( + IRuntimeManager runtimeManager, + IPluginManager pluginManager, + INotificationManager notificationManager, + IUserManager userManager, + IAuthenticationManager authenticationManager, + IPublicKeyManager publicKeyManager, + IRepositoryManager repositoryManager, + IProjectManager projectManager, + IFederationManager federationManager) { - /** - * Construct a Gitblit WAR/Express context. - */ - public GitBlit() { - this.goSettings = null; - this.goBaseFolder = null; - gitblit = this; + super(runtimeManager, + pluginManager, + notificationManager, + userManager, + authenticationManager, + publicKeyManager, + repositoryManager, + projectManager, + federationManager); + + this.injector = ObjectGraph.create(getModules()); + + this.servicesManager = new ServicesManager(this); } - /** - * Construct a Gitblit GO context. - * - * @param settings - * @param baseFolder - */ - public GitBlit(IStoredSettings settings, File baseFolder) { - this.goSettings = settings; - this.goBaseFolder = baseFolder; - gitblit = this; - } - - /** - * This method is only used for unit and integration testing. - * - * @param managerClass - * @return a manager - */ - @SuppressWarnings("unchecked") - public static <X extends IManager> X getManager(Class<X> managerClass) { - for (IManager manager : gitblit.managers) { - if (managerClass.isAssignableFrom(manager.getClass())) { - return (X) manager; - } - } - return null; - } - - /** - * Returns Gitblit's Dagger injection modules. - */ @Override + public GitBlit start() { + super.start(); + logger.info("Starting services manager..."); + servicesManager.start(); + configureTicketService(); + return this; + } + + @Override + public GitBlit stop() { + super.stop(); + servicesManager.stop(); + ticketService.stop(); + return this; + } + + @Override + public boolean isServingRepositories() { + return servicesManager.isServingRepositories(); + } + protected Object [] getModules() { - return new Object [] { new DaggerModule() }; + return new Object [] { new GitBlitModule()}; + } + + protected boolean acceptPush(Transport byTransport) { + if (byTransport == null) { + logger.info("Unknown transport, push rejected!"); + return false; + } + + Set<Transport> transports = new HashSet<Transport>(); + for (String value : getSettings().getStrings(Keys.git.acceptedPushTransports)) { + Transport transport = Transport.fromString(value); + if (transport == null) { + logger.info(String.format("Ignoring unknown registered transport %s", value)); + continue; + } + + transports.add(transport); + } + + if (transports.isEmpty()) { + // no transports are explicitly specified, all are acceptable + return true; + } + + // verify that the transport is permitted + return transports.contains(byTransport); } /** - * Prepare runtime settings and start all manager instances. + * Returns a list of repository URLs and the user access permission. + * + * @param request + * @param user + * @param repository + * @return a list of repository urls */ @Override - protected void beforeServletInjection(ServletContext context) { - ObjectGraph injector = getInjector(context); + public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) { + if (user == null) { + user = UserModel.ANONYMOUS; + } + String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username); - // create the runtime settings object - IStoredSettings runtimeSettings = injector.get(IStoredSettings.class); - final File baseFolder; + List<RepositoryUrl> list = new ArrayList<RepositoryUrl>(); - if (goSettings != null) { - // Gitblit GO - baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings); - } else { - // servlet container - WebXmlSettings webxmlSettings = new WebXmlSettings(context); - String contextRealPath = context.getRealPath("/"); - File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null; + // http/https url + if (settings.getBoolean(Keys.git.enableGitServlet, true)) { + AccessPermission permission = user.getRepositoryPermission(repository).permission; + if (permission.exceeds(AccessPermission.NONE)) { + Transport transport = Transport.fromString(request.getScheme()); + if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } + list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission)); + } + } - if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) { - // RedHat OpenShift - baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings); + // ssh daemon url + String sshDaemonUrl = servicesManager.getSshDaemonUrl(request, user, repository); + if (!StringUtils.isEmpty(sshDaemonUrl)) { + AccessPermission permission = user.getRepositoryPermission(repository).permission; + if (permission.exceeds(AccessPermission.NONE)) { + if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } + + list.add(new RepositoryUrl(sshDaemonUrl, permission)); + } + } + + // git daemon url + String gitDaemonUrl = servicesManager.getGitDaemonUrl(request, user, repository); + if (!StringUtils.isEmpty(gitDaemonUrl)) { + AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository); + if (permission.exceeds(AccessPermission.NONE)) { + if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } + 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 { - // standard WAR - baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings); + // external url does not require username + list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null)); } - - // Test for Tomcat forward-slash/%2F issue and auto-adjust settings - ContainerUtils.CVE_2007_0450.test(runtimeSettings); } - // Manually configure IRuntimeManager - logManager(IRuntimeManager.class); - IRuntimeManager runtime = injector.get(IRuntimeManager.class); - runtime.setBaseFolder(baseFolder); - runtime.getStatus().isGO = goSettings != null; - runtime.getStatus().servletContainer = context.getServerInfo(); - runtime.start(); - managers.add(runtime); + // sort transports by highest permission and then by transport security + Collections.sort(list, new Comparator<RepositoryUrl>() { - // start all other managers - startManager(injector, INotificationManager.class); - startManager(injector, IUserManager.class); - startManager(injector, ISessionManager.class); - startManager(injector, IRepositoryManager.class); - startManager(injector, IProjectManager.class); - startManager(injector, IGitblitManager.class); - startManager(injector, IFederationManager.class); - startManager(injector, IServicesManager.class); + @Override + public int compare(RepositoryUrl o1, RepositoryUrl o2) { + if (!o1.isExternal() && o2.isExternal()) { + // prefer Gitblit over external + return -1; + } else if (o1.isExternal() && !o2.isExternal()) { + // prefer Gitblit over external + return 1; + } else if (o1.isExternal() && o2.isExternal()) { + // sort by Transport ordinal + return o1.transport.compareTo(o2.transport); + } else if (o1.permission.exceeds(o2.permission)) { + // prefer highest permission + return -1; + } else if (o2.permission.exceeds(o1.permission)) { + // prefer highest permission + return 1; + } - logger.info(""); - logger.info("All managers started."); - logger.info(""); - } + // prefer more secure transports + return o1.transport.compareTo(o2.transport); + } + }); - protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) { - logManager(clazz); - X x = injector.get(clazz); - x.start(); - managers.add(x); - return x; - } - - protected void logManager(Class<? extends IManager> clazz) { - logger.info(""); - logger.info("----[{}]----", clazz.getName()); + return list; } /** - * Instantiate and inject all filters and servlets into the container using - * the servlet 3 specification. + * Detect renames and reindex as appropriate. */ @Override - protected void injectServlets(ServletContext context) { - // access restricted servlets - serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class); - serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class); - serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class); - serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class); - serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class); + 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); + } - // servlets - serve(context, Constants.FEDERATION_PATH, FederationServlet.class); - serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class); - serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class); - file(context, "/robots.txt", RobotsTxtServlet.class); - file(context, "/logo.png", LogoServlet.class); + super.updateRepositoryModel(repositoryName, repository, isCreate); - // optional force basic authentication - filter(context, "/*", EnforceAuthenticationFilter.class, null); - - // Wicket - String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ","); - Map<String, String> params = new HashMap<String, String>(); - params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*"); - params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore); - filter(context, "/*", GitblitWicketFilter.class, params); + if (isRename && ticketService != null) { + ticketService.rename(oldModel, repository); + } } /** - * Gitblit is being shutdown either because the servlet container is - * shutting down or because the servlet container is re-deploying Gitblit. + * Delete the user and all associated public ssh keys. */ @Override - protected void destroyContext(ServletContext context) { - logger.info("Gitblit context destroyed by servlet container."); - for (IManager manager : managers) { - logger.debug("stopping {}", manager.getClass().getSimpleName()); - manager.stop(); + public boolean deleteUser(String username) { + UserModel user = userManager.getUserModel(username); + return deleteUserModel(user); + } + + @Override + public boolean deleteUserModel(UserModel model) { + boolean success = userManager.deleteUserModel(model); + if (success) { + getPublicKeyManager().removeAllKeys(model.username); } + return success; } /** - * Configures Gitblit GO - * - * @param context - * @param settings - * @param baseFolder - * @param runtimeSettings - * @return the base folder + * Delete the repository and all associated tickets. */ - protected File configureGO( - ServletContext context, - IStoredSettings goSettings, - File goBaseFolder, - IStoredSettings runtimeSettings) { - - logger.debug("configuring Gitblit GO"); - - // merge the stored settings into the runtime settings - // - // if runtimeSettings is also a FileSettings w/o a specified target file, - // the target file for runtimeSettings is set to "localSettings". - runtimeSettings.merge(goSettings); - File base = goBaseFolder; - return base; + @Override + public boolean deleteRepository(String repositoryName) { + RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName); + return deleteRepositoryModel(repository); } + @Override + public boolean deleteRepositoryModel(RepositoryModel model) { + boolean success = repositoryManager.deleteRepositoryModel(model); + if (success && ticketService != null) { + ticketService.deleteAll(model); + } + return success; + } /** - * Configures a standard WAR instance of Gitblit. + * Returns the configured ticket service. * - * @param context - * @param webxmlSettings - * @param contextFolder - * @param runtimeSettings - * @return the base folder + * @return a ticket service */ - protected File configureWAR( - ServletContext context, - WebXmlSettings webxmlSettings, - File contextFolder, - IStoredSettings runtimeSettings) { + @Override + public ITicketService getTicketService() { + return ticketService; + } - // Gitblit is running in a standard servlet container - logger.debug("configuring Gitblit WAR"); - logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>")); - - String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data"); - - if (path.contains(Constants.contextFolder$) && contextFolder == null) { - // warn about null contextFolder (issue-199) - logger.error(""); - logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!", - Constants.baseFolder, Constants.contextFolder$, context.getServerInfo())); - logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder)); - logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder)); - logger.error(""); + protected void configureTicketService() { + String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName()); + if (StringUtils.isEmpty(clazz)) { + clazz = NullTicketService.class.getName(); } - try { - // try to lookup JNDI env-entry for the baseFolder - InitialContext ic = new InitialContext(); - Context env = (Context) ic.lookup("java:comp/env"); - String val = (String) env.lookup("baseFolder"); - if (!StringUtils.isEmpty(val)) { - path = val; + Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz); + ticketService = injector.get(serviceClass).start(); + if (ticketService instanceof NullTicketService) { + logger.warn("No ticket service configured."); + } else if (ticketService.isReady()) { + logger.info("{} is ready.", ticketService); + } else { + logger.warn("{} is disabled.", ticketService); } - } catch (NamingException n) { - logger.error("Failed to get JNDI env-entry: " + n.getExplanation()); + } catch (Exception e) { + logger.error("failed to create ticket service " + clazz, e); + ticketService = injector.get(NullTicketService.class).start(); } - - File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path); - base.mkdirs(); - - // try to extract the data folder resource to the baseFolder - File localSettings = new File(base, "gitblit.properties"); - if (!localSettings.exists()) { - extractResources(context, "/WEB-INF/data/", base); - } - - // delegate all config to baseFolder/gitblit.properties file - FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); - - // merge the stored settings into the runtime settings - // - // if runtimeSettings is also a FileSettings w/o a specified target file, - // the target file for runtimeSettings is set to "localSettings". - runtimeSettings.merge(fileSettings); - - return base; } /** - * Configures an OpenShift instance of Gitblit. + * A nested Dagger graph is used for constructor dependency injection of + * complex classes. * - * @param context - * @param webxmlSettings - * @param contextFolder - * @param runtimeSettings - * @return the base folder + * @author James Moger + * */ - private File configureExpress( - ServletContext context, - WebXmlSettings webxmlSettings, - File contextFolder, - IStoredSettings runtimeSettings) { + @Module( + library = true, + injects = { + IStoredSettings.class, - // Gitblit is running in OpenShift/JBoss - logger.debug("configuring Gitblit Express"); - String openShift = System.getenv("OPENSHIFT_DATA_DIR"); - File base = new File(openShift); - logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath()); + // core managers + IRuntimeManager.class, + IPluginManager.class, + INotificationManager.class, + IUserManager.class, + IAuthenticationManager.class, + IRepositoryManager.class, + IProjectManager.class, + IFederationManager.class, - // Copy the included scripts to the configured groovy folder - String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"); - File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path); - if (!localScripts.exists()) { - File warScripts = new File(contextFolder, "/WEB-INF/data/groovy"); - if (!warScripts.equals(localScripts)) { - try { - com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles()); - } catch (IOException e) { - logger.error(MessageFormat.format( - "Failed to copy included Groovy scripts from {0} to {1}", - warScripts, localScripts)); + // the monolithic manager + IGitblit.class, + + // ticket services + NullTicketService.class, + FileTicketService.class, + BranchTicketService.class, + RedisTicketService.class } - } + ) + class GitBlitModule { + + @Provides @Singleton IStoredSettings provideSettings() { + return settings; } - // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty) - runtimeSettings.merge(webxmlSettings); + @Provides @Singleton IRuntimeManager provideRuntimeManager() { + return runtimeManager; + } - // settings are to be stored in openshift/gitblit.properties - File localSettings = new File(base, "gitblit.properties"); - FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); + @Provides @Singleton IPluginManager providePluginManager() { + return pluginManager; + } - // merge the stored settings into the runtime settings - // - // if runtimeSettings is also a FileSettings w/o a specified target file, - // the target file for runtimeSettings is set to "localSettings". - runtimeSettings.merge(fileSettings); + @Provides @Singleton INotificationManager provideNotificationManager() { + return notificationManager; + } - return base; - } + @Provides @Singleton IUserManager provideUserManager() { + return userManager; + } - protected void extractResources(ServletContext context, String path, File toDir) { - for (String resource : context.getResourcePaths(path)) { - // extract the resource to the directory if it does not exist - File f = new File(toDir, resource.substring(path.length())); - if (!f.exists()) { - InputStream is = null; - OutputStream os = null; - try { - if (resource.charAt(resource.length() - 1) == '/') { - // directory - f.mkdirs(); - extractResources(context, resource, f); - } else { - // file - f.getParentFile().mkdirs(); - is = context.getResourceAsStream(resource); - os = new FileOutputStream(f); - byte [] buffer = new byte[4096]; - int len = 0; - while ((len = is.read(buffer)) > -1) { - os.write(buffer, 0, len); - } - } - } catch (FileNotFoundException e) { - logger.error("Failed to find resource \"" + resource + "\"", e); - } catch (IOException e) { - logger.error("Failed to copy resource \"" + resource + "\" to " + f, e); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // ignore - } - } - if (os != null) { - try { - os.close(); - } catch (IOException e) { - // ignore - } - } - } - } + @Provides @Singleton IAuthenticationManager provideAuthenticationManager() { + return authenticationManager; + } + + @Provides @Singleton IRepositoryManager provideRepositoryManager() { + return repositoryManager; + } + + @Provides @Singleton IProjectManager provideProjectManager() { + return projectManager; + } + + @Provides @Singleton IFederationManager provideFederationManager() { + return federationManager; + } + + @Provides @Singleton IGitblit provideGitblit() { + return GitBlit.this; + } + + @Provides @Singleton NullTicketService provideNullTicketService() { + return new NullTicketService( + runtimeManager, + pluginManager, + notificationManager, + userManager, + repositoryManager); + } + + @Provides @Singleton FileTicketService provideFileTicketService() { + return new FileTicketService( + runtimeManager, + pluginManager, + notificationManager, + userManager, + repositoryManager); + } + + @Provides @Singleton BranchTicketService provideBranchTicketService() { + return new BranchTicketService( + runtimeManager, + pluginManager, + notificationManager, + userManager, + repositoryManager); + } + + @Provides @Singleton RedisTicketService provideRedisTicketService() { + return new RedisTicketService( + runtimeManager, + pluginManager, + notificationManager, + userManager, + repositoryManager); } } } -- Gitblit v1.9.1