| | |
| | | /* |
| | | * 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. |
| | |
| | | */ |
| | | package com.gitblit; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.File; |
| | | import java.io.FileFilter; |
| | | import java.io.FileInputStream; |
| | | import java.io.FileNotFoundException; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InputStreamReader; |
| | | import java.io.OutputStream; |
| | | import java.lang.reflect.Type; |
| | | import java.text.MessageFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.TreeMap; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.ScheduledExecutorService; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | 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 org.apache.wicket.resource.ContextRelativeResource; |
| | | import org.apache.wicket.util.resource.ResourceStreamNotFoundException; |
| | | import org.eclipse.jgit.storage.file.FileBasedConfig; |
| | | import org.eclipse.jgit.util.FS; |
| | | import org.slf4j.Logger; |
| | | |
| | | import com.gitblit.Constants.AccessPermission; |
| | | import com.gitblit.Constants.AccessRestrictionType; |
| | | import com.gitblit.Constants.FederationRequest; |
| | | import com.gitblit.Constants.FederationToken; |
| | | import com.gitblit.dagger.DaggerContextListener; |
| | | import com.gitblit.fanout.FanoutNioService; |
| | | import com.gitblit.fanout.FanoutService; |
| | | import com.gitblit.fanout.FanoutSocketService; |
| | | import com.gitblit.git.GitDaemon; |
| | | import com.gitblit.git.GitServlet; |
| | | 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.ISessionManager; |
| | | import com.gitblit.manager.IUserManager; |
| | | import com.gitblit.models.FederationModel; |
| | | import com.gitblit.models.FederationProposal; |
| | | import com.gitblit.models.FederationSet; |
| | | import com.gitblit.models.GitClientApplication; |
| | | import com.gitblit.models.ProjectModel; |
| | | import com.gitblit.manager.ServicesManager; |
| | | import com.gitblit.models.RepositoryModel; |
| | | import com.gitblit.models.RepositoryUrl; |
| | | import com.gitblit.models.ServerSettings; |
| | | import com.gitblit.models.SettingModel; |
| | | import com.gitblit.models.TeamModel; |
| | | import com.gitblit.models.UserModel; |
| | | import com.gitblit.utils.ArrayUtils; |
| | | import com.gitblit.utils.ContainerUtils; |
| | | import com.gitblit.utils.DeepCopier; |
| | | import com.gitblit.utils.FederationUtils; |
| | | import com.gitblit.utils.HttpUtils; |
| | | import com.gitblit.utils.JGitUtils; |
| | | import com.gitblit.utils.JsonUtils; |
| | | import com.gitblit.utils.ModelUtils; |
| | | import com.gitblit.utils.ObjectCache; |
| | | 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 com.gitblit.wicket.WicketUtils; |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.JsonIOException; |
| | | import com.google.gson.JsonSyntaxException; |
| | | import com.google.gson.reflect.TypeToken; |
| | | |
| | | import dagger.Module; |
| | | import dagger.ObjectGraph; |
| | | import dagger.Provides; |
| | | |
| | | /** |
| | | * GitBlit is the servlet context listener singleton that acts as the core for |
| | | * the web ui and the servlets. This class is either directly instantiated by |
| | | * the GitBlitServer class (Gitblit GO) or is reflectively instantiated by the |
| | | * servlet 3 container (Gitblit WAR or Express). |
| | | * |
| | | * This class is the central logic processor for Gitblit. All settings, user |
| | | * object, and repository object operations pass through this class. |
| | | * 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 |
| | | implements IProjectManager, |
| | | IFederationManager, |
| | | IGitblitManager { |
| | | public class GitBlit extends GitblitManager { |
| | | |
| | | private static GitBlit gitblit; |
| | | private final ObjectGraph injector; |
| | | |
| | | private final IStoredSettings goSettings; |
| | | private final ServicesManager servicesManager; |
| | | |
| | | private final File goBaseFolder; |
| | | private ITicketService ticketService; |
| | | |
| | | private final List<IManager> managers = new ArrayList<IManager>(); |
| | | public GitBlit( |
| | | IRuntimeManager runtimeManager, |
| | | IPluginManager pluginManager, |
| | | INotificationManager notificationManager, |
| | | IUserManager userManager, |
| | | IAuthenticationManager authenticationManager, |
| | | IPublicKeyManager publicKeyManager, |
| | | IRepositoryManager repositoryManager, |
| | | IProjectManager projectManager, |
| | | IFederationManager federationManager) { |
| | | |
| | | private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10); |
| | | super(runtimeManager, |
| | | pluginManager, |
| | | notificationManager, |
| | | userManager, |
| | | authenticationManager, |
| | | publicKeyManager, |
| | | repositoryManager, |
| | | projectManager, |
| | | federationManager); |
| | | |
| | | private final List<FederationModel> federationRegistrations = Collections |
| | | .synchronizedList(new ArrayList<FederationModel>()); |
| | | this.injector = ObjectGraph.create(getModules()); |
| | | |
| | | private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>(); |
| | | |
| | | private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>(); |
| | | |
| | | private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>(); |
| | | |
| | | private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>(); |
| | | |
| | | private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>(); |
| | | |
| | | private IStoredSettings settings; |
| | | |
| | | private FileBasedConfig projectConfigs; |
| | | |
| | | private FanoutService fanoutService; |
| | | |
| | | private GitDaemon gitDaemon; |
| | | |
| | | public GitBlit() { |
| | | this.goSettings = null; |
| | | this.goBaseFolder = null; |
| | | this.servicesManager = new ServicesManager(this); |
| | | } |
| | | |
| | | public GitBlit(IStoredSettings settings, File baseFolder) { |
| | | this.goSettings = settings; |
| | | this.goBaseFolder = baseFolder; |
| | | gitblit = this; |
| | | } |
| | | |
| | | /** |
| | | * Returns the Gitblit singleton. |
| | | * |
| | | * @return gitblit singleton |
| | | */ |
| | | public static GitBlit self() { |
| | | return gitblit; |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | public static <X> X getManager(Class<X> managerClass) { |
| | | if (managerClass.isAssignableFrom(GitBlit.class)) { |
| | | return (X) gitblit; |
| | | } |
| | | |
| | | for (IManager manager : gitblit.managers) { |
| | | if (managerClass.isAssignableFrom(manager.getClass())) { |
| | | return (X) manager; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Returns the path of the proposals folder. This method checks to see if |
| | | * Gitblit is running on a cloud service and may return an adjusted path. |
| | | * |
| | | * @return the proposals folder path |
| | | */ |
| | | @Override |
| | | public File getProposalsFolder() { |
| | | return getManager(IRuntimeManager.class).getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals"); |
| | | 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 GitBlitModule()}; |
| | | } |
| | | |
| | | /** |
| | |
| | | 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; |
| | |
| | | } |
| | | } |
| | | |
| | | // 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)) { |
| | | list.add(new RepositoryUrl(sshDaemonUrl, permission)); |
| | | } |
| | | } |
| | | |
| | | // git daemon url |
| | | String gitDaemonUrl = getGitDaemonUrl(request, user, repository); |
| | | String gitDaemonUrl = servicesManager.getGitDaemonUrl(request, user, repository); |
| | | if (!StringUtils.isEmpty(gitDaemonUrl)) { |
| | | AccessPermission permission = getGitDaemonAccessPermission(user, repository); |
| | | AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository); |
| | | if (permission.exceeds(AccessPermission.NONE)) { |
| | | list.add(new RepositoryUrl(gitDaemonUrl, permission)); |
| | | } |
| | |
| | | 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)) { |
| | | if (!StringUtils.isEmpty(username)) { |
| | | list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null)); |
| | | } |
| | | } else { |
| | |
| | | return list; |
| | | } |
| | | |
| | | 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); |
| | | |
| | | // inject username into repository url if authentication is required |
| | | if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE) |
| | | && !StringUtils.isEmpty(username)) { |
| | | sb.insert(sb.indexOf("://") + 3, username + "@"); |
| | | /** |
| | | * Detect renames and reindex as appropriate. |
| | | */ |
| | | @Override |
| | | 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); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | super.updateRepositoryModel(repositoryName, repository, isCreate); |
| | | |
| | | 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; |
| | | if (isRename && ticketService != null) { |
| | | ticketService.rename(oldModel, repository); |
| | | } |
| | | return AccessPermission.NONE; |
| | | } |
| | | |
| | | /** |
| | | * 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(getManager(IRuntimeManager.class).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 = 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"); |
| | | public boolean deleteUser(String username) { |
| | | UserModel user = userManager.getUserModel(username); |
| | | return deleteUserModel(user); |
| | | } |
| | | |
| | | private Collection<GitClientApplication> readClientApplications(InputStream is) { |
| | | @Override |
| | | public boolean deleteUserModel(UserModel model) { |
| | | boolean success = userManager.deleteUserModel(model); |
| | | if (success) { |
| | | getPublicKeyManager().removeAllKeys(model.username); |
| | | } |
| | | return success; |
| | | } |
| | | |
| | | /** |
| | | * Delete the repository and all associated tickets. |
| | | */ |
| | | @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; |
| | | } |
| | | |
| | | /** |
| | | * Returns the configured ticket service. |
| | | * |
| | | * @return a ticket service |
| | | */ |
| | | @Override |
| | | public ITicketService getTicketService() { |
| | | return ticketService; |
| | | } |
| | | |
| | | protected void configureTicketService() { |
| | | String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName()); |
| | | if (StringUtils.isEmpty(clazz)) { |
| | | clazz = NullTicketService.class.getName(); |
| | | } |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | | * Open a file resource using the Servlet container. |
| | | * @param file to open |
| | | * @return InputStream of the opened file |
| | | * @throws ResourceStreamNotFoundException |
| | | */ |
| | | public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException { |
| | | ContextRelativeResource res = WicketUtils.getResource(file); |
| | | return res.getResourceStream().getInputStream(); |
| | | } |
| | | |
| | | @Override |
| | | public UserModel getFederationUser() { |
| | | // the federation user is an administrator |
| | | UserModel federationUser = new UserModel(Constants.FEDERATION_USER); |
| | | federationUser.canAdmin = true; |
| | | return federationUser; |
| | | } |
| | | |
| | | /** |
| | | * 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 (getManager(IUserManager.class).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 : getManager(IRepositoryManager.class).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(); |
| | | getManager(IRepositoryManager.class).updateRepositoryModel(oldRepositoryName, model, false); |
| | | } else if (model.isOwner(username)) { |
| | | // common/shared repo |
| | | model.addOwner(user.username); |
| | | getManager(IRepositoryManager.class).updateRepositoryModel(model.name, model, false); |
| | | } |
| | | } |
| | | } |
| | | if (!getManager(IUserManager.class).updateUserModel(username, user)) { |
| | | throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Updates the TeamModel object for the specified name. |
| | | * |
| | | * @param teamname |
| | | * @param team |
| | | * @param isCreate |
| | | */ |
| | | @Override |
| | | public void updateTeamModel(String teamname, TeamModel team, boolean isCreate) |
| | | throws GitBlitException { |
| | | if (!teamname.equalsIgnoreCase(team.name)) { |
| | | if (getManager(IUserManager.class).getTeamModel(team.name) != null) { |
| | | throw new GitBlitException(MessageFormat.format( |
| | | "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname, |
| | | team.name)); |
| | | } |
| | | } |
| | | if (!getManager(IUserManager.class).updateTeamModel(teamname, team)) { |
| | | throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!"); |
| | | } |
| | | } |
| | | |
| | | private void reloadProjectMarkdown(ProjectModel project) { |
| | | // project markdown |
| | | File pmkd = new File(getManager(IRepositoryManager.class).getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd"); |
| | | if (pmkd.exists()) { |
| | | Date lm = new Date(pmkd.lastModified()); |
| | | if (!projectMarkdownCache.hasCurrent(project.name, lm)) { |
| | | String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n"); |
| | | projectMarkdownCache.updateObject(project.name, lm, mkd); |
| | | } |
| | | project.projectMarkdown = projectMarkdownCache.getObject(project.name); |
| | | } |
| | | |
| | | // project repositories markdown |
| | | File rmkd = new File(getManager(IRepositoryManager.class).getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd"); |
| | | if (rmkd.exists()) { |
| | | Date lm = new Date(rmkd.lastModified()); |
| | | if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) { |
| | | String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n"); |
| | | projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd); |
| | | } |
| | | project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns the map of project config. This map is cached and reloaded if |
| | | * the underlying projects.conf file changes. |
| | | * |
| | | * @return project config map |
| | | */ |
| | | private Map<String, ProjectModel> getProjectConfigs() { |
| | | if (projectCache.isEmpty() || projectConfigs.isOutdated()) { |
| | | |
| | | try { |
| | | projectConfigs.load(); |
| | | } catch (Exception e) { |
| | | } |
| | | |
| | | // project configs |
| | | String rootName = settings.getString(Keys.web.repositoryRootGroupName, "main"); |
| | | ProjectModel rootProject = new ProjectModel(rootName, true); |
| | | |
| | | Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>(); |
| | | // cache the root project under its alias and an empty path |
| | | configs.put("", rootProject); |
| | | configs.put(rootProject.name.toLowerCase(), rootProject); |
| | | |
| | | for (String name : projectConfigs.getSubsections("project")) { |
| | | ProjectModel project; |
| | | if (name.equalsIgnoreCase(rootName)) { |
| | | project = rootProject; |
| | | } else { |
| | | project = new ProjectModel(name); |
| | | } |
| | | project.title = projectConfigs.getString("project", name, "title"); |
| | | project.description = projectConfigs.getString("project", name, "description"); |
| | | |
| | | reloadProjectMarkdown(project); |
| | | |
| | | configs.put(name.toLowerCase(), project); |
| | | } |
| | | projectCache.clear(); |
| | | projectCache.putAll(configs); |
| | | } |
| | | return projectCache; |
| | | } |
| | | |
| | | /** |
| | | * Returns a list of project models for the user. |
| | | * |
| | | * @param user |
| | | * @param includeUsers |
| | | * @return list of projects that are accessible to the user |
| | | */ |
| | | @Override |
| | | public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) { |
| | | Map<String, ProjectModel> configs = getProjectConfigs(); |
| | | |
| | | // per-user project lists, this accounts for security and visibility |
| | | Map<String, ProjectModel> map = new TreeMap<String, ProjectModel>(); |
| | | // root project |
| | | map.put("", configs.get("")); |
| | | |
| | | for (RepositoryModel model : getManager(IRepositoryManager.class).getRepositoryModels(user)) { |
| | | String rootPath = StringUtils.getRootPath(model.name).toLowerCase(); |
| | | if (!map.containsKey(rootPath)) { |
| | | ProjectModel project; |
| | | if (configs.containsKey(rootPath)) { |
| | | // clone the project model because it's repository list will |
| | | // be tailored for the requesting user |
| | | project = DeepCopier.copy(configs.get(rootPath)); |
| | | } else { |
| | | project = new ProjectModel(rootPath); |
| | | } |
| | | map.put(rootPath, project); |
| | | } |
| | | map.get(rootPath).addRepository(model); |
| | | } |
| | | |
| | | // sort projects, root project first |
| | | List<ProjectModel> projects; |
| | | if (includeUsers) { |
| | | // all projects |
| | | projects = new ArrayList<ProjectModel>(map.values()); |
| | | Collections.sort(projects); |
| | | projects.remove(map.get("")); |
| | | projects.add(0, map.get("")); |
| | | } else { |
| | | // all non-user projects |
| | | projects = new ArrayList<ProjectModel>(); |
| | | ProjectModel root = map.remove(""); |
| | | for (ProjectModel model : map.values()) { |
| | | if (!model.isUserProject()) { |
| | | projects.add(model); |
| | | } |
| | | } |
| | | Collections.sort(projects); |
| | | projects.add(0, root); |
| | | } |
| | | return projects; |
| | | } |
| | | |
| | | /** |
| | | * Returns the project model for the specified user. |
| | | * |
| | | * @param name |
| | | * @param user |
| | | * @return a project model, or null if it does not exist |
| | | */ |
| | | @Override |
| | | public ProjectModel getProjectModel(String name, UserModel user) { |
| | | for (ProjectModel project : getProjectModels(user, true)) { |
| | | if (project.name.equalsIgnoreCase(name)) { |
| | | return project; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Returns a project model for the Gitblit/system user. |
| | | * |
| | | * @param name a project name |
| | | * @return a project model or null if the project does not exist |
| | | */ |
| | | @Override |
| | | public ProjectModel getProjectModel(String name) { |
| | | Map<String, ProjectModel> configs = getProjectConfigs(); |
| | | ProjectModel project = configs.get(name.toLowerCase()); |
| | | if (project == null) { |
| | | project = new ProjectModel(name); |
| | | if (ModelUtils.isPersonalRepository(name)) { |
| | | UserModel user = getManager(IUserManager.class).getUserModel(ModelUtils.getUserNameFromRepoPath(name)); |
| | | if (user != null) { |
| | | project.title = user.getDisplayName(); |
| | | project.description = "personal repositories"; |
| | | } |
| | | } |
| | | } else { |
| | | // clone the object |
| | | project = DeepCopier.copy(project); |
| | | } |
| | | if (StringUtils.isEmpty(name)) { |
| | | // get root repositories |
| | | for (String repository : getManager(IRepositoryManager.class).getRepositoryList()) { |
| | | if (repository.indexOf('/') == -1) { |
| | | project.addRepository(repository); |
| | | } |
| | | } |
| | | } else { |
| | | // get repositories in subfolder |
| | | String folder = name.toLowerCase() + "/"; |
| | | for (String repository : getManager(IRepositoryManager.class).getRepositoryList()) { |
| | | if (repository.toLowerCase().startsWith(folder)) { |
| | | project.addRepository(repository); |
| | | } |
| | | } |
| | | } |
| | | if (project.repositories.size() == 0) { |
| | | // no repositories == no project |
| | | return null; |
| | | } |
| | | |
| | | reloadProjectMarkdown(project); |
| | | return project; |
| | | } |
| | | |
| | | /** |
| | | * Returns the list of project models that are referenced by the supplied |
| | | * repository model list. This is an alternative method exists to ensure |
| | | * Gitblit does not call getRepositoryModels(UserModel) twice in a request. |
| | | * |
| | | * @param repositoryModels |
| | | * @param includeUsers |
| | | * @return a list of project models |
| | | */ |
| | | @Override |
| | | public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) { |
| | | Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>(); |
| | | for (RepositoryModel repository : repositoryModels) { |
| | | if (!includeUsers && repository.isPersonalRepository()) { |
| | | // exclude personal repositories |
| | | continue; |
| | | } |
| | | if (!projects.containsKey(repository.projectPath)) { |
| | | ProjectModel project = getProjectModel(repository.projectPath); |
| | | if (project == null) { |
| | | logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!", |
| | | repository.projectPath)); |
| | | continue; |
| | | } |
| | | projects.put(repository.projectPath, project); |
| | | // clear the repo list in the project because that is the system |
| | | // list, not the user-accessible list and start building the |
| | | // user-accessible list |
| | | project.repositories.clear(); |
| | | project.repositories.add(repository.name); |
| | | project.lastChange = repository.lastChange; |
| | | 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 { |
| | | // update the user-accessible list |
| | | // this is used for repository count |
| | | ProjectModel project = projects.get(repository.projectPath); |
| | | project.repositories.add(repository.name); |
| | | if (project.lastChange.before(repository.lastChange)) { |
| | | project.lastChange = repository.lastChange; |
| | | } |
| | | logger.warn("{} is disabled.", ticketService); |
| | | } |
| | | } |
| | | return new ArrayList<ProjectModel>(projects.values()); |
| | | } |
| | | |
| | | /** |
| | | * Returns Gitblit's scheduled executor service for scheduling tasks. |
| | | * |
| | | * @return scheduledExecutor |
| | | */ |
| | | public ScheduledExecutorService executor() { |
| | | return scheduledExecutor; |
| | | } |
| | | |
| | | @Override |
| | | public boolean canFederate() { |
| | | String passphrase = settings.getString(Keys.federation.passphrase, ""); |
| | | return !StringUtils.isEmpty(passphrase); |
| | | } |
| | | |
| | | /** |
| | | * Configures this Gitblit instance to pull any registered federated gitblit |
| | | * instances. |
| | | */ |
| | | private void configureFederation() { |
| | | boolean validPassphrase = true; |
| | | String passphrase = settings.getString(Keys.federation.passphrase, ""); |
| | | if (StringUtils.isEmpty(passphrase)) { |
| | | logger.warn("Federation passphrase is blank! This server can not be PULLED from."); |
| | | validPassphrase = false; |
| | | } |
| | | if (validPassphrase) { |
| | | // standard tokens |
| | | for (FederationToken tokenType : FederationToken.values()) { |
| | | logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(), |
| | | getFederationToken(tokenType))); |
| | | } |
| | | |
| | | // federation set tokens |
| | | for (String set : settings.getStrings(Keys.federation.sets)) { |
| | | logger.info(MessageFormat.format("Federation Set {0} token = {1}", set, |
| | | getFederationToken(set))); |
| | | } |
| | | } |
| | | |
| | | // Schedule the federation executor |
| | | List<FederationModel> registrations = getFederationRegistrations(); |
| | | if (registrations.size() > 0) { |
| | | FederationPullExecutor executor = new FederationPullExecutor(registrations, true); |
| | | scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Returns the list of federated gitblit instances that this instance will |
| | | * try to pull. |
| | | * |
| | | * @return list of registered gitblit instances |
| | | */ |
| | | @Override |
| | | public List<FederationModel> getFederationRegistrations() { |
| | | if (federationRegistrations.isEmpty()) { |
| | | federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings)); |
| | | } |
| | | return federationRegistrations; |
| | | } |
| | | |
| | | /** |
| | | * Retrieve the specified federation registration. |
| | | * |
| | | * @param name |
| | | * the name of the registration |
| | | * @return a federation registration |
| | | */ |
| | | @Override |
| | | public FederationModel getFederationRegistration(String url, String name) { |
| | | // check registrations |
| | | for (FederationModel r : getFederationRegistrations()) { |
| | | if (r.name.equals(name) && r.url.equals(url)) { |
| | | return r; |
| | | } |
| | | } |
| | | |
| | | // check the results |
| | | for (FederationModel r : getFederationResultRegistrations()) { |
| | | if (r.name.equals(name) && r.url.equals(url)) { |
| | | return r; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Returns the list of federation sets. |
| | | * |
| | | * @return list of federation sets |
| | | */ |
| | | @Override |
| | | public List<FederationSet> getFederationSets(String gitblitUrl) { |
| | | List<FederationSet> list = new ArrayList<FederationSet>(); |
| | | // generate standard tokens |
| | | for (FederationToken type : FederationToken.values()) { |
| | | FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type)); |
| | | fset.repositories = getRepositories(gitblitUrl, fset.token); |
| | | list.add(fset); |
| | | } |
| | | // generate tokens for federation sets |
| | | for (String set : settings.getStrings(Keys.federation.sets)) { |
| | | FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES, |
| | | getFederationToken(set)); |
| | | fset.repositories = getRepositories(gitblitUrl, fset.token); |
| | | list.add(fset); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | /** |
| | | * Returns the list of possible federation tokens for this Gitblit instance. |
| | | * |
| | | * @return list of federation tokens |
| | | */ |
| | | @Override |
| | | public List<String> getFederationTokens() { |
| | | List<String> tokens = new ArrayList<String>(); |
| | | // generate standard tokens |
| | | for (FederationToken type : FederationToken.values()) { |
| | | tokens.add(getFederationToken(type)); |
| | | } |
| | | // generate tokens for federation sets |
| | | for (String set : settings.getStrings(Keys.federation.sets)) { |
| | | tokens.add(getFederationToken(set)); |
| | | } |
| | | return tokens; |
| | | } |
| | | |
| | | /** |
| | | * Returns the specified federation token for this Gitblit instance. |
| | | * |
| | | * @param type |
| | | * @return a federation token |
| | | */ |
| | | @Override |
| | | public String getFederationToken(FederationToken type) { |
| | | return getFederationToken(type.name()); |
| | | } |
| | | |
| | | /** |
| | | * Returns the specified federation token for this Gitblit instance. |
| | | * |
| | | * @param value |
| | | * @return a federation token |
| | | */ |
| | | @Override |
| | | public String getFederationToken(String value) { |
| | | String passphrase = settings.getString(Keys.federation.passphrase, ""); |
| | | return StringUtils.getSHA1(passphrase + "-" + value); |
| | | } |
| | | |
| | | /** |
| | | * Compares the provided token with this Gitblit instance's tokens and |
| | | * determines if the requested permission may be granted to the token. |
| | | * |
| | | * @param req |
| | | * @param token |
| | | * @return true if the request can be executed |
| | | */ |
| | | @Override |
| | | public boolean validateFederationRequest(FederationRequest req, String token) { |
| | | String all = getFederationToken(FederationToken.ALL); |
| | | String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES); |
| | | String jur = getFederationToken(FederationToken.REPOSITORIES); |
| | | switch (req) { |
| | | case PULL_REPOSITORIES: |
| | | return token.equals(all) || token.equals(unr) || token.equals(jur); |
| | | case PULL_USERS: |
| | | case PULL_TEAMS: |
| | | return token.equals(all) || token.equals(unr); |
| | | case PULL_SETTINGS: |
| | | case PULL_SCRIPTS: |
| | | return token.equals(all); |
| | | default: |
| | | break; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Acknowledge and cache the status of a remote Gitblit instance. |
| | | * |
| | | * @param identification |
| | | * the identification of the pulling Gitblit instance |
| | | * @param registration |
| | | * the registration from the pulling Gitblit instance |
| | | * @return true if acknowledged |
| | | */ |
| | | @Override |
| | | public boolean acknowledgeFederationStatus(String identification, FederationModel registration) { |
| | | // reset the url to the identification of the pulling Gitblit instance |
| | | registration.url = identification; |
| | | String id = identification; |
| | | if (!StringUtils.isEmpty(registration.folder)) { |
| | | id += "-" + registration.folder; |
| | | } |
| | | federationPullResults.put(id, registration); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Returns the list of registration results. |
| | | * |
| | | * @return the list of registration results |
| | | */ |
| | | @Override |
| | | public List<FederationModel> getFederationResultRegistrations() { |
| | | return new ArrayList<FederationModel>(federationPullResults.values()); |
| | | } |
| | | |
| | | /** |
| | | * Submit a federation proposal. The proposal is cached locally and the |
| | | * Gitblit administrator(s) are notified via email. |
| | | * |
| | | * @param proposal |
| | | * the proposal |
| | | * @param gitblitUrl |
| | | * the url of your gitblit instance to send an email to |
| | | * administrators |
| | | * @return true if the proposal was submitted |
| | | */ |
| | | @Override |
| | | public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) { |
| | | // convert proposal to json |
| | | String json = JsonUtils.toJsonString(proposal); |
| | | |
| | | try { |
| | | // make the proposals folder |
| | | File proposalsFolder = getProposalsFolder(); |
| | | proposalsFolder.mkdirs(); |
| | | |
| | | // cache json to a file |
| | | File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT); |
| | | com.gitblit.utils.FileUtils.writeContent(file, json); |
| | | } catch (Exception e) { |
| | | logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e); |
| | | logger.error("failed to create ticket service " + clazz, e); |
| | | ticketService = injector.get(NullTicketService.class).start(); |
| | | } |
| | | |
| | | // send an email, if possible |
| | | getManager(INotificationManager.class).sendMailToAdministrators("Federation proposal from " + proposal.url, |
| | | "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Returns the list of pending federation proposals |
| | | * A nested Dagger graph is used for constructor dependency injection of |
| | | * complex classes. |
| | | * |
| | | * @return list of federation proposals |
| | | * @author James Moger |
| | | * |
| | | */ |
| | | @Override |
| | | public List<FederationProposal> getPendingFederationProposals() { |
| | | List<FederationProposal> list = new ArrayList<FederationProposal>(); |
| | | File folder = getProposalsFolder(); |
| | | if (folder.exists()) { |
| | | File[] files = folder.listFiles(new FileFilter() { |
| | | @Override |
| | | public boolean accept(File file) { |
| | | return file.isFile() |
| | | && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT); |
| | | @Module( |
| | | library = true, |
| | | injects = { |
| | | IStoredSettings.class, |
| | | |
| | | // core managers |
| | | IRuntimeManager.class, |
| | | INotificationManager.class, |
| | | IUserManager.class, |
| | | IAuthenticationManager.class, |
| | | IRepositoryManager.class, |
| | | IProjectManager.class, |
| | | IFederationManager.class, |
| | | |
| | | // the monolithic manager |
| | | IGitblit.class, |
| | | |
| | | // ticket services |
| | | NullTicketService.class, |
| | | FileTicketService.class, |
| | | BranchTicketService.class, |
| | | RedisTicketService.class |
| | | } |
| | | }); |
| | | for (File file : files) { |
| | | String json = com.gitblit.utils.FileUtils.readContent(file, null); |
| | | FederationProposal proposal = JsonUtils.fromJsonString(json, |
| | | FederationProposal.class); |
| | | list.add(proposal); |
| | | } |
| | | } |
| | | return list; |
| | | } |
| | | ) |
| | | class GitBlitModule { |
| | | |
| | | /** |
| | | * Get repositories for the specified token. |
| | | * |
| | | * @param gitblitUrl |
| | | * the base url of this gitblit instance |
| | | * @param token |
| | | * the federation token |
| | | * @return a map of <cloneurl, RepositoryModel> |
| | | */ |
| | | @Override |
| | | public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) { |
| | | Map<String, String> federationSets = new HashMap<String, String>(); |
| | | for (String set : settings.getStrings(Keys.federation.sets)) { |
| | | federationSets.put(getFederationToken(set), set); |
| | | @Provides @Singleton IStoredSettings provideSettings() { |
| | | return settings; |
| | | } |
| | | |
| | | // Determine the Gitblit clone url |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append(gitblitUrl); |
| | | sb.append(Constants.GIT_PATH); |
| | | sb.append("{0}"); |
| | | String cloneUrl = sb.toString(); |
| | | |
| | | // Retrieve all available repositories |
| | | UserModel user = getFederationUser(); |
| | | List<RepositoryModel> list = getManager(IRepositoryManager.class).getRepositoryModels(user); |
| | | |
| | | // create the [cloneurl, repositoryModel] map |
| | | Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>(); |
| | | for (RepositoryModel model : list) { |
| | | // by default, setup the url for THIS repository |
| | | String url = MessageFormat.format(cloneUrl, model.name); |
| | | switch (model.federationStrategy) { |
| | | case EXCLUDE: |
| | | // skip this repository |
| | | continue; |
| | | case FEDERATE_ORIGIN: |
| | | // federate the origin, if it is defined |
| | | if (!StringUtils.isEmpty(model.origin)) { |
| | | url = model.origin; |
| | | } |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | |
| | | if (federationSets.containsKey(token)) { |
| | | // include repositories only for federation set |
| | | String set = federationSets.get(token); |
| | | if (model.federationSets.contains(set)) { |
| | | repositories.put(url, model); |
| | | } |
| | | } else { |
| | | // standard federation token for ALL |
| | | repositories.put(url, model); |
| | | } |
| | | } |
| | | return repositories; |
| | | } |
| | | |
| | | /** |
| | | * Creates a proposal from the token. |
| | | * |
| | | * @param gitblitUrl |
| | | * the url of this Gitblit instance |
| | | * @param token |
| | | * @return a potential proposal |
| | | */ |
| | | @Override |
| | | public FederationProposal createFederationProposal(String gitblitUrl, String token) { |
| | | FederationToken tokenType = FederationToken.REPOSITORIES; |
| | | for (FederationToken type : FederationToken.values()) { |
| | | if (token.equals(getFederationToken(type))) { |
| | | tokenType = type; |
| | | break; |
| | | } |
| | | } |
| | | Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token); |
| | | FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token, |
| | | repositories); |
| | | return proposal; |
| | | } |
| | | |
| | | /** |
| | | * Returns the proposal identified by the supplied token. |
| | | * |
| | | * @param token |
| | | * @return the specified proposal or null |
| | | */ |
| | | @Override |
| | | public FederationProposal getPendingFederationProposal(String token) { |
| | | List<FederationProposal> list = getPendingFederationProposals(); |
| | | for (FederationProposal proposal : list) { |
| | | if (proposal.token.equals(token)) { |
| | | return proposal; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Deletes a pending federation proposal. |
| | | * |
| | | * @param a |
| | | * proposal |
| | | * @return true if the proposal was deleted |
| | | */ |
| | | @Override |
| | | public boolean deletePendingFederationProposal(FederationProposal proposal) { |
| | | File folder = getProposalsFolder(); |
| | | File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT); |
| | | return file.delete(); |
| | | } |
| | | |
| | | /** |
| | | * Parse the properties file and aggregate all the comments by the setting |
| | | * key. A setting model tracks the current value, the default value, the |
| | | * description of the setting and and directives about the setting. |
| | | * |
| | | * @return Map<String, SettingModel> |
| | | */ |
| | | private ServerSettings loadSettingModels(ServerSettings settingsModel) { |
| | | // this entire "supports" concept will go away with user service refactoring |
| | | UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT); |
| | | externalUser.password = Constants.EXTERNAL_ACCOUNT; |
| | | IUserManager userManager = getManager(IUserManager.class); |
| | | settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser); |
| | | settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser); |
| | | settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser); |
| | | settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser); |
| | | try { |
| | | // 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"); |
| | | BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is)); |
| | | StringBuilder description = new StringBuilder(); |
| | | SettingModel setting = new SettingModel(); |
| | | String line = null; |
| | | while ((line = propertiesReader.readLine()) != null) { |
| | | if (line.length() == 0) { |
| | | description.setLength(0); |
| | | setting = new SettingModel(); |
| | | } else { |
| | | if (line.charAt(0) == '#') { |
| | | if (line.length() > 1) { |
| | | String text = line.substring(1).trim(); |
| | | if (SettingModel.CASE_SENSITIVE.equals(text)) { |
| | | setting.caseSensitive = true; |
| | | } else if (SettingModel.RESTART_REQUIRED.equals(text)) { |
| | | setting.restartRequired = true; |
| | | } else if (SettingModel.SPACE_DELIMITED.equals(text)) { |
| | | setting.spaceDelimited = true; |
| | | } else if (text.startsWith(SettingModel.SINCE)) { |
| | | try { |
| | | setting.since = text.split(" ")[1]; |
| | | } catch (Exception e) { |
| | | setting.since = text; |
| | | } |
| | | } else { |
| | | description.append(text); |
| | | description.append('\n'); |
| | | } |
| | | } |
| | | } else { |
| | | String[] kvp = line.split("=", 2); |
| | | String key = kvp[0].trim(); |
| | | setting.name = key; |
| | | setting.defaultValue = kvp[1].trim(); |
| | | setting.currentValue = setting.defaultValue; |
| | | setting.description = description.toString().trim(); |
| | | settingsModel.add(setting); |
| | | description.setLength(0); |
| | | setting = new SettingModel(); |
| | | } |
| | | } |
| | | } |
| | | propertiesReader.close(); |
| | | } catch (NullPointerException e) { |
| | | logger.error("Failed to find resource copy of gitblit.properties"); |
| | | } catch (IOException e) { |
| | | logger.error("Failed to load resource copy of gitblit.properties"); |
| | | } |
| | | return settingsModel; |
| | | } |
| | | |
| | | protected void configureFanout() { |
| | | // startup Fanout PubSub service |
| | | if (settings.getInteger(Keys.fanout.port, 0) > 0) { |
| | | String bindInterface = settings.getString(Keys.fanout.bindInterface, null); |
| | | int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT); |
| | | boolean useNio = settings.getBoolean(Keys.fanout.useNio, true); |
| | | int limit = settings.getInteger(Keys.fanout.connectionLimit, 0); |
| | | |
| | | if (useNio) { |
| | | if (StringUtils.isEmpty(bindInterface)) { |
| | | fanoutService = new FanoutNioService(port); |
| | | } else { |
| | | fanoutService = new FanoutNioService(bindInterface, port); |
| | | } |
| | | } else { |
| | | if (StringUtils.isEmpty(bindInterface)) { |
| | | fanoutService = new FanoutSocketService(port); |
| | | } else { |
| | | fanoutService = new FanoutSocketService(bindInterface, port); |
| | | } |
| | | } |
| | | |
| | | fanoutService.setConcurrentConnectionLimit(limit); |
| | | fanoutService.setAllowAllChannelAnnouncements(false); |
| | | fanoutService.start(); |
| | | } |
| | | } |
| | | |
| | | protected void configureGitDaemon() { |
| | | int port = settings.getInteger(Keys.git.daemonPort, 0); |
| | | String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); |
| | | if (port > 0) { |
| | | try { |
| | | // HACK temporary pending manager separation and injection |
| | | Gitblit gitblit = new Gitblit( |
| | | getManager(IRuntimeManager.class), |
| | | getManager(INotificationManager.class), |
| | | getManager(IUserManager.class), |
| | | getManager(ISessionManager.class), |
| | | getManager(IRepositoryManager.class), |
| | | this, |
| | | this, |
| | | this); |
| | | gitDaemon = new GitDaemon(gitblit); |
| | | gitDaemon.start(); |
| | | } catch (IOException e) { |
| | | gitDaemon = null; |
| | | logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | protected final Logger getLogger() { |
| | | return logger; |
| | | } |
| | | |
| | | protected final ScheduledExecutorService getScheduledExecutor() { |
| | | return scheduledExecutor; |
| | | } |
| | | |
| | | /** |
| | | * Configure Gitblit from the web.xml, if no configuration has already been |
| | | * specified. |
| | | * |
| | | * @see ServletContextListener.contextInitialize(ServletContextEvent) |
| | | */ |
| | | @Override |
| | | protected void beforeServletInjection(ServletContext context) { |
| | | ObjectGraph injector = getInjector(context); |
| | | |
| | | // create the runtime settings object |
| | | IStoredSettings runtimeSettings = injector.get(IStoredSettings.class); |
| | | this.settings = runtimeSettings; // XXX remove me eventually |
| | | final File baseFolder; |
| | | |
| | | if (goSettings != null) { |
| | | // Gitblit GO |
| | | logger.debug("configuring 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; |
| | | |
| | | if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) { |
| | | // RedHat OpenShift |
| | | logger.debug("configuring Gitblit Express"); |
| | | baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings); |
| | | } else { |
| | | // standard WAR |
| | | logger.debug("configuring Gitblit WAR"); |
| | | baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings); |
| | | } |
| | | |
| | | // Test for Tomcat forward-slash/%2F issue and auto-adjust settings |
| | | ContainerUtils.CVE_2007_0450.test(runtimeSettings); |
| | | @Provides @Singleton IRuntimeManager provideRuntimeManager() { |
| | | return runtimeManager; |
| | | } |
| | | |
| | | // Runtime manager is a container for settings and other parameters |
| | | IRuntimeManager runtime = startManager(injector, IRuntimeManager.class); |
| | | runtime.setBaseFolder(baseFolder); |
| | | runtime.getStatus().isGO = goSettings != null; |
| | | runtime.getStatus().servletContainer = context.getServerInfo(); |
| | | |
| | | startManager(injector, INotificationManager.class); |
| | | startManager(injector, IUserManager.class); |
| | | startManager(injector, ISessionManager.class); |
| | | startManager(injector, IRepositoryManager.class); |
| | | |
| | | logger.info("Gitblit base folder = " + baseFolder.getAbsolutePath()); |
| | | |
| | | loadSettingModels(runtime.getSettingsModel()); |
| | | |
| | | // load and cache the project metadata |
| | | projectConfigs = new FileBasedConfig(runtime.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect()); |
| | | getProjectConfigs(); |
| | | |
| | | if (true/*startFederation*/) { |
| | | configureFederation(); |
| | | } |
| | | configureFanout(); |
| | | configureGitDaemon(); |
| | | } |
| | | |
| | | /** |
| | | * Configures Gitblit GO |
| | | * |
| | | * @param context |
| | | * @param settings |
| | | * @param baseFolder |
| | | * @param runtimeSettings |
| | | * @return the base folder |
| | | */ |
| | | protected File configureGO( |
| | | ServletContext context, |
| | | IStoredSettings goSettings, |
| | | File goBaseFolder, |
| | | IStoredSettings runtimeSettings) { |
| | | |
| | | // 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; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Configures a standard WAR instance of Gitblit. |
| | | * |
| | | * @param context |
| | | * @param webxmlSettings |
| | | * @param contextFolder |
| | | * @param runtimeSettings |
| | | * @return the base folder |
| | | */ |
| | | protected File configureWAR( |
| | | ServletContext context, |
| | | WebXmlSettings webxmlSettings, |
| | | File contextFolder, |
| | | IStoredSettings runtimeSettings) { |
| | | |
| | | // Gitblit is running in a standard servlet container |
| | | 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(""); |
| | | @Provides @Singleton INotificationManager provideNotificationManager() { |
| | | return notificationManager; |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | } catch (NamingException n) { |
| | | logger.error("Failed to get JNDI env-entry: " + n.getExplanation()); |
| | | @Provides @Singleton IUserManager provideUserManager() { |
| | | return userManager; |
| | | } |
| | | |
| | | 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); |
| | | @Provides @Singleton IAuthenticationManager provideAuthenticationManager() { |
| | | return authenticationManager; |
| | | } |
| | | |
| | | // 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. |
| | | * |
| | | * @param context |
| | | * @param webxmlSettings |
| | | * @param contextFolder |
| | | * @param runtimeSettings |
| | | * @return the base folder |
| | | */ |
| | | private File configureExpress( |
| | | ServletContext context, |
| | | WebXmlSettings webxmlSettings, |
| | | File contextFolder, |
| | | IStoredSettings runtimeSettings) { |
| | | |
| | | // Gitblit is running in OpenShift/JBoss |
| | | String openShift = System.getenv("OPENSHIFT_DATA_DIR"); |
| | | File base = new File(openShift); |
| | | logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath()); |
| | | |
| | | // 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)); |
| | | } |
| | | } |
| | | @Provides @Singleton IRepositoryManager provideRepositoryManager() { |
| | | return repositoryManager; |
| | | } |
| | | |
| | | // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty) |
| | | runtimeSettings.merge(webxmlSettings); |
| | | |
| | | // settings are to be stored in openshift/gitblit.properties |
| | | File localSettings = new File(base, "gitblit.properties"); |
| | | 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; |
| | | } |
| | | |
| | | 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 |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Gitblit is being shutdown either because the servlet container is |
| | | * shutting down or because the servlet container is re-deploying Gitblit. |
| | | */ |
| | | @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(); |
| | | @Provides @Singleton IProjectManager provideProjectManager() { |
| | | return projectManager; |
| | | } |
| | | |
| | | scheduledExecutor.shutdownNow(); |
| | | if (fanoutService != null) { |
| | | fanoutService.stop(); |
| | | } |
| | | if (gitDaemon != null) { |
| | | gitDaemon.stop(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 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}", getManager(IRepositoryManager.class).getRepositoriesFolder().getAbsolutePath(), repository.name); |
| | | |
| | | // clone the repository |
| | | try { |
| | | JGitUtils.cloneRepository(getManager(IRepositoryManager.class).getRepositoriesFolder(), cloneName, fromUrl, true, null); |
| | | } catch (Exception e) { |
| | | throw new GitBlitException(e); |
| | | @Provides @Singleton IFederationManager provideFederationManager() { |
| | | return federationManager; |
| | | } |
| | | |
| | | // create a Gitblit repository model for the clone |
| | | RepositoryModel cloneModel = repository.cloneAs(cloneName); |
| | | // owner has REWIND/RW+ permissions |
| | | cloneModel.addOwner(user.username); |
| | | getManager(IRepositoryManager.class).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 = getManager(IUserManager.class).getUserModel(owner); |
| | | if (originOwner != null) { |
| | | originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE); |
| | | updateUserModel(originOwner.username, originOwner, false); |
| | | } |
| | | } |
| | | @Provides @Singleton IGitblit provideGitblit() { |
| | | return GitBlit.this; |
| | | } |
| | | |
| | | // grant origin's user list clone permission to fork |
| | | List<String> users = getManager(IRepositoryManager.class).getRepositoryUsers(repository); |
| | | List<UserModel> cloneUsers = new ArrayList<UserModel>(); |
| | | for (String name : users) { |
| | | if (!name.equalsIgnoreCase(user.username)) { |
| | | UserModel cloneUser = getManager(IUserManager.class).getUserModel(name); |
| | | if (cloneUser.canClone(repository)) { |
| | | // origin user can clone origin, grant clone access to fork |
| | | cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE); |
| | | } |
| | | cloneUsers.add(cloneUser); |
| | | } |
| | | @Provides @Singleton NullTicketService provideNullTicketService() { |
| | | return new NullTicketService( |
| | | runtimeManager, |
| | | notificationManager, |
| | | userManager, |
| | | repositoryManager); |
| | | } |
| | | getManager(IUserManager.class).updateUserModels(cloneUsers); |
| | | |
| | | // grant origin's team list clone permission to fork |
| | | List<String> teams = getManager(IRepositoryManager.class).getRepositoryTeams(repository); |
| | | List<TeamModel> cloneTeams = new ArrayList<TeamModel>(); |
| | | for (String name : teams) { |
| | | TeamModel cloneTeam = getManager(IUserManager.class).getTeamModel(name); |
| | | if (cloneTeam.canClone(repository)) { |
| | | // origin team can clone origin, grant clone access to fork |
| | | cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE); |
| | | } |
| | | cloneTeams.add(cloneTeam); |
| | | @Provides @Singleton FileTicketService provideFileTicketService() { |
| | | return new FileTicketService( |
| | | runtimeManager, |
| | | notificationManager, |
| | | userManager, |
| | | repositoryManager); |
| | | } |
| | | getManager(IUserManager.class).updateTeamModels(cloneTeams); |
| | | |
| | | // add this clone to the cached model |
| | | getManager(IRepositoryManager.class).addToCachedRepositoryList(cloneModel); |
| | | return cloneModel; |
| | | } |
| | | @Provides @Singleton BranchTicketService provideBranchTicketService() { |
| | | return new BranchTicketService( |
| | | runtimeManager, |
| | | notificationManager, |
| | | userManager, |
| | | repositoryManager); |
| | | } |
| | | |
| | | @Override |
| | | protected Object [] getModules() { |
| | | return new Object [] { new DaggerModule(this) }; |
| | | } |
| | | |
| | | protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) { |
| | | logger.debug("injecting and starting {}", clazz.getSimpleName()); |
| | | X x = injector.get(clazz); |
| | | x.setup(); |
| | | managers.add(x); |
| | | return x; |
| | | } |
| | | |
| | | /** |
| | | * Instantiate and inject all filters and servlets into the container using |
| | | * the servlet 3 specification. |
| | | */ |
| | | @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); |
| | | |
| | | // 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); |
| | | |
| | | // 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); |
| | | @Provides @Singleton RedisTicketService provideRedisTicketService() { |
| | | return new RedisTicketService( |
| | | runtimeManager, |
| | | notificationManager, |
| | | userManager, |
| | | repositoryManager); |
| | | } |
| | | } |
| | | } |