| | |
| | | package com.gitblit.manager; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.URI; |
| | | import java.text.MessageFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.Date; |
| | | import java.util.HashSet; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.ScheduledExecutorService; |
| | | import java.util.concurrent.TimeUnit; |
| | |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import com.gitblit.Constants; |
| | | import com.gitblit.Constants.AccessPermission; |
| | | import com.gitblit.Constants.AccessRestrictionType; |
| | | import com.gitblit.Constants.FederationToken; |
| | | import com.gitblit.Constants.Transport; |
| | | import com.gitblit.IStoredSettings; |
| | | import com.gitblit.Keys; |
| | | import com.gitblit.fanout.FanoutNioService; |
| | |
| | | import com.gitblit.fanout.FanoutSocketService; |
| | | import com.gitblit.models.FederationModel; |
| | | import com.gitblit.models.RepositoryModel; |
| | | import com.gitblit.models.RepositoryUrl; |
| | | import com.gitblit.models.UserModel; |
| | | import com.gitblit.service.FederationPullService; |
| | | import com.gitblit.transport.git.GitDaemon; |
| | | import com.gitblit.transport.ssh.SshDaemon; |
| | | import com.gitblit.utils.IdGenerator; |
| | | import com.gitblit.utils.HttpUtils; |
| | | import com.gitblit.utils.StringUtils; |
| | | import com.gitblit.utils.TimeUtils; |
| | | import com.gitblit.utils.WorkQueue; |
| | | import com.google.inject.Inject; |
| | | import com.google.inject.Provider; |
| | | import com.google.inject.Singleton; |
| | | |
| | | /** |
| | | * Services manager manages long-running services/processes that either have no |
| | |
| | | * @author James Moger |
| | | * |
| | | */ |
| | | public class ServicesManager implements IManager { |
| | | @Singleton |
| | | public class ServicesManager implements IServicesManager { |
| | | |
| | | private final Logger logger = LoggerFactory.getLogger(getClass()); |
| | | |
| | | private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5); |
| | | |
| | | private final Provider<WorkQueue> workQueueProvider; |
| | | |
| | | private final IStoredSettings settings; |
| | | |
| | |
| | | |
| | | private SshDaemon sshDaemon; |
| | | |
| | | public ServicesManager(IGitblit gitblit) { |
| | | this.settings = gitblit.getSettings(); |
| | | @Inject |
| | | public ServicesManager( |
| | | Provider<WorkQueue> workQueueProvider, |
| | | IStoredSettings settings, |
| | | IGitblit gitblit) { |
| | | |
| | | this.workQueueProvider = workQueueProvider; |
| | | |
| | | this.settings = settings; |
| | | this.gitblit = gitblit; |
| | | } |
| | | |
| | |
| | | if (sshDaemon != null) { |
| | | sshDaemon.stop(); |
| | | } |
| | | workQueueProvider.get().stop(); |
| | | return this; |
| | | } |
| | | |
| | | protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) { |
| | | String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null); |
| | | if (StringUtils.isEmpty(gitblitUrl)) { |
| | | gitblitUrl = HttpUtils.getGitblitURL(request); |
| | | } |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append(gitblitUrl); |
| | | sb.append(Constants.R_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 + "@"); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * Returns a list of repository URLs and the user access permission. |
| | | * |
| | | * @param request |
| | | * @param user |
| | | * @param repository |
| | | * @return a list of repository urls |
| | | */ |
| | | @Override |
| | | public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) { |
| | | if (user == null) { |
| | | user = UserModel.ANONYMOUS; |
| | | } |
| | | 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) && |
| | | settings.getBoolean(Keys.web.showHttpServletUrls, true)) { |
| | | AccessPermission permission = user.getRepositoryPermission(repository).permission; |
| | | if (permission.exceeds(AccessPermission.NONE)) { |
| | | String repoUrl = getRepositoryUrl(request, username, repository); |
| | | Transport transport = Transport.fromUrl(repoUrl); |
| | | if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(transport)) { |
| | | // downgrade the repo permission for this transport |
| | | // because it is not an acceptable PUSH transport |
| | | permission = AccessPermission.CLONE; |
| | | } |
| | | list.add(new RepositoryUrl(repoUrl, permission)); |
| | | } |
| | | } |
| | | |
| | | // ssh daemon url |
| | | String sshDaemonUrl = getSshDaemonUrl(request, user, repository); |
| | | if (!StringUtils.isEmpty(sshDaemonUrl) && |
| | | settings.getBoolean(Keys.web.showSshDaemonUrls, true)) { |
| | | AccessPermission permission = user.getRepositoryPermission(repository).permission; |
| | | if (permission.exceeds(AccessPermission.NONE)) { |
| | | if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(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 = getGitDaemonUrl(request, user, repository); |
| | | if (!StringUtils.isEmpty(gitDaemonUrl) && |
| | | settings.getBoolean(Keys.web.showGitDaemonUrls, true)) { |
| | | AccessPermission permission = getGitDaemonAccessPermission(user, repository); |
| | | if (permission.exceeds(AccessPermission.NONE)) { |
| | | if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(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 |
| | | boolean advertisePermsForOther = settings.getBoolean(Keys.web.advertiseAccessPermissionForOtherUrls, false); |
| | | for (String url : settings.getStrings(Keys.web.otherUrls)) { |
| | | String externalUrl = null; |
| | | |
| | | if (url.contains("{1}")) { |
| | | // external url requires username, only add url IF we have one |
| | | if (StringUtils.isEmpty(username)) { |
| | | continue; |
| | | } else { |
| | | externalUrl = MessageFormat.format(url, repository.name, username); |
| | | } |
| | | } else { |
| | | // external url does not require username, just do repo name formatting |
| | | externalUrl = MessageFormat.format(url, repository.name); |
| | | } |
| | | |
| | | AccessPermission permission = null; |
| | | if (advertisePermsForOther) { |
| | | permission = user.getRepositoryPermission(repository).permission; |
| | | if (permission.exceeds(AccessPermission.NONE)) { |
| | | Transport transport = Transport.fromUrl(externalUrl); |
| | | if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(transport)) { |
| | | // downgrade the repo permission for this transport |
| | | // because it is not an acceptable PUSH transport |
| | | permission = AccessPermission.CLONE; |
| | | } |
| | | } |
| | | } |
| | | list.add(new RepositoryUrl(externalUrl, permission)); |
| | | } |
| | | |
| | | // sort transports by highest permission and then by transport security |
| | | Collections.sort(list, new Comparator<RepositoryUrl>() { |
| | | |
| | | @Override |
| | | public int compare(RepositoryUrl o1, RepositoryUrl o2) { |
| | | if (o1.hasPermission() && !o2.hasPermission()) { |
| | | // prefer known permission items over unknown |
| | | return -1; |
| | | } else if (!o1.hasPermission() && o2.hasPermission()) { |
| | | // prefer known permission items over unknown |
| | | return 1; |
| | | } else if (!o1.hasPermission() && !o2.hasPermission()) { |
| | | // 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; |
| | | } |
| | | |
| | | // prefer more secure transports |
| | | return o1.transport.compareTo(o2.transport); |
| | | } |
| | | }); |
| | | |
| | | // consider the user's transport preference |
| | | RepositoryUrl preferredUrl = null; |
| | | Transport preferredTransport = user.getPreferences().getTransport(); |
| | | if (preferredTransport != null) { |
| | | Iterator<RepositoryUrl> itr = list.iterator(); |
| | | while (itr.hasNext()) { |
| | | RepositoryUrl url = itr.next(); |
| | | if (url.transport.equals(preferredTransport)) { |
| | | itr.remove(); |
| | | preferredUrl = url; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | if (preferredUrl != null) { |
| | | list.add(0, preferredUrl); |
| | | } |
| | | |
| | | return list; |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see com.gitblit.manager.IServicesManager#isServingRepositories() |
| | | */ |
| | | @Override |
| | | public boolean isServingRepositories() { |
| | | return isServingHTTPS() |
| | | || isServingHTTP() |
| | | || isServingGIT() |
| | | || isServingSSH(); |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see com.gitblit.manager.IServicesManager#isServingHTTP() |
| | | */ |
| | | @Override |
| | | public boolean isServingHTTP() { |
| | | return settings.getBoolean(Keys.git.enableGitServlet, true) |
| | | && ((gitblit.getStatus().isGO && settings.getInteger(Keys.server.httpPort, 0) > 0) |
| | | || !gitblit.getStatus().isGO); |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see com.gitblit.manager.IServicesManager#isServingHTTPS() |
| | | */ |
| | | @Override |
| | | public boolean isServingHTTPS() { |
| | | return settings.getBoolean(Keys.git.enableGitServlet, true) |
| | | && ((gitblit.getStatus().isGO && settings.getInteger(Keys.server.httpsPort, 0) > 0) |
| | | || !gitblit.getStatus().isGO); |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see com.gitblit.manager.IServicesManager#isServingGIT() |
| | | */ |
| | | @Override |
| | | public boolean isServingGIT() { |
| | | return gitDaemon != null && gitDaemon.isRunning(); |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see com.gitblit.manager.IServicesManager#isServingSSH() |
| | | */ |
| | | @Override |
| | | public boolean isServingSSH() { |
| | | return sshDaemon != null && sshDaemon.isRunning(); |
| | | } |
| | | |
| | | protected void configureFederation() { |
| | |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean acceptsPush(Transport byTransport) { |
| | | if (byTransport == null) { |
| | | logger.info("Unknown transport, push rejected!"); |
| | | return false; |
| | | } |
| | | |
| | | Set<Transport> transports = new HashSet<Transport>(); |
| | | for (String value : settings.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); |
| | | } |
| | | |
| | | protected void configureGitDaemon() { |
| | | int port = settings.getInteger(Keys.git.daemonPort, 0); |
| | | String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); |
| | |
| | | String bindInterface = settings.getString(Keys.git.sshBindInterface, "localhost"); |
| | | if (port > 0) { |
| | | try { |
| | | sshDaemon = new SshDaemon(gitblit, new IdGenerator()); |
| | | sshDaemon = new SshDaemon(gitblit, workQueueProvider.get()); |
| | | sshDaemon.start(); |
| | | } catch (IOException e) { |
| | | sshDaemon = null; |
| | |
| | | return null; |
| | | } |
| | | if (user.canClone(repository)) { |
| | | String servername = request.getServerName(); |
| | | String url = gitDaemon.formatUrl(servername, repository.name); |
| | | String hostname = getHostname(request); |
| | | String url = gitDaemon.formatUrl(hostname, repository.name); |
| | | return url; |
| | | } |
| | | } |
| | |
| | | return AccessPermission.NONE; |
| | | } |
| | | |
| | | public String getSshDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) { |
| | | if (user == null || UserModel.ANONYMOUS.equals(user)) { |
| | | // SSH always requires authentication - anonymous access prohibited |
| | | return null; |
| | | } |
| | | if (sshDaemon != null) { |
| | | String bindInterface = settings.getString(Keys.git.sshBindInterface, "localhost"); |
| | | if (bindInterface.equals("localhost") |
| | | && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) { |
| | | // ssh daemon is bound to localhost and the request is from elsewhere |
| | | return null; |
| | | } |
| | | if (user.canClone(repository)) { |
| | | String hostname = getHostname(request); |
| | | String url = sshDaemon.formatUrl(user.username, hostname, repository.name); |
| | | return url; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Extract the hostname from the canonical url or return the |
| | | * hostname from the servlet request. |
| | | * |
| | | * @param request |
| | | * @return |
| | | */ |
| | | protected String getHostname(HttpServletRequest request) { |
| | | String hostname = request.getServerName(); |
| | | String canonicalUrl = settings.getString(Keys.web.canonicalUrl, null); |
| | | if (!StringUtils.isEmpty(canonicalUrl)) { |
| | | try { |
| | | URI uri = new URI(canonicalUrl); |
| | | String host = uri.getHost(); |
| | | if (!StringUtils.isEmpty(host) && !"localhost".equals(host)) { |
| | | hostname = host; |
| | | } |
| | | } catch (Exception e) { |
| | | } |
| | | } |
| | | return hostname; |
| | | } |
| | | |
| | | private class FederationPuller extends FederationPullService { |
| | | |
| | |
| | | @Override |
| | | public void reschedule(FederationModel registration) { |
| | | // schedule the next pull |
| | | int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency); |
| | | int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency, 5); |
| | | registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L)); |
| | | scheduledExecutor.schedule(new FederationPuller(registration), mins, TimeUnit.MINUTES); |
| | | logger.info(MessageFormat.format( |