123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- /*
- * 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.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- 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 javax.servlet.http.HttpServletRequest;
-
- 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.FanoutService;
- 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.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
- * direct relation to other managers OR require really high-level manager
- * integration (i.e. a Gitblit instance).
- *
- * @author James Moger
- *
- */
- @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 IGitblit gitblit;
-
- private FanoutService fanoutService;
-
- private GitDaemon gitDaemon;
-
- private SshDaemon sshDaemon;
-
- @Inject
- public ServicesManager(
- Provider<WorkQueue> workQueueProvider,
- IStoredSettings settings,
- IGitblit gitblit) {
-
- this.workQueueProvider = workQueueProvider;
-
- this.settings = settings;
- this.gitblit = gitblit;
- }
-
- @Override
- public ServicesManager start() {
- configureFederation();
- configureFanout();
- configureGitDaemon();
- configureSshDaemon();
-
- return this;
- }
-
- @Override
- public ServicesManager stop() {
- scheduledExecutor.shutdownNow();
- if (fanoutService != null) {
- fanoutService.stop();
- }
- 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) &&
- 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
- 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 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() {
- boolean validPassphrase = true;
- String passphrase = settings.getString(Keys.federation.passphrase, "");
- if (StringUtils.isEmpty(passphrase)) {
- logger.info("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(),
- gitblit.getFederationToken(tokenType)));
- }
-
- // federation set tokens
- for (String set : settings.getStrings(Keys.federation.sets)) {
- logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
- gitblit.getFederationToken(set)));
- }
- }
-
- // Schedule or run the federation executor
- List<FederationModel> registrations = gitblit.getFederationRegistrations();
- if (registrations.size() > 0) {
- FederationPuller executor = new FederationPuller(registrations);
- scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
- }
- }
-
- @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");
- if (port > 0) {
- try {
- 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);
- }
- } 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);
- }
- }
- }
-
- 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();
- } else {
- logger.info("Fanout PubSub service is disabled.");
- }
- }
-
- public 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 hostname = getHostname(request);
- String url = gitDaemon.formatUrl(hostname, repository.name);
- return url;
- }
- }
- return null;
- }
-
- public AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
- if (gitDaemon != null && user.canClone(repository)) {
- AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
- if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
- if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
- // can not authenticate clone via anonymous git protocol
- gitDaemonPermission = AccessPermission.NONE;
- } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
- // can not authenticate push via anonymous git protocol
- gitDaemonPermission = AccessPermission.CLONE;
- } else {
- // normal user permission
- }
- }
- return gitDaemonPermission;
- }
- return AccessPermission.NONE;
- }
-
- 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(gitblit, Arrays.asList(registration));
- }
-
- public FederationPuller(List<FederationModel> registrations) {
- super(gitblit, registrations);
- }
-
- @Override
- public void reschedule(FederationModel registration) {
- // schedule the next pull
- 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));
- }
- }
- }
|