Proxy support for pull, push and mirror commands (#1773)

Apply proxy support for jGit by extracting the required functionality from the DefaultAdvancedHttpClient into its own class HttpURLConnectionFactory. This new class is now used by the DefaultAdvancedHttpClient and jGit.
The HttpURLConnection also fixes proxy server authentication, which was non functional in DefaultAdvancedHttpClient.
The proxy support for SVNKit is implemented by using the provided method of the BasicAuthenticationManager.
For mercurial the support is configured by writing the required settings to a temporary hgrc file.
This commit is contained in:
Sebastian Sdorra
2021-08-19 11:27:51 +02:00
committed by GitHub
parent a7bb67f36b
commit 7f9f4e566c
54 changed files with 2996 additions and 1098 deletions

View File

@@ -26,16 +26,20 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Changeset;
import com.aragost.javahg.commands.CommitCommand;
import com.google.common.annotations.VisibleForTesting;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry;
import sonia.scm.repository.Branch;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.api.BranchRequest;
import sonia.scm.repository.work.WorkingCopy;
import sonia.scm.user.User;
import javax.inject.Inject;
/**
* Mercurial implementation of the {@link BranchCommand}.
* Note that this creates an empty commit to "persist" the new branch.
@@ -44,6 +48,12 @@ public class HgBranchCommand extends AbstractWorkingCopyCommand implements Branc
private static final Logger LOG = LoggerFactory.getLogger(HgBranchCommand.class);
@Inject
HgBranchCommand(HgCommandContext context, HgRepositoryHandler handler) {
this(context, handler.getWorkingCopyFactory());
}
@VisibleForTesting
HgBranchCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
super(context, workingCopyFactory);
}

View File

@@ -0,0 +1,48 @@
/*
* 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 sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.Repository;
import javax.inject.Inject;
public class HgCommandContextFactory {
private final HgConfigResolver configResolver;
private final HgRepositoryFactory repositoryFactory;
@Inject
public HgCommandContextFactory(HgConfigResolver configResolver, HgRepositoryFactory repositoryFactory) {
this.configResolver = configResolver;
this.repositoryFactory = repositoryFactory;
}
public HgCommandContext create(Repository repository) {
return new HgCommandContext(configResolver, repositoryFactory, repository);
}
}

View File

@@ -33,6 +33,7 @@ import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.spi.javahg.HgIncomingChangesetCommand;
import javax.inject.Inject;
import java.io.File;
import java.util.Collections;
import java.util.List;
@@ -58,6 +59,7 @@ public class HgIncomingCommand extends AbstractCommand
* @param context
* @param handler
*/
@Inject
HgIncomingCommand(HgCommandContext context, HgRepositoryHandler handler)
{
super(context);
@@ -87,7 +89,7 @@ public class HgIncomingCommand extends AbstractCommand
{
if (ex.getCommand().getReturnCode() == NO_INCOMING_CHANGESETS)
{
changesets = Collections.EMPTY_LIST;
changesets = Collections.emptyList();
}
else
{

View File

@@ -1,79 +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.spi;
import sonia.scm.io.INIConfiguration;
import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection;
import sonia.scm.repository.HgRepositoryHandler;
import java.io.File;
import java.io.IOException;
import java.net.URI;
public class HgIniConfigurator {
private final HgCommandContext context;
private static final String AUTH_SECTION = "auth";
public HgIniConfigurator(HgCommandContext context) {
this.context = context;
}
public void addAuthenticationConfig(RemoteCommandRequest request, String url) throws IOException {
INIConfiguration ini = readIniConfiguration();
INISection authSection = ini.getSection(AUTH_SECTION);
if (authSection == null) {
authSection = new INISection(AUTH_SECTION);
ini.addSection(authSection);
}
URI parsedUrl = URI.create(url);
authSection.setParameter("import.prefix", parsedUrl.getHost());
authSection.setParameter("import.schemes", parsedUrl.getScheme());
authSection.setParameter("import.username", request.getUsername());
authSection.setParameter("import.password", request.getPassword());
writeIniConfiguration(ini);
}
public void removeAuthenticationConfig() throws IOException {
INIConfiguration ini = readIniConfiguration();
ini.removeSection(AUTH_SECTION);
writeIniConfiguration(ini);
}
public INIConfiguration readIniConfiguration() throws IOException {
return new INIConfigurationReader().read(getHgrcFile());
}
public void writeIniConfiguration(INIConfiguration ini) throws IOException {
new INIConfigurationWriter().write(ini, getHgrcFile());
}
public File getHgrcFile() {
return new File(context.getDirectory(), HgRepositoryHandler.PATH_HGRC);
}
}

View File

@@ -30,6 +30,7 @@ import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.Person;
import sonia.scm.repository.Repository;
import javax.inject.Inject;
import java.util.Iterator;
import java.util.concurrent.Callable;
@@ -38,9 +39,10 @@ class HgLazyChangesetResolver implements Callable<Iterable<Changeset>> {
private final HgRepositoryFactory factory;
private final Repository repository;
HgLazyChangesetResolver(HgRepositoryFactory factory, Repository repository) {
@Inject
HgLazyChangesetResolver(HgRepositoryFactory factory, HgCommandContext context) {
this.factory = factory;
this.repository = repository;
this.repository = context.getScmRepository();
}
@Override

View File

@@ -30,12 +30,15 @@ import com.aragost.javahg.commands.CommitCommand;
import com.aragost.javahg.commands.ExecutionException;
import com.aragost.javahg.commands.RemoveCommand;
import com.aragost.javahg.commands.StatusCommand;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NoChangesMadeException;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.work.WorkingCopy;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
@@ -48,8 +51,13 @@ public class HgModifyCommand extends AbstractWorkingCopyCommand implements Modif
private static final Logger LOG = LoggerFactory.getLogger(HgModifyCommand.class);
@Inject
public HgModifyCommand(HgCommandContext context, HgRepositoryHandler handler) {
super(context, handler.getWorkingCopyFactory());
}
public HgModifyCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
@VisibleForTesting
HgModifyCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
super(context, workingCopyFactory);
}

View File

@@ -33,6 +33,7 @@ import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.spi.javahg.HgOutgoingChangesetCommand;
import javax.inject.Inject;
import java.io.File;
import java.util.Collections;
import java.util.List;
@@ -58,7 +59,8 @@ public class HgOutgoingCommand extends AbstractCommand
* @param context
* @param handler
*/
public HgOutgoingCommand(HgCommandContext context, HgRepositoryHandler handler)
@Inject
HgOutgoingCommand(HgCommandContext context, HgRepositoryHandler handler)
{
super(context);
this.handler = handler;
@@ -87,7 +89,7 @@ public class HgOutgoingCommand extends AbstractCommand
{
if (ex.getCommand().getReturnCode() == NO_OUTGOING_CHANGESETS)
{
changesets = Collections.EMPTY_LIST;
changesets = Collections.emptyList();
}
else
{

View File

@@ -29,55 +29,58 @@ import com.aragost.javahg.commands.ExecutionException;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.api.ImportFailedException;
import sonia.scm.repository.api.PullResponse;
import javax.inject.Inject;
import java.io.IOException;
import java.util.List;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
public class HgPullCommand extends AbstractHgPushOrPullCommand implements PullCommand {
private static final Logger LOG = LoggerFactory.getLogger(HgPullCommand.class);
private final ScmEventBus eventBus;
private final HgLazyChangesetResolver changesetResolver;
private final HgRepositoryHookEventFactory eventFactory;
private final TemporaryConfigFactory configFactory;
@Inject
public HgPullCommand(HgRepositoryHandler handler,
HgCommandContext context,
ScmEventBus eventBus,
HgLazyChangesetResolver changesetResolver,
HgRepositoryHookEventFactory eventFactory
HgRepositoryHookEventFactory eventFactory,
TemporaryConfigFactory configFactory
) {
super(handler, context);
this.eventBus = eventBus;
this.changesetResolver = changesetResolver;
this.eventFactory = eventFactory;
this.configFactory = configFactory;
}
@Override
@SuppressWarnings({"java:S3252"})
public PullResponse pull(PullCommandRequest request)
throws IOException {
public PullResponse pull(PullCommandRequest request) throws IOException {
String url = getRemoteUrl(request);
HgIniConfigurator iniConfigurator = new HgIniConfigurator(getContext());
LOG.debug("pull changes from {} to {}", url, getContext().getScmRepository());
List<Changeset> result;
TemporaryConfigFactory.Builder builder = configFactory.withContext(context);
if (!Strings.isNullOrEmpty(request.getUsername()) && !Strings.isNullOrEmpty(request.getPassword())) {
iniConfigurator.addAuthenticationConfig(request, url);
builder.withCredentials(url, request.getUsername(), request.getPassword());
}
List<Changeset> result;
try {
result = com.aragost.javahg.commands.PullCommand.on(open()).execute(url);
result = builder.call(() -> com.aragost.javahg.commands.PullCommand.on(open()).execute(url));
} catch (ExecutionException ex) {
throw new ImportFailedException(ContextEntry.ContextBuilder.entity(getRepository()).build(), "could not execute pull command", ex);
} finally {
iniConfigurator.removeAuthenticationConfig();
throw new ImportFailedException(entity(getRepository()).build(), "could not execute pull command", ex);
}
firePostReceiveRepositoryHookEvent();
@@ -88,4 +91,5 @@ public class HgPullCommand extends AbstractHgPushOrPullCommand implements PullCo
private void firePostReceiveRepositoryHookEvent() {
eventBus.post(eventFactory.createEvent(context, changesetResolver));
}
}

View File

@@ -24,47 +24,59 @@
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.aragost.javahg.Changeset;
import com.aragost.javahg.commands.ExecutionException;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.api.ImportFailedException;
import sonia.scm.repository.api.PushResponse;
import javax.inject.Inject;
import java.io.IOException;
import java.util.List;
import static com.aragost.javahg.commands.flags.PushCommandFlags.on;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
public class HgPushCommand extends AbstractHgPushOrPullCommand implements PushCommand {
private static final Logger LOG = LoggerFactory.getLogger(HgPushCommand.class);
public HgPushCommand(HgRepositoryHandler handler, HgCommandContext context) {
private final TemporaryConfigFactory configFactory;
@Inject
public HgPushCommand(HgRepositoryHandler handler, HgCommandContext context, TemporaryConfigFactory configFactory) {
super(handler, context);
this.configFactory = configFactory;
}
@Override
public PushResponse push(PushCommandRequest request)
throws IOException {
@SuppressWarnings("java:S3252") // this is how javahg is used
public PushResponse push(PushCommandRequest request) throws IOException {
String url = getRemoteUrl(request);
LOG.debug("push changes from {} to {}", getRepository(), url);
List<Changeset> result;
HgIniConfigurator iniConfigurator = new HgIniConfigurator(getContext());
try {
if (!Strings.isNullOrEmpty(request.getUsername()) && !Strings.isNullOrEmpty(request.getPassword())) {
iniConfigurator.addAuthenticationConfig(request, url);
}
TemporaryConfigFactory.Builder builder = configFactory.withContext(context);
if (!Strings.isNullOrEmpty(request.getUsername()) && !Strings.isNullOrEmpty(request.getPassword())) {
builder.withCredentials(url, request.getUsername(), request.getPassword());
}
result = on(open()).execute(url);
List<Changeset> result;
try {
result = com.aragost.javahg.commands.PushCommand.on(open()).execute(url);
} catch (ExecutionException ex) {
throw new InternalRepositoryException(getRepository(), "could not execute push command", ex);
} finally {
iniConfigurator.removeAuthenticationConfig();
throw new ImportFailedException(entity(getRepository()).build(), "could not execute pull command", ex);
}
return new PushResponse(result.size());

View File

@@ -24,13 +24,9 @@
package sonia.scm.repository.spi;
import com.google.common.io.Closeables;
import sonia.scm.event.ScmEventBus;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import sonia.scm.repository.Feature;
import sonia.scm.repository.HgConfigResolver;
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;
@@ -68,28 +64,22 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
Feature.MODIFICATIONS_BETWEEN_REVISIONS
);
private final HgRepositoryHandler handler;
private final Injector commandInjector;
private final HgCommandContext context;
private final HgLazyChangesetResolver lazyChangesetResolver;
private final HgRepositoryHookEventFactory eventFactory;
private final ScmEventBus eventBus;
HgRepositoryServiceProvider(HgRepositoryHandler handler,
HgConfigResolver configResolver,
HgRepositoryFactory factory,
HgRepositoryHookEventFactory eventFactory,
ScmEventBus eventBus,
Repository repository) {
this.handler = handler;
this.eventBus = eventBus;
this.eventFactory = eventFactory;
this.context = new HgCommandContext(configResolver, factory, repository);
this.lazyChangesetResolver = new HgLazyChangesetResolver(factory, repository);
HgRepositoryServiceProvider(Injector injector, HgCommandContext context) {
this.commandInjector = injector.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
bind(HgCommandContext.class).toInstance(context);
}
});
this.context = context;
}
@Override
public void close() throws IOException {
Closeables.close(context, true);
context.close();
}
@Override
@@ -104,7 +94,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
@Override
public BranchCommand getBranchCommand() {
return new HgBranchCommand(context, handler.getWorkingCopyFactory());
return commandInjector.getInstance(HgBranchCommand.class);
}
@Override
@@ -124,7 +114,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
@Override
public IncomingCommand getIncomingCommand() {
return new HgIncomingCommand(context, handler);
return commandInjector.getInstance(HgIncomingCommand.class);
}
@Override
@@ -145,22 +135,22 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
@Override
public OutgoingCommand getOutgoingCommand() {
return new HgOutgoingCommand(context, handler);
return commandInjector.getInstance(HgOutgoingCommand.class);
}
@Override
public PullCommand getPullCommand() {
return new HgPullCommand(handler, context, eventBus, lazyChangesetResolver, eventFactory);
return commandInjector.getInstance(HgPullCommand.class);
}
@Override
public PushCommand getPushCommand() {
return new HgPushCommand(handler, context);
return commandInjector.getInstance(HgPushCommand.class);
}
@Override
public ModifyCommand getModifyCommand() {
return new HgModifyCommand(context, handler.getWorkingCopyFactory());
return commandInjector.getInstance(HgModifyCommand.class);
}
@Override
@@ -180,7 +170,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
@Override
public TagCommand getTagCommand() {
return new HgTagCommand(context, handler.getWorkingCopyFactory());
return commandInjector.getInstance(HgTagCommand.class);
}
@Override
@@ -190,7 +180,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
@Override
public UnbundleCommand getUnbundleCommand() {
return new HgUnbundleCommand(context, lazyChangesetResolver, eventFactory);
return commandInjector.getInstance(HgUnbundleCommand.class);
}
@Override

View File

@@ -25,10 +25,8 @@
package sonia.scm.repository.spi;
import com.google.inject.Inject;
import sonia.scm.event.ScmEventBus;
import com.google.inject.Injector;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -38,34 +36,20 @@ import sonia.scm.repository.Repository;
@Extension
public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
private final HgRepositoryHandler handler;
private final HgConfigResolver configResolver;
private final HgRepositoryFactory factory;
private final ScmEventBus eventBus;
private final HgRepositoryHookEventFactory eventFactory;
private final Injector injector;
private final HgCommandContextFactory commandContextFactory;
@Inject
public HgRepositoryServiceResolver(HgRepositoryHandler handler,
HgConfigResolver configResolver,
HgRepositoryFactory factory,
ScmEventBus eventBus,
HgRepositoryHookEventFactory eventFactory
) {
this.handler = handler;
this.configResolver = configResolver;
this.factory = factory;
this.eventBus = eventBus;
this.eventFactory = eventFactory;
public HgRepositoryServiceResolver(Injector injector, HgCommandContextFactory commandContextFactory) {
this.injector = injector;
this.commandContextFactory = commandContextFactory;
}
@Override
public HgRepositoryServiceProvider resolve(Repository repository) {
HgRepositoryServiceProvider provider = null;
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
provider = new HgRepositoryServiceProvider(handler, configResolver, factory, eventFactory, eventBus, repository);
return new HgRepositoryServiceProvider(injector, commandContextFactory.create(repository));
}
return provider;
return null;
}
}

View File

@@ -25,21 +25,31 @@
package sonia.scm.repository.spi;
import com.aragost.javahg.Repository;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.apache.shiro.SecurityUtils;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Tag;
import sonia.scm.repository.api.TagCreateRequest;
import sonia.scm.repository.api.TagDeleteRequest;
import sonia.scm.repository.work.WorkingCopy;
import sonia.scm.user.User;
import javax.inject.Inject;
import static sonia.scm.repository.spi.UserFormatter.getUserStringFor;
public class HgTagCommand extends AbstractWorkingCopyCommand implements TagCommand {
public static final String DEFAULT_BRANCH_NAME = "default";
public HgTagCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
@Inject
public HgTagCommand(HgCommandContext context, HgRepositoryHandler handler) {
this(context, handler.getWorkingCopyFactory());
}
@VisibleForTesting
HgTagCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
super(context, workingCopyFactory);
}

View File

@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import sonia.scm.repository.RepositoryHookEvent;
import sonia.scm.repository.api.UnbundleResponse;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -44,6 +45,7 @@ public class HgUnbundleCommand implements UnbundleCommand {
private final HgLazyChangesetResolver changesetResolver;
private final HgRepositoryHookEventFactory eventFactory;
@Inject
HgUnbundleCommand(HgCommandContext context,
HgLazyChangesetResolver changesetResolver,
HgRepositoryHookEventFactory eventFactory

View File

@@ -0,0 +1,200 @@
/*
* 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 com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.io.INIConfiguration;
import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection;
import sonia.scm.net.GlobalProxyConfiguration;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.util.Util;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.net.URI;
public class TemporaryConfigFactory {
private static final Logger LOG = LoggerFactory.getLogger(TemporaryConfigFactory.class);
private static final String SECTION_PROXY = "http_proxy";
private static final String SECTION_AUTH = "auth";
private static final String AUTH_PREFIX = "temporary.";
private final GlobalProxyConfiguration globalProxyConfiguration;
@Inject
public TemporaryConfigFactory(GlobalProxyConfiguration globalProxyConfiguration) {
this.globalProxyConfiguration = globalProxyConfiguration;
}
public Builder withContext(HgCommandContext context) {
return new Builder(context);
}
public class Builder {
private final HgCommandContext context;
private String url;
private String username;
private String password;
private INIConfiguration hgrc;
private INISection previousProxyConfiguration;
private Builder(HgCommandContext context) {
this.context = context;
}
public Builder withCredentials(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
return this;
}
@SuppressWarnings("java:S4042") // we know that we delete a file
public <T> T call(HgCallable<T> callable) throws IOException {
File file = new File(context.getDirectory(), HgRepositoryHandler.PATH_HGRC);
boolean exists = file.exists();
if (isModificationRequired()) {
setupHgrc(file);
}
try {
return callable.call();
} finally {
if (!exists && file.exists() && !file.delete()) {
LOG.error("failed to delete temporary hgrc {}", file);
} else if (exists && file.exists()) {
cleanUpHgrc(file);
}
}
}
private void write(File file) throws IOException {
INIConfigurationWriter writer = new INIConfigurationWriter();
writer.write(hgrc, file);
}
private void setupHgrc(File file) throws IOException {
if (file.exists()) {
INIConfigurationReader reader = new INIConfigurationReader();
hgrc = reader.read(file);
} else {
hgrc = new INIConfiguration();
}
if (isAuthenticationEnabled()) {
applyAuthentication(hgrc);
}
if (globalProxyConfiguration.isEnabled()) {
applyProxyConfiguration(hgrc);
}
write(file);
}
private void applyProxyConfiguration(INIConfiguration hgrc) {
previousProxyConfiguration = hgrc.getSection(SECTION_PROXY);
hgrc.removeSection(SECTION_PROXY);
INISection proxy = new INISection(SECTION_PROXY);
proxy.setParameter("host", globalProxyConfiguration.getHost() + ":" + globalProxyConfiguration.getPort());
String user = globalProxyConfiguration.getUsername();
String passwd = globalProxyConfiguration.getPassword();
if (!Strings.isNullOrEmpty(user) && !Strings.isNullOrEmpty(passwd)) {
proxy.setParameter("user", user);
proxy.setParameter("passwd", passwd);
}
if (Util.isNotEmpty(globalProxyConfiguration.getExcludes())) {
proxy.setParameter("no", Joiner.on(',').join(globalProxyConfiguration.getExcludes()));
}
hgrc.addSection(proxy);
}
private void applyAuthentication(INIConfiguration hgrc) {
INISection auth = hgrc.getSection(SECTION_AUTH);
if (auth == null) {
auth = new INISection(SECTION_AUTH);
hgrc.addSection(auth);
}
URI uri = URI.create(url);
auth.setParameter(AUTH_PREFIX + "prefix", uri.getHost());
auth.setParameter(AUTH_PREFIX + "schemes", uri.getScheme());
auth.setParameter(AUTH_PREFIX + "username", username);
auth.setParameter(AUTH_PREFIX + "password", password);
}
private boolean isModificationRequired() {
return isAuthenticationEnabled() || globalProxyConfiguration.isEnabled();
}
private boolean isAuthenticationEnabled() {
return !Strings.isNullOrEmpty(url)
&& !Strings.isNullOrEmpty(username)
&& !Strings.isNullOrEmpty(password);
}
private void cleanUpHgrc(File file) throws IOException {
INISection auth = hgrc.getSection(SECTION_AUTH);
if (isAuthenticationEnabled() && auth != null) {
for (String key : auth.getParameterKeys()) {
if (key.startsWith(AUTH_PREFIX)) {
auth.removeParameter(key);
}
}
}
if (globalProxyConfiguration.isEnabled()) {
hgrc.removeSection(SECTION_PROXY);
if (previousProxyConfiguration != null) {
hgrc.addSection(previousProxyConfiguration);
}
}
if (isModificationRequired()) {
write(file);
}
}
}
@FunctionalInterface
public interface HgCallable<T> {
T call() throws IOException;
}
}