diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index f2d436dffd..3b761077a2 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -9,23 +9,21 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -public class SimpleWorkdirFactory { +public abstract class SimpleWorkdirFactory { private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class); private final File poolDirectory; private final CloneProvider cloneProvider; - private final Repository repository; - public SimpleWorkdirFactory(Repository repository, CloneProvider cloneProvider) { - this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), repository, cloneProvider); + public SimpleWorkdirFactory(CloneProvider cloneProvider) { + this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), cloneProvider); } - public SimpleWorkdirFactory(File poolDirectory, Repository repository, CloneProvider cloneProvider) { + public SimpleWorkdirFactory(File poolDirectory, CloneProvider cloneProvider) { this.poolDirectory = poolDirectory; this.cloneProvider = cloneProvider; - this.repository = repository; poolDirectory.mkdirs(); } @@ -35,10 +33,12 @@ public class SimpleWorkdirFactory { T clone = cloneProvider.cloneRepository(context, directory); return new WorkingCopy<>(clone, this::close, directory); } catch (IOException e) { - throw new InternalRepositoryException(repository, "could not create temporary directory for clone of repository", e); + throw new InternalRepositoryException(getRepository(context), "could not create temporary directory for clone of repository", e); } } + protected abstract Repository getRepository(C context); + private File createNewWorkdir() throws IOException { return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java index bd38329ea3..d9fed92968 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java @@ -4,26 +4,20 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ScmTransportProtocol; -import org.eclipse.jgit.util.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.util.SimpleWorkdirFactory; -import sonia.scm.repository.util.WorkingCopy; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { public SimpleGitWorkdirFactory() { - super(null, new GitCloneProvider()); + super(new GitCloneProvider()); } public SimpleGitWorkdirFactory(File poolDirectory) { - super(poolDirectory, null, new GitCloneProvider()); + super(poolDirectory, new GitCloneProvider()); } private static class GitCloneProvider implements CloneProvider { @@ -45,4 +39,9 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory + + io.jsonwebtoken + jjwt + 0.4 + diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java index d80c0c5afb..c87cc0acea 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java @@ -35,15 +35,9 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Strings; - -import org.apache.shiro.codec.Base64; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.CipherUtil; -import sonia.scm.util.HttpUtil; import sonia.scm.web.HgUtil; //~--- JDK imports ------------------------------------------------------------ @@ -68,14 +62,7 @@ public final class HgEnvironment /** Field description */ private static final String ENV_URL = "SCM_URL"; - /** Field description */ - private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; - - /** - * the logger for HgEnvironment - */ - private static final Logger logger = - LoggerFactory.getLogger(HgEnvironment.class); + private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN"; //~--- constructors --------------------------------------------------------- @@ -98,14 +85,14 @@ public final class HgEnvironment */ public static void prepareEnvironment(Map environment, HgRepositoryHandler handler, HgHookManager hookManager, - HttpServletRequest request) + HttpServletRequest request, AccessTokenBuilderFactory accessTokenBuilderFactory) { String hookUrl; if (request != null) { hookUrl = hookManager.createUrl(request); - environment.put(SCM_CREDENTIALS, getCredentials(request)); + environment.put(SCM_BEARER_TOKEN, getCredentials(accessTokenBuilderFactory)); } else { @@ -126,9 +113,9 @@ public final class HgEnvironment * @param hookManager */ public static void prepareEnvironment(Map environment, - HgRepositoryHandler handler, HgHookManager hookManager) + HgRepositoryHandler handler, HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) { - prepareEnvironment(environment, handler, hookManager, null); + prepareEnvironment(environment, handler, hookManager, null, accessTokenBuilderFactory); } //~--- get methods ---------------------------------------------------------- @@ -139,31 +126,10 @@ public final class HgEnvironment * * @return */ - private static String getCredentials(HttpServletRequest request) + private static String getCredentials(AccessTokenBuilderFactory accessTokenBuilderFactory) { - String credentials = null; - String header = request.getHeader(HttpUtil.HEADER_AUTHORIZATION); + AccessToken accessToken = accessTokenBuilderFactory.create().build(); - if (!Strings.isNullOrEmpty(header)) - { - String[] parts = header.split("\\s+"); - - if (parts.length > 0) - { - CipherUtil cu = CipherUtil.getInstance(); - - credentials = cu.encode(Base64.decodeToString(parts[1])); - } - else - { - logger.warn("invalid basic authentication header"); - } - } - else - { - logger.warn("could not find authentication header on request"); - } - - return Strings.nullToEmpty(credentials); + return CipherUtil.getInstance().encode(accessToken.compact()); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index 99976f59df..0d69e5bc75 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -32,15 +32,12 @@ package sonia.scm.repository.spi; import com.aragost.javahg.Changeset; import com.aragost.javahg.commands.CommitCommand; -import com.aragost.javahg.commands.PushCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.Repository; import sonia.scm.repository.util.WorkingCopy; -import java.io.IOException; - /** * Mercurial implementation of the {@link BranchCommand}. * Note that this creates an empty commit to "persist" the new branch. @@ -57,7 +54,7 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { } @Override - public Branch branch(String name) throws IOException { + public Branch branch(String name) { try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext())) { com.aragost.javahg.Repository repository = workingCopy.get().get(); com.aragost.javahg.commands.BranchCommand.on(repository).set(name); @@ -68,14 +65,6 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { .message("Create new branch " + name) .execute(); - PushCommand pushCommand = PushCommand - .on(repository) - .branch(name) - .newBranch(); - pushCommand - .cmdAppend("--config", ""); - pushCommand .execute(); - LOG.debug("Created new branch '{}' in repository {} with changeset {}", name, getRepository().getNamespaceAndName(), emptyChangeset.getNode()); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java index fee73513cd..e21a8adca0 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java @@ -42,6 +42,7 @@ import com.google.common.base.Strings; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.web.HgUtil; //~--- JDK imports ------------------------------------------------------------ @@ -49,6 +50,8 @@ import sonia.scm.web.HgUtil; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.util.Map; +import java.util.function.BiConsumer; /** * @@ -66,44 +69,46 @@ public class HgCommandContext implements Closeable * Constructs ... * * - * - * @param hookManager + * @param hookManager * @param handler * @param repository * @param directory + * @param accessTokenBuilderFactory */ public HgCommandContext(HgHookManager hookManager, - HgRepositoryHandler handler, sonia.scm.repository.Repository repository, - File directory) + HgRepositoryHandler handler, sonia.scm.repository.Repository repository, + File directory, AccessTokenBuilderFactory accessTokenBuilderFactory) { this(hookManager, handler, repository, directory, - handler.getHgContext().isPending()); + handler.getHgContext().isPending(), accessTokenBuilderFactory); } /** * Constructs ... * * - * - * @param hookManager - * @param hanlder + * @param hookManager + * @param handler * @param repository * @param directory * @param pending + * @param accessTokenBuilderFactory */ public HgCommandContext(HgHookManager hookManager, - HgRepositoryHandler hanlder, sonia.scm.repository.Repository repository, - File directory, boolean pending) + HgRepositoryHandler handler, sonia.scm.repository.Repository repository, + File directory, boolean pending, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.hookManager = hookManager; - this.hanlder = hanlder; + this.handler = handler; this.directory = directory; + this.scmRepository = repository; this.encoding = repository.getProperty(PROPERTY_ENCODING); this.pending = pending; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; if (Strings.isNullOrEmpty(encoding)) { - encoding = hanlder.getConfig().getEncoding(); + encoding = handler.getConfig().getEncoding(); } } @@ -134,13 +139,19 @@ public class HgCommandContext implements Closeable { if (repository == null) { - repository = HgUtil.open(hanlder, hookManager, directory, encoding, - pending); + repository = HgUtil.open(handler, hookManager, directory, encoding, + pending, accessTokenBuilderFactory); } return repository; } + public Repository openWithSpecialEnvironment(BiConsumer> prepareEnvironment) + { + return HgUtil.open(handler, directory, encoding, + pending, environment -> prepareEnvironment.accept(scmRepository, environment)); + } + //~--- get methods ---------------------------------------------------------- /** @@ -151,7 +162,7 @@ public class HgCommandContext implements Closeable */ public HgConfig getConfig() { - return hanlder.getConfig(); + return handler.getConfig(); } //~--- fields --------------------------------------------------------------- @@ -163,7 +174,7 @@ public class HgCommandContext implements Closeable private String encoding; /** Field description */ - private HgRepositoryHandler hanlder; + private HgRepositoryHandler handler; /** Field description */ private HgHookManager hookManager; @@ -173,4 +184,8 @@ public class HgCommandContext implements Closeable /** Field description */ private Repository repository; + + private final sonia.scm.repository.Repository scmRepository; + + private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java index e4e3dc238e..21d31d9926 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java @@ -40,6 +40,7 @@ import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.spi.javahg.HgLogChangesetCommand; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.web.HgUtil; import java.io.File; @@ -62,14 +63,15 @@ public class HgHookChangesetProvider implements HookChangesetProvider //~--- constructors --------------------------------------------------------- public HgHookChangesetProvider(HgRepositoryHandler handler, - File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type) + File repositoryDirectory, HgHookManager hookManager, String startRev, + RepositoryHookType type, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.handler = handler; this.repositoryDirectory = repositoryDirectory; this.hookManager = hookManager; this.startRev = startRev; this.type = type; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } //~--- methods -------------------------------------------------------------- @@ -128,7 +130,7 @@ public class HgHookChangesetProvider implements HookChangesetProvider // TODO get repository encoding return HgUtil.open(handler, hookManager, repositoryDirectory, null, - pending); + pending, accessTokenBuilderFactory); } //~--- fields --------------------------------------------------------------- @@ -150,4 +152,6 @@ public class HgHookChangesetProvider implements HookChangesetProvider /** Field description */ private RepositoryHookType type; + + private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java index 414cfe27b8..df1ee2d501 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java @@ -43,6 +43,7 @@ import sonia.scm.repository.api.HookBranchProvider; import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookMessageProvider; import sonia.scm.repository.api.HookTagProvider; +import sonia.scm.security.AccessTokenBuilderFactory; import java.io.File; import java.util.EnumSet; @@ -75,9 +76,9 @@ public class HgHookContextProvider extends HookContextProvider */ public HgHookContextProvider(HgRepositoryHandler handler, File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type) + RepositoryHookType type, AccessTokenBuilderFactory accessTokenBuilderFactory) { - this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type); + this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type, accessTokenBuilderFactory); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java index d60e888cac..9ae71691aa 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java @@ -40,6 +40,7 @@ import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.CommandNotSupportedException; +import sonia.scm.security.AccessTokenBuilderFactory; import java.io.File; import java.io.IOException; @@ -77,13 +78,13 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider //~--- constructors --------------------------------------------------------- HgRepositoryServiceProvider(HgRepositoryHandler handler, - HgHookManager hookManager, Repository repository) + HgHookManager hookManager, Repository repository, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.repository = repository; this.handler = handler; this.repositoryDirectory = handler.getDirectory(repository.getId()); this.context = new HgCommandContext(hookManager, handler, repository, - repositoryDirectory); + repositoryDirectory, accessTokenBuilderFactory); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java index d6d04ee017..56ce57ffcc 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java @@ -38,6 +38,7 @@ import sonia.scm.plugin.Extension; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; +import sonia.scm.security.AccessTokenBuilderFactory; /** * @@ -47,15 +48,17 @@ import sonia.scm.repository.Repository; public class HgRepositoryServiceResolver implements RepositoryServiceResolver { - private HgRepositoryHandler handler; - private HgHookManager hookManager; + private final HgRepositoryHandler handler; + private final HgHookManager hookManager; + private final AccessTokenBuilderFactory accessTokenBuilderFactory; @Inject public HgRepositoryServiceResolver(HgRepositoryHandler handler, - HgHookManager hookManager) + HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.handler = handler; this.hookManager = hookManager; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } @Override @@ -63,7 +66,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver HgRepositoryServiceProvider provider = null; if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) { - provider = new HgRepositoryServiceProvider(handler, hookManager, repository); + provider = new HgRepositoryServiceProvider(handler, hookManager, repository, accessTokenBuilderFactory); } return provider; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java index b893b53fec..2abf4253ff 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java @@ -2,35 +2,65 @@ package sonia.scm.repository.spi; import com.aragost.javahg.Repository; import com.aragost.javahg.commands.CloneCommand; +import com.aragost.javahg.commands.PullCommand; import sonia.scm.repository.util.SimpleWorkdirFactory; +import sonia.scm.web.HgRepositoryEnvironmentBuilder; +import javax.inject.Inject; +import javax.inject.Provider; import java.io.File; import java.io.IOException; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { - public SimpleHgWorkdirFactory() { - super(null, new HgCloneProvider()); + + @Inject + public SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder) { + this(hgRepositoryEnvironmentBuilder, new HookConfigurer()); } - public SimpleHgWorkdirFactory(File poolDirectory) { - super(poolDirectory, null, new HgCloneProvider()); + SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder, Consumer hookConfigurer) { + super(new HgCloneProvider(hgRepositoryEnvironmentBuilder, hookConfigurer)); } private static class HgCloneProvider implements CloneProvider { + private final Provider hgRepositoryEnvironmentBuilder; + private final Consumer hookConfigurer; + + private HgCloneProvider(Provider hgRepositoryEnvironmentBuilder, Consumer hookConfigurer) { + this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; + this.hookConfigurer = hookConfigurer; + } + @Override public RepositoryCloseableWrapper cloneRepository(HgCommandContext context, File target) throws IOException { - String execute = CloneCommand.on(context.open()).execute(target.getAbsolutePath()); - return new RepositoryCloseableWrapper(Repository.open(target)); + BiConsumer> repositoryMapBiConsumer = (repository, environment) -> { + hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); + }; + Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer); + CloneCommand.on(centralRepository).execute(target.getAbsolutePath()); + return new RepositoryCloseableWrapper(Repository.open(target), centralRepository, hookConfigurer); } } + + @Override + protected sonia.scm.repository.Repository getRepository(HgCommandContext context) { + return null; + } } class RepositoryCloseableWrapper implements AutoCloseable { private final Repository delegate; + private final Repository centralRepository; + private final Consumer hookConfigurer; - RepositoryCloseableWrapper(Repository delegate) { + RepositoryCloseableWrapper(Repository delegate, Repository centralRepository, Consumer hookConfigurer) { this.delegate = delegate; + this.centralRepository = centralRepository; + this.hookConfigurer = hookConfigurer; } Repository get() { @@ -39,5 +69,21 @@ class RepositoryCloseableWrapper implements AutoCloseable { @Override public void close() { + try { + PullCommand pullCommand = PullCommand.on(centralRepository); + hookConfigurer.accept(pullCommand); + pullCommand.execute(delegate.getDirectory().getAbsolutePath()); + } catch (Exception e) { + throw new RuntimeException(e); + } + centralRepository.close(); + } +} + +class HookConfigurer implements Consumer { + @Override + public void accept(PullCommand pullCommand) { + pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.postHook"); + pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.preHook"); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java index 5084e4d38b..803db0f129 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java @@ -201,7 +201,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet executor.setExceptionHandler(exceptionHandler); executor.setStatusCodeHandler(exceptionHandler); executor.setContentLengthWorkaround(true); - hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment()); + hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap()); // unused ??? HttpSession session = request.getSession(false); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java index 1b31eb11ca..3ab2a06eb3 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java @@ -42,6 +42,7 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,8 +55,9 @@ import sonia.scm.repository.api.HgHookMessage; import sonia.scm.repository.api.HgHookMessage.Severity; import sonia.scm.repository.spi.HgHookContextProvider; import sonia.scm.repository.spi.HookEventFacade; +import sonia.scm.security.AccessTokenBuilderFactory; +import sonia.scm.security.BearerToken; import sonia.scm.security.CipherUtil; -import sonia.scm.security.Tokens; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -93,7 +95,7 @@ public class HgHookCallbackServlet extends HttpServlet private static final String PARAM_CHALLENGE = "challenge"; /** Field description */ - private static final String PARAM_CREDENTIALS = "credentials"; + private static final String PARAM_TOKEN = "token"; /** Field description */ private static final String PARAM_NODE = "node"; @@ -117,12 +119,13 @@ public class HgHookCallbackServlet extends HttpServlet @Inject public HgHookCallbackServlet(HookEventFacade hookEventFacade, HgRepositoryHandler handler, HgHookManager hookManager, - Provider contextProvider) + Provider contextProvider, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.hookEventFacade = hookEventFacade; this.handler = handler; this.hookManager = hookManager; this.contextProvider = contextProvider; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } //~--- methods -------------------------------------------------------------- @@ -179,11 +182,11 @@ public class HgHookCallbackServlet extends HttpServlet if (Util.isNotEmpty(node)) { - String credentials = request.getParameter(PARAM_CREDENTIALS); + String token = request.getParameter(PARAM_TOKEN); - if (Util.isNotEmpty(credentials)) + if (Util.isNotEmpty(token)) { - authenticate(request, credentials); + authenticate(token); } hookCallback(response, type, repositoryId, challenge, node); @@ -209,34 +212,20 @@ public class HgHookCallbackServlet extends HttpServlet } } - private void authenticate(HttpServletRequest request, String credentials) + private void authenticate(String token) { try { - credentials = CipherUtil.getInstance().decode(credentials); + token = CipherUtil.getInstance().decode(token); - if (Util.isNotEmpty(credentials)) + if (Util.isNotEmpty(token)) { - int index = credentials.indexOf(':'); + Subject subject = SecurityUtils.getSubject(); - if (index > 0 && index < credentials.length()) - { - Subject subject = SecurityUtils.getSubject(); + AuthenticationToken accessToken = createToken(token); - //J- - subject.login( - Tokens.createAuthenticationToken( - request, - credentials.substring(0, index), - credentials.substring(index + 1) - ) - ); - //J+ - } - else - { - logger.error("could not find delimiter"); - } + //J- + subject.login(accessToken); } } catch (Exception ex) @@ -245,6 +234,11 @@ public class HgHookCallbackServlet extends HttpServlet } } + private AuthenticationToken createToken(String tokenString) + { + return BearerToken.valueOf(tokenString); + } + private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type) throws IOException { @@ -259,7 +253,7 @@ public class HgHookCallbackServlet extends HttpServlet File repositoryDirectory = handler.getDirectory(repositoryId); context = new HgHookContextProvider(handler, repositoryDirectory, hookManager, - node, type); + node, type, accessTokenBuilderFactory); hookEventFacade.handle(repositoryId).fireHookEvent(type, context); @@ -460,4 +454,6 @@ public class HgHookCallbackServlet extends HttpServlet /** Field description */ private final HgHookManager hookManager; + + private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java index 3e7e590270..16405ed8bf 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java @@ -1,96 +1,73 @@ package sonia.scm.web; -import com.google.common.base.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.repository.HgEnvironment; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.CipherUtil; -import sonia.scm.util.HttpUtil; -import sonia.scm.web.cgi.EnvList; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import java.io.File; -import java.util.Base64; import java.util.Map; public class HgRepositoryEnvironmentBuilder { - private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryEnvironmentBuilder.class); - private static final String ENV_REPOSITORY_NAME = "REPO_NAME"; private static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH"; private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID"; private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY"; private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS"; - private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; + private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN"; private final HgRepositoryHandler handler; private final HgHookManager hookManager; + private final AccessTokenBuilderFactory accessTokenBuilderFactory; @Inject - public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) { + public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.handler = handler; this.hookManager = hookManager; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } - void buildFor(Repository repository, HttpServletRequest request, EnvList environment) { + public void buildFor(Repository repository, HttpServletRequest request, Map environment) { File directory = handler.getDirectory(repository.getId()); - environment.set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); - environment.set(ENV_REPOSITORY_ID, repository.getId()); - environment.set(ENV_REPOSITORY_PATH, + environment.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); + environment.put(ENV_REPOSITORY_ID, repository.getId()); + environment.put(ENV_REPOSITORY_PATH, directory.getAbsolutePath()); // add hook environment - Map environmentMap = environment.asMutableMap(); if (handler.getConfig().isDisableHookSSLValidation()) { // disable ssl validation // Issue 959: https://goo.gl/zH5eY8 - environmentMap.put(ENV_PYTHON_HTTPS_VERIFY, "0"); + environment.put(ENV_PYTHON_HTTPS_VERIFY, "0"); } // enable experimental httppostargs protocol of mercurial // Issue 970: https://goo.gl/poascp - environmentMap.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs())); + environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs())); HgEnvironment.prepareEnvironment( - environmentMap, + environment, handler, hookManager, - request + request, + accessTokenBuilderFactory ); - addCredentials(environment, request); + addCredentials(environment); } - private void addCredentials(EnvList env, HttpServletRequest request) - { - String authorization = request.getHeader(HttpUtil.HEADER_AUTHORIZATION); + private void addCredentials(Map env) { - if (!Strings.isNullOrEmpty(authorization)) - { - if (authorization.startsWith(HttpUtil.AUTHORIZATION_SCHEME_BASIC)) - { - String encodedUserInfo = - authorization.substring( - HttpUtil.AUTHORIZATION_SCHEME_BASIC.length()).trim(); - // TODO check encoding of user-agent ? - String userInfo = new String(Base64.getDecoder().decode(encodedUserInfo)); + AccessToken accessToken = accessTokenBuilderFactory.create().build(); - env.set(SCM_CREDENTIALS, CipherUtil.getInstance().encode(userInfo)); - } - else - { - LOG.warn("unknown authentication scheme used"); - } - } - else - { - LOG.trace("no authorization header found"); - } + String encodedToken = CipherUtil.getInstance().encode(accessToken.compact()); + env.put(SCM_BEARER_TOKEN, encodedToken); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java index b6b085f3ac..0a1531b8a1 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java @@ -50,6 +50,7 @@ import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgPythonScript; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.spi.javahg.HgFileviewExtension; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -58,6 +59,8 @@ import sonia.scm.util.Util; import java.io.File; import java.nio.charset.Charset; +import java.util.Map; +import java.util.function.Consumer; import javax.servlet.http.HttpServletRequest; @@ -102,7 +105,21 @@ public final class HgUtil * @return */ public static Repository open(HgRepositoryHandler handler, - HgHookManager hookManager, File directory, String encoding, boolean pending) + HgHookManager hookManager, File directory, String encoding, boolean pending, + AccessTokenBuilderFactory accessTokenBuilderFactory) + { + return open( + handler, + directory, + encoding, + pending, + environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager, accessTokenBuilderFactory) + ); + } + + public static Repository open(HgRepositoryHandler handler, + File directory, String encoding, boolean pending, + Consumer> prepareEnvironment) { String enc = encoding; @@ -113,8 +130,7 @@ public final class HgUtil RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT; - HgEnvironment.prepareEnvironment(repoConfiguration.getEnvironment(), - handler, hookManager); + prepareEnvironment.accept(repoConfiguration.getEnvironment()); repoConfiguration.addExtension(HgFileviewExtension.class); repoConfiguration.setEnablePendingChangesets(pending); diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py index 6ad8081512..637aa16331 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py @@ -40,7 +40,7 @@ import os, urllib, urllib2 baseUrl = os.environ['SCM_URL'] challenge = os.environ['SCM_CHALLENGE'] -credentials = os.environ['SCM_CREDENTIALS'] +token = os.environ['SCM_BEARER_TOKEN'] repositoryId = os.environ['SCM_REPOSITORY_ID'] def printMessages(ui, msgs): @@ -54,13 +54,13 @@ def callHookUrl(ui, repo, hooktype, node): try: url = baseUrl + hooktype ui.debug( "send scm-hook to " + url + " and " + node + "\n" ) - data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root, 'repositoryId': repositoryId}) + data = urllib.urlencode({'node': node, 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId}) # open url but ignore proxy settings proxy_handler = urllib2.ProxyHandler({}) opener = urllib2.build_opener(proxy_handler) req = urllib2.Request(url, data) conn = opener.open(req) - if conn.code >= 200 and conn.code < 300: + if 200 <= conn.code < 300: ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" ) printMessages(ui, conn) abort = False diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java index 2e06d4c6ea..171b917e8e 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java @@ -83,7 +83,7 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase HgTestUtil.checkForSkip(handler); cmdContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, - RepositoryTestData.createHeartOfGold(), repositoryDirectory); + RepositoryTestData.createHeartOfGold(), repositoryDirectory, null); } //~--- set methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index 24b262948f..e0756f5293 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -1,18 +1,41 @@ package sonia.scm.repository.spi; +import com.google.inject.util.Providers; import org.assertj.core.api.Assertions; import org.junit.Test; import sonia.scm.repository.Branch; +import sonia.scm.repository.HgHookManager; +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilder; +import sonia.scm.security.AccessTokenBuilderFactory; +import sonia.scm.web.HgRepositoryEnvironmentBuilder; -import java.io.IOException; import java.util.List; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class HgBranchCommandTest extends AbstractHgCommandTestBase { @Test - public void x() throws IOException { + public void shouldCreateBranch() { Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); - new HgBranchCommand(cmdContext, repository, new SimpleHgWorkdirFactory()).branch("new_branch"); + HgHookManager hookManager = mock(HgHookManager.class); + when(hookManager.getChallenge()).thenReturn("1"); + when(hookManager.createUrl()).thenReturn("http://example.com/"); + AccessTokenBuilderFactory tokenBuilderFactory = mock(AccessTokenBuilderFactory.class); + AccessTokenBuilder tokenBuilder = mock(AccessTokenBuilder.class); + when(tokenBuilderFactory.create()).thenReturn(tokenBuilder); + AccessToken accessToken = mock(AccessToken.class); + when(tokenBuilder.build()).thenReturn(accessToken); + when(accessToken.compact()).thenReturn(""); + + HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder = + new HgRepositoryEnvironmentBuilder(handler, hookManager, tokenBuilderFactory); + + SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), pc -> {}); + + new HgBranchCommand(cmdContext, repository, workdirFactory).branch("new_branch"); Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java index 4d7b7c75d9..8e374329cb 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java @@ -129,6 +129,6 @@ public class HgIncomingCommandTest extends IncomingOutgoingTestBase return new HgIncomingCommand( new HgCommandContext( HgTestUtil.createHookManager(), handler, incomingRepository, - incomingDirectory), incomingRepository, handler); + incomingDirectory, null), incomingRepository, handler); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java index eae8319b89..71fee6fe84 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java @@ -19,7 +19,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { @Before public void init() { - HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory); + HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory, null); outgoingModificationsCommand = new HgModificationsCommand(outgoingContext, outgoingRepository); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java index 354481b9b3..38f4fa4967 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java @@ -125,6 +125,6 @@ public class HgOutgoingCommandTest extends IncomingOutgoingTestBase return new HgOutgoingCommand( new HgCommandContext( HgTestUtil.createHookManager(), handler, outgoingRepository, - outgoingDirectory), outgoingRepository, handler); + outgoingDirectory, null), outgoingRepository, handler); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java index efe9983951..23de5ee218 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java @@ -20,7 +20,7 @@ public class HgHookCallbackServletTest { @Test public void shouldExtractCorrectRepositoryId() throws ServletException, IOException { HgRepositoryHandler handler = mock(HgRepositoryHandler.class); - HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null); + HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null, null); HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class);