From 14181da41a98817aef2b528f92d58613e61e495f Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 08 Sep 2014 13:41:28 -0400
Subject: [PATCH] Merge branch 'ticket/161' into develop
---
src/main/java/com/gitblit/manager/ServicesManager.java | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 302 insertions(+), 12 deletions(-)
diff --git a/src/main/java/com/gitblit/manager/ServicesManager.java b/src/main/java/com/gitblit/manager/ServicesManager.java
index 6cc9456..7185854 100644
--- a/src/main/java/com/gitblit/manager/ServicesManager.java
+++ b/src/main/java/com/gitblit/manager/ServicesManager.java
@@ -16,10 +16,17 @@
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;
@@ -29,22 +36,30 @@
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.GitBlit;
+import com.gitblit.Constants.Transport;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.fanout.FanoutNioService;
import com.gitblit.fanout.FanoutService;
import com.gitblit.fanout.FanoutSocketService;
-import com.gitblit.git.GitDaemon;
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.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
@@ -54,22 +69,34 @@
* @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 final GitBlit gitblit;
+ private final IGitblit gitblit;
private FanoutService fanoutService;
private GitDaemon gitDaemon;
- public ServicesManager(GitBlit gitblit) {
- this.settings = gitblit.getSettings();
+ private SshDaemon sshDaemon;
+
+ @Inject
+ public ServicesManager(
+ Provider<WorkQueue> workQueueProvider,
+ IStoredSettings settings,
+ IGitblit gitblit) {
+
+ this.workQueueProvider = workQueueProvider;
+
+ this.settings = settings;
this.gitblit = gitblit;
}
@@ -78,6 +105,7 @@
configureFederation();
configureFanout();
configureGitDaemon();
+ configureSshDaemon();
return this;
}
@@ -91,7 +119,186 @@
if (gitDaemon != null) {
gitDaemon.stop();
}
+ 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)) {
+ 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));
+ }
+ }
+
+ // ssh daemon url
+ String sshDaemonUrl = 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 = getGitDaemonUrl(request, user, repository);
+ if (!StringUtils.isEmpty(gitDaemonUrl)) {
+ AccessPermission permission = 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 {
+ // external url does not require username
+ list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
+ }
+ }
+
+ // 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.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;
+ }
+
+ // 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 isServingHTTP()
+ || isServingGIT()
+ || isServingSSH();
+ }
+
+ /* (non-Javadoc)
+ * @see com.gitblit.manager.IServicesManager#isServingHTTP()
+ */
+ @Override
+ public boolean isServingHTTP() {
+ return settings.getBoolean(Keys.git.enableGitServlet, true);
+ }
+
+ /* (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() {
@@ -123,6 +330,32 @@
}
}
+ 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 : 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");
@@ -136,6 +369,20 @@
}
} else {
logger.info("Git Daemon is disabled.");
+ }
+ }
+
+ protected void configureSshDaemon() {
+ int port = settings.getInteger(Keys.git.sshPort, 0);
+ String bindInterface = settings.getString(Keys.git.sshBindInterface, "localhost");
+ if (port > 0) {
+ try {
+ sshDaemon = new SshDaemon(gitblit, workQueueProvider.get());
+ sshDaemon.start();
+ } catch (IOException e) {
+ sshDaemon = null;
+ logger.error(MessageFormat.format("Failed to start SSH daemon on {0}:{1,number,0}", bindInterface, port), e);
+ }
}
}
@@ -178,8 +425,8 @@
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;
}
}
@@ -205,27 +452,70 @@
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 {
public FederationPuller(FederationModel registration) {
- super(Arrays.asList(registration));
+ super(gitblit, Arrays.asList(registration));
}
public FederationPuller(List<FederationModel> registrations) {
- super(registrations);
+ super(gitblit, registrations);
}
@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(
"Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}",
registration.name, registration.url, registration.nextPull));
}
-
}
}
--
Gitblit v1.9.1