mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 09:25:43 +01:00
merge
This commit is contained in:
@@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -14,7 +13,7 @@ import java.util.Arrays;
|
||||
* builder for each method.
|
||||
*
|
||||
* <pre>
|
||||
* LinkBuilder builder = new LinkBuilder(uriInfo, MainResource.class, SubResource.class);
|
||||
* LinkBuilder builder = new LinkBuilder(pathInfo, MainResource.class, SubResource.class);
|
||||
* Link link = builder
|
||||
* .method("sub")
|
||||
* .parameters("param")
|
||||
@@ -25,16 +24,16 @@ import java.util.Arrays;
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Non-public will result in IllegalAccessError for plugins
|
||||
public class LinkBuilder {
|
||||
private final UriInfo uriInfo;
|
||||
private final ScmPathInfo pathInfo;
|
||||
private final Class[] classes;
|
||||
private final ImmutableList<Call> calls;
|
||||
|
||||
public LinkBuilder(UriInfo uriInfo, Class... classes) {
|
||||
this(uriInfo, classes, ImmutableList.of());
|
||||
public LinkBuilder(ScmPathInfo pathInfo, Class... classes) {
|
||||
this(pathInfo, classes, ImmutableList.of());
|
||||
}
|
||||
|
||||
private LinkBuilder(UriInfo uriInfo, Class[] classes, ImmutableList<Call> calls) {
|
||||
this.uriInfo = uriInfo;
|
||||
private LinkBuilder(ScmPathInfo pathInfo, Class[] classes, ImmutableList<Call> calls) {
|
||||
this.pathInfo = pathInfo;
|
||||
this.classes = classes;
|
||||
this.calls = calls;
|
||||
}
|
||||
@@ -51,7 +50,7 @@ public class LinkBuilder {
|
||||
throw new IllegalStateException("not enough methods for all classes");
|
||||
}
|
||||
|
||||
URI baseUri = uriInfo.getBaseUri();
|
||||
URI baseUri = pathInfo.getApiRestUri();
|
||||
URI relativeUri = createRelativeUri();
|
||||
return baseUri.resolve(relativeUri);
|
||||
}
|
||||
@@ -61,7 +60,7 @@ public class LinkBuilder {
|
||||
}
|
||||
|
||||
private LinkBuilder add(String method, String[] parameters) {
|
||||
return new LinkBuilder(uriInfo, classes, appendNewCall(method, parameters));
|
||||
return new LinkBuilder(pathInfo, classes, appendNewCall(method, parameters));
|
||||
}
|
||||
|
||||
private ImmutableList<Call> appendNewCall(String method, String[] parameters) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public interface ScmPathInfo {
|
||||
|
||||
String REST_API_PATH = "/api/rest";
|
||||
|
||||
URI getApiRestUri();
|
||||
|
||||
default URI getRootUri() {
|
||||
return getApiRestUri().resolve("../..");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
public class ScmPathInfoStore {
|
||||
|
||||
private ScmPathInfo pathInfo;
|
||||
|
||||
public ScmPathInfo get() {
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
public void set(ScmPathInfo info) {
|
||||
if (this.pathInfo != null) {
|
||||
throw new IllegalStateException("UriInfo already set");
|
||||
}
|
||||
this.pathInfo = info;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
public class UriInfoStore {
|
||||
|
||||
private UriInfo uriInfo;
|
||||
|
||||
public UriInfo get() {
|
||||
return uriInfo;
|
||||
}
|
||||
|
||||
public void set(UriInfo uriInfo) {
|
||||
if (this.uriInfo != null) {
|
||||
throw new IllegalStateException("UriInfo already set");
|
||||
}
|
||||
this.uriInfo = uriInfo;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
package sonia.scm.filter;
|
||||
|
||||
import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
|
||||
|
||||
/**
|
||||
* Useful constants for filter implementations.
|
||||
*
|
||||
@@ -44,26 +46,26 @@ public final class Filters
|
||||
public static final String PATTERN_ALL = "/*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_CONFIG = "/api/rest/config*";
|
||||
public static final String PATTERN_CONFIG = REST_API_PATH + "/config*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_DEBUG = "/debug.html";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_GROUPS = "/api/rest/groups*";
|
||||
public static final String PATTERN_GROUPS = REST_API_PATH + "/groups*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_PLUGINS = "/api/rest/plugins*";
|
||||
public static final String PATTERN_PLUGINS = REST_API_PATH + "/plugins*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_RESOURCE_REGEX =
|
||||
"^/(?:resources|api|plugins|index)[\\./].*(?:html|\\.css|\\.js|\\.xml|\\.json|\\.txt)";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_RESTAPI = "/api/rest/*";
|
||||
public static final String PATTERN_RESTAPI = REST_API_PATH + "/*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_USERS = "/api/rest/users*";
|
||||
public static final String PATTERN_USERS = REST_API_PATH + "/users*";
|
||||
|
||||
/** authentication priority */
|
||||
public static final int PRIORITY_AUTHENTICATION = 5000;
|
||||
|
||||
@@ -40,7 +40,6 @@ import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Lists;
|
||||
import sonia.scm.BasicPropertiesAware;
|
||||
import sonia.scm.ModelObject;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
@@ -349,17 +348,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
||||
// do not copy health check results
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the url of the repository.
|
||||
*
|
||||
* @param baseUrl base url of the server including the context path
|
||||
* @return url of the repository
|
||||
* @since 1.17
|
||||
*/
|
||||
public String createUrl(String baseUrl) {
|
||||
return HttpUtil.concatenate(baseUrl, type, namespace, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the {@link Repository} is the same as the obj argument.
|
||||
*
|
||||
|
||||
@@ -38,7 +38,6 @@ package sonia.scm.repository;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.TypeManager;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -99,29 +98,6 @@ public interface RepositoryManager
|
||||
*/
|
||||
public Collection<RepositoryType> getConfiguredTypes();
|
||||
|
||||
/**
|
||||
* Returns the {@link Repository} associated to the request uri.
|
||||
*
|
||||
*
|
||||
* @param request the current http request
|
||||
*
|
||||
* @return associated to the request uri
|
||||
* @since 1.9
|
||||
*/
|
||||
public Repository getFromRequest(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Returns the {@link Repository} associated to the request uri.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param uri request uri without context path
|
||||
*
|
||||
* @return associated to the request uri
|
||||
* @since 1.9
|
||||
*/
|
||||
public Repository getFromUri(String uri);
|
||||
|
||||
/**
|
||||
* Returns a {@link RepositoryHandler} by the given type (hg, git, svn ...).
|
||||
*
|
||||
|
||||
@@ -39,7 +39,6 @@ import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.ManagerDecorator;
|
||||
import sonia.scm.Type;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -120,34 +119,6 @@ public class RepositoryManagerDecorator
|
||||
return decorated;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Repository getFromRequest(HttpServletRequest request)
|
||||
{
|
||||
return decorated.getFromRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
*
|
||||
* @param uri
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Repository getFromUri(String uri)
|
||||
{
|
||||
return decorated.getFromUri(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
||||
@@ -44,8 +44,8 @@ import sonia.scm.NotFoundException;
|
||||
public class RepositoryNotFoundException extends NotFoundException
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -6583078808900520166L;
|
||||
private static final String TYPE_REPOSITORY = "repository";
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
@@ -55,10 +55,14 @@ public class RepositoryNotFoundException extends NotFoundException
|
||||
*
|
||||
*/
|
||||
public RepositoryNotFoundException(Repository repository) {
|
||||
super("repository", repository.getName() + "/" + repository.getNamespace());
|
||||
super(TYPE_REPOSITORY, repository.getName() + "/" + repository.getNamespace());
|
||||
}
|
||||
|
||||
public RepositoryNotFoundException(String repositoryId) {
|
||||
super("repository", repositoryId);
|
||||
super(TYPE_REPOSITORY, repositoryId);
|
||||
}
|
||||
|
||||
public RepositoryNotFoundException(NamespaceAndName namespaceAndName) {
|
||||
super(TYPE_REPOSITORY, namespaceAndName.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
@@ -26,35 +26,21 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.throwingproviders.CheckedProvider;
|
||||
|
||||
import sonia.scm.security.ScmSecurityException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.10
|
||||
*/
|
||||
public interface RepositoryProvider extends CheckedProvider<Repository>
|
||||
{
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws ScmSecurityException
|
||||
*/
|
||||
public interface RepositoryProvider extends CheckedProvider<Repository> {
|
||||
@Override
|
||||
public Repository get() throws ScmSecurityException;
|
||||
Repository get();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
@@ -42,6 +43,8 @@ import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* From the {@link RepositoryService} it is possible to access all commands for
|
||||
@@ -78,30 +81,32 @@ import java.io.IOException;
|
||||
* @apiviz.uses sonia.scm.repository.api.UnbundleCommandBuilder
|
||||
* @since 1.17
|
||||
*/
|
||||
@Slf4j
|
||||
public final class RepositoryService implements Closeable {
|
||||
private CacheManager cacheManager;
|
||||
private PreProcessorUtil preProcessorUtil;
|
||||
private RepositoryServiceProvider provider;
|
||||
private Repository repository;
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(RepositoryService.class);
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RepositoryService.class);
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
private final PreProcessorUtil preProcessorUtil;
|
||||
private final RepositoryServiceProvider provider;
|
||||
private final Repository repository;
|
||||
private final Set<ScmProtocolProvider> protocolProviders;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link RepositoryService}. This constructor should only
|
||||
* be called from the {@link RepositoryServiceFactory}.
|
||||
*
|
||||
* @param cacheManager cache manager
|
||||
* @param cacheManager cache manager
|
||||
* @param provider implementation for {@link RepositoryServiceProvider}
|
||||
* @param repository the repository
|
||||
* @param preProcessorUtil
|
||||
*/
|
||||
RepositoryService(CacheManager cacheManager,
|
||||
RepositoryServiceProvider provider, Repository repository,
|
||||
PreProcessorUtil preProcessorUtil) {
|
||||
RepositoryServiceProvider provider, Repository repository,
|
||||
PreProcessorUtil preProcessorUtil, Set<ScmProtocolProvider> protocolProviders) {
|
||||
this.cacheManager = cacheManager;
|
||||
this.provider = provider;
|
||||
this.repository = repository;
|
||||
this.preProcessorUtil = preProcessorUtil;
|
||||
this.protocolProviders = protocolProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +130,7 @@ public final class RepositoryService implements Closeable {
|
||||
try {
|
||||
provider.close();
|
||||
} catch (IOException ex) {
|
||||
logger.error("Could not close repository service provider", ex);
|
||||
log.error("Could not close repository service provider", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +143,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public BlameCommandBuilder getBlameCommand() {
|
||||
logger.debug("create blame command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new BlameCommandBuilder(cacheManager, provider.getBlameCommand(),
|
||||
repository, preProcessorUtil);
|
||||
@@ -153,7 +158,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public BranchesCommandBuilder getBranchesCommand() {
|
||||
logger.debug("create branches command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new BranchesCommandBuilder(cacheManager,
|
||||
provider.getBranchesCommand(), repository);
|
||||
@@ -168,7 +173,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public BrowseCommandBuilder getBrowseCommand() {
|
||||
logger.debug("create browse command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new BrowseCommandBuilder(cacheManager, provider.getBrowseCommand(),
|
||||
repository, preProcessorUtil);
|
||||
@@ -184,7 +189,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public BundleCommandBuilder getBundleCommand() {
|
||||
logger.debug("create bundle command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new BundleCommandBuilder(provider.getBundleCommand(), repository);
|
||||
}
|
||||
@@ -198,7 +203,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public CatCommandBuilder getCatCommand() {
|
||||
logger.debug("create cat command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new CatCommandBuilder(provider.getCatCommand());
|
||||
}
|
||||
@@ -213,7 +218,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public DiffCommandBuilder getDiffCommand() {
|
||||
logger.debug("create diff command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new DiffCommandBuilder(provider.getDiffCommand());
|
||||
}
|
||||
@@ -229,7 +234,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public IncomingCommandBuilder getIncomingCommand() {
|
||||
logger.debug("create incoming command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new IncomingCommandBuilder(cacheManager,
|
||||
provider.getIncomingCommand(), repository, preProcessorUtil);
|
||||
@@ -244,7 +249,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public LogCommandBuilder getLogCommand() {
|
||||
logger.debug("create log command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new LogCommandBuilder(cacheManager, provider.getLogCommand(),
|
||||
repository, preProcessorUtil);
|
||||
@@ -258,7 +263,7 @@ public final class RepositoryService implements Closeable {
|
||||
* by the implementation of the repository service provider.
|
||||
*/
|
||||
public ModificationsCommandBuilder getModificationsCommand() {
|
||||
logger.debug("create modifications command for repository {}",repository.getNamespaceAndName());
|
||||
logger.debug("create modifications command for repository {}", repository.getNamespaceAndName());
|
||||
return new ModificationsCommandBuilder(provider.getModificationsCommand(),repository, cacheManager.getCache(ModificationsCommandBuilder.CACHE_NAME), preProcessorUtil);
|
||||
}
|
||||
|
||||
@@ -272,7 +277,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public OutgoingCommandBuilder getOutgoingCommand() {
|
||||
logger.debug("create outgoing command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new OutgoingCommandBuilder(cacheManager,
|
||||
provider.getOutgoingCommand(), repository, preProcessorUtil);
|
||||
@@ -288,7 +293,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public PullCommandBuilder getPullCommand() {
|
||||
logger.debug("create pull command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new PullCommandBuilder(provider.getPullCommand(), repository);
|
||||
}
|
||||
@@ -303,7 +308,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public PushCommandBuilder getPushCommand() {
|
||||
logger.debug("create push command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new PushCommandBuilder(provider.getPushCommand());
|
||||
}
|
||||
@@ -326,7 +331,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public TagsCommandBuilder getTagsCommand() {
|
||||
logger.debug("create tags command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new TagsCommandBuilder(cacheManager, provider.getTagsCommand(),
|
||||
repository);
|
||||
@@ -342,7 +347,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public UnbundleCommandBuilder getUnbundleCommand() {
|
||||
logger.debug("create unbundle command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new UnbundleCommandBuilder(provider.getUnbundleCommand(),
|
||||
repository);
|
||||
@@ -369,5 +374,20 @@ public final class RepositoryService implements Closeable {
|
||||
return provider.getSupportedFeatures().contains(feature);
|
||||
}
|
||||
|
||||
public <T extends ScmProtocol> Stream<T> getSupportedProtocols() {
|
||||
return protocolProviders.stream()
|
||||
.filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType()))
|
||||
.map(this::<T>createProviderInstanceForRepository);
|
||||
}
|
||||
|
||||
private <T extends ScmProtocol> T createProviderInstanceForRepository(ScmProtocolProvider<T> protocolProvider) {
|
||||
return protocolProvider.get(repository);
|
||||
}
|
||||
|
||||
public <T extends ScmProtocol> T getProtocol(Class<T> clazz) {
|
||||
return this.<T>getSupportedProtocols()
|
||||
.filter(scmProtocol -> clazz.isAssignableFrom(scmProtocol.getClass()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(String.format("no implementation for %s and repository type %s", clazz.getName(),getRepository().getType())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,13 +137,15 @@ public final class RepositoryServiceFactory
|
||||
@Inject
|
||||
public RepositoryServiceFactory(ScmConfiguration configuration,
|
||||
CacheManager cacheManager, RepositoryManager repositoryManager,
|
||||
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil)
|
||||
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil,
|
||||
Set<ScmProtocolProvider> protocolProviders)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.cacheManager = cacheManager;
|
||||
this.repositoryManager = repositoryManager;
|
||||
this.resolvers = resolvers;
|
||||
this.preProcessorUtil = preProcessorUtil;
|
||||
this.protocolProviders = protocolProviders;
|
||||
|
||||
ScmEventBus.getInstance().register(new CacheClearHook(cacheManager));
|
||||
}
|
||||
@@ -208,9 +210,7 @@ public final class RepositoryServiceFactory
|
||||
|
||||
if (repository == null)
|
||||
{
|
||||
String msg = "could not find a repository with namespace/name " + namespaceAndName;
|
||||
|
||||
throw new RepositoryNotFoundException(msg);
|
||||
throw new RepositoryNotFoundException(namespaceAndName);
|
||||
}
|
||||
|
||||
return create(repository);
|
||||
@@ -254,7 +254,7 @@ public final class RepositoryServiceFactory
|
||||
}
|
||||
|
||||
service = new RepositoryService(cacheManager, provider, repository,
|
||||
preProcessorUtil);
|
||||
preProcessorUtil, protocolProviders);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -369,4 +369,6 @@ public final class RepositoryServiceFactory
|
||||
|
||||
/** service resolvers */
|
||||
private final Set<RepositoryServiceResolver> resolvers;
|
||||
|
||||
private Set<ScmProtocolProvider> protocolProviders;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
/**
|
||||
* An ScmProtocol represents a concrete protocol provided by the SCM-Manager instance
|
||||
* to interact with a repository depending on its type. There may be multiple protocols
|
||||
* available for a repository type (eg. http and ssh).
|
||||
*/
|
||||
public interface ScmProtocol {
|
||||
|
||||
/**
|
||||
* The type of the concrete protocol, eg. "http" or "ssh".
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* The URL to access the repository providing this protocol.
|
||||
*/
|
||||
String getUrl();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
@ExtensionPoint(multi = true)
|
||||
public interface ScmProtocolProvider<T extends ScmProtocol> {
|
||||
|
||||
String getType();
|
||||
|
||||
T get(Repository repository);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.ScmProtocol;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
public abstract class HttpScmProtocol implements ScmProtocol {
|
||||
|
||||
private final Repository repository;
|
||||
private final String basePath;
|
||||
|
||||
public HttpScmProtocol(Repository repository, String basePath) {
|
||||
this.repository = repository;
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return URI.create(basePath + "/").resolve(String.format("repo/%s/%s", repository.getNamespace(), repository.getName())).toASCIIString();
|
||||
}
|
||||
|
||||
public final void serve(HttpServletRequest request, HttpServletResponse response, ServletConfig config) throws ServletException, IOException {
|
||||
serve(request, response, repository, config);
|
||||
}
|
||||
|
||||
protected abstract void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) throws ServletException, IOException;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.ScmProtocolProvider;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
@Slf4j
|
||||
public abstract class InitializingHttpScmProtocolWrapper implements ScmProtocolProvider<HttpScmProtocol> {
|
||||
|
||||
private final Provider<? extends ScmProviderHttpServlet> delegateProvider;
|
||||
private final Provider<ScmPathInfoStore> pathInfoStore;
|
||||
private final ScmConfiguration scmConfiguration;
|
||||
|
||||
private volatile boolean isInitialized = false;
|
||||
|
||||
|
||||
protected InitializingHttpScmProtocolWrapper(Provider<? extends ScmProviderHttpServlet> delegateProvider, Provider<ScmPathInfoStore> pathInfoStore, ScmConfiguration scmConfiguration) {
|
||||
this.delegateProvider = delegateProvider;
|
||||
this.pathInfoStore = pathInfoStore;
|
||||
this.scmConfiguration = scmConfiguration;
|
||||
}
|
||||
|
||||
protected void initializeServlet(ServletConfig config, ScmProviderHttpServlet httpServlet) throws ServletException {
|
||||
httpServlet.init(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpScmProtocol get(Repository repository) {
|
||||
if (!repository.getType().equals(getType())) {
|
||||
throw new IllegalArgumentException(String.format("cannot handle repository with type %s with protocol for type %s", repository.getType(), getType()));
|
||||
}
|
||||
return new ProtocolWrapper(repository, computeBasePath());
|
||||
}
|
||||
|
||||
private String computeBasePath() {
|
||||
return getPathFromScmPathInfoIfAvailable().orElse(getPathFromConfiguration());
|
||||
}
|
||||
|
||||
private Optional<String> getPathFromScmPathInfoIfAvailable() {
|
||||
try {
|
||||
ScmPathInfoStore scmPathInfoStore = pathInfoStore.get();
|
||||
if (scmPathInfoStore != null && scmPathInfoStore.get() != null) {
|
||||
return of(scmPathInfoStore.get().getRootUri().toASCIIString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("could not get ScmPathInfoStore from context", e);
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
private String getPathFromConfiguration() {
|
||||
log.debug("using base path from configuration: {}", scmConfiguration.getBaseUrl());
|
||||
return scmConfiguration.getBaseUrl();
|
||||
}
|
||||
|
||||
private class ProtocolWrapper extends HttpScmProtocol {
|
||||
|
||||
public ProtocolWrapper(Repository repository, String basePath) {
|
||||
super(repository, basePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) throws ServletException, IOException {
|
||||
if (!isInitialized) {
|
||||
synchronized (InitializingHttpScmProtocolWrapper.this) {
|
||||
if (!isInitialized) {
|
||||
ScmProviderHttpServlet httpServlet = delegateProvider.get();
|
||||
initializeServlet(config, httpServlet);
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegateProvider.get().service(request, response, repository);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,6 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ScmProviderHttpServlet {
|
||||
|
||||
void service(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException;
|
||||
|
||||
void init(ServletConfig config) throws ServletException;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ScmProviderHttpServletDecorator implements ScmProviderHttpServlet {
|
||||
|
||||
private final ScmProviderHttpServlet object;
|
||||
|
||||
public ScmProviderHttpServletDecorator(ScmProviderHttpServlet object) {
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException {
|
||||
object.service(request, response, repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
object.init(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.DecoratorFactory;
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
@ExtensionPoint
|
||||
public interface ScmProviderHttpServletDecoratorFactory extends DecoratorFactory<ScmProviderHttpServlet> {
|
||||
/**
|
||||
* Has to return <code>true</code> if this factory provides a decorator for the given scm type (eg. "git", "hg" or
|
||||
* "svn").
|
||||
* @param type The current scm type this factory can provide a decorator for.
|
||||
* @return <code>true</code> when the provided decorator should be used for the given scm type.
|
||||
*/
|
||||
boolean handlesScmType(String type);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import sonia.scm.util.Decorators;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
public abstract class ScmProviderHttpServletProvider implements Provider<ScmProviderHttpServlet> {
|
||||
|
||||
@Inject(optional = true)
|
||||
private Set<ScmProviderHttpServletDecoratorFactory> decoratorFactories;
|
||||
|
||||
private final String type;
|
||||
|
||||
protected ScmProviderHttpServletProvider(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScmProviderHttpServlet get() {
|
||||
return Decorators.decorate(getRootServlet(), getDecoratorsForType());
|
||||
}
|
||||
|
||||
private List<ScmProviderHttpServletDecoratorFactory> getDecoratorsForType() {
|
||||
return decoratorFactories.stream().filter(d -> d.handlesScmType(type)).collect(toList());
|
||||
}
|
||||
|
||||
protected abstract ScmProviderHttpServlet getRootServlet();
|
||||
}
|
||||
@@ -30,56 +30,69 @@
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm;
|
||||
|
||||
package sonia.scm.util;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.DecoratorFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.17
|
||||
*/
|
||||
public class ArgumentIsInvalidException extends IllegalStateException
|
||||
public final class Decorators
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
* the logger for Decorators
|
||||
*/
|
||||
public ArgumentIsInvalidException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(Decorators.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param s
|
||||
*/
|
||||
public ArgumentIsInvalidException(String s)
|
||||
{
|
||||
super(s);
|
||||
}
|
||||
private Decorators() {}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param cause
|
||||
* @param object
|
||||
* @param decoratorFactories
|
||||
* @param <T>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ArgumentIsInvalidException(Throwable cause)
|
||||
public static <T> T decorate(T object,
|
||||
Iterable<? extends DecoratorFactory<T>> decoratorFactories)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
if (decoratorFactories != null)
|
||||
{
|
||||
for (DecoratorFactory<T> decoratorFactory : decoratorFactories)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("decorate {} with {}", object.getClass(),
|
||||
decoratorFactory.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param message
|
||||
* @param cause
|
||||
*/
|
||||
public ArgumentIsInvalidException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
object = decoratorFactory.createDecorator(object);
|
||||
}
|
||||
}
|
||||
else if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("no decorators found for {}", object.getClass());
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
}
|
||||
@@ -33,39 +33,32 @@
|
||||
|
||||
package sonia.scm.web.filter;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ArgumentIsInvalidException;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServletDecorator;
|
||||
import sonia.scm.security.Role;
|
||||
import sonia.scm.security.ScmSecurityException;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Abstract http filter to check repository permissions.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public abstract class PermissionFilter extends HttpFilter
|
||||
public abstract class PermissionFilter extends ScmProviderHttpServletDecorator
|
||||
{
|
||||
|
||||
/** the logger for PermissionFilter */
|
||||
@@ -81,23 +74,14 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
public PermissionFilter(ScmConfiguration configuration)
|
||||
protected PermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate)
|
||||
{
|
||||
super(delegate);
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the requested repository.
|
||||
*
|
||||
*
|
||||
* @param request current http request
|
||||
*
|
||||
* @return requested repository
|
||||
*/
|
||||
protected abstract Repository getRepository(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Returns true if the current request is a write request.
|
||||
*
|
||||
@@ -117,66 +101,38 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
*
|
||||
* @param request http request
|
||||
* @param response http response
|
||||
* @param chain filter chain
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Override
|
||||
protected void doFilter(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain chain)
|
||||
public void service(HttpServletRequest request,
|
||||
HttpServletResponse response, Repository repository)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
|
||||
try
|
||||
{
|
||||
Repository repository = getRepository(request);
|
||||
boolean writeRequest = isWriteRequest(request);
|
||||
|
||||
if (repository != null)
|
||||
if (hasPermission(repository, writeRequest))
|
||||
{
|
||||
boolean writeRequest = isWriteRequest(request);
|
||||
logger.trace("{} access to repository {} for user {} granted",
|
||||
getActionAsString(writeRequest), repository.getName(),
|
||||
getUserName(subject));
|
||||
|
||||
if (hasPermission(repository, writeRequest))
|
||||
{
|
||||
logger.trace("{} access to repository {} for user {} granted",
|
||||
getActionAsString(writeRequest), repository.getName(),
|
||||
getUserName(subject));
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.info("{} access to repository {} for user {} denied",
|
||||
getActionAsString(writeRequest), repository.getName(),
|
||||
getUserName(subject));
|
||||
|
||||
sendAccessDenied(request, response, subject);
|
||||
}
|
||||
super.service(request, response, repository);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("repository not found");
|
||||
logger.info("{} access to repository {} for user {} denied",
|
||||
getActionAsString(writeRequest), repository.getName(),
|
||||
getUserName(subject));
|
||||
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
sendAccessDenied(request, response, subject);
|
||||
}
|
||||
}
|
||||
catch (ArgumentIsInvalidException ex)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace(
|
||||
"wrong request at ".concat(request.getRequestURI()).concat(
|
||||
" send redirect"), ex);
|
||||
}
|
||||
else if (logger.isWarnEnabled())
|
||||
{
|
||||
logger.warn("wrong request at {} send redirect",
|
||||
request.getRequestURI());
|
||||
}
|
||||
|
||||
response.sendRedirect(getRepositoryRootHelpUrl(request));
|
||||
}
|
||||
catch (ScmSecurityException | AuthorizationException ex)
|
||||
{
|
||||
logger.warn("user " + subject.getPrincipal() + " has not enough permissions", ex);
|
||||
@@ -217,29 +173,6 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
HttpUtil.sendUnauthorized(response, configuration.getRealmDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the type of the repositroy from url.
|
||||
*
|
||||
*
|
||||
* @param request http request
|
||||
*
|
||||
* @return type of repository
|
||||
*/
|
||||
private String extractType(HttpServletRequest request)
|
||||
{
|
||||
Iterator<String> it = Splitter.on(
|
||||
HttpUtil.SEPARATOR_PATH).omitEmptyStrings().split(
|
||||
request.getRequestURI()).iterator();
|
||||
String type = it.next();
|
||||
|
||||
if (Util.isNotEmpty(request.getContextPath()))
|
||||
{
|
||||
type = it.next();
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send access denied to the servlet response.
|
||||
*
|
||||
@@ -280,25 +213,6 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
: "read";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repository root help url.
|
||||
*
|
||||
*
|
||||
* @param request current http request
|
||||
*
|
||||
* @return repository root help url
|
||||
*/
|
||||
private String getRepositoryRootHelpUrl(HttpServletRequest request)
|
||||
{
|
||||
String type = extractType(request);
|
||||
String helpUrl = HttpUtil.getCompleteUrl(request,
|
||||
"/api/rest/help/repository-root/");
|
||||
|
||||
helpUrl = helpUrl.concat(type).concat(".html");
|
||||
|
||||
return helpUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username from the given subject or anonymous.
|
||||
*
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.web.filter;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.inject.ProvisionException;
|
||||
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.9
|
||||
*/
|
||||
public abstract class ProviderPermissionFilter extends PermissionFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for ProviderPermissionFilter
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(ProviderPermissionFilter.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param configuration
|
||||
* @param repositoryProvider
|
||||
* @since 1.21
|
||||
*/
|
||||
public ProviderPermissionFilter(ScmConfiguration configuration,
|
||||
RepositoryProvider repositoryProvider)
|
||||
{
|
||||
super(configuration);
|
||||
this.repositoryProvider = repositoryProvider;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected Repository getRepository(HttpServletRequest request)
|
||||
{
|
||||
Repository repository = null;
|
||||
|
||||
try
|
||||
{
|
||||
repository = repositoryProvider.get();
|
||||
}
|
||||
catch (ProvisionException ex)
|
||||
{
|
||||
Throwables.propagateIfPossible(ex.getCause(),
|
||||
IllegalStateException.class, AuthorizationException.class);
|
||||
logger.error("could not get repository from request", ex);
|
||||
}
|
||||
|
||||
return repository;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final RepositoryProvider repositoryProvider;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class RepositoryTest
|
||||
{
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testCreateUrl()
|
||||
{
|
||||
Repository repository = new Repository("123", "hg", "test", "repo");
|
||||
|
||||
assertEquals("http://localhost:8080/scm/hg/test/repo",
|
||||
repository.createUrl("http://localhost:8080/scm"));
|
||||
assertEquals("http://localhost:8080/scm/hg/test/repo",
|
||||
repository.createUrl("http://localhost:8080/scm/"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.HttpScmProtocol;
|
||||
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.assertj.core.util.IterableUtil.sizeOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class RepositoryServiceTest {
|
||||
|
||||
private final RepositoryServiceProvider provider = mock(RepositoryServiceProvider.class);
|
||||
private final Repository repository = new Repository("", "git", "space", "repo");
|
||||
|
||||
@Test
|
||||
public void shouldReturnMatchingProtocolsFromProvider() {
|
||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()));
|
||||
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
|
||||
|
||||
assertThat(sizeOf(supportedProtocols.collect(Collectors.toList()))).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindKnownProtocol() {
|
||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()));
|
||||
|
||||
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
|
||||
|
||||
assertThat(protocol).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailForUnknownProtocol() {
|
||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
repositoryService.getProtocol(UnknownScmProtocol.class);
|
||||
});
|
||||
}
|
||||
|
||||
private static class DummyHttpProtocol extends HttpScmProtocol {
|
||||
public DummyHttpProtocol(Repository repository) {
|
||||
super(repository, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class DummyScmProtocolProvider implements ScmProtocolProvider {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "git";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScmProtocol get(Repository repository) {
|
||||
return new DummyHttpProtocol(repository);
|
||||
}
|
||||
}
|
||||
|
||||
private interface UnknownScmProtocol extends ScmProtocol {}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HttpScmProtocolTest {
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicTest> shouldCreateCorrectUrlsWithContextPath() {
|
||||
return Stream.of("http://localhost/scm", "http://localhost/scm/")
|
||||
.map(url -> assertResultingUrl(url, "http://localhost/scm/repo/space/name"));
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicTest> shouldCreateCorrectUrlsWithPort() {
|
||||
return Stream.of("http://localhost:8080", "http://localhost:8080/")
|
||||
.map(url -> assertResultingUrl(url, "http://localhost:8080/repo/space/name"));
|
||||
}
|
||||
|
||||
DynamicTest assertResultingUrl(String baseUrl, String expectedUrl) {
|
||||
String actualUrl = createInstanceOfHttpScmProtocol(baseUrl).getUrl();
|
||||
return DynamicTest.dynamicTest(baseUrl + " -> " + expectedUrl, () -> assertThat(actualUrl).isEqualTo(expectedUrl));
|
||||
}
|
||||
|
||||
private HttpScmProtocol createInstanceOfHttpScmProtocol(String baseUrl) {
|
||||
return new HttpScmProtocol(new Repository("", "", "space", "name"), baseUrl) {
|
||||
@Override
|
||||
protected void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.ProvisionException;
|
||||
import com.google.inject.util.Providers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.OngoingStubbing;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfo;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
public class InitializingHttpScmProtocolWrapperTest {
|
||||
|
||||
private static final Repository REPOSITORY = new Repository("", "git", "space", "name");
|
||||
|
||||
@Mock
|
||||
private ScmProviderHttpServlet delegateServlet;
|
||||
@Mock
|
||||
private ScmPathInfoStore pathInfoStore;
|
||||
@Mock
|
||||
private ScmConfiguration scmConfiguration;
|
||||
private Provider<ScmPathInfoStore> pathInfoStoreProvider;
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
@Mock
|
||||
private ServletConfig servletConfig;
|
||||
|
||||
private InitializingHttpScmProtocolWrapper wrapper;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
initMocks(this);
|
||||
pathInfoStoreProvider = mock(Provider.class);
|
||||
when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
|
||||
|
||||
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(this.delegateServlet), pathInfoStoreProvider, scmConfiguration) {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "git";
|
||||
}
|
||||
};
|
||||
when(scmConfiguration.getBaseUrl()).thenReturn("http://example.com/scm");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUsePathFromPathInfo() {
|
||||
mockSetPathInfo();
|
||||
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseConfigurationWhenPathInfoNotSet() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseConfigurationWhenNotInRequestScope() {
|
||||
when(pathInfoStoreProvider.get()).thenThrow(new ProvisionException("test"));
|
||||
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeAndDelegateRequestThroughFilter() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
|
||||
verify(delegateServlet).init(servletConfig);
|
||||
verify(delegateServlet).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeOnlyOnce() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
|
||||
verify(delegateServlet, times(1)).init(servletConfig);
|
||||
verify(delegateServlet, times(2)).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldFailForIllegalScmType() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(new Repository("", "other", "space", "name"));
|
||||
}
|
||||
|
||||
private OngoingStubbing<ScmPathInfo> mockSetPathInfo() {
|
||||
return when(pathInfoStore.get()).thenReturn(() -> URI.create("http://example.com/scm/api/rest/"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package sonia.scm.web.filter;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro.ini")
|
||||
public class PermissionFilterTest {
|
||||
|
||||
public static final Repository REPOSITORY = new Repository("1", "git", "space", "name");
|
||||
|
||||
@Rule
|
||||
public final ShiroRule shiroRule = new ShiroRule();
|
||||
|
||||
private final ScmProviderHttpServlet delegateServlet = mock(ScmProviderHttpServlet.class);
|
||||
|
||||
private final PermissionFilter permissionFilter = new PermissionFilter(new ScmConfiguration(), delegateServlet) {
|
||||
@Override
|
||||
protected boolean isWriteRequest(HttpServletRequest request) {
|
||||
return writeRequest;
|
||||
}
|
||||
};
|
||||
|
||||
private final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
private final HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
|
||||
private boolean writeRequest = false;
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "reader", password = "secret")
|
||||
public void shouldPassForReaderOnReadRequest() throws IOException, ServletException {
|
||||
writeRequest = false;
|
||||
|
||||
permissionFilter.service(request, response, REPOSITORY);
|
||||
|
||||
verify(delegateServlet).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "reader", password = "secret")
|
||||
public void shouldBlockForReaderOnWriteRequest() throws IOException, ServletException {
|
||||
writeRequest = true;
|
||||
|
||||
permissionFilter.service(request, response, REPOSITORY);
|
||||
|
||||
verify(response).sendError(eq(401), anyString());
|
||||
verify(delegateServlet, never()).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writer", password = "secret")
|
||||
public void shouldPassForWriterOnWriteRequest() throws IOException, ServletException {
|
||||
writeRequest = true;
|
||||
|
||||
permissionFilter.service(request, response, REPOSITORY);
|
||||
|
||||
verify(delegateServlet).service(request, response, REPOSITORY);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
[users]
|
||||
trillian = secret, user
|
||||
admin = secret, admin
|
||||
writer = secret, repo_write
|
||||
reader = secret, repo_read
|
||||
unpriv = secret
|
||||
|
||||
[roles]
|
||||
admin = *
|
||||
user = something:*
|
||||
user = something:*
|
||||
repo_read = "repository:read:1"
|
||||
repo_write = "repository:push:1"
|
||||
|
||||
Reference in New Issue
Block a user