From d518af4ccc7dda0afb3bb10208eed9fae65a33c6 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 7 Nov 2020 15:52:22 +0100 Subject: [PATCH] Refactor nearly the whole scm-hg-plugin for new hook implementation --- .../scm/api/v2/resources/HgConfigDto.java | 6 +- .../scm/repository/AbstractHgHandler.java | 391 --------------- .../DefaultHgEnvironmentBuilder.java | 129 +++++ .../java/sonia/scm/repository/HgConfig.java | 27 +- .../java/sonia/scm/repository/HgContext.java | 121 ----- .../scm/repository/HgContextProvider.java | 96 ---- .../sonia/scm/repository/HgEnvironment.java | 127 ----- ...stStore.java => HgEnvironmentBuilder.java} | 27 +- .../sonia/scm/repository/HgHookManager.java | 354 -------------- .../scm/repository/HgRepositoryFactory.java | 106 +++++ .../scm/repository/HgRepositoryHandler.java | 90 +--- .../java/sonia/scm/repository/HgVersion.java | 15 +- .../scm/repository/api/HgHookMessage.java | 68 +-- ...okHandler.java => DefaultHookHandler.java} | 36 +- .../HookContextProviderFactory.java} | 67 +-- .../scm/repository/hooks/HookHandler.java | 28 ++ .../repository/hooks/HookHandlerFactory.java | 5 +- ...ookHandlerFactory.java => HookModule.java} | 31 +- .../scm/repository/hooks/HookServer.java | 39 +- .../scm/repository/spi/HgCommandContext.java | 132 +----- .../spi/HgHookChangesetProvider.java | 112 +---- .../repository/spi/HgHookContextProvider.java | 79 +--- .../spi/HgRepositoryServiceProvider.java | 38 +- .../spi/HgRepositoryServiceResolver.java | 17 +- .../scm/repository/spi/HgVersionCommand.java | 114 +++++ .../spi/SimpleHgWorkingCopyFactory.java | 20 +- .../main/java/sonia/scm/web/HgCGIServlet.java | 60 +-- .../sonia/scm/web/HgHookCallbackServlet.java | 446 ------------------ .../web/HgRepositoryEnvironmentBuilder.java | 80 ---- .../java/sonia/scm/web/HgServletModule.java | 26 +- .../src/main/java/sonia/scm/web/HgUtil.java | 155 +----- .../resources/sonia/scm/python/scmhooks.py | 109 ++--- .../HgConfigDtoToHgConfigMapperTest.java | 4 +- .../DefaultHgEnvironmentBuilderTest.java | 149 ++++++ .../EmptyHgEnvironmentBuilder.java} | 30 +- .../scm/repository/HgContextProviderTest.java | 111 ----- .../repository/HgRepositoryFactoryTest.java | 125 +++++ .../repository/HgRepositoryHandlerTest.java | 29 +- .../java/sonia/scm/repository/HgTestUtil.java | 54 +-- ...rTest.java => DefaultHookHandlerTest.java} | 83 ++-- .../HookContextProviderFactoryTest.java} | 60 +-- .../scm/repository/hooks/HookServerTest.java | 2 +- .../spi/AbstractHgCommandTestBase.java | 20 +- .../repository/spi/HgBranchCommandTest.java | 6 +- .../repository/spi/HgIncomingCommandTest.java | 17 +- .../spi/HgModificationsCommandTest.java | 27 +- .../repository/spi/HgModifyCommandTest.java | 8 +- .../repository/spi/HgOutgoingCommandTest.java | 17 +- .../repository/spi/HgVersionCommandTest.java | 137 ++++++ .../spi/IncomingOutgoingTestBase.java | 4 +- .../spi/SimpleHgWorkingCopyFactoryTest.java | 7 +- .../scm/web/HgHookCallbackServletTest.java | 60 --- .../lifecycle/modules/ScmSecurityModule.java | 4 +- 53 files changed, 1230 insertions(+), 2875 deletions(-) delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/{HgContextRequestStore.java => HgEnvironmentBuilder.java} (59%) delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/{HgHookHandler.java => DefaultHookHandler.java} (81%) rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/{spi/AbstractHgCommand.java => hooks/HookContextProviderFactory.java} (52%) create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/{DefaultHookHandlerFactory.java => HookModule.java} (60%) create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/DefaultHgEnvironmentBuilderTest.java rename scm-plugins/scm-hg-plugin/src/{main/java/sonia/scm/repository/HgVersionHandler.java => test/java/sonia/scm/repository/EmptyHgEnvironmentBuilder.java} (66%) delete mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryFactoryTest.java rename scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/{HgHookHandlerTest.java => DefaultHookHandlerTest.java} (63%) rename scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/{HgEnvironmentTest.java => hooks/HookContextProviderFactoryTest.java} (52%) create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgVersionCommandTest.java delete mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java index b5039cbb44..464e896767 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; @@ -30,9 +30,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -@NoArgsConstructor @Getter @Setter +@NoArgsConstructor +@SuppressWarnings("java:S2160") // we don't need equals for dto public class HgConfigDto extends HalRepresentation { private boolean disabled; @@ -44,7 +45,6 @@ public class HgConfigDto extends HalRepresentation { private boolean useOptimizedBytecode; private boolean showRevisionInId; private boolean enableHttpPostArgs; - private boolean disableHookSSLValidation; @Override @SuppressWarnings("squid:S1185") // We want to have this method available in this package diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java deleted file mode 100644 index a74fdf9b21..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.repository; - -//~--- non-JDK imports -------------------------------------------------------- - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.SCMContext; -import sonia.scm.util.IOUtil; -import sonia.scm.util.Util; -import sonia.scm.web.HgUtil; - -import javax.xml.bind.JAXBException; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -//~--- JDK imports ------------------------------------------------------------ - -/** - * - * @author Sebastian Sdorra - */ -public class AbstractHgHandler -{ - - /** Field description */ - protected static final String ENV_ID_REVISION = "SCM_ID_REVISION"; - - /** Field description */ - protected static final String ENV_NODE = "HG_NODE"; - - /** Field description */ - protected static final String ENV_PAGE_LIMIT = "SCM_PAGE_LIMIT"; - - /** Field description */ - protected static final String ENV_PAGE_START = "SCM_PAGE_START"; - - /** Field description */ - protected static final String ENV_PATH = "SCM_PATH"; - - /** Field description */ - protected static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH"; - - /** Field description */ - protected static final String ENV_REVISION = "SCM_REVISION"; - - /** Field description */ - protected static final String ENV_REVISION_END = "SCM_REVISION_END"; - - /** Field description */ - protected static final String ENV_REVISION_START = "SCM_REVISION_START"; - - /** Field description */ - private static final String ENCODING = "UTF-8"; - - /** mercurial encoding */ - private static final String ENV_HGENCODING = "HGENCODING"; - - /** Field description */ - private static final String ENV_PENDING = "HG_PENDING"; - - /** python encoding */ - private static final String ENV_PYTHONIOENCODING = "PYTHONIOENCODING"; - - /** Field description */ - private static final String ENV_PYTHONPATH = "PYTHONPATH"; - - /** - * the logger for AbstractHgCommand - */ - private static final Logger logger = - LoggerFactory.getLogger(AbstractHgHandler.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * - * @param handler - * @param context - * @param repository - */ - protected AbstractHgHandler(HgRepositoryHandler handler, HgContext context, - Repository repository) - { - this(handler, context, repository, handler.getDirectory(repository.getId())); - } - - /** - * Constructs ... - * - * - * - * @param handler - * @param context - * @param repository - * @param repositoryDirectory - */ - protected AbstractHgHandler(HgRepositoryHandler handler, HgContext context, - Repository repository, File repositoryDirectory) - { - this.handler = handler; - this.context = context; - this.repository = repository; - this.repositoryDirectory = repositoryDirectory; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param revision - * @param path - * - * @return - */ - protected Map createEnvironment(String revision, String path) - { - Map env = new HashMap<>(); - - env.put(ENV_REVISION, HgUtil.getRevision(revision)); - env.put(ENV_PATH, Util.nonNull(path)); - - return env; - } - - /** - * Method description - * - * - * @param args - * - * @return - * - * @throws IOException - */ - protected Process createHgProcess(String... args) throws IOException - { - return createHgProcess(new HashMap(), args); - } - - /** - * Method description - * - * - * @param extraEnv - * @param args - * - * @return - * - * @throws IOException - */ - protected Process createHgProcess(Map extraEnv, - String... args) - throws IOException - { - return createProcess(extraEnv, handler.getConfig().getHgBinary(), args); - } - - /** - * Method description - * - * - * @param script - * @param extraEnv - * - * @return - * - * @throws IOException - */ - protected Process createScriptProcess(HgPythonScript script, - Map extraEnv) - throws IOException - { - return createProcess(extraEnv, handler.getConfig().getPythonBinary(), - script.getFile(SCMContext.getContext()).getAbsolutePath()); - } - - /** - * Method description - * - * - * @param errorStream - */ - protected void handleErrorStream(final InputStream errorStream) - { - if (errorStream != null) - { - new Thread(new Runnable() - { - @Override - public void run() - { - try - { - String content = IOUtil.getContent(errorStream); - - if (Util.isNotEmpty(content)) - { - logger.error(content.trim()); - } - } - catch (IOException ex) - { - logger.error("error during logging", ex); - } - } - }).start(); - } - } - - //~--- get methods ---------------------------------------------------------- - - protected T getResultFromScript(Class resultType, HgPythonScript script) throws IOException { - return getResultFromScript(resultType, script, - new HashMap()); - } - - @SuppressWarnings("unchecked") - protected T getResultFromScript(Class resultType, - HgPythonScript script, Map extraEnv) - throws IOException - { - Process p = createScriptProcess(script, extraEnv); - - handleErrorStream(p.getErrorStream()); - try (InputStream input = p.getInputStream()) { - return (T) handler.getJaxbContext().createUnmarshaller().unmarshal(input); - } catch (JAXBException ex) { - logger.error("could not parse result", ex); - - throw new InternalRepositoryException(repository, "could not parse result", ex); - } - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param extraEnv - * @param cmd - * @param args - * - * @return - * - * @throws IOException - */ - private Process createProcess(Map extraEnv, String cmd, - String... args) - throws IOException - { - HgConfig config = handler.getConfig(); - List cmdList = new ArrayList(); - - cmdList.add(cmd); - - if (Util.isNotEmpty(args)) - { - cmdList.addAll(Arrays.asList(args)); - } - - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder("create process for ["); - Iterator it = cmdList.iterator(); - - while (it.hasNext()) - { - msg.append(it.next()); - - if (it.hasNext()) - { - msg.append(", "); - } - } - - msg.append("]"); - logger.debug(msg.toString()); - } - - ProcessBuilder pb = new ProcessBuilder(cmdList); - - pb.directory(repositoryDirectory); - - Map env = pb.environment(); - - // force utf-8 encoding for mercurial and python - env.put(ENV_PYTHONIOENCODING, ENCODING); - env.put(ENV_HGENCODING, ENCODING); - - //J- - env.put(ENV_ID_REVISION, - String.valueOf(handler.getConfig().isShowRevisionInId()) - ); - //J+ - - if (context.isSystemEnvironment()) - { - env.putAll(System.getenv()); - } - - if (context.isPending()) - { - if (logger.isDebugEnabled()) - { - logger.debug("enable hg pending for {}", - repositoryDirectory.getAbsolutePath()); - } - - env.put(ENV_PENDING, repositoryDirectory.getAbsolutePath()); - - if (extraEnv.containsKey(ENV_REVISION_START)) - { - env.put(ENV_NODE, extraEnv.get(ENV_REVISION_START)); - } - } - - env.put(ENV_PYTHONPATH, HgUtil.getPythonPath(config)); - env.put(ENV_REPOSITORY_PATH, repositoryDirectory.getAbsolutePath()); - env.putAll(extraEnv); - - if (logger.isTraceEnabled()) - { - StringBuilder msg = new StringBuilder("start process in directory '"); - - msg.append(repositoryDirectory.getAbsolutePath()).append( - "' with env: \n"); - - for (Map.Entry e : env.entrySet()) - { - msg.append(" ").append(e.getKey()); - msg.append(" = ").append(e.getValue()); - msg.append("\n"); - } - - logger.trace(msg.toString()); - } - - return pb.start(); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - protected Repository repository; - - /** Field description */ - protected File repositoryDirectory; - - /** Field description */ - private HgContext context; - - /** Field description */ - private HgRepositoryHandler handler; -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java new file mode 100644 index 0000000000..c4f37bda3b --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java @@ -0,0 +1,129 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import sonia.scm.repository.hooks.HookEnvironment; +import sonia.scm.repository.hooks.HookServer; +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilderFactory; +import sonia.scm.security.CipherUtil; +import sonia.scm.web.HgUtil; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.util.Map; + +public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder { + + @VisibleForTesting + static final String ENV_PYTHON_PATH = "PYTHONPATH"; + @VisibleForTesting + static final String ENV_HOOK_PORT = "SCM_HOOK_PORT"; + @VisibleForTesting + static final String ENV_CHALLENGE = "SCM_CHALLENGE"; + @VisibleForTesting + static final String ENV_BEARER_TOKEN = "SCM_BEARER_TOKEN"; + @VisibleForTesting + static final String ENV_REPOSITORY_NAME = "REPO_NAME"; + @VisibleForTesting + static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH"; + @VisibleForTesting + static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID"; + @VisibleForTesting + static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS"; + + private final AccessTokenBuilderFactory accessTokenBuilderFactory; + private final HgRepositoryHandler repositoryHandler; + private final HookEnvironment hookEnvironment; + private final HookServer server; + + private int hookPort = -1; + + @Inject + public DefaultHgEnvironmentBuilder( + AccessTokenBuilderFactory accessTokenBuilderFactory, HgRepositoryHandler repositoryHandler, + HookEnvironment hookEnvironment, HookServer server + ) { + this.accessTokenBuilderFactory = accessTokenBuilderFactory; + this.repositoryHandler = repositoryHandler; + this.hookEnvironment = hookEnvironment; + this.server = server; + } + + + @Override + public Map read(Repository repository) { + ImmutableMap.Builder env = ImmutableMap.builder(); + read(env, repository); + return env.build(); + } + + @Override + public Map write(Repository repository) { + ImmutableMap.Builder env = ImmutableMap.builder(); + read(env, repository); + write(env); + return env.build(); + } + + private void read(ImmutableMap.Builder env, Repository repository) { + HgConfig config = repositoryHandler.getConfig(); + env.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(config)); + + File directory = repositoryHandler.getDirectory(repository.getId()); + + env.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); + env.put(ENV_REPOSITORY_ID, repository.getId()); + env.put(ENV_REPOSITORY_PATH, directory.getAbsolutePath()); + + // enable experimental httppostargs protocol of mercurial + // Issue 970: https://goo.gl/poascp + env.put(ENV_HTTP_POST_ARGS, String.valueOf(config.isEnableHttpPostArgs())); + } + + private void write(ImmutableMap.Builder env) { + env.put(ENV_HOOK_PORT, String.valueOf(getHookPort())); + AccessToken accessToken = accessTokenBuilderFactory.create().build(); + env.put(ENV_BEARER_TOKEN, CipherUtil.getInstance().encode(accessToken.compact())); + env.put(ENV_CHALLENGE, hookEnvironment.getChallenge()); + } + + private synchronized int getHookPort() { + if (hookPort > 0) { + return hookPort; + } + try { + hookPort = server.start(); + } catch (IOException ex) { + throw new IllegalStateException("failed to start mercurial hook server"); + } + return hookPort; + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java index daf3414a63..3189239dbd 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; @@ -36,20 +36,10 @@ import javax.xml.bind.annotation.XmlTransient; * @author Sebastian Sdorra */ @XmlRootElement(name = "config") -public class HgConfig extends RepositoryConfig -{ +public class HgConfig extends RepositoryConfig { public static final String PERMISSION = "hg"; - /** - * Constructs ... - * - */ - public HgConfig() {} - - //~--- get methods ---------------------------------------------------------- - - @Override @XmlTransient // Only for permission checks, don't serialize to XML public String getId() { @@ -123,10 +113,6 @@ public class HgConfig extends RepositoryConfig return useOptimizedBytecode; } - public boolean isDisableHookSSLValidation() { - return disableHookSSLValidation; - } - public boolean isEnableHttpPostArgs() { return enableHttpPostArgs; } @@ -216,10 +202,6 @@ public class HgConfig extends RepositoryConfig this.useOptimizedBytecode = useOptimizedBytecode; } - public void setDisableHookSSLValidation(boolean disableHookSSLValidation) { - this.disableHookSSLValidation = disableHookSSLValidation; - } - //~--- fields --------------------------------------------------------------- /** Field description */ @@ -242,9 +224,4 @@ public class HgConfig extends RepositoryConfig private boolean enableHttpPostArgs = false; - /** - * disable validation of ssl certificates for mercurial hook - * @see Issue 959 - */ - private boolean disableHookSSLValidation = false; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java deleted file mode 100644 index 2791fff3c0..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.repository; - -//~--- non-JDK imports -------------------------------------------------------- - -/** - * - * @author Sebastian Sdorra - */ -public class HgContext -{ - - /** - * Constructs ... - * - */ - public HgContext() {} - - /** - * Constructs ... - * - * - * @param pending - */ - public HgContext(boolean pending) - { - this.pending = pending; - } - - /** - * Constructs ... - * - * - * @param pending - * @param systemEnvironment - */ - public HgContext(boolean pending, boolean systemEnvironment) - { - this.pending = pending; - this.systemEnvironment = systemEnvironment; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public boolean isPending() - { - return pending; - } - - /** - * Method description - * - * - * @return - */ - public boolean isSystemEnvironment() - { - return systemEnvironment; - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param pending - */ - public void setPending(boolean pending) - { - this.pending = pending; - } - - /** - * Method description - * - * - * @param systemEnvironment - */ - public void setSystemEnvironment(boolean systemEnvironment) - { - this.systemEnvironment = systemEnvironment; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private boolean pending = false; - - /** Field description */ - private boolean systemEnvironment = true; -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java deleted file mode 100644 index 6b09416db2..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.repository; - -//~--- non-JDK imports -------------------------------------------------------- - - -import com.google.common.annotations.VisibleForTesting; -import com.google.inject.OutOfScopeException; -import com.google.inject.Provider; -import com.google.inject.ProvisionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; - -/** - * Injection provider for {@link HgContext}. - * This provider returns an instance {@link HgContext} from request scope, if no {@link HgContext} could be found in - * request scope (mostly because the scope is not available) a new {@link HgContext} gets returned. - * - * @author Sebastian Sdorra - */ -public class HgContextProvider implements Provider -{ - - /** - * the LOG for HgContextProvider - */ - private static final Logger LOG = - LoggerFactory.getLogger(HgContextProvider.class); - - //~--- get methods ---------------------------------------------------------- - - private Provider requestStoreProvider; - - @Inject - public HgContextProvider(Provider requestStoreProvider) { - this.requestStoreProvider = requestStoreProvider; - } - - @VisibleForTesting - public HgContextProvider() { - } - - @Override - public HgContext get() { - HgContext context = fetchContextFromRequest(); - if (context != null) { - LOG.trace("return HgContext from request store"); - return context; - } - LOG.trace("could not find context in request scope, returning new instance"); - return new HgContext(); - } - - private HgContext fetchContextFromRequest() { - try { - if (requestStoreProvider != null) { - return requestStoreProvider.get().get(); - } else { - LOG.trace("no request store provider defined, could not return context from request"); - return null; - } - } catch (ProvisionException ex) { - if (ex.getCause() instanceof OutOfScopeException) { - LOG.trace("we are currently out of request scope, failed to retrieve context"); - return null; - } else { - throw ex; - } - } - } -} 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 deleted file mode 100644 index a997bb2069..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.repository; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.inject.ProvisionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.security.AccessToken; -import sonia.scm.security.CipherUtil; -import sonia.scm.security.Xsrf; -import sonia.scm.web.HgUtil; - -import javax.servlet.http.HttpServletRequest; -import java.util.Map; - -//~--- JDK imports ------------------------------------------------------------ - -/** - * - * @author Sebastian Sdorra - */ -public final class HgEnvironment -{ - - private static final Logger LOG = LoggerFactory.getLogger(HgEnvironment.class); - - /** Field description */ - public static final String ENV_PYTHON_PATH = "PYTHONPATH"; - - /** Field description */ - private static final String ENV_CHALLENGE = "SCM_CHALLENGE"; - - /** Field description */ - private static final String ENV_URL = "SCM_URL"; - - private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN"; - - private static final String SCM_XSRF = "SCM_XSRF"; - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - */ - private HgEnvironment() {} - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param environment - * @param handler - * @param hookManager - */ - public static void prepareEnvironment(Map environment, - HgRepositoryHandler handler, HgHookManager hookManager) - { - prepareEnvironment(environment, handler, hookManager, null); - } - - /** - * Method description - * - * - * @param environment - * @param handler - * @param hookManager - * @param request - */ - public static void prepareEnvironment(Map environment, - HgRepositoryHandler handler, HgHookManager hookManager, - HttpServletRequest request) - { - String hookUrl; - - if (request != null) - { - hookUrl = hookManager.createUrl(request); - } - else - { - hookUrl = hookManager.createUrl(); - } - - try { - AccessToken accessToken = hookManager.getAccessToken(); - environment.put(SCM_BEARER_TOKEN, CipherUtil.getInstance().encode(accessToken.compact())); - extractXsrfKey(environment, accessToken); - } catch (ProvisionException e) { - LOG.debug("could not create bearer token; looks like currently we are not in a request; probably you can ignore the following exception:", e); - } - environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig())); - environment.put(ENV_URL, hookUrl); - environment.put(ENV_CHALLENGE, hookManager.getChallenge()); - } - - private static void extractXsrfKey(Map environment, AccessToken accessToken) { - environment.put(SCM_XSRF, accessToken.getCustom(Xsrf.TOKEN_KEY).orElse("-")); - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java similarity index 59% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java index 3fa04bd1e6..3738d92d88 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java @@ -21,28 +21,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; -import com.google.inject.servlet.RequestScoped; +import com.google.inject.ImplementedBy; -/** - * Holds an instance of {@link HgContext} in the request scope. - * - *

The problem seems to be that guice had multiple options for injecting HgContext. {@link HgContextProvider} - * bound via Module and {@link HgContext} bound void {@link RequestScoped} annotation. It looks like that Guice 4 - * injects randomly the one or the other, in SCMv1 (Guice 3) everything works as expected.

- * - *

To fix the problem we have created this class annotated with {@link RequestScoped}, which holds an instance - * of {@link HgContext}. This way only the {@link HgContextProvider} is used for injection.

- */ -@RequestScoped -public class HgContextRequestStore { - - private final HgContext context = new HgContext(); - - public HgContext get() { - return context; - } +import java.util.Map; +@ImplementedBy(DefaultHgEnvironmentBuilder.class) +public interface HgEnvironmentBuilder { + Map read(Repository repository); + Map write(Repository repository); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java deleted file mode 100644 index 3d333015ee..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.repository; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.github.legman.Subscribe; -import com.google.common.base.MoreObjects; -import com.google.inject.Inject; -import com.google.inject.OutOfScopeException; -import com.google.inject.Provider; -import com.google.inject.ProvisionException; -import com.google.inject.Singleton; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.config.ScmConfigurationChangedEvent; -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.security.AccessToken; -import sonia.scm.security.AccessTokenBuilderFactory; -import sonia.scm.util.HttpUtil; -import sonia.scm.util.Util; - -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.util.UUID; - -//~--- JDK imports ------------------------------------------------------------ - -/** - * - * @author Sebastian Sdorra - */ -@Singleton -public class HgHookManager { - - @SuppressWarnings("java:S1075") // this url is fixed - private static final String URL_HOOKPATH = "/hook/hg/"; - - /** - * the logger for HgHookManager - */ - private static final Logger logger = - LoggerFactory.getLogger(HgHookManager.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * @param configuration - * @param httpServletRequestProvider - * @param httpClient - * @param accessTokenBuilderFactory - */ - @Inject - public HgHookManager(ScmConfiguration configuration, - Provider httpServletRequestProvider, - AdvancedHttpClient httpClient, AccessTokenBuilderFactory accessTokenBuilderFactory) - { - this.configuration = configuration; - this.httpServletRequestProvider = httpServletRequestProvider; - this.httpClient = httpClient; - this.accessTokenBuilderFactory = accessTokenBuilderFactory; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param config - */ - @Subscribe(async = false) - public void configChanged(ScmConfigurationChangedEvent config) - { - hookUrl = null; - } - - /** - * Method description - * - * - * @param request - * - * @return - */ - public String createUrl(HttpServletRequest request) - { - if (hookUrl == null) - { - synchronized (this) - { - if (hookUrl == null) - { - buildHookUrl(request); - - if (logger.isInfoEnabled() && Util.isNotEmpty(hookUrl)) - { - logger.info("use {} for mercurial hooks", hookUrl); - } - } - } - } - - return hookUrl; - } - - /** - * Method description - * - * - * @return - */ - public String createUrl() - { - String url = hookUrl; - - if (url == null) - { - HttpServletRequest request = getHttpServletRequest(); - - if (request != null) - { - url = createUrl(request); - } - else - { - url = createConfiguredUrl(); - logger.warn( - "created url {} without request, in some cases this could cause problems", - url); - } - } - - return url; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public String getChallenge() - { - return challenge; - } - - /** - * Method description - * - * - * @param challenge - * - * @return - */ - public boolean isAcceptAble(String challenge) - { - return this.challenge.equals(challenge); - } - - public AccessToken getAccessToken() - { - return accessTokenBuilderFactory.create().build(); - } - - private void buildHookUrl(HttpServletRequest request) { - if (configuration.isForceBaseUrl()) { - logger.debug("create hook url from configured base url because force base url is enabled"); - - hookUrl = createConfiguredUrl(); - if (!isUrlWorking(hookUrl)) { - disableHooks(); - } - } else { - logger.debug("create hook url from request"); - - hookUrl = HttpUtil.getCompleteUrl(request, URL_HOOKPATH); - if (!isUrlWorking(hookUrl)) { - logger.warn("hook url {} from request does not work, try now localhost", hookUrl); - - hookUrl = createLocalUrl(request); - if (!isUrlWorking(hookUrl)) { - logger.warn("localhost hook url {} does not work, try now from configured base url", hookUrl); - - hookUrl = createConfiguredUrl(); - if (!isUrlWorking(hookUrl)) { - disableHooks(); - } - } - } - } - } - - /** - * Method description - * - * - * @return - */ - private String createConfiguredUrl() - { - //J- - return HttpUtil.getUriWithoutEndSeperator( - MoreObjects.firstNonNull( - configuration.getBaseUrl(), - "http://localhost:8080/scm" - ) - ).concat(URL_HOOKPATH); - //J+ - } - - /** - * Method description - * - * - * @param request - * - * @return - */ - private String createLocalUrl(HttpServletRequest request) - { - StringBuilder sb = new StringBuilder(request.getScheme()); - - sb.append("://localhost:").append(request.getLocalPort()); - sb.append(request.getContextPath()).append(URL_HOOKPATH); - - return sb.toString(); - } - - /** - * Method description - * - */ - private void disableHooks() - { - if (logger.isErrorEnabled()) - { - logger.error( - "disabling mercurial hooks, because hook url {} seems not to work", - hookUrl); - } - - hookUrl = Util.EMPTY_STRING; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - private HttpServletRequest getHttpServletRequest() - { - HttpServletRequest request = null; - - try - { - request = httpServletRequestProvider.get(); - } - catch (ProvisionException | OutOfScopeException ex) - { - logger.debug("http servlet request is not available"); - } - - return request; - } - - /** - * Method description - * - * - * @param url - * - * @return - */ - private boolean isUrlWorking(String url) - { - boolean result = false; - - try - { - url = url.concat("?ping=true"); - - logger.trace("check hook url {}", url); - //J- - int sc = httpClient.get(url) - .disableHostnameValidation(true) - .disableCertificateValidation(true) - .ignoreProxySettings(true) - .disableTracing() - .request() - .getStatus(); - //J+ - result = sc == 204; - } - catch (IOException ex) - { - if (logger.isTraceEnabled()) - { - logger.trace("url test failed for url ".concat(url), ex); - } - } - - return result; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private String challenge = UUID.randomUUID().toString(); - - /** Field description */ - private ScmConfiguration configuration; - - /** Field description */ - private volatile String hookUrl; - - /** Field description */ - private AdvancedHttpClient httpClient; - - /** Field description */ - private Provider httpServletRequestProvider; - - private final AccessTokenBuilderFactory accessTokenBuilderFactory; -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java new file mode 100644 index 0000000000..5b17ad8bd4 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java @@ -0,0 +1,106 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import com.aragost.javahg.RepositoryConfiguration; +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.hooks.HookEnvironment; +import sonia.scm.repository.spi.javahg.HgFileviewExtension; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.File; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Map; +import java.util.function.Function; + +@Singleton +public class HgRepositoryFactory { + + private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryFactory.class); + + private final HgRepositoryHandler handler; + private final HookEnvironment hookEnvironment; + private final HgEnvironmentBuilder environmentBuilder; + private final Function directoryResolver; + + @Inject + public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder) { + this( + handler, hookEnvironment, environmentBuilder, + repository -> handler.getDirectory(repository.getId()) + ); + } + + @VisibleForTesting + public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder, Function directoryResolver) { + this.handler = handler; + this.hookEnvironment = hookEnvironment; + this.environmentBuilder = environmentBuilder; + this.directoryResolver = directoryResolver; + } + + public com.aragost.javahg.Repository openForRead(Repository repository) { + return open(repository, environmentBuilder.read(repository)); + } + + public com.aragost.javahg.Repository openForWrite(Repository repository) { + return open(repository, environmentBuilder.write(repository)); + } + + private com.aragost.javahg.Repository open(Repository repository, Map environment) { + File directory = directoryResolver.apply(repository); + + RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT; + repoConfiguration.getEnvironment().putAll(environment); + repoConfiguration.addExtension(HgFileviewExtension.class); + + boolean pending = hookEnvironment.isPending(); + repoConfiguration.setEnablePendingChangesets(pending); + + Charset encoding = encoding(); + repoConfiguration.setEncoding(encoding); + + repoConfiguration.setHgBin(handler.getConfig().getHgBinary()); + + LOG.trace("open hg repository {}: encoding: {}, pending: {}", directory, encoding, pending); + + return com.aragost.javahg.Repository.open(repoConfiguration, directory); + } + + private Charset encoding() { + String charset = handler.getConfig().getEncoding(); + try { + return Charset.forName(charset); + } catch (UnsupportedCharsetException ex) { + LOG.warn("unknown charset {} in hg config, fallback to utf-8", charset); + return StandardCharsets.UTF_8; + } + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index bdc36eb54b..14cd54866d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -27,11 +27,9 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.ConfigurationException; import sonia.scm.SCMContextProvider; import sonia.scm.autoconfig.AutoConfigurator; import sonia.scm.installer.HgInstaller; @@ -43,14 +41,14 @@ import sonia.scm.io.INISection; import sonia.scm.plugin.Extension; import sonia.scm.plugin.PluginLoader; import sonia.scm.repository.spi.HgRepositoryServiceProvider; +import sonia.scm.repository.spi.HgVersionCommand; import sonia.scm.repository.spi.HgWorkingCopyFactory; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.util.IOUtil; import sonia.scm.util.SystemUtil; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -63,14 +61,15 @@ import java.util.Optional; public class HgRepositoryHandler extends AbstractSimpleRepositoryHandler { - public static final String PATH_HOOK = ".hook-1.8"; public static final String RESOURCE_VERSION = "sonia/scm/version/scm-hg-plugin"; public static final String TYPE_DISPLAYNAME = "Mercurial"; public static final String TYPE_NAME = "hg"; - public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, + public static final RepositoryType TYPE = new RepositoryType( + TYPE_NAME, TYPE_DISPLAYNAME, HgRepositoryServiceProvider.COMMANDS, - HgRepositoryServiceProvider.FEATURES); + HgRepositoryServiceProvider.FEATURES + ); private static final Logger logger = LoggerFactory.getLogger(HgRepositoryHandler.class); @@ -78,28 +77,14 @@ public class HgRepositoryHandler private static final String CONFIG_SECTION_SCMM = "scmm"; private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid"; - private final Provider hgContextProvider; - private final HgWorkingCopyFactory workingCopyFactory; - private final JAXBContext jaxbContext; - @Inject public HgRepositoryHandler(ConfigurationStoreFactory storeFactory, - Provider hgContextProvider, RepositoryLocationResolver repositoryLocationResolver, PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) { super(storeFactory, repositoryLocationResolver, pluginLoader); - this.hgContextProvider = hgContextProvider; this.workingCopyFactory = workingCopyFactory; - - try { - this.jaxbContext = JAXBContext.newInstance(BrowserResult.class, - BlameResult.class, Changeset.class, ChangesetPagingResult.class, - HgVersion.class); - } catch (JAXBException ex) { - throw new ConfigurationException("could not create jaxbcontext", ex); - } } public void doAutoConfiguration(HgConfig autoConfig) { @@ -107,8 +92,7 @@ public class HgRepositoryHandler try { if (logger.isDebugEnabled()) { - logger.debug("installing mercurial with {}", - installer.getClass().getName()); + logger.debug("installing mercurial with {}", installer.getClass().getName()); } installer.install(baseDirectory, autoConfig); @@ -154,16 +138,6 @@ public class HgRepositoryHandler } } - public HgContext getHgContext() { - HgContext context = hgContextProvider.get(); - - if (context == null) { - context = new HgContext(); - } - - return context; - } - @Override public ImportHandler getImportHandler() { return new HgImportHandler(this); @@ -176,28 +150,14 @@ public class HgRepositoryHandler @Override public String getVersionInformation() { - String version = getStringFromResource(RESOURCE_VERSION, - DEFAULT_VERSION_INFORMATION); + return getVersionInformation(new HgVersionCommand(getConfig())); + } - try { - HgVersion hgVersion = new HgVersionHandler(this, hgContextProvider.get(), - baseDirectory).getVersion(); - - if (hgVersion != null) { - if (logger.isDebugEnabled()) { - logger.debug("mercurial/python informations: {}", hgVersion); - } - - version = MessageFormat.format(version, hgVersion.getPython(), - hgVersion.getMercurial()); - } else if (logger.isWarnEnabled()) { - logger.warn("could not retrieve version informations"); - } - } catch (Exception ex) { - logger.error("could not read version informations", ex); - } - - return version; + String getVersionInformation(HgVersionCommand command) { + String version = getStringFromResource(RESOURCE_VERSION, DEFAULT_VERSION_INFORMATION); + HgVersion hgVersion = command.get(); + logger.debug("mercurial/python informations: {}", hgVersion); + return MessageFormat.format(version, hgVersion.getPython(), hgVersion.getMercurial()); } @Override @@ -253,28 +213,24 @@ public class HgRepositoryHandler logger.debug("write python script {}", script.getName()); } - InputStream content = null; - OutputStream output = null; - - try { - content = HgRepositoryHandler.class.getResourceAsStream( - script.getResourcePath()); - output = new FileOutputStream(script.getFile(context)); + try (InputStream content = input(script); OutputStream output = output(context, script)) { IOUtil.copy(content, output); } catch (IOException ex) { logger.error("could not write script", ex); - } finally { - IOUtil.close(content); - IOUtil.close(output); } } } + private InputStream input(HgPythonScript script) { + return HgRepositoryHandler.class.getResourceAsStream(script.getResourcePath()); + } + + private OutputStream output(SCMContextProvider context, HgPythonScript script) throws FileNotFoundException { + return new FileOutputStream(script.getFile(context)); + } + public HgWorkingCopyFactory getWorkingCopyFactory() { return workingCopyFactory; } - public JAXBContext getJaxbContext() { - return jaxbContext; - } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java index 36bd6abfa3..a517c2fe08 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java @@ -24,10 +24,8 @@ package sonia.scm.repository; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; +import lombok.AllArgsConstructor; +import lombok.Data; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -37,13 +35,14 @@ import javax.xml.bind.annotation.XmlRootElement; * * @author Sebastian Sdorra */ +@Data +@AllArgsConstructor @XmlRootElement(name = "version") @XmlAccessorType(XmlAccessType.FIELD) -@EqualsAndHashCode -@Getter -@Setter -@ToString public class HgVersion { + + public static final String UNKNOWN = "x.y.z (unknown)"; + private String mercurial; private String python; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java index ea1671d7ab..0cd78ac932 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java @@ -21,75 +21,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.api; //~--- JDK imports ------------------------------------------------------------ +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + import java.io.Serializable; /** * * @author Sebastian Sdorra */ -public final class HgHookMessage implements Serializable -{ +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public final class HgHookMessage implements Serializable { - /** Field description */ private static final long serialVersionUID = 1804492842452344326L; - //~--- constant enums ------------------------------------------------------- - - /** - * Enum description - * - */ - public static enum Severity { NOTE, ERROR; } - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param severity - * @param message - */ - public HgHookMessage(Severity severity, String message) - { - this.severity = severity; - this.message = message; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public String getMessage() - { - return message; - } - - /** - * Method description - * - * - * @return - */ - public Severity getSeverity() - { - return severity; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ + private Severity severity; private String message; - /** Field description */ - private Severity severity; + public enum Severity { NOTE, ERROR } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HgHookHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java similarity index 81% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HgHookHandler.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java index e2398b4498..f4d8ba2a08 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HgHookHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java @@ -24,6 +24,7 @@ package sonia.scm.repository.hooks; +import com.google.inject.assistedinject.Assisted; import lombok.AllArgsConstructor; import lombok.Data; import org.apache.shiro.SecurityUtils; @@ -31,7 +32,7 @@ import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.NotFoundException; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.api.HgHookMessage; import sonia.scm.repository.spi.HgHookContextProvider; @@ -39,9 +40,7 @@ import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.security.BearerToken; import sonia.scm.security.CipherUtil; -import javax.annotation.Nonnull; -import javax.inject.Provider; -import java.io.File; +import javax.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -50,19 +49,20 @@ import java.util.List; import static java.util.Collections.singletonList; -class HgHookHandler implements Runnable { +class DefaultHookHandler implements HookHandler { - private static final Logger LOG = LoggerFactory.getLogger(HgHookHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(DefaultHookHandler.class); - private final HgRepositoryHandler handler; private final HookEventFacade hookEventFacade; - private final Provider environmentProvider; + private final HookEnvironment environment; + private final HookContextProviderFactory hookContextProviderFactory; private final Socket socket; - HgHookHandler(HgRepositoryHandler handler, HookEventFacade hookEventFacade, Provider environmentProvider, Socket socket) { - this.handler = handler; + @Inject + public DefaultHookHandler(HookContextProviderFactory hookContextProviderFactory, HookEventFacade hookEventFacade, HookEnvironment environment, @Assisted Socket socket) { + this.hookContextProviderFactory = hookContextProviderFactory; this.hookEventFacade = hookEventFacade; - this.environmentProvider = environmentProvider; + this.environment = environment; this.socket = socket; } @@ -84,7 +84,6 @@ class HgHookHandler implements Runnable { } private Response handleHookRequest(Request request) { - HookEnvironment environment = environmentProvider.get(); try { if (!environment.isAcceptAble(request.getChallenge())) { return error("invalid hook challenge"); @@ -93,13 +92,16 @@ class HgHookHandler implements Runnable { authenticate(request); environment.setPending(request.getType() == RepositoryHookType.PRE_RECEIVE); - HgHookContextProvider context = createHookContextProvider(request); + HgHookContextProvider context = hookContextProviderFactory.create(request.getRepositoryId(), request.getNode()); hookEventFacade.handle(request.getRepositoryId()).fireHookEvent(request.getType(), context); return new Response(context.getHgMessageProvider().getMessages(), false); } catch (AuthenticationException ex) { LOG.warn("hook authentication failed", ex); return error("hook authentication failed"); + } catch (NotFoundException ex) { + LOG.warn("could not find repository with id {}", request.getRepositoryId(), ex); + return error("repository not found"); } catch (Exception ex) { LOG.warn("unknown error on hook occurred", ex); return error("unknown error"); @@ -108,14 +110,6 @@ class HgHookHandler implements Runnable { } } - @Nonnull - private HgHookContextProvider createHookContextProvider(Request request) { - File repositoryDirectory = handler.getDirectory(request.getRepositoryId()); - return new HgHookContextProvider( - handler, repositoryDirectory, null, request.node, request.type - ); - } - private void authenticate(Request request) { String token = CipherUtil.getInstance().decode(request.getToken()); BearerToken bearer = BearerToken.valueOf(token); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java similarity index 52% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java index 259996e2ed..04f88189ce 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java @@ -21,60 +21,37 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.repository.spi; -//~--- non-JDK imports -------------------------------------------------------- +package sonia.scm.repository.hooks; -import sonia.scm.repository.AbstractHgHandler; -import sonia.scm.repository.HgContext; +import sonia.scm.NotFoundException; +import sonia.scm.repository.HgRepositoryFactory; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.spi.HgHookContextProvider; -//~--- JDK imports ------------------------------------------------------------ +import javax.inject.Inject; -import java.io.File; +public class HookContextProviderFactory { -import java.util.Map; + private final RepositoryManager repositoryManager; + private final HgRepositoryHandler repositoryHandler; + private final HgRepositoryFactory repositoryFactory; -/** - * - * @author Sebastian Sdorra - */ -public class AbstractHgCommand extends AbstractHgHandler -{ - - /** - * Constructs ... - * - * - * @param handler - * @param context - * @param repository - * @param repositoryDirectory - */ - protected AbstractHgCommand(HgRepositoryHandler handler, HgContext context, - Repository repository, File repositoryDirectory) - { - super(handler, context, repository, repositoryDirectory); + @Inject + public HookContextProviderFactory(RepositoryManager repositoryManager, HgRepositoryHandler repositoryHandler, HgRepositoryFactory repositoryFactory) { + this.repositoryManager = repositoryManager; + this.repositoryHandler = repositoryHandler; + this.repositoryFactory = repositoryFactory; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param revision - * @param path - * - * @param request - * - * @return - */ - protected Map createEnvironment(FileBaseCommandRequest request) - { - return createEnvironment(request.getRevision(), request.getPath()); + HgHookContextProvider create(String repositoryId, String node) { + Repository repository = repositoryManager.get(repositoryId); + if (repository == null) { + throw new NotFoundException(Repository.class, repositoryId); + } + return new HgHookContextProvider(repositoryHandler, repositoryFactory, repository, node); } + } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java new file mode 100644 index 0000000000..f2f7e7ccdd --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java @@ -0,0 +1,28 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.hooks; + +public interface HookHandler extends Runnable { +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java index 61d3a1340d..4eaf205bdd 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java @@ -24,14 +24,11 @@ package sonia.scm.repository.hooks; -import com.google.inject.ImplementedBy; - import java.net.Socket; @FunctionalInterface -@ImplementedBy(DefaultHookHandlerFactory.class) interface HookHandlerFactory { - Runnable create(Socket socket); + HookHandler create(Socket socket); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandlerFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java similarity index 60% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandlerFactory.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java index 93f0915486..2eed226a09 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandlerFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java @@ -24,28 +24,17 @@ package sonia.scm.repository.hooks; -import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.spi.HookEventFacade; - -import javax.inject.Inject; -import javax.inject.Provider; -import java.net.Socket; - -public class DefaultHookHandlerFactory implements HookHandlerFactory { - - private final HgRepositoryHandler handler; - private final HookEventFacade hookEventFacade; - private final Provider hookEnvironment; - - @Inject - public DefaultHookHandlerFactory(HgRepositoryHandler handler, HookEventFacade hookEventFacade, Provider hookEnvironment) { - this.handler = handler; - this.hookEventFacade = hookEventFacade; - this.hookEnvironment = hookEnvironment; - } +import com.google.inject.AbstractModule; +import com.google.inject.assistedinject.FactoryModuleBuilder; +import sonia.scm.plugin.Extension; +@Extension +public class HookModule extends AbstractModule { @Override - public HgHookHandler create(Socket socket) { - return new HgHookHandler(handler, hookEventFacade, hookEnvironment, socket); + protected void configure() { + install(new FactoryModuleBuilder() + .implement(HookHandler.class, DefaultHookHandler.class) + .build(HookHandlerFactory.class) + ); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java index 0ecbe180c4..40fb4a68e3 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java @@ -25,7 +25,9 @@ package sonia.scm.repository.hooks; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.apache.shiro.concurrent.SubjectAwareExecutorService; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.util.ThreadContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,6 +40,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; @Singleton public class HookServer implements AutoCloseable { @@ -56,18 +59,24 @@ public class HookServer implements AutoCloseable { } private ExecutorService createAcceptor() { - return new SubjectAwareExecutorService(Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder().setNameFormat("HgHookAcceptor").build() - )); + return Executors.newSingleThreadExecutor( + createThreadFactory("HgHookAcceptor") + ); } private ExecutorService createWorkerPool() { - return new SubjectAwareExecutorService(Executors.newCachedThreadPool( - new ThreadFactoryBuilder().setNameFormat("HgHookWorker-%d").build() - ) + return Executors.newCachedThreadPool( + createThreadFactory("HgHookWorker-%d") ); } + @Nonnull + private ThreadFactory createThreadFactory(String hgHookAcceptor) { + return new ThreadFactoryBuilder() + .setNameFormat(hgHookAcceptor) + .build(); + } + public int start() throws IOException { acceptor = createAcceptor(); workerPool = createWorkerPool(); @@ -83,12 +92,14 @@ public class HookServer implements AutoCloseable { } private void accept() { + SecurityManager securityManager = SecurityUtils.getSecurityManager(); acceptor.submit(() -> { while (!serverSocket.isClosed()) { try { Socket clientSocket = serverSocket.accept(); LOG.trace("accept incoming hook client from {}", clientSocket.getInetAddress()); - workerPool.submit(handlerFactory.create(clientSocket)); + HookHandler hookHandler = handlerFactory.create(clientSocket); + workerPool.submit(associateSecurityManager(securityManager, hookHandler)); } catch (IOException ex) { LOG.debug("failed to accept socket, possible closed", ex); } @@ -96,6 +107,18 @@ public class HookServer implements AutoCloseable { }); } + private Runnable associateSecurityManager(SecurityManager securityManager, HookHandler hookHandler) { + return () -> { + ThreadContext.bind(securityManager); + try { + hookHandler.run(); + } finally { + ThreadContext.unbindSubject(); + ThreadContext.unbindSecurityManager(); + } + }; + } + @Nonnull private ServerSocket createServerSocket() throws IOException { return new ServerSocket(0, 0, InetAddress.getLoopbackAddress()); 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 39181d3d67..5169d3c389 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 @@ -27,18 +27,12 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import com.aragost.javahg.Repository; -import com.google.common.base.Strings; import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.HgRepositoryFactory; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.RepositoryProvider; -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; //~--- JDK imports ------------------------------------------------------------ @@ -46,105 +40,32 @@ import java.util.function.BiConsumer; * * @author Sebastian Sdorra */ -public class HgCommandContext implements Closeable, RepositoryProvider -{ +public class HgCommandContext implements Closeable, RepositoryProvider { - /** Field description */ - private static final String PROPERTY_ENCODING = "hg.encoding"; + private final HgRepositoryHandler handler; + private final HgRepositoryFactory factory; + private final sonia.scm.repository.Repository scmRepository; - //~--- constructors --------------------------------------------------------- + private Repository repository; - /** - * Constructs ... - * - * - * @param hookManager - * @param handler - * @param repository - * @param directory - */ - public HgCommandContext(HgHookManager hookManager, - HgRepositoryHandler handler, sonia.scm.repository.Repository repository, - File directory) - { - this(hookManager, handler, repository, directory, - handler.getHgContext().isPending()); - } - - /** - * Constructs ... - * - * - * @param hookManager - * @param handler - * @param repository - * @param directory - * @param pending - */ - public HgCommandContext(HgHookManager hookManager, - HgRepositoryHandler handler, sonia.scm.repository.Repository repository, - File directory, boolean pending) - { - this.hookManager = hookManager; + public HgCommandContext(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository) { this.handler = handler; - this.directory = directory; - this.scmRepository = repository; - this.encoding = repository.getProperty(PROPERTY_ENCODING); - this.pending = pending; - - if (Strings.isNullOrEmpty(encoding)) - { - encoding = handler.getConfig().getEncoding(); - } + this.factory = factory; + this.scmRepository = scmRepository; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @throws IOException - */ - @Override - public void close() throws IOException - { - if (repository != null) - { - repository.close(); + public Repository open() { + if (repository == null) { + repository = factory.openForRead(scmRepository); } - } - - /** - * Method description - * - * - * @return - */ - public Repository open() - { - if (repository == null) - { - repository = HgUtil.open(handler, hookManager, directory, encoding, pending); - } - return repository; } - public Repository openWithSpecialEnvironment(BiConsumer> prepareEnvironment) - { - return HgUtil.open(handler, directory, encoding, - pending, environment -> prepareEnvironment.accept(scmRepository, environment)); + public Repository openForWrite() { + return factory.openForWrite(scmRepository); } - //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @return - */ public HgConfig getConfig() { return handler.getConfig(); @@ -159,25 +80,12 @@ public class HgCommandContext implements Closeable, RepositoryProvider return getScmRepository(); } - //~--- fields --------------------------------------------------------------- - /** Field description */ - private File directory; + @Override + public void close() { + if (repository != null) { + repository.close(); + } + } - /** Field description */ - private String encoding; - - /** Field description */ - private HgRepositoryHandler handler; - - /** Field description */ - private HgHookManager hookManager; - - /** Field description */ - private boolean pending; - - /** Field description */ - private Repository repository; - - private final sonia.scm.repository.Repository scmRepository; } 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 10fe43bcf7..98bddd8c7c 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 @@ -21,85 +21,56 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.repository.spi; -//~--- non-JDK imports -------------------------------------------------------- +package sonia.scm.repository.spi; import com.aragost.javahg.Repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.HgRepositoryFactory; import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.spi.javahg.HgLogChangesetCommand; import sonia.scm.web.HgUtil; -import java.io.File; - -//~--- JDK imports ------------------------------------------------------------ - /** * * @author Sebastian Sdorra */ -public class HgHookChangesetProvider implements HookChangesetProvider -{ +public class HgHookChangesetProvider implements HookChangesetProvider { - /** - * the logger for HgHookChangesetProvider - */ - private static final Logger logger = - LoggerFactory.getLogger(HgHookChangesetProvider.class); + private static final Logger LOG = LoggerFactory.getLogger(HgHookChangesetProvider.class); - //~--- constructors --------------------------------------------------------- + private final HgRepositoryHandler handler; + private final HgRepositoryFactory factory; + private final sonia.scm.repository.Repository scmRepository; + private final String startRev; - public HgHookChangesetProvider(HgRepositoryHandler handler, - File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type) - { + private HookChangesetResponse response; + + public HgHookChangesetProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository, String startRev) { this.handler = handler; - this.repositoryDirectory = repositoryDirectory; - this.hookManager = hookManager; + this.factory = factory; + this.scmRepository = scmRepository; this.startRev = startRev; - this.type = type; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * - * @return - */ @Override - public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) - { - if (response == null) - { + public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) { + if (response == null) { Repository repository = null; - try - { - repository = open(); + try { + repository = factory.openForRead(scmRepository); - HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, - handler.getConfig()); + HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, handler.getConfig()); response = new HookChangesetResponse( - cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute()); - } - catch (Exception ex) - { - logger.error("could not retrieve changesets", ex); - } - finally - { - if (repository != null) - { + cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute() + ); + } catch (Exception ex) { + LOG.error("could not retrieve changesets", ex); + } finally { + if (repository != null) { repository.close(); } } @@ -108,39 +79,4 @@ public class HgHookChangesetProvider implements HookChangesetProvider return response; } - /** - * Method description - * - * - * @return - */ - private Repository open() - { - // use HG_PENDING only for pre receive hooks - boolean pending = type == RepositoryHookType.PRE_RECEIVE; - - // TODO get repository encoding - return HgUtil.open(handler, hookManager, repositoryDirectory, null, - pending); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private HgRepositoryHandler handler; - - /** Field description */ - private HgHookManager hookManager; - - /** Field description */ - private File repositoryDirectory; - - /** Field description */ - private HookChangesetResponse response; - - /** Field description */ - private String startRev; - - /** Field description */ - private RepositoryHookType type; } 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 1f72d56e24..0282729baa 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 @@ -21,14 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.HgRepositoryFactory; import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.RepositoryHookType; +import sonia.scm.repository.Repository; import sonia.scm.repository.api.HgHookBranchProvider; import sonia.scm.repository.api.HgHookMessageProvider; import sonia.scm.repository.api.HgHookTagProvider; @@ -37,7 +37,6 @@ import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookMessageProvider; import sonia.scm.repository.api.HookTagProvider; -import java.io.File; import java.util.EnumSet; import java.util.Set; @@ -45,55 +44,40 @@ import java.util.Set; /** * Mercurial implementation of {@link HookContextProvider}. - * + * * @author Sebastian Sdorra */ -public class HgHookContextProvider extends HookContextProvider -{ +public class HgHookContextProvider extends HookContextProvider { - private static final Set SUPPORTED_FEATURES = - EnumSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.MESSAGE_PROVIDER, - HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER); + private static final Set SUPPORTED_FEATURES = EnumSet.of( + HookFeature.CHANGESET_PROVIDER, + HookFeature.MESSAGE_PROVIDER, + HookFeature.BRANCH_PROVIDER, + HookFeature.TAG_PROVIDER + ); - //~--- constructors --------------------------------------------------------- + private final HgHookChangesetProvider hookChangesetProvider; + private HgHookMessageProvider hgMessageProvider; + private HgHookBranchProvider hookBranchProvider; + private HgHookTagProvider hookTagProvider; - /** - * Constructs a new instance. - * - * @param handler mercurial repository handler - * @param repositoryDirectory the directory of the changed repository - * @param hookManager mercurial hook manager - * @param startRev start revision - * @param type type of hook - */ - public HgHookContextProvider(HgRepositoryHandler handler, - File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type) - { - this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type); + public HgHookContextProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository, String startRev) { + this.hookChangesetProvider = new HgHookChangesetProvider(handler, factory, repository, startRev); } - //~--- get methods ---------------------------------------------------------- - @Override - public HookBranchProvider getBranchProvider() - { - if (hookBranchProvider == null) - { + public HookBranchProvider getBranchProvider() { + if (hookBranchProvider == null) { hookBranchProvider = new HgHookBranchProvider(hookChangesetProvider); } - return hookBranchProvider; } @Override - public HookTagProvider getTagProvider() - { - if (hookTagProvider == null) - { + public HookTagProvider getTagProvider() { + if (hookTagProvider == null) { hookTagProvider = new HgHookTagProvider(hookChangesetProvider); } - return hookTagProvider; } @@ -102,14 +86,11 @@ public class HgHookContextProvider extends HookContextProvider { return hookChangesetProvider; } - - public HgHookMessageProvider getHgMessageProvider() - { - if (hgMessageProvider == null) - { + + public HgHookMessageProvider getHgMessageProvider() { + if (hgMessageProvider == null) { hgMessageProvider = new HgHookMessageProvider(); } - return hgMessageProvider; } @@ -119,21 +100,9 @@ public class HgHookContextProvider extends HookContextProvider return SUPPORTED_FEATURES; } - //~--- methods -------------------------------------------------------------- - @Override protected HookMessageProvider createMessageProvider() { return getHgMessageProvider(); } - - //~--- fields --------------------------------------------------------------- - - private final HgHookChangesetProvider hookChangesetProvider; - - private HgHookMessageProvider hgMessageProvider; - - private HgHookBranchProvider hookBranchProvider; - - private HgHookTagProvider hookTagProvider; } 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 ce64b06982..0188078b77 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 @@ -26,13 +26,12 @@ package sonia.scm.repository.spi; import com.google.common.io.Closeables; import sonia.scm.repository.Feature; -import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.HgRepositoryFactory; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.CommandNotSupportedException; -import java.io.File; import java.io.IOException; import java.util.EnumSet; import java.util.Set; @@ -41,11 +40,8 @@ import java.util.Set; * * @author Sebastian Sdorra */ -public class HgRepositoryServiceProvider extends RepositoryServiceProvider -{ +public class HgRepositoryServiceProvider extends RepositoryServiceProvider { - /** Field description */ - //J- public static final Set COMMANDS = EnumSet.of( Command.BLAME, Command.BROWSE, @@ -61,25 +57,19 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider Command.PULL, Command.MODIFY ); - //J+ - /** Field description */ - public static final Set FEATURES = - EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH); + public static final Set FEATURES = EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH); - //~--- constructors --------------------------------------------------------- + private final HgRepositoryHandler handler; + private final HgCommandContext context; - HgRepositoryServiceProvider(HgRepositoryHandler handler, - HgHookManager hookManager, Repository repository) - { + HgRepositoryServiceProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository) { this.handler = handler; - this.repositoryDirectory = handler.getDirectory(repository.getId()); - this.context = new HgCommandContext(hookManager, handler, repository, - repositoryDirectory); + this.context = new HgCommandContext(handler, factory, repository); } - //~--- methods -------------------------------------------------------------- + /** * Method description * @@ -91,9 +81,9 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider { Closeables.close(context, true); } - //~--- get methods ---------------------------------------------------------- + /** * Method description * @@ -271,14 +261,4 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider return new HgTagsCommand(context); } - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private HgCommandContext context; - - /** Field description */ - private HgRepositoryHandler handler; - - /** Field description */ - private File repositoryDirectory; } 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 4af799f30a..87a5d6d6fb 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 @@ -21,12 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; import com.google.inject.Inject; import sonia.scm.plugin.Extension; -import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.HgRepositoryFactory; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; @@ -35,18 +35,15 @@ import sonia.scm.repository.Repository; * @author Sebastian Sdorra */ @Extension -public class HgRepositoryServiceResolver implements RepositoryServiceResolver -{ +public class HgRepositoryServiceResolver implements RepositoryServiceResolver { private final HgRepositoryHandler handler; - private final HgHookManager hookManager; + private final HgRepositoryFactory factory; @Inject - public HgRepositoryServiceResolver(HgRepositoryHandler handler, - HgHookManager hookManager) - { + public HgRepositoryServiceResolver(HgRepositoryHandler handler, HgRepositoryFactory factory) { this.handler = handler; - this.hookManager = hookManager; + this.factory = factory; } @Override @@ -54,7 +51,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, factory, repository); } return provider; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java new file mode 100644 index 0000000000..7ea5b97dde --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java @@ -0,0 +1,114 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.io.ByteStreams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgVersion; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class HgVersionCommand { + + private static final Logger LOG = LoggerFactory.getLogger(HgVersionCommand.class); + + @VisibleForTesting + static final String[] HG_ARGS = { + "version", "--template", "{ver}" + }; + + @VisibleForTesting + static final String[] PYTHON_ARGS = { + "-c", "import sys; print(sys.version)" + }; + + private final HgConfig config; + private final ProcessExecutor executor; + + public HgVersionCommand(HgConfig config) { + this(config, command -> new ProcessBuilder(command).start()); + } + + HgVersionCommand(HgConfig config, ProcessExecutor executor) { + this.config = config; + this.executor = executor; + } + + public HgVersion get() { + return new HgVersion(getHgVersion(), getPythonVersion()); + } + + @Nonnull + private String getPythonVersion() { + try { + String content = exec(config.getPythonBinary(), PYTHON_ARGS); + int index = content.indexOf(' '); + if (index > 0) { + return content.substring(0, index); + } + } catch (IOException ex) { + LOG.warn("failed to get python version", ex); + } + return HgVersion.UNKNOWN; + } + + @Nonnull + private String getHgVersion() { + try { + return exec(config.getHgBinary(), HG_ARGS).trim(); + } catch (IOException ex) { + LOG.warn("failed to get mercurial version", ex); + return HgVersion.UNKNOWN; + } + } + + @SuppressWarnings("UnstableApiUsage") + private String exec(String command, String[] args) throws IOException { + List cmd = new ArrayList<>(); + cmd.add(command); + cmd.addAll(Arrays.asList(args)); + + Process process = executor.execute(cmd); + byte[] bytes = ByteStreams.toByteArray(process.getInputStream()); + int exitCode = process.exitValue(); + if (exitCode != 0) { + throw new IOException("process ends with exit code " + exitCode); + } + return new String(bytes, StandardCharsets.UTF_8); + } + + @FunctionalInterface + interface ProcessExecutor { + Process execute(List command) throws IOException; + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java index 4e4c4cf80c..c11e15ee4c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java @@ -36,27 +36,21 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.work.SimpleWorkingCopyFactory; import sonia.scm.repository.work.WorkingCopyPool; import sonia.scm.util.IOUtil; -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; public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory implements HgWorkingCopyFactory { - private final Provider hgRepositoryEnvironmentBuilder; - @Inject - public SimpleHgWorkingCopyFactory(Provider hgRepositoryEnvironmentBuilder, WorkingCopyPool workdirProvider) { + public SimpleHgWorkingCopyFactory(WorkingCopyPool workdirProvider) { super(workdirProvider); - this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; } + @Override public ParentAndClone initialize(HgCommandContext context, File target, String initialBranch) { - Repository centralRepository = openCentral(context); + Repository centralRepository = context.openForWrite(); CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository); if (initialBranch != null) { cloneCommand.updaterev(initialBranch); @@ -76,7 +70,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory reclaim(HgCommandContext context, File target, String initialBranch) throws ReclaimFailedException { - Repository centralRepository = openCentral(context); + Repository centralRepository = context.openForWrite(); try { BaseRepository clone = Repository.open(target); for (String unknown : StatusCommand.on(clone).execute().getUnknown()) { @@ -89,12 +83,6 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory> repositoryMapBiConsumer = - (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); - return context.openWithSpecialEnvironment(repositoryMapBiConsumer); - } - private void delete(File directory, String unknownFile) throws IOException { IOUtil.delete(new File(directory, unknownFile)); } 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 9762431d05..90fc52a77c 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; import com.google.common.base.Stopwatch; @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgEnvironmentBuilder; import sonia.scm.repository.HgPythonScript; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; @@ -42,29 +43,21 @@ import sonia.scm.web.cgi.CGIExecutor; import sonia.scm.web.cgi.CGIExecutorFactory; import sonia.scm.web.cgi.EnvList; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.IOException; - -import java.util.Enumeration; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import java.io.File; +import java.io.IOException; + +//~--- JDK imports ------------------------------------------------------------ /** * * @author Sebastian Sdorra */ @Singleton -public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet -{ - - /** Field description */ - public static final String ENV_SESSION_PREFIX = "SCM_"; +public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet { /** Field description */ private static final long serialVersionUID = -3492811300905099810L; @@ -80,13 +73,13 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet ScmConfiguration configuration, HgRepositoryHandler handler, RepositoryRequestListenerUtil requestListenerUtil, - HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder) + HgEnvironmentBuilder environmentBuilder) { this.cgiExecutorFactory = cgiExecutorFactory; this.configuration = configuration; this.handler = handler; this.requestListenerUtil = requestListenerUtil; - this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; + this.environmentBuilder = environmentBuilder; this.exceptionHandler = new HgCGIExceptionHandler(); this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext()); } @@ -108,11 +101,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet { handleRequest(request, response, repository); } - catch (ServletException ex) - { - exceptionHandler.handleException(request, response, ex); - } - catch (IOException ex) + catch (ServletException | IOException ex) { exceptionHandler.handleException(request, response, ex); } @@ -146,29 +135,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet } } - /** - * Method description - * - * - * @param env - * @param session - */ - @SuppressWarnings("unchecked") - private void passSessionAttributes(EnvList env, HttpSession session) - { - Enumeration enm = session.getAttributeNames(); - - while (enm.hasMoreElements()) - { - String key = enm.nextElement(); - - if (key.startsWith(ENV_SESSION_PREFIX)) - { - env.set(key, session.getAttribute(key).toString()); - } - } - } - /** * Method description * @@ -192,7 +158,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet executor.setExceptionHandler(exceptionHandler); executor.setStatusCodeHandler(exceptionHandler); executor.setContentLengthWorkaround(true); - hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap()); + + EnvList env = executor.getEnvironment(); + environmentBuilder.write(repository).forEach(env::set); String interpreter = getInterpreter(); @@ -248,5 +216,5 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet /** Field description */ private final RepositoryRequestListenerUtil requestListenerUtil; - private final HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder; + private final HgEnvironmentBuilder environmentBuilder; } 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 deleted file mode 100644 index 5b0609cc72..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.web; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.io.Closeables; -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; -import sonia.scm.NotFoundException; -import sonia.scm.repository.HgContext; -import sonia.scm.repository.HgHookManager; -import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.RepositoryHookType; -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.BearerToken; -import sonia.scm.security.CipherUtil; -import sonia.scm.util.HttpUtil; -import sonia.scm.util.Util; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -//~--- JDK imports ------------------------------------------------------------ - -/** - * - * @author Sebastian Sdorra - */ -@Singleton -public class HgHookCallbackServlet extends HttpServlet -{ - - /** Field description */ - public static final String HGHOOK_POST_RECEIVE = "changegroup"; - - /** Field description */ - public static final String HGHOOK_PRE_RECEIVE = "pretxnchangegroup"; - - /** Field description */ - public static final String PARAM_REPOSITORYID = "repositoryId"; - - /** Field description */ - private static final String PARAM_CHALLENGE = "challenge"; - - /** Field description */ - private static final String PARAM_TOKEN = "token"; - - /** Field description */ - private static final String PARAM_NODE = "node"; - - /** Field description */ - private static final String PARAM_PING = "ping"; - - /** Field description */ - private static final Pattern REGEX_URL = - Pattern.compile("^/hook/hg/([^/]+)$"); - - /** the logger for HgHookCallbackServlet */ - private static final Logger logger = - LoggerFactory.getLogger(HgHookCallbackServlet.class); - - /** Field description */ - private static final long serialVersionUID = 3531596724828189353L; - - //~--- constructors --------------------------------------------------------- - - @Inject - public HgHookCallbackServlet(HookEventFacade hookEventFacade, - HgRepositoryHandler handler, HgHookManager hookManager, - Provider contextProvider) - { - this.hookEventFacade = hookEventFacade; - this.handler = handler; - this.hookManager = hookManager; - this.contextProvider = contextProvider; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * @param response - * - * @throws IOException - * @throws ServletException - */ - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - { - String ping = request.getParameter(PARAM_PING); - - if (Util.isNotEmpty(ping) && Boolean.parseBoolean(ping)) - { - response.setStatus(HttpServletResponse.SC_NO_CONTENT); - } - else - { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - try { - handlePostRequest(request, response); - } catch (IOException ex) { - logger.warn("error in hook callback execution, sending internal server error", ex); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } - - private void handlePostRequest(HttpServletRequest request, HttpServletResponse response) throws IOException - { - String strippedURI = HttpUtil.getStrippedURI(request); - Matcher m = REGEX_URL.matcher(strippedURI); - - if (m.matches()) - { - String repositoryId = getRepositoryId(request); - String type = m.group(1); - String challenge = request.getParameter(PARAM_CHALLENGE); - - if (Util.isNotEmpty(challenge)) - { - String node = request.getParameter(PARAM_NODE); - - if (Util.isNotEmpty(node)) - { - String token = request.getParameter(PARAM_TOKEN); - - if (Util.isNotEmpty(token)) - { - authenticate(token); - } - - hookCallback(response, type, repositoryId, challenge, node); - } - else if (logger.isDebugEnabled()) - { - logger.debug("node parameter not found"); - } - } - else if (logger.isDebugEnabled()) - { - logger.debug("challenge parameter not found"); - } - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("url does not match"); - } - - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - } - } - - private void authenticate(String token) - { - try - { - token = CipherUtil.getInstance().decode(token); - - if (Util.isNotEmpty(token)) - { - Subject subject = SecurityUtils.getSubject(); - - AuthenticationToken accessToken = createToken(token); - - //J- - subject.login(accessToken); - } - } - catch (Exception ex) - { - logger.error("could not authenticate user", ex); - } - } - - private AuthenticationToken createToken(String tokenString) - { - return BearerToken.valueOf(tokenString); - } - - private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type) - throws IOException - { - HgHookContextProvider context = null; - - try - { - if (type == RepositoryHookType.PRE_RECEIVE) - { - contextProvider.get().setPending(true); - } - - File repositoryDirectory = handler.getDirectory(repositoryId); - context = new HgHookContextProvider(handler, repositoryDirectory, hookManager, - node, type); - - hookEventFacade.handle(repositoryId).fireHookEvent(type, context); - - printMessages(response, context); - } - catch (NotFoundException ex) - { - logger.error(ex.getMessage()); - - logger.trace("repository not found", ex); - - response.sendError(HttpServletResponse.SC_NOT_FOUND); - } - catch (Exception ex) - { - sendError(response, context, ex); - } - } - - private void hookCallback(HttpServletResponse response, String typeName, String repositoryId, String challenge, String node) throws IOException { - if (hookManager.isAcceptAble(challenge)) - { - RepositoryHookType type = null; - - if (HGHOOK_PRE_RECEIVE.equals(typeName)) - { - type = RepositoryHookType.PRE_RECEIVE; - } - else if (HGHOOK_POST_RECEIVE.equals(typeName)) - { - type = RepositoryHookType.POST_RECEIVE; - } - - if (type != null) - { - fireHook(response, repositoryId, node, type); - } - else - { - if (logger.isWarnEnabled()) - { - logger.warn("unknown hook type {}", typeName); - } - - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - } - } - else - { - if (logger.isWarnEnabled()) - { - logger.warn("hg hook challenge is not accept able"); - } - - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - } - } - - /** - * Method description - * - * - * @param writer - * @param msg - */ - private void printMessage(PrintWriter writer, HgHookMessage msg) - { - writer.append('_'); - - if (msg.getSeverity() == Severity.ERROR) - { - writer.append("e[SCM] Error: "); - } - else - { - writer.append("n[SCM] "); - } - - writer.println(msg.getMessage()); - } - - /** - * Method description - * - * - * @param response - * @param context - * - * @throws IOException - */ - private void printMessages(HttpServletResponse response, - HgHookContextProvider context) - throws IOException - { - List msgs = context.getHgMessageProvider().getMessages(); - - if (Util.isNotEmpty(msgs)) - { - PrintWriter writer = null; - - try - { - writer = response.getWriter(); - - printMessages(writer, msgs); - } - finally - { - Closeables.close(writer, false); - } - } - } - - /** - * Method description - * - * - * @param writer - * @param msgs - */ - private void printMessages(PrintWriter writer, List msgs) - { - for (HgHookMessage msg : msgs) - { - printMessage(writer, msg); - } - } - - /** - * Method description - * - * - * @param response - * @param context - * @param ex - * - * @throws IOException - */ - private void sendError(HttpServletResponse response, - HgHookContextProvider context, Exception ex) - throws IOException - { - logger.warn("hook ended with exception", ex); - response.setStatus(HttpServletResponse.SC_CONFLICT); - - String msg = ex.getMessage(); - List msgs = null; - - if (context != null) - { - msgs = context.getHgMessageProvider().getMessages(); - } - - if (!Strings.isNullOrEmpty(msg) || Util.isNotEmpty(msgs)) - { - PrintWriter writer = null; - - try - { - writer = response.getWriter(); - - if (Util.isNotEmpty(msgs)) - { - printMessages(writer, msgs); - } - - if (!Strings.isNullOrEmpty(msg)) - { - printMessage(writer, new HgHookMessage(Severity.ERROR, msg)); - } - } - finally - { - Closeables.close(writer, true); - } - } - } - - //~--- get methods ---------------------------------------------------------- - - private String getRepositoryId(HttpServletRequest request) - { - String id = request.getParameter(PARAM_REPOSITORYID); - Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "repository id not found in request"); - return id; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final Provider contextProvider; - - /** Field description */ - private final HgRepositoryHandler handler; - - /** Field description */ - private final HookEventFacade hookEventFacade; - - /** Field description */ - private final HgHookManager hookManager; -} 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 deleted file mode 100644 index 6b626b5c33..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.web; - -import sonia.scm.repository.HgEnvironment; -import sonia.scm.repository.HgHookManager; -import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.Repository; - -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; -import java.io.File; -import java.util.Map; - -public class HgRepositoryEnvironmentBuilder { - - 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 final HgRepositoryHandler handler; - private final HgHookManager hookManager; - - @Inject - public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) { - this.handler = handler; - this.hookManager = hookManager; - } - - public void buildFor(Repository repository, HttpServletRequest request, Map environment) { - File directory = handler.getDirectory(repository.getId()); - - 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 - if (handler.getConfig().isDisableHookSSLValidation()) { - // disable ssl validation - // Issue 959: https://goo.gl/zH5eY8 - environment.put(ENV_PYTHON_HTTPS_VERIFY, "0"); - } - - // enable experimental httppostargs protocol of mercurial - // Issue 970: https://goo.gl/poascp - environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs())); - - HgEnvironment.prepareEnvironment( - environment, - handler, - hookManager, - request - ); - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java index 5271751faa..64a27990a8 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java @@ -34,9 +34,6 @@ import sonia.scm.api.v2.resources.HgConfigPackagesToDtoMapper; import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper; import sonia.scm.installer.HgPackageReader; import sonia.scm.plugin.Extension; -import sonia.scm.repository.HgContext; -import sonia.scm.repository.HgContextProvider; -import sonia.scm.repository.HgHookManager; import sonia.scm.repository.spi.HgWorkingCopyFactory; import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory; @@ -45,26 +42,10 @@ import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory; * @author Sebastian Sdorra */ @Extension -public class HgServletModule extends ServletModule -{ +public class HgServletModule extends ServletModule { - /** Field description */ - public static final String MAPPING_HG = "/hg/*"; - - /** Field description */ - public static final String MAPPING_HOOK = "/hook/hg/*"; - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - */ @Override - protected void configureServlets() - { - bind(HgContext.class).toProvider(HgContextProvider.class); - bind(HgHookManager.class); + protected void configureServlets() { bind(HgPackageReader.class); bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass()); @@ -72,9 +53,6 @@ public class HgServletModule extends ServletModule bind(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass()); bind(HgConfigInstallationsToDtoMapper.class); - // bind servlets - serve(MAPPING_HOOK).with(HgHookCallbackServlet.class); - bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class); } } 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 234580ed6f..3ba797aff2 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 @@ -21,189 +21,48 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; -//~--- non-JDK imports -------------------------------------------------------- - -import com.aragost.javahg.Repository; -import com.aragost.javahg.RepositoryConfiguration; - -import com.google.common.base.Strings; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import sonia.scm.SCMContext; import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgEnvironment; -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.util.HttpUtil; import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; -import java.nio.charset.Charset; -import java.util.Map; -import java.util.function.Consumer; - -import javax.servlet.http.HttpServletRequest; - /** * * @author Sebastian Sdorra */ -public final class HgUtil -{ +public final class HgUtil { - /** Field description */ public static final String REVISION_TIP = "tip"; - /** Field description */ - private static final String USERAGENT_HG = "mercurial/"; - - /** - * the logger for HgUtil - */ - private static final Logger logger = LoggerFactory.getLogger(HgUtil.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - */ private HgUtil() {} - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param handler - * @param hookManager - * @param directory - * @param encoding - * @param pending - * - * @return - */ - public static Repository open(HgRepositoryHandler handler, - HgHookManager hookManager, File directory, String encoding, boolean pending) - { - return open( - handler, - directory, - encoding, - pending, - environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager) - ); - } - - public static Repository open(HgRepositoryHandler handler, - File directory, String encoding, boolean pending, - Consumer> prepareEnvironment) - { - String enc = encoding; - - if (Strings.isNullOrEmpty(enc)) - { - enc = handler.getConfig().getEncoding(); - } - - RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT; - - prepareEnvironment.accept(repoConfiguration.getEnvironment()); - - repoConfiguration.addExtension(HgFileviewExtension.class); - repoConfiguration.setEnablePendingChangesets(pending); - - try - { - Charset charset = Charset.forName(enc); - - logger.trace("set encoding {} for mercurial", enc); - - repoConfiguration.setEncoding(charset); - } - catch (IllegalArgumentException ex) - { - logger.error("could not set encoding for mercurial", ex); - } - - repoConfiguration.setHgBin(handler.getConfig().getHgBinary()); - - logger.debug("open hg repository {}: encoding: {}, pending: {}", directory, enc, pending); - - return Repository.open(repoConfiguration, directory); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param config - * - * @return - */ - public static String getPythonPath(HgConfig config) - { + public static String getPythonPath(HgConfig config) { String pythonPath = Util.EMPTY_STRING; - if (config != null) - { + if (config != null) { pythonPath = Util.nonNull(config.getPythonPath()); } - if (Util.isNotEmpty(pythonPath)) - { + if (Util.isNotEmpty(pythonPath)) { pythonPath = pythonPath.concat(File.pathSeparator); } - //J- pythonPath = pythonPath.concat( HgPythonScript.getScriptDirectory( SCMContext.getContext() ).getAbsolutePath() ); - //J+ return pythonPath; } - /** - * Method description - * - * - * @param revision - * - * @return - */ - public static String getRevision(String revision) - { - return Util.isEmpty(revision) - ? REVISION_TIP - : revision; + public static String getRevision(String revision) { + return Util.isEmpty(revision) ? REVISION_TIP : revision; } - /** - * Returns true if the request comes from a mercurial client. - * - * - * @param request servlet request - * - * @return true if the client is mercurial - */ - public static boolean isHgClient(HttpServletRequest request) - { - return HttpUtil.userAgentStartsWith(request, USERAGENT_HG); - } } 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 eb7718bb87..f6166fab1a 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 @@ -29,87 +29,40 @@ # changegroup.scm = python:scmhooks.callback # -import os, sys - -client = None - -# compatibility layer between python 2 and 3 urllib implementations -if sys.version_info[0] < 3: - import urllib, urllib2 - # create type alias for url error - URLError = urllib2.URLError - - class Python2Client: - def post(self, url, values): - data = urllib.urlencode(values) - # open url but ignore proxy settings - proxy_handler = urllib2.ProxyHandler({}) - opener = urllib2.build_opener(proxy_handler) - req = urllib2.Request(url, data) - req.add_header("X-XSRF-Token", xsrf) - return opener.open(req) - - client = Python2Client() -else: - import urllib.parse, urllib.request, urllib.error - # create type alias for url error - URLError = urllib.error.URLError - - class Python3Client: - def post(self, url, values): - data = urllib.parse.urlencode(values) - # open url but ignore proxy settings - proxy_handler = urllib.request.ProxyHandler({}) - opener = urllib.request.build_opener(proxy_handler) - req = urllib.request.Request(url, data.encode()) - req.add_header("X-XSRF-Token", xsrf) - return opener.open(req) - - client = Python3Client() +import os, sys, json, socket # read environment -baseUrl = os.environ['SCM_URL'] +port = os.environ['SCM_HOOK_PORT'] challenge = os.environ['SCM_CHALLENGE'] token = os.environ['SCM_BEARER_TOKEN'] -xsrf = os.environ['SCM_XSRF'] repositoryId = os.environ['SCM_REPOSITORY_ID'] -def printMessages(ui, msgs): - for raw in msgs: - line = raw - if hasattr(line, "encode"): - line = line.encode() - if line.startswith(b"_e") or line.startswith(b"_n"): - line = line[2:] - ui.warn(b'%s\n' % line.rstrip()) +def print_messages(ui, messages): + for message in messages: + ui.warn(b'%s: %s\n' % message['severity'], message['message']) -def callHookUrl(ui, repo, hooktype, node): +def fire_hook(ui, repo, hooktype, node): abort = True try: - url = baseUrl + hooktype.decode("utf-8") - ui.debug( b"send scm-hook to " + url.encode() + b" and " + node + b"\n" ) - values = {'node': node.decode("utf-8"), 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId} - conn = client.post(url, values) - if 200 <= conn.code < 300: - ui.debug( b"scm-hook " + hooktype + b" success with status code " + str(conn.code).encode() + b"\n" ) - printMessages(ui, conn) - abort = False - else: - ui.warn( b"ERROR: scm-hook failed with error code " + str(conn.code).encode() + b"\n" ) - except URLError as e: - msg = None - # some URLErrors have no read method - if hasattr(e, "read"): - msg = e.read() - elif hasattr(e, "code"): - msg = "scm-hook failed with error code " + e.code + "\n" - else: - msg = str(e) - if len(msg) > 0: - printMessages(ui, msg.splitlines(True)) - else: - ui.warn( b"ERROR: scm-hook failed with an unknown error\n" ) - ui.traceback() + values = {'token': token, 'type': hooktype, 'repositoryId': repositoryId, 'challenge': challenge, 'node': node.decode('utf8') } + ui.debug( b"send scm-hook for " + node + b"\n" ) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(("127.0.0.1", int(port))) + s.sendall(json.dumps(values).encode('utf-8')) + s.sendall(b'\0') + + received = [] + received = s.recv(1) + while b != b'\0': + received.append(b) + received = s.recv(1) + + message = b''.join(bytes).decode('utf-8') + response = json.loads(message) + + abort = response['abort'] + print_messages(ui, response['messages']) + except ValueError: ui.warn( b"scm-hook failed with an exception\n" ) ui.traceback() @@ -118,8 +71,8 @@ def callHookUrl(ui, repo, hooktype, node): def callback(ui, repo, hooktype, node=None): abort = True if node != None: - if len(baseUrl) > 0: - abort = callHookUrl(ui, repo, hooktype, node) + if len(port) > 0: + abort = fire_hook(ui, repo, hooktype, node) else: ui.warn(b"ERROR: scm-manager hooks are disabled, please check your configuration and the scm-manager log for details\n") abort = False @@ -127,7 +80,7 @@ def callback(ui, repo, hooktype, node=None): ui.warn(b"changeset node is not available") return abort -def preHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs): +def pre_hook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs): # older mercurial versions if pending != None: pending() @@ -146,7 +99,7 @@ def preHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs): ui.debug(b"mercurial does not support currenttransation") # do nothing - return callback(ui, repo, hooktype, node) + return callback(ui, repo, "PRE_RECEIVE", node) -def postHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs): - return callback(ui, repo, hooktype, node) +def post_hook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs): + return callback(ui, repo, "POST_RECEIVE", node) diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java index 028276cd62..7779380638 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import org.junit.Test; @@ -52,7 +52,6 @@ public class HgConfigDtoToHgConfigMapperTest { assertEquals("/etc/", config.getPythonPath()); assertTrue(config.isShowRevisionInId()); assertTrue(config.isUseOptimizedBytecode()); - assertTrue(config.isDisableHookSSLValidation()); assertTrue(config.isEnableHttpPostArgs()); } @@ -65,7 +64,6 @@ public class HgConfigDtoToHgConfigMapperTest { configDto.setPythonPath("/etc/"); configDto.setShowRevisionInId(true); configDto.setUseOptimizedBytecode(true); - configDto.setDisableHookSSLValidation(true); configDto.setEnableHttpPostArgs(true); return configDto; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/DefaultHgEnvironmentBuilderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/DefaultHgEnvironmentBuilderTest.java new file mode 100644 index 0000000000..53ae99e631 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/DefaultHgEnvironmentBuilderTest.java @@ -0,0 +1,149 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.SCMContext; +import sonia.scm.repository.hooks.HookEnvironment; +import sonia.scm.repository.hooks.HookServer; +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilderFactory; +import sonia.scm.security.CipherUtil; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static sonia.scm.repository.DefaultHgEnvironmentBuilder.*; + + +@ExtendWith(MockitoExtension.class) +class DefaultHgEnvironmentBuilderTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private AccessTokenBuilderFactory accessTokenBuilderFactory; + + @Mock + private HgRepositoryHandler repositoryHandler; + + @Mock + private HookEnvironment hookEnvironment; + + @Mock + private HookServer server; + + @InjectMocks + private DefaultHgEnvironmentBuilder builder; + + private Path directory; + + @BeforeEach + void setBaseDir(@TempDir Path directory) { + this.directory = directory; + TempSCMContextProvider context = (TempSCMContextProvider) SCMContext.getContext(); + context.setBaseDirectory(directory.resolve("home").toFile()); + } + + @Test + void shouldReturnReadEnvironment() { + Repository heartOfGold = prepareForRead("/usr/lib/python", "42"); + + Map env = builder.read(heartOfGold); + assertReadEnv(env, "/usr/lib/python", "42"); + } + + @Test + void shouldReturnWriteEnvironment() throws IOException { + Repository heartOfGold = prepareForWrite("/opt/python", "21"); + + Map env = builder.write(heartOfGold); + assertReadEnv(env, "/opt/python", "21"); + + String bearer = CipherUtil.getInstance().decode(env.get(ENV_BEARER_TOKEN)); + assertThat(bearer).isEqualTo("secretAC"); + assertThat(env) + .containsEntry(ENV_CHALLENGE, "challenge") + .containsEntry(ENV_HOOK_PORT, "2042"); + } + + @Test + void shouldThrowIllegalStateIfServerCouldNotBeStarted() throws IOException { + when(server.start()).thenThrow(new IOException("failed to start")); + Repository repository = prepareForRead("/usr", "42"); + assertThrows(IllegalStateException.class, () -> builder.write(repository)); + } + + private Repository prepareForWrite(String pythonPath, String id) throws IOException { + Repository heartOfGold = prepareForRead(pythonPath, id); + applyAccessToken("secretAC"); + when(server.start()).thenReturn(2042); + when(hookEnvironment.getChallenge()).thenReturn("challenge"); + return heartOfGold; + } + + private void applyAccessToken(String compact) { + AccessToken accessToken = mock(AccessToken.class); + when(accessTokenBuilderFactory.create().build()).thenReturn(accessToken); + when(accessToken.compact()).thenReturn(compact); + } + + + private void assertReadEnv(Map env, String pythonPath, String repositoryId) { + assertThat(env) + .containsEntry(ENV_REPOSITORY_ID, repositoryId) + .containsEntry(ENV_REPOSITORY_NAME, "hitchhiker/HeartOfGold") + .containsEntry(ENV_HTTP_POST_ARGS, "false") + .containsEntry(ENV_REPOSITORY_PATH, directory.resolve("repo").toAbsolutePath().toString()) + .containsEntry(ENV_PYTHON_PATH, pythonPath + File.pathSeparator + directory.resolve("home/lib/python")); + } + + @Nonnull + private Repository prepareForRead(String pythonPath, String id) { + when(repositoryHandler.getDirectory(id)).thenReturn(directory.resolve("repo").toFile()); + + HgConfig config = new HgConfig(); + config.setPythonPath(pythonPath); + when(repositoryHandler.getConfig()).thenReturn(config); + + Repository heartOfGold = RepositoryTestData.createHeartOfGold(); + heartOfGold.setId(id); + + return heartOfGold; + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersionHandler.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/EmptyHgEnvironmentBuilder.java similarity index 66% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersionHandler.java rename to scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/EmptyHgEnvironmentBuilder.java index 7ee499741f..140802ce7f 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersionHandler.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/EmptyHgEnvironmentBuilder.java @@ -21,30 +21,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; -//~--- JDK imports ------------------------------------------------------------ +import java.util.Collections; +import java.util.Map; -import java.io.File; -import java.io.IOException; - -/** - * - * @author Sebastian Sdorra - */ -public class HgVersionHandler extends AbstractHgHandler -{ - - public HgVersionHandler(HgRepositoryHandler handler, HgContext context, - File directory) - { - super(handler, context, null, directory); +public class EmptyHgEnvironmentBuilder implements HgEnvironmentBuilder { + @Override + public Map read(Repository repository) { + return Collections.emptyMap(); } - //~--- get methods ---------------------------------------------------------- - - public HgVersion getVersion() throws IOException { - return getResultFromScript(HgVersion.class, HgPythonScript.VERSION); + @Override + public Map write(Repository repository) { + return Collections.emptyMap(); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java deleted file mode 100644 index a65e4cd8e2..0000000000 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.repository; - -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Key; -import com.google.inject.OutOfScopeException; -import com.google.inject.Provider; -import com.google.inject.ProvisionException; -import com.google.inject.Scope; -import com.google.inject.servlet.RequestScoped; -import com.google.inject.util.Providers; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class HgContextProviderTest { - - @Mock - private Scope scope; - - @Test - void shouldThrowNonOutOfScopeProvisionExceptions() { - Provider provider = () -> { - throw new RuntimeException("something different"); - }; - - when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); - - Injector injector = Guice.createInjector(new HgContextModule(scope)); - - assertThrows(ProvisionException.class, () -> injector.getInstance(HgContext.class)); - } - - @Test - void shouldCreateANewInstanceIfOutOfRequestScope() { - Provider provider = () -> { - throw new OutOfScopeException("no request"); - }; - when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); - - Injector injector = Guice.createInjector(new HgContextModule(scope)); - - HgContext contextOne = injector.getInstance(HgContext.class); - HgContext contextTwo = injector.getInstance(HgContext.class); - - assertThat(contextOne).isNotSameAs(contextTwo); - } - - @Test - void shouldInjectFromRequestScope() { - HgContextRequestStore requestStore = new HgContextRequestStore(); - Provider provider = Providers.of(requestStore); - - when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); - - Injector injector = Guice.createInjector(new HgContextModule(scope)); - - HgContext contextOne = injector.getInstance(HgContext.class); - HgContext contextTwo = injector.getInstance(HgContext.class); - - assertThat(contextOne).isSameAs(contextTwo); - } - - private static class HgContextModule extends AbstractModule { - - private Scope scope; - - private HgContextModule(Scope scope) { - this.scope = scope; - } - - @Override - protected void configure() { - bindScope(RequestScoped.class, scope); - bind(HgContextRequestStore.class); - bind(HgContext.class).toProvider(HgContextProvider.class); - } - } -} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryFactoryTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryFactoryTest.java new file mode 100644 index 0000000000..ffeb42f613 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryFactoryTest.java @@ -0,0 +1,125 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import com.aragost.javahg.Repository; +import com.google.common.collect.ImmutableMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.hooks.HookEnvironment; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class HgRepositoryFactoryTest { + + private HgRepositoryHandler handler; + + @Mock + private HookEnvironment hookEnvironment; + + @Mock + private HgEnvironmentBuilder environmentBuilder; + + private HgRepositoryFactory factory; + + private sonia.scm.repository.Repository heartOfGold; + + @BeforeEach + void setUpFactory(@TempDir Path directory) { + handler = HgTestUtil.createHandler(directory.toFile()); + assumeTrue(handler.isConfigured()); + + factory = new HgRepositoryFactory(handler, hookEnvironment, environmentBuilder); + heartOfGold = createRepository(); + } + + @Test + void shouldOpenRepositoryForRead() { + Repository repository = factory.openForRead(heartOfGold); + + assertThat(repository).isNotNull(); + verify(environmentBuilder).read(heartOfGold); + } + + @Test + void shouldOpenRepositoryForWrite() { + Repository repository = factory.openForWrite(heartOfGold); + + assertThat(repository).isNotNull(); + verify(environmentBuilder).write(heartOfGold); + } + + @Test + void shouldFallbackToUTF8OnUnknownEncoding() { + handler.getConfig().setEncoding("unknown"); + + Repository repository = factory.openForRead(heartOfGold); + + assertThat(repository.getBaseRepository().getConfiguration().getEncoding()).isEqualTo(StandardCharsets.UTF_8); + } + + @Test + void shouldSetPendingChangesetState() { + when(hookEnvironment.isPending()).thenReturn(true); + + Repository repository = factory.openForRead(heartOfGold); + + assertThat(repository.getBaseRepository().getConfiguration().isEnablePendingChangesets()) + .isTrue(); + } + + @Test + void shouldPassEnvironment() { + when(environmentBuilder.read(heartOfGold)).thenReturn(ImmutableMap.of("spaceship", "heartOfGold")); + + Repository repository = factory.openForRead(heartOfGold); + + assertThat(repository.getBaseRepository().getConfiguration().getEnvironment()) + .containsEntry("spaceship", "heartOfGold"); + } + + @Nonnull + private sonia.scm.repository.Repository createRepository() { + sonia.scm.repository.Repository heartOfGold = RepositoryTestData.createHeartOfGold("hg"); + heartOfGold.setId("42"); + + handler.create(heartOfGold); + return heartOfGold; + } + + +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index e91659e841..7d0dc6f6db 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- @@ -32,13 +32,17 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.repository.spi.HgVersionCommand; import sonia.scm.store.ConfigurationStoreFactory; import java.io.File; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; //~--- JDK imports ------------------------------------------------------------ @@ -52,9 +56,6 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock private ConfigurationStoreFactory factory; - @Mock - private com.google.inject.Provider provider; - @Override protected void checkDirectory(File directory) { File hgDirectory = new File(directory, ".hg"); @@ -70,7 +71,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { - HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null, null); + HgRepositoryHandler handler = new HgRepositoryHandler(factory, locationResolver, null, null); handler.init(contextProvider); HgTestUtil.checkForSkip(handler); @@ -80,7 +81,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { - HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null, null); + HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, locationResolver, null, null); HgConfig hgConfig = new HgConfig(); hgConfig.setHgBinary("hg"); @@ -91,4 +92,20 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { File path = repositoryHandler.getDirectory(repository.getId()); assertEquals(repoPath.toString() + File.separator + RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); } + + @Test + public void shouldReturnVersionInformation() { + PluginLoader pluginLoader = mock(PluginLoader.class); + when(pluginLoader.getUberClassLoader()).thenReturn(HgRepositoryHandler.class.getClassLoader()); + + HgVersionCommand versionCommand = mock(HgVersionCommand.class); + when(versionCommand.get()).thenReturn(new HgVersion("5.2.0", "3.7.2")); + + HgRepositoryHandler handler = new HgRepositoryHandler( + factory, locationResolver, pluginLoader, null + ); + + String versionInformation = handler.getVersionInformation(versionCommand); + assertThat(versionInformation).startsWith("scm-hg-version/").endsWith("python/3.7.2 mercurial/5.2.0"); + } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java index 72d276b2b6..48b8e4c506 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- @@ -29,16 +29,13 @@ package sonia.scm.repository; import org.junit.Assume; import sonia.scm.SCMContext; import sonia.scm.TempDirRepositoryLocationResolver; -import sonia.scm.security.AccessToken; +import sonia.scm.repository.hooks.HookEnvironment; import sonia.scm.store.InMemoryConfigurationStoreFactory; -import javax.servlet.http.HttpServletRequest; import java.io.File; -import java.nio.file.Path; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; //~--- JDK imports ------------------------------------------------------------ @@ -80,49 +77,26 @@ public final class HgTestUtil } } - /** - * Method description - * - * - * @param directory - * - * @return - */ - public static HgRepositoryHandler createHandler(File directory) { - TempSCMContextProvider context = - (TempSCMContextProvider) SCMContext.getContext(); + public static HgRepositoryHandler createHandler(File directory) { + TempSCMContextProvider context = (TempSCMContextProvider) SCMContext.getContext(); context.setBaseDirectory(directory); - RepositoryDAO repoDao = mock(RepositoryDAO.class); - RepositoryLocationResolver repositoryLocationResolver = new TempDirRepositoryLocationResolver(directory); - HgRepositoryHandler handler = - new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null); + HgRepositoryHandler handler = new HgRepositoryHandler( + new InMemoryConfigurationStoreFactory(), + repositoryLocationResolver, + null, + null + ); handler.init(context); return handler; } - /** - * Method description - * - * - * @return - */ - public static HgHookManager createHookManager() - { - HgHookManager hookManager = mock(HgHookManager.class); - - when(hookManager.getChallenge()).thenReturn("challenge"); - when(hookManager.createUrl()).thenReturn( - "http://localhost:8081/scm/hook/hg/"); - when(hookManager.createUrl(any(HttpServletRequest.class))).thenReturn( - "http://localhost:8081/scm/hook/hg/"); - AccessToken accessToken = mock(AccessToken.class); - when(accessToken.compact()).thenReturn(""); - when(hookManager.getAccessToken()).thenReturn(accessToken); - - return hookManager; + public static HgRepositoryFactory createFactory(HgRepositoryHandler handler, File directory) { + return new HgRepositoryFactory( + handler, new HookEnvironment(), new EmptyHgEnvironmentBuilder(), repository -> directory + ); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HgHookHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/DefaultHookHandlerTest.java similarity index 63% rename from scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HgHookHandlerTest.java rename to scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/DefaultHookHandlerTest.java index 516b66c4a3..45a9bc5aa5 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HgHookHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/DefaultHookHandlerTest.java @@ -24,7 +24,6 @@ package sonia.scm.repository.hooks; -import com.google.inject.util.Providers; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; @@ -35,10 +34,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.Repository; +import sonia.scm.NotFoundException; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.api.HgHookMessage; +import sonia.scm.repository.api.HgHookMessageProvider; import sonia.scm.repository.spi.HgHookContextProvider; import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.security.CipherUtil; @@ -48,6 +47,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.Socket; +import java.util.concurrent.atomic.AtomicReference; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -55,10 +55,13 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class HgHookHandlerTest { +class DefaultHookHandlerTest { @Mock - private HgRepositoryHandler repositoryHandler; + private HookContextProviderFactory hookContextProviderFactory; + + @Mock + private HgHookContextProvider contextProvider; @Mock private HookEventFacade hookEventFacade; @@ -71,7 +74,7 @@ class HgHookHandlerTest { private HookEnvironment hookEnvironment; - private HgHookHandler handler; + private DefaultHookHandler handler; @Mock private Subject subject; @@ -81,11 +84,13 @@ class HgHookHandlerTest { ThreadContext.bind(subject); hookEnvironment = new HookEnvironment(); - DefaultHookHandlerFactory factory = new DefaultHookHandlerFactory( - repositoryHandler, hookEventFacade, Providers.of(hookEnvironment) - ); - handler = factory.create(socket); + handler = new DefaultHookHandler(hookContextProviderFactory, hookEventFacade, hookEnvironment, socket); + } + + private void mockMessageProvider() { + when(hookContextProviderFactory.create("42", "abc")).thenReturn(contextProvider); + when(contextProvider.getHgMessageProvider()).thenReturn(new HgHookMessageProvider()); } @AfterEach @@ -95,10 +100,11 @@ class HgHookHandlerTest { @Test void shouldFireHook() throws IOException { + mockMessageProvider(); when(hookEventFacade.handle("42")).thenReturn(hookEventHandler); - HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE); - HgHookHandler.Response response = send(request); + DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE); + DefaultHookHandler.Response response = send(request); assertSuccess(response, RepositoryHookType.POST_RECEIVE); assertThat(hookEnvironment.isPending()).isFalse(); @@ -106,13 +112,22 @@ class HgHookHandlerTest { @Test void shouldSetPendingStateOnPreReceiveHooks() throws IOException { + mockMessageProvider(); when(hookEventFacade.handle("42")).thenReturn(hookEventHandler); - HgHookHandler.Request request = createRequest(RepositoryHookType.PRE_RECEIVE); - HgHookHandler.Response response = send(request); + // we have to capture the pending state, when the hook is fired + // because the state is cleared before the method ends + AtomicReference ref = new AtomicReference<>(Boolean.FALSE); + doAnswer(ic -> { + ref.set(hookEnvironment.isPending()); + return null; + }).when(hookEventHandler).fireHookEvent(RepositoryHookType.PRE_RECEIVE, contextProvider); + + DefaultHookHandler.Request request = createRequest(RepositoryHookType.PRE_RECEIVE); + DefaultHookHandler.Response response = send(request); assertSuccess(response, RepositoryHookType.PRE_RECEIVE); - assertThat(hookEnvironment.isPending()).isTrue(); + assertThat(ref.get()).isTrue(); } @Test @@ -121,8 +136,8 @@ class HgHookHandlerTest { .when(hookEventFacade) .handle("42"); - HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE); - HgHookHandler.Response response = send(request); + DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE); + DefaultHookHandler.Response response = send(request); assertError(response, "unknown"); } @@ -133,28 +148,40 @@ class HgHookHandlerTest { .when(subject) .login(any(AuthenticationToken.class)); - HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE); - HgHookHandler.Response response = send(request); + DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE); + DefaultHookHandler.Response response = send(request); assertError(response, "authentication"); } + @Test + void shouldHandleNotFoundException() throws IOException { + doThrow(NotFoundException.class) + .when(hookEventFacade) + .handle("42"); + + DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE); + DefaultHookHandler.Response response = send(request); + + assertError(response, "not found"); + } + @Test void shouldReturnErrorWithInvalidChallenge() throws IOException { - HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE, "something-different"); - HgHookHandler.Response response = send(request); + DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE, "something-different"); + DefaultHookHandler.Response response = send(request); assertError(response, "challenge"); } - private void assertSuccess(HgHookHandler.Response response, RepositoryHookType type) { + private void assertSuccess(DefaultHookHandler.Response response, RepositoryHookType type) { assertThat(response.getMessages()).isEmpty(); assertThat(response.isAbort()).isFalse(); verify(hookEventHandler).fireHookEvent(eq(type), any(HgHookContextProvider.class)); } - private void assertError(HgHookHandler.Response response, String message) { + private void assertError(DefaultHookHandler.Response response, String message) { assertThat(response.isAbort()).isTrue(); assertThat(response.getMessages()).hasSize(1); HgHookMessage hgHookMessage = response.getMessages().get(0); @@ -163,19 +190,19 @@ class HgHookHandlerTest { } @Nonnull - private HgHookHandler.Request createRequest(RepositoryHookType type) { + private DefaultHookHandler.Request createRequest(RepositoryHookType type) { return createRequest(type, hookEnvironment.getChallenge()); } @Nonnull - private HgHookHandler.Request createRequest(RepositoryHookType type, String challenge) { + private DefaultHookHandler.Request createRequest(RepositoryHookType type, String challenge) { String secret = CipherUtil.getInstance().encode("secret"); - return new HgHookHandler.Request( + return new DefaultHookHandler.Request( secret, type, "42", challenge, "abc" ); } - private HgHookHandler.Response send(HgHookHandler.Request request) throws IOException { + private DefaultHookHandler.Response send(DefaultHookHandler.Request request) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); Sockets.send(buffer, request); ByteArrayInputStream input = new ByteArrayInputStream(buffer.toByteArray()); @@ -185,7 +212,7 @@ class HgHookHandlerTest { handler.run(); - return Sockets.read(new ByteArrayInputStream(output.toByteArray()), HgHookHandler.Response.class); + return Sockets.read(new ByteArrayInputStream(output.toByteArray()), DefaultHookHandler.Response.class); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgEnvironmentTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HookContextProviderFactoryTest.java similarity index 52% rename from scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgEnvironmentTest.java rename to scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HookContextProviderFactoryTest.java index b290eb229a..b5b74509ee 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgEnvironmentTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HookContextProviderFactoryTest.java @@ -21,58 +21,50 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.repository; +package sonia.scm.repository.hooks; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import sonia.scm.security.AccessToken; -import sonia.scm.security.Xsrf; +import sonia.scm.NotFoundException; +import sonia.scm.repository.HgRepositoryFactory; +import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.repository.spi.HgHookContextProvider; -import java.util.HashMap; -import java.util.Map; - -import static java.util.Optional.empty; -import static java.util.Optional.of; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.Mockito.mock; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class HgEnvironmentTest { +class HookContextProviderFactoryTest { @Mock - HgRepositoryHandler handler; + private RepositoryManager repositoryManager; + @Mock - HgHookManager hookManager; + private HgRepositoryHandler repositoryHandler; + + @Mock + private HgRepositoryFactory repositoryFactory; + + @InjectMocks + private HookContextProviderFactory factory; @Test - void shouldExtractXsrfTokenWhenSet() { - AccessToken accessToken = mock(AccessToken.class); - when(accessToken.compact()).thenReturn(""); - when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(of("XSRF Token")); - when(hookManager.getAccessToken()).thenReturn(accessToken); - - Map environment = new HashMap<>(); - HgEnvironment.prepareEnvironment(environment, handler, hookManager); - - assertThat(environment).contains(entry("SCM_XSRF", "XSRF Token")); + void shouldCreateHookContextProvider() { + when(repositoryManager.get("42")).thenReturn(RepositoryTestData.create42Puzzle()); + HgHookContextProvider provider = factory.create("42", "xyz"); + assertThat(provider).isNotNull(); } @Test - void shouldIgnoreXsrfWhenNotSetButStillContainDummy() { - AccessToken accessToken = mock(AccessToken.class); - when(accessToken.compact()).thenReturn(""); - when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(empty()); - when(hookManager.getAccessToken()).thenReturn(accessToken); - - Map environment = new HashMap<>(); - HgEnvironment.prepareEnvironment(environment, handler, hookManager); - - assertThat(environment).containsKeys("SCM_XSRF"); + void shouldThrowNotFoundExceptionWithoutRepository() { + assertThrows(NotFoundException.class, () -> factory.create("42", "xyz")); } + } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HookServerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HookServerTest.java index afd485c477..95467edc25 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HookServerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/hooks/HookServerTest.java @@ -89,7 +89,7 @@ class HookServerTest { } } - public static class HelloHandler implements Runnable { + public static class HelloHandler implements HookHandler { private final Socket socket; 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 f026169f87..d115123c52 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -29,13 +29,16 @@ package sonia.scm.repository.spi; import org.junit.After; import org.junit.Before; +import sonia.scm.repository.HgRepositoryFactory; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgTestUtil; import sonia.scm.repository.RepositoryTestData; +import sonia.scm.repository.hooks.HookEnvironment; import sonia.scm.util.MockUtil; //~--- JDK imports ------------------------------------------------------------ +import java.io.File; import java.io.IOException; /** @@ -49,31 +52,22 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase * Method description * * - * @throws IOException */ @After - public void close() throws IOException - { + public void close() { if (cmdContext != null) { cmdContext.close(); } } - /** - * Method description - * - * - * @throws IOException - */ @Before public void initHgHandler() throws IOException { this.handler = HgTestUtil.createHandler(tempFolder.newFolder()); - HgTestUtil.checkForSkip(handler); - cmdContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, - RepositoryTestData.createHeartOfGold(), repositoryDirectory); + HgRepositoryFactory factory = HgTestUtil.createFactory(handler, repositoryDirectory); + cmdContext = new HgCommandContext(handler, factory, RepositoryTestData.createHeartOfGold()); } //~--- 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 f3a632fa59..241d416d2c 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 @@ -29,12 +29,10 @@ import com.google.inject.util.Providers; import org.junit.Before; import org.junit.Test; import sonia.scm.repository.Branch; -import sonia.scm.repository.HgTestUtil; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.BranchRequest; import sonia.scm.repository.work.NoneCachingWorkingCopyPool; import sonia.scm.repository.work.WorkdirProvider; -import sonia.scm.web.HgRepositoryEnvironmentBuilder; import java.util.List; @@ -47,10 +45,8 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { @Before public void initWorkingCopyFactory() { - HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder = - new HgRepositoryEnvironmentBuilder(handler, HgTestUtil.createHookManager()); - workingCopyFactory = new SimpleHgWorkingCopyFactory(Providers.of(hgRepositoryEnvironmentBuilder), new NoneCachingWorkingCopyPool(new WorkdirProvider())) { + workingCopyFactory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())) { @Override public void configure(PullCommand pullCommand) { // we do not want to configure http hooks in this unit test 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 a128e23075..9d04255027 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -110,17 +110,10 @@ public class HgIncomingCommandTest extends IncomingOutgoingTestBase cmd.getIncomingChangesets(request); } - /** - * Method description - * - * - * @return - */ - private HgIncomingCommand createIncomingCommand() - { + private HgIncomingCommand createIncomingCommand() { return new HgIncomingCommand( - new HgCommandContext( - HgTestUtil.createHookManager(), handler, incomingRepository, - incomingDirectory), handler); + new HgCommandContext(handler, HgTestUtil.createFactory(handler, incomingDirectory), 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 914a063790..2e645c04c7 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 @@ -40,12 +40,11 @@ import static org.assertj.core.api.Assertions.assertThat; public class HgModificationsCommandTest extends IncomingOutgoingTestBase { - private HgModificationsCommand outgoingModificationsCommand; @Before public void init() { - HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory); + HgCommandContext outgoingContext = new HgCommandContext(handler, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository); outgoingModificationsCommand = new HgModificationsCommand(outgoingContext); } @@ -116,10 +115,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { assertThat(modifications).isNotNull(); assertThat(modifications.getAdded()) .as("added files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getModified()) .as("modified files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getRemoved()) .as("removed files modifications") .hasSize(1) @@ -136,10 +135,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { assertThat(modifications).isNotNull(); assertThat(modifications.getAdded()) .as("added files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getModified()) .as("modified files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getRemoved()) .as("removed files modifications") .isEmpty(); @@ -161,10 +160,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { assertThat(modifications).isNotNull(); assertThat(modifications.getAdded()) .as("added files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getModified()) .as("modified files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getRemoved()) .as("removed files modifications") .isEmpty(); @@ -189,7 +188,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { assertThat(modifications).isNotNull(); assertThat(modifications.getAdded()) .as("added files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getModified()) .as("modified files modifications") .hasSize(1) @@ -197,10 +196,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { .containsOnly(file); assertThat(modifications.getRemoved()) .as("removed files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getRenamed()) .as("renamed files modifications") - .hasSize(0); + .isEmpty(); }; } @@ -214,13 +213,13 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { .containsOnly(addedFile); assertThat(modifications.getModified()) .as("modified files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getRemoved()) .as("removed files modifications") - .hasSize(0); + .isEmpty(); assertThat(modifications.getRenamed()) .as("renamed files modifications") - .hasSize(0); + .isEmpty(); }; } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java index d55005f675..0ccc7d94c6 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java @@ -24,7 +24,6 @@ package sonia.scm.repository.spi; -import com.google.inject.util.Providers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -32,12 +31,9 @@ import org.junit.rules.TemporaryFolder; import sonia.scm.AlreadyExistsException; import sonia.scm.NoChangesMadeException; import sonia.scm.NotFoundException; -import sonia.scm.repository.HgHookManager; -import sonia.scm.repository.HgTestUtil; import sonia.scm.repository.Person; import sonia.scm.repository.work.NoneCachingWorkingCopyPool; import sonia.scm.repository.work.WorkdirProvider; -import sonia.scm.web.HgRepositoryEnvironmentBuilder; import java.io.File; import java.io.FileOutputStream; @@ -54,9 +50,7 @@ public class HgModifyCommandTest extends AbstractHgCommandTestBase { @Before public void initHgModifyCommand() { - HgHookManager hookManager = HgTestUtil.createHookManager(); - HgRepositoryEnvironmentBuilder environmentBuilder = new HgRepositoryEnvironmentBuilder(handler, hookManager); - SimpleHgWorkingCopyFactory workingCopyFactory = new SimpleHgWorkingCopyFactory(Providers.of(environmentBuilder), new NoneCachingWorkingCopyPool(new WorkdirProvider())) { + SimpleHgWorkingCopyFactory workingCopyFactory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())) { @Override public void configure(com.aragost.javahg.commands.PullCommand pullCommand) { // we do not want to configure http hooks in this unit test 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 192b425e6c..426eff8537 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -106,17 +106,10 @@ public class HgOutgoingCommandTest extends IncomingOutgoingTestBase System.out.println(cpr.getChangesets()); } - /** - * Method description - * - * - * @return - */ - private HgOutgoingCommand createOutgoingCommand() - { + private HgOutgoingCommand createOutgoingCommand() { return new HgOutgoingCommand( - new HgCommandContext( - HgTestUtil.createHookManager(), handler, outgoingRepository, - outgoingDirectory), handler); + new HgCommandContext(handler, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository), + handler + ); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgVersionCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgVersionCommandTest.java new file mode 100644 index 0000000000..6c020efcc0 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgVersionCommandTest.java @@ -0,0 +1,137 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +import com.google.common.base.Joiner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgVersion; + +import javax.annotation.Nonnull; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class HgVersionCommandTest { + + private static final String PYTHON_OUTPUT = String.join("\n", + "3.9.0 (default, Oct 27 2020, 14:15:17)", + "[Clang 12.0.0 (clang-1200.0.32.21)]" + ); + + private Map outputs; + + @BeforeEach + void setUpOutputs() { + outputs = new HashMap<>(); + } + + @Test + void shouldReturnHgVersion() { + command("/usr/local/bin/hg", HgVersionCommand.HG_ARGS, "5.5.2", 0); + command("/opt/python/bin/python", HgVersionCommand.PYTHON_ARGS, PYTHON_OUTPUT, 0); + + HgVersion hgVersion = getVersion("/usr/local/bin/hg", "/opt/python/bin/python"); + assertThat(hgVersion.getMercurial()).isEqualTo("5.5.2"); + assertThat(hgVersion.getPython()).isEqualTo("3.9.0"); + } + + @Test + void shouldReturnUnknownMercurialVersionOnNonZeroExitCode() { + command("hg", HgVersionCommand.HG_ARGS, "", 1); + command("python", HgVersionCommand.PYTHON_ARGS, PYTHON_OUTPUT, 0); + + HgVersion hgVersion = getVersion("hg", "python"); + assertThat(hgVersion.getMercurial()).isEqualTo(HgVersion.UNKNOWN); + assertThat(hgVersion.getPython()).isEqualTo("3.9.0"); + } + + @Test + void shouldReturnUnknownPythonVersionOnNonZeroExitCode() { + command("hg", HgVersionCommand.HG_ARGS, "4.4.2", 0); + command("python", HgVersionCommand.PYTHON_ARGS, "", 1); + + HgVersion hgVersion = getVersion("hg", "python"); + assertThat(hgVersion.getMercurial()).isEqualTo("4.4.2"); + assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN); + } + + @Test + void shouldReturnUnknownForInvalidPythonOutput() { + command("hg", HgVersionCommand.HG_ARGS, "1.0.0", 0); + command("python", HgVersionCommand.PYTHON_ARGS, "abcdef", 0); + + HgVersion hgVersion = getVersion("hg", "python"); + assertThat(hgVersion.getMercurial()).isEqualTo("1.0.0"); + assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN); + } + + @Test + void shouldReturnUnknownForIOException() { + HgVersionCommand command = new HgVersionCommand(new HgConfig(), cmd -> { + throw new IOException("failed"); + }); + + HgVersion hgVersion = command.get(); + assertThat(hgVersion.getMercurial()).isEqualTo(HgVersion.UNKNOWN); + assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN); + } + + private Process command(String command, String[] args, String content, int exitValue) { + Process process = mock(Process.class); + when(process.getInputStream()).thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); + when(process.exitValue()).thenReturn(exitValue); + + List cmdLine = new ArrayList<>(); + cmdLine.add(command); + cmdLine.addAll(Arrays.asList(args)); + + outputs.put(Joiner.on(' ').join(cmdLine), process); + + return process; + } + + @Nonnull + private HgVersion getVersion(String hg, String python) { + HgConfig config = new HgConfig(); + config.setHgBinary(hg); + config.setPythonBinary(python); + return new HgVersionCommand(config, command -> outputs.get(Joiner.on(' ').join(command))).get(); + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/IncomingOutgoingTestBase.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/IncomingOutgoingTestBase.java index 6162351148..a5b103ed93 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/IncomingOutgoingTestBase.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/IncomingOutgoingTestBase.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -39,7 +39,6 @@ import org.junit.Rule; import org.junit.rules.TemporaryFolder; import sonia.scm.AbstractTestBase; import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgContext; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgTestUtil; import sonia.scm.user.User; @@ -88,7 +87,6 @@ public abstract class IncomingOutgoingTestBase extends AbstractTestBase when(handler.getDirectory(outgoingRepository.getId())).thenReturn( outgoingDirectory); when(handler.getConfig()).thenReturn(temp.getConfig()); - when(handler.getHgContext()).thenReturn(new HgContext()); } //~--- set methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactoryTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactoryTest.java index 8a8164e072..d429371a04 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactoryTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactoryTest.java @@ -30,12 +30,11 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.HgEnvironmentBuilder; import sonia.scm.repository.HgTestUtil; import sonia.scm.repository.work.SimpleCachingWorkingCopyPool; import sonia.scm.repository.work.WorkdirProvider; import sonia.scm.repository.work.WorkingCopy; -import sonia.scm.web.HgRepositoryEnvironmentBuilder; import java.io.File; import java.io.IOException; @@ -57,9 +56,7 @@ public class SimpleHgWorkingCopyFactoryTest extends AbstractHgCommandTestBase { @Before public void bindScmProtocol() throws IOException { workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); - HgHookManager hookManager = HgTestUtil.createHookManager(); - HgRepositoryEnvironmentBuilder environmentBuilder = new HgRepositoryEnvironmentBuilder(handler, hookManager); - workingCopyFactory = new SimpleHgWorkingCopyFactory(Providers.of(environmentBuilder), new SimpleCachingWorkingCopyPool(workdirProvider)) { + workingCopyFactory = new SimpleHgWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider)) { @Override public void configure(com.aragost.javahg.commands.PullCommand pullCommand) { // we do not want to configure http hooks in this unit test 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 deleted file mode 100644 index b7dd2346c5..0000000000 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.web; - -import org.junit.Test; -import sonia.scm.repository.HgRepositoryHandler; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static sonia.scm.web.HgHookCallbackServlet.PARAM_REPOSITORYID; - -public class HgHookCallbackServletTest { - - @Test - public void shouldExtractCorrectRepositoryId() throws ServletException, IOException { - HgRepositoryHandler handler = mock(HgRepositoryHandler.class); - HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - - when(request.getContextPath()).thenReturn("http://example.com/scm"); - when(request.getRequestURI()).thenReturn("http://example.com/scm/hook/hg/pretxnchangegroup"); - String path = "/tmp/hg/12345"; - when(request.getParameter(PARAM_REPOSITORYID)).thenReturn(path); - - servlet.doPost(request, response); - - verify(response, never()).sendError(anyInt()); - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmSecurityModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmSecurityModule.java index 985c53ddbd..6a7375f925 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmSecurityModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmSecurityModule.java @@ -114,14 +114,14 @@ public class ScmSecurityModule extends ShiroWebModule } // bind constant - bindConstant().annotatedWith(Names.named("shiro.loginUrl")).to( - "/index.html"); + bindConstant().annotatedWith(Names.named("shiro.loginUrl")).to("/index.html"); // disable access to mustache resources addFilterChain("/**.mustache", filterConfig(ROLES, "nobody")); // disable session addFilterChain("/**", NO_SESSION_CREATION); + bindConstant().annotatedWith(Names.named("shiro.sessionStorageEnabled")).to(false); } /**