Use bearer tokens to authenticate hg hook callbacks

This commit is contained in:
René Pfeuffer
2019-03-28 08:40:56 +01:00
parent daaa50f08c
commit cc4bd6ddd1
22 changed files with 232 additions and 191 deletions

View File

@@ -9,23 +9,21 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
public class SimpleWorkdirFactory<T extends AutoCloseable, C> {
public abstract class SimpleWorkdirFactory<T extends AutoCloseable, C> {
private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class);
private final File poolDirectory;
private final CloneProvider<T, C> cloneProvider;
private final Repository repository;
public SimpleWorkdirFactory(Repository repository, CloneProvider<T, C> cloneProvider) {
this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), repository, cloneProvider);
public SimpleWorkdirFactory(CloneProvider<T, C> cloneProvider) {
this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), cloneProvider);
}
public SimpleWorkdirFactory(File poolDirectory, Repository repository, CloneProvider<T, C> cloneProvider) {
public SimpleWorkdirFactory(File poolDirectory, CloneProvider<T, C> cloneProvider) {
this.poolDirectory = poolDirectory;
this.cloneProvider = cloneProvider;
this.repository = repository;
poolDirectory.mkdirs();
}
@@ -35,10 +33,12 @@ public class SimpleWorkdirFactory<T extends AutoCloseable, C> {
T clone = cloneProvider.cloneRepository(context, directory);
return new WorkingCopy<>(clone, this::close, directory);
} catch (IOException e) {
throw new InternalRepositoryException(repository, "could not create temporary directory for clone of repository", e);
throw new InternalRepositoryException(getRepository(context), "could not create temporary directory for clone of repository", e);
}
}
protected abstract Repository getRepository(C context);
private File createNewWorkdir() throws IOException {
return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile();
}

View File

@@ -4,26 +4,20 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ScmTransportProtocol;
import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitWorkdirFactory;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.util.SimpleWorkdirFactory;
import sonia.scm.repository.util.WorkingCopy;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory<Repository, GitContext> implements GitWorkdirFactory {
public SimpleGitWorkdirFactory() {
super(null, new GitCloneProvider());
super(new GitCloneProvider());
}
public SimpleGitWorkdirFactory(File poolDirectory) {
super(poolDirectory, null, new GitCloneProvider());
super(poolDirectory, new GitCloneProvider());
}
private static class GitCloneProvider implements CloneProvider<Repository, GitContext> {
@@ -45,4 +39,9 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory<Repository, Gi
return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath();
}
}
@Override
protected sonia.scm.repository.Repository getRepository(GitContext context) {
return context.getRepository();
}
}

View File

@@ -28,6 +28,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.4</version>
</dependency>
</dependencies>

View File

@@ -35,15 +35,9 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
import org.apache.shiro.codec.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.CipherUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.web.HgUtil;
//~--- JDK imports ------------------------------------------------------------
@@ -68,14 +62,7 @@ public final class HgEnvironment
/** Field description */
private static final String ENV_URL = "SCM_URL";
/** Field description */
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
/**
* the logger for HgEnvironment
*/
private static final Logger logger =
LoggerFactory.getLogger(HgEnvironment.class);
private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN";
//~--- constructors ---------------------------------------------------------
@@ -98,14 +85,14 @@ public final class HgEnvironment
*/
public static void prepareEnvironment(Map<String, String> environment,
HgRepositoryHandler handler, HgHookManager hookManager,
HttpServletRequest request)
HttpServletRequest request, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
String hookUrl;
if (request != null)
{
hookUrl = hookManager.createUrl(request);
environment.put(SCM_CREDENTIALS, getCredentials(request));
environment.put(SCM_BEARER_TOKEN, getCredentials(accessTokenBuilderFactory));
}
else
{
@@ -126,9 +113,9 @@ public final class HgEnvironment
* @param hookManager
*/
public static void prepareEnvironment(Map<String, String> environment,
HgRepositoryHandler handler, HgHookManager hookManager)
HgRepositoryHandler handler, HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
prepareEnvironment(environment, handler, hookManager, null);
prepareEnvironment(environment, handler, hookManager, null, accessTokenBuilderFactory);
}
//~--- get methods ----------------------------------------------------------
@@ -139,31 +126,10 @@ public final class HgEnvironment
*
* @return
*/
private static String getCredentials(HttpServletRequest request)
private static String getCredentials(AccessTokenBuilderFactory accessTokenBuilderFactory)
{
String credentials = null;
String header = request.getHeader(HttpUtil.HEADER_AUTHORIZATION);
AccessToken accessToken = accessTokenBuilderFactory.create().build();
if (!Strings.isNullOrEmpty(header))
{
String[] parts = header.split("\\s+");
if (parts.length > 0)
{
CipherUtil cu = CipherUtil.getInstance();
credentials = cu.encode(Base64.decodeToString(parts[1]));
}
else
{
logger.warn("invalid basic authentication header");
}
}
else
{
logger.warn("could not find authentication header on request");
}
return Strings.nullToEmpty(credentials);
return CipherUtil.getInstance().encode(accessToken.compact());
}
}

View File

@@ -32,15 +32,12 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Changeset;
import com.aragost.javahg.commands.CommitCommand;
import com.aragost.javahg.commands.PushCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.Branch;
import sonia.scm.repository.Repository;
import sonia.scm.repository.util.WorkingCopy;
import java.io.IOException;
/**
* Mercurial implementation of the {@link BranchCommand}.
* Note that this creates an empty commit to "persist" the new branch.
@@ -57,7 +54,7 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand {
}
@Override
public Branch branch(String name) throws IOException {
public Branch branch(String name) {
try (WorkingCopy<RepositoryCloseableWrapper> workingCopy = workdirFactory.createWorkingCopy(getContext())) {
com.aragost.javahg.Repository repository = workingCopy.get().get();
com.aragost.javahg.commands.BranchCommand.on(repository).set(name);
@@ -68,14 +65,6 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand {
.message("Create new branch " + name)
.execute();
PushCommand pushCommand = PushCommand
.on(repository)
.branch(name)
.newBranch();
pushCommand
.cmdAppend("--config", "");
pushCommand .execute();
LOG.debug("Created new branch '{}' in repository {} with changeset {}",
name, getRepository().getNamespaceAndName(), emptyChangeset.getNode());

View File

@@ -42,6 +42,7 @@ import com.google.common.base.Strings;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.web.HgUtil;
//~--- JDK imports ------------------------------------------------------------
@@ -49,6 +50,8 @@ import sonia.scm.web.HgUtil;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.function.BiConsumer;
/**
*
@@ -66,44 +69,46 @@ public class HgCommandContext implements Closeable
* Constructs ...
*
*
*
* @param hookManager
* @param handler
* @param repository
* @param directory
* @param accessTokenBuilderFactory
*/
public HgCommandContext(HgHookManager hookManager,
HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
File directory)
File directory, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
this(hookManager, handler, repository, directory,
handler.getHgContext().isPending());
handler.getHgContext().isPending(), accessTokenBuilderFactory);
}
/**
* Constructs ...
*
*
*
* @param hookManager
* @param hanlder
* @param handler
* @param repository
* @param directory
* @param pending
* @param accessTokenBuilderFactory
*/
public HgCommandContext(HgHookManager hookManager,
HgRepositoryHandler hanlder, sonia.scm.repository.Repository repository,
File directory, boolean pending)
HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
File directory, boolean pending, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
this.hookManager = hookManager;
this.hanlder = hanlder;
this.handler = handler;
this.directory = directory;
this.scmRepository = repository;
this.encoding = repository.getProperty(PROPERTY_ENCODING);
this.pending = pending;
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
if (Strings.isNullOrEmpty(encoding))
{
encoding = hanlder.getConfig().getEncoding();
encoding = handler.getConfig().getEncoding();
}
}
@@ -134,13 +139,19 @@ public class HgCommandContext implements Closeable
{
if (repository == null)
{
repository = HgUtil.open(hanlder, hookManager, directory, encoding,
pending);
repository = HgUtil.open(handler, hookManager, directory, encoding,
pending, accessTokenBuilderFactory);
}
return repository;
}
public Repository openWithSpecialEnvironment(BiConsumer<sonia.scm.repository.Repository, Map<String, String>> prepareEnvironment)
{
return HgUtil.open(handler, directory, encoding,
pending, environment -> prepareEnvironment.accept(scmRepository, environment));
}
//~--- get methods ----------------------------------------------------------
/**
@@ -151,7 +162,7 @@ public class HgCommandContext implements Closeable
*/
public HgConfig getConfig()
{
return hanlder.getConfig();
return handler.getConfig();
}
//~--- fields ---------------------------------------------------------------
@@ -163,7 +174,7 @@ public class HgCommandContext implements Closeable
private String encoding;
/** Field description */
private HgRepositoryHandler hanlder;
private HgRepositoryHandler handler;
/** Field description */
private HgHookManager hookManager;
@@ -173,4 +184,8 @@ public class HgCommandContext implements Closeable
/** Field description */
private Repository repository;
private final sonia.scm.repository.Repository scmRepository;
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
}

View File

@@ -40,6 +40,7 @@ import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.RepositoryHookType;
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.web.HgUtil;
import java.io.File;
@@ -63,13 +64,14 @@ public class HgHookChangesetProvider implements HookChangesetProvider
public HgHookChangesetProvider(HgRepositoryHandler handler,
File repositoryDirectory, HgHookManager hookManager, String startRev,
RepositoryHookType type)
RepositoryHookType type, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
this.handler = handler;
this.repositoryDirectory = repositoryDirectory;
this.hookManager = hookManager;
this.startRev = startRev;
this.type = type;
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
}
//~--- methods --------------------------------------------------------------
@@ -128,7 +130,7 @@ public class HgHookChangesetProvider implements HookChangesetProvider
// TODO get repository encoding
return HgUtil.open(handler, hookManager, repositoryDirectory, null,
pending);
pending, accessTokenBuilderFactory);
}
//~--- fields ---------------------------------------------------------------
@@ -150,4 +152,6 @@ public class HgHookChangesetProvider implements HookChangesetProvider
/** Field description */
private RepositoryHookType type;
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
}

View File

@@ -43,6 +43,7 @@ import sonia.scm.repository.api.HookBranchProvider;
import sonia.scm.repository.api.HookFeature;
import sonia.scm.repository.api.HookMessageProvider;
import sonia.scm.repository.api.HookTagProvider;
import sonia.scm.security.AccessTokenBuilderFactory;
import java.io.File;
import java.util.EnumSet;
@@ -75,9 +76,9 @@ public class HgHookContextProvider extends HookContextProvider
*/
public HgHookContextProvider(HgRepositoryHandler handler,
File repositoryDirectory, HgHookManager hookManager, String startRev,
RepositoryHookType type)
RepositoryHookType type, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type);
this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type, accessTokenBuilderFactory);
}
//~--- get methods ----------------------------------------------------------

View File

@@ -40,6 +40,7 @@ import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.CommandNotSupportedException;
import sonia.scm.security.AccessTokenBuilderFactory;
import java.io.File;
import java.io.IOException;
@@ -77,13 +78,13 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
//~--- constructors ---------------------------------------------------------
HgRepositoryServiceProvider(HgRepositoryHandler handler,
HgHookManager hookManager, Repository repository)
HgHookManager hookManager, Repository repository, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
this.repository = repository;
this.handler = handler;
this.repositoryDirectory = handler.getDirectory(repository.getId());
this.context = new HgCommandContext(hookManager, handler, repository,
repositoryDirectory);
repositoryDirectory, accessTokenBuilderFactory);
}
//~--- methods --------------------------------------------------------------

View File

@@ -38,6 +38,7 @@ import sonia.scm.plugin.Extension;
import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.security.AccessTokenBuilderFactory;
/**
*
@@ -47,15 +48,17 @@ import sonia.scm.repository.Repository;
public class HgRepositoryServiceResolver implements RepositoryServiceResolver
{
private HgRepositoryHandler handler;
private HgHookManager hookManager;
private final HgRepositoryHandler handler;
private final HgHookManager hookManager;
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
@Inject
public HgRepositoryServiceResolver(HgRepositoryHandler handler,
HgHookManager hookManager)
HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
this.handler = handler;
this.hookManager = hookManager;
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
}
@Override
@@ -63,7 +66,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver
HgRepositoryServiceProvider provider = null;
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
provider = new HgRepositoryServiceProvider(handler, hookManager, repository);
provider = new HgRepositoryServiceProvider(handler, hookManager, repository, accessTokenBuilderFactory);
}
return provider;

View File

@@ -2,35 +2,65 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Repository;
import com.aragost.javahg.commands.CloneCommand;
import com.aragost.javahg.commands.PullCommand;
import sonia.scm.repository.util.SimpleWorkdirFactory;
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
import javax.inject.Inject;
import javax.inject.Provider;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory<RepositoryCloseableWrapper, HgCommandContext> implements HgWorkdirFactory {
public SimpleHgWorkdirFactory() {
super(null, new HgCloneProvider());
@Inject
public SimpleHgWorkdirFactory(Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder) {
this(hgRepositoryEnvironmentBuilder, new HookConfigurer());
}
public SimpleHgWorkdirFactory(File poolDirectory) {
super(poolDirectory, null, new HgCloneProvider());
SimpleHgWorkdirFactory(Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder, Consumer<PullCommand> hookConfigurer) {
super(new HgCloneProvider(hgRepositoryEnvironmentBuilder, hookConfigurer));
}
private static class HgCloneProvider implements CloneProvider<RepositoryCloseableWrapper, HgCommandContext> {
private final Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder;
private final Consumer<PullCommand> hookConfigurer;
private HgCloneProvider(Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder, Consumer<PullCommand> hookConfigurer) {
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
this.hookConfigurer = hookConfigurer;
}
@Override
public RepositoryCloseableWrapper cloneRepository(HgCommandContext context, File target) throws IOException {
String execute = CloneCommand.on(context.open()).execute(target.getAbsolutePath());
return new RepositoryCloseableWrapper(Repository.open(target));
BiConsumer<sonia.scm.repository.Repository, Map<String, String>> repositoryMapBiConsumer = (repository, environment) -> {
hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment);
};
Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer);
CloneCommand.on(centralRepository).execute(target.getAbsolutePath());
return new RepositoryCloseableWrapper(Repository.open(target), centralRepository, hookConfigurer);
}
}
@Override
protected sonia.scm.repository.Repository getRepository(HgCommandContext context) {
return null;
}
}
class RepositoryCloseableWrapper implements AutoCloseable {
private final Repository delegate;
private final Repository centralRepository;
private final Consumer<PullCommand> hookConfigurer;
RepositoryCloseableWrapper(Repository delegate) {
RepositoryCloseableWrapper(Repository delegate, Repository centralRepository, Consumer<PullCommand> hookConfigurer) {
this.delegate = delegate;
this.centralRepository = centralRepository;
this.hookConfigurer = hookConfigurer;
}
Repository get() {
@@ -39,5 +69,21 @@ class RepositoryCloseableWrapper implements AutoCloseable {
@Override
public void close() {
try {
PullCommand pullCommand = PullCommand.on(centralRepository);
hookConfigurer.accept(pullCommand);
pullCommand.execute(delegate.getDirectory().getAbsolutePath());
} catch (Exception e) {
throw new RuntimeException(e);
}
centralRepository.close();
}
}
class HookConfigurer implements Consumer<PullCommand> {
@Override
public void accept(PullCommand pullCommand) {
pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.postHook");
pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.preHook");
}
}

View File

@@ -201,7 +201,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
executor.setExceptionHandler(exceptionHandler);
executor.setStatusCodeHandler(exceptionHandler);
executor.setContentLengthWorkaround(true);
hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment());
hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap());
// unused ???
HttpSession session = request.getSession(false);

View File

@@ -42,6 +42,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -54,8 +55,9 @@ import sonia.scm.repository.api.HgHookMessage;
import sonia.scm.repository.api.HgHookMessage.Severity;
import sonia.scm.repository.spi.HgHookContextProvider;
import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.BearerToken;
import sonia.scm.security.CipherUtil;
import sonia.scm.security.Tokens;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
@@ -93,7 +95,7 @@ public class HgHookCallbackServlet extends HttpServlet
private static final String PARAM_CHALLENGE = "challenge";
/** Field description */
private static final String PARAM_CREDENTIALS = "credentials";
private static final String PARAM_TOKEN = "token";
/** Field description */
private static final String PARAM_NODE = "node";
@@ -117,12 +119,13 @@ public class HgHookCallbackServlet extends HttpServlet
@Inject
public HgHookCallbackServlet(HookEventFacade hookEventFacade,
HgRepositoryHandler handler, HgHookManager hookManager,
Provider<HgContext> contextProvider)
Provider<HgContext> contextProvider, AccessTokenBuilderFactory accessTokenBuilderFactory)
{
this.hookEventFacade = hookEventFacade;
this.handler = handler;
this.hookManager = hookManager;
this.contextProvider = contextProvider;
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
}
//~--- methods --------------------------------------------------------------
@@ -179,11 +182,11 @@ public class HgHookCallbackServlet extends HttpServlet
if (Util.isNotEmpty(node))
{
String credentials = request.getParameter(PARAM_CREDENTIALS);
String token = request.getParameter(PARAM_TOKEN);
if (Util.isNotEmpty(credentials))
if (Util.isNotEmpty(token))
{
authenticate(request, credentials);
authenticate(token);
}
hookCallback(response, type, repositoryId, challenge, node);
@@ -209,34 +212,20 @@ public class HgHookCallbackServlet extends HttpServlet
}
}
private void authenticate(HttpServletRequest request, String credentials)
private void authenticate(String token)
{
try
{
credentials = CipherUtil.getInstance().decode(credentials);
token = CipherUtil.getInstance().decode(token);
if (Util.isNotEmpty(credentials))
{
int index = credentials.indexOf(':');
if (index > 0 && index < credentials.length())
if (Util.isNotEmpty(token))
{
Subject subject = SecurityUtils.getSubject();
AuthenticationToken accessToken = createToken(token);
//J-
subject.login(
Tokens.createAuthenticationToken(
request,
credentials.substring(0, index),
credentials.substring(index + 1)
)
);
//J+
}
else
{
logger.error("could not find delimiter");
}
subject.login(accessToken);
}
}
catch (Exception ex)
@@ -245,6 +234,11 @@ public class HgHookCallbackServlet extends HttpServlet
}
}
private AuthenticationToken createToken(String tokenString)
{
return BearerToken.valueOf(tokenString);
}
private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type)
throws IOException
{
@@ -259,7 +253,7 @@ public class HgHookCallbackServlet extends HttpServlet
File repositoryDirectory = handler.getDirectory(repositoryId);
context = new HgHookContextProvider(handler, repositoryDirectory, hookManager,
node, type);
node, type, accessTokenBuilderFactory);
hookEventFacade.handle(repositoryId).fireHookEvent(type, context);
@@ -460,4 +454,6 @@ public class HgHookCallbackServlet extends HttpServlet
/** Field description */
private final HgHookManager hookManager;
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
}

View File

@@ -1,96 +1,73 @@
package sonia.scm.web;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.HgEnvironment;
import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.CipherUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.web.cgi.EnvList;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.Base64;
import java.util.Map;
public class HgRepositoryEnvironmentBuilder {
private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryEnvironmentBuilder.class);
private static final String ENV_REPOSITORY_NAME = "REPO_NAME";
private static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN";
private final HgRepositoryHandler handler;
private final HgHookManager hookManager;
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
@Inject
public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) {
public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) {
this.handler = handler;
this.hookManager = hookManager;
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
}
void buildFor(Repository repository, HttpServletRequest request, EnvList environment) {
public void buildFor(Repository repository, HttpServletRequest request, Map<String, String> environment) {
File directory = handler.getDirectory(repository.getId());
environment.set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
environment.set(ENV_REPOSITORY_ID, repository.getId());
environment.set(ENV_REPOSITORY_PATH,
environment.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
environment.put(ENV_REPOSITORY_ID, repository.getId());
environment.put(ENV_REPOSITORY_PATH,
directory.getAbsolutePath());
// add hook environment
Map<String, String> environmentMap = environment.asMutableMap();
if (handler.getConfig().isDisableHookSSLValidation()) {
// disable ssl validation
// Issue 959: https://goo.gl/zH5eY8
environmentMap.put(ENV_PYTHON_HTTPS_VERIFY, "0");
environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
}
// enable experimental httppostargs protocol of mercurial
// Issue 970: https://goo.gl/poascp
environmentMap.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
HgEnvironment.prepareEnvironment(
environmentMap,
environment,
handler,
hookManager,
request
request,
accessTokenBuilderFactory
);
addCredentials(environment, request);
addCredentials(environment);
}
private void addCredentials(EnvList env, HttpServletRequest request)
{
String authorization = request.getHeader(HttpUtil.HEADER_AUTHORIZATION);
private void addCredentials(Map<String, String> env) {
if (!Strings.isNullOrEmpty(authorization))
{
if (authorization.startsWith(HttpUtil.AUTHORIZATION_SCHEME_BASIC))
{
String encodedUserInfo =
authorization.substring(
HttpUtil.AUTHORIZATION_SCHEME_BASIC.length()).trim();
// TODO check encoding of user-agent ?
String userInfo = new String(Base64.getDecoder().decode(encodedUserInfo));
AccessToken accessToken = accessTokenBuilderFactory.create().build();
env.set(SCM_CREDENTIALS, CipherUtil.getInstance().encode(userInfo));
}
else
{
LOG.warn("unknown authentication scheme used");
}
}
else
{
LOG.trace("no authorization header found");
}
String encodedToken = CipherUtil.getInstance().encode(accessToken.compact());
env.put(SCM_BEARER_TOKEN, encodedToken);
}
}

View File

@@ -50,6 +50,7 @@ import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgPythonScript;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.spi.javahg.HgFileviewExtension;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
@@ -58,6 +59,8 @@ import sonia.scm.util.Util;
import java.io.File;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
@@ -102,7 +105,21 @@ public final class HgUtil
* @return
*/
public static Repository open(HgRepositoryHandler handler,
HgHookManager hookManager, File directory, String encoding, boolean pending)
HgHookManager hookManager, File directory, String encoding, boolean pending,
AccessTokenBuilderFactory accessTokenBuilderFactory)
{
return open(
handler,
directory,
encoding,
pending,
environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager, accessTokenBuilderFactory)
);
}
public static Repository open(HgRepositoryHandler handler,
File directory, String encoding, boolean pending,
Consumer<Map<String, String>> prepareEnvironment)
{
String enc = encoding;
@@ -113,8 +130,7 @@ public final class HgUtil
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
HgEnvironment.prepareEnvironment(repoConfiguration.getEnvironment(),
handler, hookManager);
prepareEnvironment.accept(repoConfiguration.getEnvironment());
repoConfiguration.addExtension(HgFileviewExtension.class);
repoConfiguration.setEnablePendingChangesets(pending);

View File

@@ -40,7 +40,7 @@ import os, urllib, urllib2
baseUrl = os.environ['SCM_URL']
challenge = os.environ['SCM_CHALLENGE']
credentials = os.environ['SCM_CREDENTIALS']
token = os.environ['SCM_BEARER_TOKEN']
repositoryId = os.environ['SCM_REPOSITORY_ID']
def printMessages(ui, msgs):
@@ -54,13 +54,13 @@ def callHookUrl(ui, repo, hooktype, node):
try:
url = baseUrl + hooktype
ui.debug( "send scm-hook to " + url + " and " + node + "\n" )
data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root, 'repositoryId': repositoryId})
data = urllib.urlencode({'node': node, 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId})
# open url but ignore proxy settings
proxy_handler = urllib2.ProxyHandler({})
opener = urllib2.build_opener(proxy_handler)
req = urllib2.Request(url, data)
conn = opener.open(req)
if conn.code >= 200 and conn.code < 300:
if 200 <= conn.code < 300:
ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" )
printMessages(ui, conn)
abort = False

View File

@@ -83,7 +83,7 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase
HgTestUtil.checkForSkip(handler);
cmdContext = new HgCommandContext(HgTestUtil.createHookManager(), handler,
RepositoryTestData.createHeartOfGold(), repositoryDirectory);
RepositoryTestData.createHeartOfGold(), repositoryDirectory, null);
}
//~--- set methods ----------------------------------------------------------

View File

@@ -1,18 +1,41 @@
package sonia.scm.repository.spi;
import com.google.inject.util.Providers;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import sonia.scm.repository.Branch;
import sonia.scm.repository.HgHookManager;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilder;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
import java.io.IOException;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class HgBranchCommandTest extends AbstractHgCommandTestBase {
@Test
public void x() throws IOException {
public void shouldCreateBranch() {
Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty();
new HgBranchCommand(cmdContext, repository, new SimpleHgWorkdirFactory()).branch("new_branch");
HgHookManager hookManager = mock(HgHookManager.class);
when(hookManager.getChallenge()).thenReturn("1");
when(hookManager.createUrl()).thenReturn("http://example.com/");
AccessTokenBuilderFactory tokenBuilderFactory = mock(AccessTokenBuilderFactory.class);
AccessTokenBuilder tokenBuilder = mock(AccessTokenBuilder.class);
when(tokenBuilderFactory.create()).thenReturn(tokenBuilder);
AccessToken accessToken = mock(AccessToken.class);
when(tokenBuilder.build()).thenReturn(accessToken);
when(accessToken.compact()).thenReturn("");
HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder =
new HgRepositoryEnvironmentBuilder(handler, hookManager, tokenBuilderFactory);
SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), pc -> {});
new HgBranchCommand(cmdContext, repository, workdirFactory).branch("new_branch");
Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty();
}

View File

@@ -129,6 +129,6 @@ public class HgIncomingCommandTest extends IncomingOutgoingTestBase
return new HgIncomingCommand(
new HgCommandContext(
HgTestUtil.createHookManager(), handler, incomingRepository,
incomingDirectory), incomingRepository, handler);
incomingDirectory, null), incomingRepository, handler);
}
}

View File

@@ -19,7 +19,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
@Before
public void init() {
HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory);
HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory, null);
outgoingModificationsCommand = new HgModificationsCommand(outgoingContext, outgoingRepository);
}

View File

@@ -125,6 +125,6 @@ public class HgOutgoingCommandTest extends IncomingOutgoingTestBase
return new HgOutgoingCommand(
new HgCommandContext(
HgTestUtil.createHookManager(), handler, outgoingRepository,
outgoingDirectory), outgoingRepository, handler);
outgoingDirectory, null), outgoingRepository, handler);
}
}

View File

@@ -20,7 +20,7 @@ public class HgHookCallbackServletTest {
@Test
public void shouldExtractCorrectRepositoryId() throws ServletException, IOException {
HgRepositoryHandler handler = mock(HgRepositoryHandler.class);
HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null);
HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null, null);
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);