Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2018-09-18 10:23:50 +02:00
187 changed files with 3501 additions and 2375 deletions

View File

@@ -33,8 +33,6 @@
package sonia.scm;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.inject.Injector;
@@ -63,8 +61,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
@@ -135,7 +131,7 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
moduleList.add(new EagerSingletonModule());
moduleList.add(ShiroWebModule.guiceFilterModule());
moduleList.add(new WebElementModule(pluginLoader));
moduleList.add(new ScmServletModule(context, pluginLoader, overrides, pluginLoader.getExtensionProcessor()));
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));
moduleList.add(
new ScmSecurityModule(context, pluginLoader.getExtensionProcessor())
);

View File

@@ -33,8 +33,6 @@
package sonia.scm;
//~--- non-JDK imports --------------------------------------------------------
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Provider;
import com.google.inject.multibindings.Multibinder;
@@ -56,17 +54,48 @@ import sonia.scm.group.xml.XmlGroupDAO;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.io.FileSystem;
import sonia.scm.net.SSLContextProvider;
import sonia.scm.net.ahc.*;
import sonia.scm.plugin.*;
import sonia.scm.repository.*;
import sonia.scm.net.ahc.AdvancedHttpClient;
import sonia.scm.net.ahc.ContentTransformer;
import sonia.scm.net.ahc.DefaultAdvancedHttpClient;
import sonia.scm.net.ahc.JsonContentTransformer;
import sonia.scm.net.ahc.XmlContentTransformer;
import sonia.scm.plugin.DefaultPluginLoader;
import sonia.scm.plugin.DefaultPluginManager;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginManager;
import sonia.scm.repository.DefaultRepositoryManager;
import sonia.scm.repository.DefaultRepositoryProvider;
import sonia.scm.repository.HealthCheckContextListener;
import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.NamespaceStrategyProvider;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryManagerProvider;
import sonia.scm.repository.RepositoryProvider;
import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler;
import sonia.scm.security.*;
import sonia.scm.store.*;
import sonia.scm.security.AuthorizationChangedEventProducer;
import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil;
import sonia.scm.security.ConfigurableLoginAttemptHandler;
import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.DefaultSecuritySystem;
import sonia.scm.security.KeyGenerator;
import sonia.scm.security.LoginAttemptHandler;
import sonia.scm.security.SecuritySystem;
import sonia.scm.store.BlobStoreFactory;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.DataStoreFactory;
import sonia.scm.store.FileBlobStoreFactory;
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.store.JAXBDataStoreFactory;
import sonia.scm.template.MustacheTemplateEngine;
import sonia.scm.template.TemplateEngine;
import sonia.scm.template.TemplateEngineFactory;
@@ -81,14 +110,16 @@ import sonia.scm.util.ScmConfigurationUtil;
import sonia.scm.web.UserAgentParser;
import sonia.scm.web.cgi.CGIExecutorFactory;
import sonia.scm.web.cgi.DefaultCGIExecutorFactory;
import sonia.scm.web.filter.AuthenticationFilter;
import sonia.scm.web.filter.LoggingFilter;
import sonia.scm.web.protocol.HttpProtocolServlet;
import sonia.scm.web.security.AdministrationContext;
import sonia.scm.web.security.DefaultAdministrationContext;
import javax.net.ssl.SSLContext;
import javax.servlet.ServletContext;
//~--- JDK imports ------------------------------------------------------------
import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
/**
*
@@ -99,14 +130,14 @@ public class ScmServletModule extends ServletModule
/** Field description */
public static final String[] PATTERN_ADMIN = new String[] {
"/api/rest/groups*",
"/api/rest/users*", "/api/rest/plguins*" };
REST_API_PATH + "/groups*",
REST_API_PATH + "/users*", REST_API_PATH + "/plguins*" };
/** Field description */
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";
@@ -155,22 +186,11 @@ public class ScmServletModule extends ServletModule
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param servletContext
* @param pluginLoader
* @param overrides
* @param extensionProcessor
*/
ScmServletModule(ServletContext servletContext,
DefaultPluginLoader pluginLoader, ClassOverrides overrides, ExtensionProcessor extensionProcessor)
ScmServletModule(ServletContext servletContext, DefaultPluginLoader pluginLoader, ClassOverrides overrides)
{
this.servletContext = servletContext;
this.pluginLoader = pluginLoader;
this.overrides = overrides;
this.extensionProcessor = extensionProcessor;
}
//~--- methods --------------------------------------------------------------
@@ -293,6 +313,8 @@ public class ScmServletModule extends ServletModule
bind(TemplateEngineFactory.class);
bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class);
filter(HttpProtocolServlet.PATTERN).through(AuthenticationFilter.class);
// bind events
// bind(LastModifiedUpdateListener.class);
@@ -389,11 +411,6 @@ public class ScmServletModule extends ServletModule
/**
* Load ScmConfiguration with JAXB
*
*
* @param context
*
* @return
*/
private ScmConfiguration getScmConfiguration()
{
@@ -414,6 +431,4 @@ public class ScmServletModule extends ServletModule
/** Field description */
private final ServletContext servletContext;
private final ExtensionProcessor extensionProcessor;
}

View File

@@ -33,7 +33,7 @@ public class WebResourceServlet extends HttpServlet {
* TODO remove old protocol servlets and hook. Move /hook/hg to api?
*/
@VisibleForTesting
static final String PATTERN = "/(?!api/|git/|hg/|svn/|hook/).*";
static final String PATTERN = "/(?!api/|git/|hg/|svn/|hook/|repo/).*";
private static final Logger LOG = LoggerFactory.getLogger(WebResourceServlet.class);

View File

@@ -1,24 +1,26 @@
package sonia.scm.api.rest;
import sonia.scm.api.v2.resources.UriInfoStore;
import sonia.scm.api.v2.resources.ScmPathInfoStore;
import javax.inject.Inject;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;
@Provider
public class UriInfoFilter implements ContainerRequestFilter {
private final javax.inject.Provider<UriInfoStore> storeProvider;
private final javax.inject.Provider<ScmPathInfoStore> storeProvider;
@Inject
public UriInfoFilter(javax.inject.Provider<UriInfoStore> storeProvider) {
public UriInfoFilter(javax.inject.Provider<ScmPathInfoStore> storeProvider) {
this.storeProvider = storeProvider;
}
@Override
public void filter(ContainerRequestContext requestContext) {
storeProvider.get().set(requestContext.getUriInfo());
UriInfo uriInfo = requestContext.getUriInfo();
storeProvider.get().set(uriInfo::getBaseUri);
}
}

View File

@@ -33,37 +33,28 @@
package sonia.scm.api.rest.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.inject.Inject;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTypePredicate;
import sonia.scm.util.HttpUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import sonia.scm.template.Viewable;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import sonia.scm.template.Viewable;
import java.io.IOException;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
*
@@ -106,13 +97,12 @@ public class RepositoryRootResource
@Produces(MediaType.TEXT_HTML)
public Viewable renderRepositoriesRoot(@Context HttpServletRequest request, @PathParam("type") final String type)
{
String baseUrl = HttpUtil.getCompleteUrl(request);
//J-
Collection<RepositoryTemplateElement> unsortedRepositories =
Collections2.transform(
Collections2.filter(
repositoryManager.getAll(), new RepositoryTypePredicate(type))
, new RepositoryTransformFunction(baseUrl)
, new RepositoryTransformFunction()
);
List<RepositoryTemplateElement> repositories = Ordering.from(
@@ -138,17 +128,9 @@ public class RepositoryRootResource
public static class RepositoryTemplateElement
{
/**
* Constructs ...
*
*
* @param repository
* @param baseUrl
*/
public RepositoryTemplateElement(Repository repository, String baseUrl)
public RepositoryTemplateElement(Repository repository)
{
this.repository = repository;
this.baseUrl = baseUrl;
}
//~--- get methods --------------------------------------------------------
@@ -175,22 +157,8 @@ public class RepositoryRootResource
return repository;
}
/**
* Method description
*
*
* @return
*/
public String getUrl()
{
return repository.createUrl(baseUrl);
}
//~--- fields -------------------------------------------------------------
/** Field description */
private String baseUrl;
/** Field description */
private Repository repository;
@@ -236,31 +204,10 @@ public class RepositoryRootResource
private static class RepositoryTransformFunction
implements Function<Repository, RepositoryTemplateElement>
{
public RepositoryTransformFunction(String baseUrl)
{
this.baseUrl = baseUrl;
}
//~--- methods ------------------------------------------------------------
/**
* Method description
*
*
* @param repository
*
* @return
*/
@Override
public RepositoryTemplateElement apply(Repository repository)
{
return new RepositoryTemplateElement(repository, baseUrl);
return new RepositoryTemplateElement(repository);
}
//~--- fields -------------------------------------------------------------
/** Field description */
private String baseUrl;
}
}

View File

@@ -1,73 +1,21 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import de.otto.edison.hal.paging.NumberedPaging;
import de.otto.edison.hal.paging.PagingRel;
import sonia.scm.ModelObject;
import sonia.scm.PageResult;
import javax.inject.Inject;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import static com.damnhandy.uri.template.UriTemplate.fromTemplate;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging;
import static java.util.stream.Collectors.toList;
abstract class BasicCollectionToDtoMapper<E extends ModelObject, D extends HalRepresentation, M extends BaseMapper<E, D>> {
private final String collectionName;
public class BasicCollectionToDtoMapper<E extends ModelObject, D extends HalRepresentation, M extends BaseMapper<E, D>> extends PagedCollectionToDtoMapper<E, D> {
private final M entityToDtoMapper;
@Inject
public BasicCollectionToDtoMapper(String collectionName, M entityToDtoMapper) {
this.collectionName = collectionName;
super(collectionName);
this.entityToDtoMapper = entityToDtoMapper;
}
CollectionDto map(int pageNumber, int pageSize, PageResult<E> pageResult, String selfLink, Optional<String> createLink) {
return map(pageNumber, pageSize, pageResult, selfLink, createLink, entityToDtoMapper::map);
}
CollectionDto map(int pageNumber, int pageSize, PageResult<E> pageResult, String selfLink, Optional<String> createLink, Function<E, ? extends HalRepresentation> mapper) {
NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount());
List<HalRepresentation> dtos = pageResult.getEntities().stream().map(mapper).collect(toList());
CollectionDto collectionDto = new CollectionDto(
createLinks(paging, selfLink, createLink),
embedDtos(dtos));
collectionDto.setPage(pageNumber);
collectionDto.setPageTotal(computePageTotal(pageSize, pageResult));
return collectionDto;
}
private int computePageTotal(int pageSize, PageResult<E> pageResult) {
if (pageResult.getOverallCount() % pageSize > 0) {
return pageResult.getOverallCount() / pageSize + 1;
} else {
return pageResult.getOverallCount() / pageSize;
}
}
private Links createLinks(NumberedPaging page, String selfLink, Optional<String> createLink) {
Links.Builder linksBuilder = linkingTo()
.with(page.links(
fromTemplate(selfLink + "{?page,pageSize}"),
EnumSet.allOf(PagingRel.class)));
createLink.ifPresent(link -> linksBuilder.single(link("create", link)));
return linksBuilder.build();
}
private Embedded embedDtos(List<HalRepresentation> dtos) {
return embeddedBuilder()
.with(collectionName, dtos)
.build();
}
}

View File

@@ -8,14 +8,14 @@ import javax.inject.Inject;
import java.util.Optional;
import java.util.function.Supplier;
public class ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper<Changeset, ChangesetDto, ChangesetToChangesetDtoMapper> {
public class ChangesetCollectionToDtoMapper extends PagedCollectionToDtoMapper<Changeset, ChangesetDto> {
private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper;
protected final ResourceLinks resourceLinks;
@Inject
public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
super("changesets", changesetToChangesetDtoMapper);
super("changesets");
this.changesetToChangesetDtoMapper = changesetToChangesetDtoMapper;
this.resourceLinks = resourceLinks;
}
@@ -32,3 +32,4 @@ public class ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper<C
return resourceLinks.changeset().all(repository.getNamespace(), repository.getName());
}
}

View File

@@ -23,7 +23,7 @@ import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
@Mapper
public abstract class ChangesetToChangesetDtoMapper extends BaseMapper<Changeset, ChangesetDto> {
public abstract class ChangesetToChangesetDtoMapper implements InstantAttributeMapper {
@Inject
private RepositoryServiceFactory serviceFactory;
@@ -65,7 +65,8 @@ public abstract class ChangesetToChangesetDtoMapper extends BaseMapper<Changeset
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId()))
.single(link("diff", resourceLinks.diff().self(namespace, name, target.getId())));
.single(link("diff", resourceLinks.diff().self(namespace, name, target.getId())))
.single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId())));
target.add(linksBuilder.build());
}

View File

@@ -16,7 +16,7 @@ import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
@Mapper
public abstract class ChangesetToParentDtoMapper extends BaseMapper<Changeset, ParentChangesetDto> {
public abstract class ChangesetToParentDtoMapper {
@Inject
private ResourceLinks resourceLinks;

View File

@@ -4,6 +4,7 @@ import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
@@ -14,11 +15,12 @@ import javax.inject.Inject;
import static de.otto.edison.hal.Link.link;
@Mapper
public abstract class FileObjectToFileObjectDtoMapper extends BaseMapper<FileObject, FileObjectDto> {
public abstract class FileObjectToFileObjectDtoMapper implements InstantAttributeMapper {
@Inject
private ResourceLinks resourceLinks;
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context String revision);
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);

View File

@@ -34,11 +34,12 @@ public class MapperModule extends AbstractModule {
bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass());
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass());
// no mapstruct required
bind(UIPluginDtoMapper.class);
bind(UIPluginDtoCollectionMapper.class);
bind(UriInfoStore.class).in(ServletScopes.REQUEST);
bind(ScmPathInfoStore.class).in(ServletScopes.REQUEST);
}
}

View File

@@ -0,0 +1,39 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
public class ModificationsDto extends HalRepresentation {
private String revision;
/**
* list of added files
*/
private List<String> added;
/**
* list of modified files
*/
private List<String> modified;
/**
* list of removed files
*/
private List<String> removed;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,62 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Modifications;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.io.IOException;
public class ModificationsRootResource {
private final RepositoryServiceFactory serviceFactory;
private final ModificationsToDtoMapper modificationsToDtoMapper;
@Inject
public ModificationsRootResource(RepositoryServiceFactory serviceFactory, ModificationsToDtoMapper modificationsToDtoMapper) {
this.serviceFactory = serviceFactory;
this.modificationsToDtoMapper = modificationsToDtoMapper;
}
/**
* Get the file modifications related to a revision.
* file modifications are for example: Modified, Added or Removed.
*/
@GET
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the modifications"),
@ResponseCode(code = 404, condition = "not found, no changeset with the specified id is available in the repository"),
@ResponseCode(code = 500, condition = "internal server error")
})
@Produces(VndMediaType.MODIFICATIONS)
@TypeHint(ModificationsDto.class)
@Path("{revision}")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException, RevisionNotFoundException, RepositoryNotFoundException , InternalRepositoryException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Modifications modifications = repositoryService.getModificationsCommand()
.revision(revision)
.getModifications();
ModificationsDto output = modificationsToDtoMapper.map(modifications, repositoryService.getRepository());
if (modifications != null ) {
return Response.ok(output).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
}
}

View File

@@ -0,0 +1,31 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.Modifications;
import sonia.scm.repository.Repository;
import javax.inject.Inject;
import static de.otto.edison.hal.Links.linkingTo;
@Mapper
public abstract class ModificationsToDtoMapper {
@Inject
private ResourceLinks resourceLinks;
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract ModificationsDto map(Modifications modifications, @Context Repository repository);
@AfterMapping
void appendLinks(@MappingTarget ModificationsDto target, @Context Repository repository) {
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.modifications().self(repository.getNamespace(), repository.getName(), target.getRevision()));
target.add(linksBuilder.build());
}
}

View File

@@ -0,0 +1,64 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import de.otto.edison.hal.paging.NumberedPaging;
import de.otto.edison.hal.paging.PagingRel;
import sonia.scm.ModelObject;
import sonia.scm.PageResult;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import static com.damnhandy.uri.template.UriTemplate.fromTemplate;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging;
import static java.util.stream.Collectors.toList;
abstract class PagedCollectionToDtoMapper<E extends ModelObject, D extends HalRepresentation> {
private final String collectionName;
PagedCollectionToDtoMapper(String collectionName) {
this.collectionName = collectionName;
}
CollectionDto map(int pageNumber, int pageSize, PageResult<E> pageResult, String selfLink, Optional<String> createLink, Function<E, ? extends HalRepresentation> mapper) {
NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount());
List<HalRepresentation> dtos = pageResult.getEntities().stream().map(mapper).collect(toList());
CollectionDto collectionDto = new CollectionDto(
createLinks(paging, selfLink, createLink),
embedDtos(dtos));
collectionDto.setPage(pageNumber);
collectionDto.setPageTotal(computePageTotal(pageSize, pageResult));
return collectionDto;
}
private int computePageTotal(int pageSize, PageResult<E> pageResult) {
if (pageResult.getOverallCount() % pageSize > 0) {
return pageResult.getOverallCount() / pageSize + 1;
} else {
return pageResult.getOverallCount() / pageSize;
}
}
private Links createLinks(NumberedPaging page, String selfLink, Optional<String> createLink) {
Links.Builder linksBuilder = linkingTo()
.with(page.links(
fromTemplate(selfLink + "{?page,pageSize}"),
EnumSet.allOf(PagingRel.class)));
createLink.ifPresent(link -> linksBuilder.single(link("create", link)));
return linksBuilder.build();
}
private Embedded embedDtos(List<HalRepresentation> dtos) {
return embeddedBuilder()
.with(collectionName, dtos)
.build();
}
}

View File

@@ -238,8 +238,9 @@ public class PermissionRootResource {
* @throws RepositoryNotFoundException if the repository does not exists
*/
private Repository load(String namespace, String name) throws RepositoryNotFoundException {
return Optional.ofNullable(manager.get(new NamespaceAndName(namespace, name)))
.orElseThrow(() -> new RepositoryNotFoundException(name));
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
return Optional.ofNullable(manager.get(namespaceAndName))
.orElseThrow(() -> new RepositoryNotFoundException(namespaceAndName));
}
/**

View File

@@ -10,8 +10,6 @@ import java.util.Optional;
import static java.util.Optional.empty;
import static java.util.Optional.of;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper<Repository, RepositoryDto, RepositoryToRepositoryDtoMapper> {
private final ResourceLinks resourceLinks;

View File

@@ -24,7 +24,7 @@ public class RepositoryDto extends HalRepresentation {
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
private String namespace;
@Pattern(regexp = "(?!^\\.\\.$)(?!^\\.$)(?!.*[\\\\\\[\\]])^[A-z0-9\\.][A-z0-9\\.\\-_/]*$")
@Pattern(regexp = "^[A-z0-9\\-_]+$")
private String name;
private boolean archived = false;
@NotEmpty

View File

@@ -40,6 +40,7 @@ public class RepositoryResource {
private final Provider<ContentResource> contentResource;
private final Provider<PermissionRootResource> permissionRootResource;
private final Provider<DiffRootResource> diffRootResource;
private final Provider<ModificationsRootResource> modificationsRootResource;
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
@Inject
@@ -52,7 +53,9 @@ public class RepositoryResource {
Provider<SourceRootResource> sourceRootResource, Provider<ContentResource> contentResource,
Provider<PermissionRootResource> permissionRootResource,
Provider<DiffRootResource> diffRootResource,
Provider<FileHistoryRootResource> fileHistoryRootResource) {
Provider<ModificationsRootResource> modificationsRootResource,
Provider<FileHistoryRootResource> fileHistoryRootResource
) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper;
@@ -64,6 +67,7 @@ public class RepositoryResource {
this.contentResource = contentResource;
this.permissionRootResource = permissionRootResource;
this.diffRootResource = diffRootResource;
this.modificationsRootResource = modificationsRootResource;
this.fileHistoryRootResource = fileHistoryRootResource;
}
@@ -188,6 +192,9 @@ public class RepositoryResource {
return permissionRootResource.get();
}
@Path("modifications/")
public ModificationsRootResource modifications() {return modificationsRootResource.get(); }
private Optional<Response> handleNotArchived(Throwable throwable) {
if (throwable instanceof RepositoryIsNotArchivedException) {
return Optional.of(Response.status(Response.Status.PRECONDITION_FAILED).build());

View File

@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
@@ -11,9 +12,13 @@ import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.ScmProtocol;
import java.util.List;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
import static java.util.stream.Collectors.toList;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@@ -30,7 +35,6 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
@AfterMapping
void appendLinks(Repository repository, @MappingTarget RepositoryDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(target.getNamespace(), target.getName()));
linksBuilder.single(link("httpProtocol", resourceLinks.repository().clone(target.getType(), target.getNamespace(), target.getName())));
if (RepositoryPermissions.delete(repository).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.repository().delete(target.getNamespace(), target.getName())));
}
@@ -39,6 +43,12 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
linksBuilder.single(link("permissions", resourceLinks.permission().all(target.getNamespace(), target.getName())));
}
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
if (RepositoryPermissions.pull(repository).isPermitted()) {
List<Link> protocolLinks = repositoryService.getSupportedProtocols()
.map(this::createProtocolLink)
.collect(toList());
linksBuilder.array(protocolLinks);
}
if (repositoryService.isSupported(Command.TAGS)) {
linksBuilder.single(link("tags", resourceLinks.tag().all(target.getNamespace(), target.getName())));
}
@@ -50,4 +60,8 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName())));
target.add(linksBuilder.build());
}
private Link createProtocolLink(ScmProtocol protocol) {
return Link.linkBuilder("protocol", protocol.getUrl()).withName(protocol.getType()).build();
}
}

View File

@@ -3,16 +3,15 @@ package sonia.scm.api.v2.resources;
import sonia.scm.repository.NamespaceAndName;
import javax.inject.Inject;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
class ResourceLinks {
private final UriInfoStore uriInfoStore;
private final ScmPathInfoStore scmPathInfoStore;
@Inject
ResourceLinks(UriInfoStore uriInfoStore) {
this.uriInfoStore = uriInfoStore;
ResourceLinks(ScmPathInfoStore scmPathInfoStore) {
this.scmPathInfoStore = scmPathInfoStore;
}
// we have to add the file path using URI, so that path separators (aka '/') will not be encoded as '%2F'
@@ -21,14 +20,14 @@ class ResourceLinks {
}
GroupLinks group() {
return new GroupLinks(uriInfoStore.get());
return new GroupLinks(scmPathInfoStore.get());
}
static class GroupLinks {
private final LinkBuilder groupLinkBuilder;
GroupLinks(UriInfo uriInfo) {
groupLinkBuilder = new LinkBuilder(uriInfo, GroupRootResource.class, GroupResource.class);
GroupLinks(ScmPathInfo pathInfo) {
groupLinkBuilder = new LinkBuilder(pathInfo, GroupRootResource.class, GroupResource.class);
}
String self(String name) {
@@ -45,14 +44,14 @@ class ResourceLinks {
}
GroupCollectionLinks groupCollection() {
return new GroupCollectionLinks(uriInfoStore.get());
return new GroupCollectionLinks(scmPathInfoStore.get());
}
static class GroupCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
GroupCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, GroupRootResource.class, GroupCollectionResource.class);
GroupCollectionLinks(ScmPathInfo pathInfo) {
collectionLinkBuilder = new LinkBuilder(pathInfo, GroupRootResource.class, GroupCollectionResource.class);
}
String self() {
@@ -65,14 +64,14 @@ class ResourceLinks {
}
UserLinks user() {
return new UserLinks(uriInfoStore.get());
return new UserLinks(scmPathInfoStore.get());
}
static class UserLinks {
private final LinkBuilder userLinkBuilder;
UserLinks(UriInfo uriInfo) {
userLinkBuilder = new LinkBuilder(uriInfo, UserRootResource.class, UserResource.class);
UserLinks(ScmPathInfo pathInfo) {
userLinkBuilder = new LinkBuilder(pathInfo, UserRootResource.class, UserResource.class);
}
String self(String name) {
@@ -89,14 +88,14 @@ class ResourceLinks {
}
UserCollectionLinks userCollection() {
return new UserCollectionLinks(uriInfoStore.get());
return new UserCollectionLinks(scmPathInfoStore.get());
}
static class UserCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
UserCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, UserRootResource.class, UserCollectionResource.class);
UserCollectionLinks(ScmPathInfo pathInfo) {
collectionLinkBuilder = new LinkBuilder(pathInfo, UserRootResource.class, UserCollectionResource.class);
}
String self() {
@@ -109,14 +108,14 @@ class ResourceLinks {
}
ConfigLinks config() {
return new ConfigLinks(uriInfoStore.get());
return new ConfigLinks(scmPathInfoStore.get());
}
static class ConfigLinks {
private final LinkBuilder configLinkBuilder;
ConfigLinks(UriInfo uriInfo) {
configLinkBuilder = new LinkBuilder(uriInfo, ConfigResource.class);
ConfigLinks(ScmPathInfo pathInfo) {
configLinkBuilder = new LinkBuilder(pathInfo, ConfigResource.class);
}
String self() {
@@ -129,26 +128,20 @@ class ResourceLinks {
}
public RepositoryLinks repository() {
return new RepositoryLinks(uriInfoStore.get());
return new RepositoryLinks(scmPathInfoStore.get());
}
static class RepositoryLinks {
private final LinkBuilder repositoryLinkBuilder;
private final UriInfo uriInfo;
RepositoryLinks(UriInfo uriInfo) {
repositoryLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class);
this.uriInfo = uriInfo;
RepositoryLinks(ScmPathInfo pathInfo) {
repositoryLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class);
}
String self(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("get").parameters().href();
}
String clone(String type, String namespace, String name) {
return uriInfo.getBaseUri().resolve(URI.create("../../" + type + "/" + namespace + "/" + name)).toASCIIString();
}
String delete(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("delete").parameters().href();
}
@@ -159,14 +152,14 @@ class ResourceLinks {
}
RepositoryCollectionLinks repositoryCollection() {
return new RepositoryCollectionLinks(uriInfoStore.get());
return new RepositoryCollectionLinks(scmPathInfoStore.get());
}
static class RepositoryCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
RepositoryCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryCollectionResource.class);
RepositoryCollectionLinks(ScmPathInfo pathInfo) {
collectionLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryCollectionResource.class);
}
String self() {
@@ -179,14 +172,14 @@ class ResourceLinks {
}
public RepositoryTypeLinks repositoryType() {
return new RepositoryTypeLinks(uriInfoStore.get());
return new RepositoryTypeLinks(scmPathInfoStore.get());
}
static class RepositoryTypeLinks {
private final LinkBuilder repositoryTypeLinkBuilder;
RepositoryTypeLinks(UriInfo uriInfo) {
repositoryTypeLinkBuilder = new LinkBuilder(uriInfo, RepositoryTypeRootResource.class, RepositoryTypeResource.class);
RepositoryTypeLinks(ScmPathInfo pathInfo) {
repositoryTypeLinkBuilder = new LinkBuilder(pathInfo, RepositoryTypeRootResource.class, RepositoryTypeResource.class);
}
String self(String name) {
@@ -195,14 +188,14 @@ class ResourceLinks {
}
public RepositoryTypeCollectionLinks repositoryTypeCollection() {
return new RepositoryTypeCollectionLinks(uriInfoStore.get());
return new RepositoryTypeCollectionLinks(scmPathInfoStore.get());
}
static class RepositoryTypeCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
RepositoryTypeCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, RepositoryTypeRootResource.class, RepositoryTypeCollectionResource.class);
RepositoryTypeCollectionLinks(ScmPathInfo pathInfo) {
collectionLinkBuilder = new LinkBuilder(pathInfo, RepositoryTypeRootResource.class, RepositoryTypeCollectionResource.class);
}
String self() {
@@ -212,14 +205,14 @@ class ResourceLinks {
public TagCollectionLinks tag() {
return new TagCollectionLinks(uriInfoStore.get());
return new TagCollectionLinks(scmPathInfoStore.get());
}
static class TagCollectionLinks {
private final LinkBuilder tagLinkBuilder;
TagCollectionLinks(UriInfo uriInfo) {
tagLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, TagRootResource.class);
TagCollectionLinks(ScmPathInfo pathInfo) {
tagLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, TagRootResource.class);
}
String self(String namespace, String name, String tagName) {
@@ -232,14 +225,14 @@ class ResourceLinks {
}
public DiffLinks diff() {
return new DiffLinks(uriInfoStore.get());
return new DiffLinks(scmPathInfoStore.get());
}
static class DiffLinks {
private final LinkBuilder diffLinkBuilder;
DiffLinks(UriInfo uriInfo) {
diffLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, DiffRootResource.class);
DiffLinks(ScmPathInfo pathInfo) {
diffLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, DiffRootResource.class);
}
String self(String namespace, String name, String id) {
@@ -252,14 +245,14 @@ class ResourceLinks {
}
public BranchLinks branch() {
return new BranchLinks(uriInfoStore.get());
return new BranchLinks(scmPathInfoStore.get());
}
static class BranchLinks {
private final LinkBuilder branchLinkBuilder;
BranchLinks(UriInfo uriInfo) {
branchLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, BranchRootResource.class);
BranchLinks(ScmPathInfo pathInfo) {
branchLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, BranchRootResource.class);
}
String self(NamespaceAndName namespaceAndName, String branch) {
@@ -272,14 +265,14 @@ class ResourceLinks {
}
public BranchCollectionLinks branchCollection() {
return new BranchCollectionLinks(uriInfoStore.get());
return new BranchCollectionLinks(scmPathInfoStore.get());
}
static class BranchCollectionLinks {
private final LinkBuilder branchLinkBuilder;
BranchCollectionLinks(UriInfo uriInfo) {
branchLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, BranchRootResource.class);
BranchCollectionLinks(ScmPathInfo pathInfo) {
branchLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, BranchRootResource.class);
}
String self(String namespace, String name) {
@@ -288,14 +281,14 @@ class ResourceLinks {
}
public ChangesetLinks changeset() {
return new ChangesetLinks(uriInfoStore.get());
return new ChangesetLinks(scmPathInfoStore.get());
}
static class ChangesetLinks {
private final LinkBuilder changesetLinkBuilder;
ChangesetLinks(UriInfo uriInfo) {
changesetLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, ChangesetRootResource.class);
ChangesetLinks(ScmPathInfo pathInfo) {
changesetLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, ChangesetRootResource.class);
}
String self(String namespace, String name, String changesetId) {
@@ -311,15 +304,30 @@ class ResourceLinks {
}
}
public ModificationsLinks modifications() {
return new ModificationsLinks(scmPathInfoStore.get());
}
static class ModificationsLinks {
private final LinkBuilder modificationsLinkBuilder;
ModificationsLinks(ScmPathInfo pathInfo) {
modificationsLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, ModificationsRootResource.class);
}
String self(String namespace, String name, String revision) {
return modificationsLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("modifications").parameters().method("get").parameters(revision).href();
}
}
public FileHistoryLinks fileHistory() {
return new FileHistoryLinks(uriInfoStore.get());
return new FileHistoryLinks(scmPathInfoStore.get());
}
static class FileHistoryLinks {
private final LinkBuilder fileHistoryLinkBuilder;
FileHistoryLinks(UriInfo uriInfo) {
fileHistoryLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, FileHistoryRootResource.class);
FileHistoryLinks(ScmPathInfo pathInfo) {
fileHistoryLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, FileHistoryRootResource.class);
}
String self(String namespace, String name, String changesetId, String path) {
@@ -329,14 +337,14 @@ class ResourceLinks {
}
public SourceLinks source() {
return new SourceLinks(uriInfoStore.get());
return new SourceLinks(scmPathInfoStore.get());
}
static class SourceLinks {
private final LinkBuilder sourceLinkBuilder;
SourceLinks(UriInfo uriInfo) {
sourceLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, SourceRootResource.class);
SourceLinks(ScmPathInfo pathInfo) {
sourceLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, SourceRootResource.class);
}
String self(String namespace, String name, String revision) {
@@ -362,14 +370,14 @@ class ResourceLinks {
}
public PermissionLinks permission() {
return new PermissionLinks(uriInfoStore.get());
return new PermissionLinks(scmPathInfoStore.get());
}
static class PermissionLinks {
private final LinkBuilder permissionLinkBuilder;
PermissionLinks(UriInfo uriInfo) {
permissionLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, PermissionRootResource.class);
PermissionLinks(ScmPathInfo pathInfo) {
permissionLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, PermissionRootResource.class);
}
String all(String namespace, String name) {
@@ -399,14 +407,14 @@ class ResourceLinks {
public UIPluginLinks uiPlugin() {
return new UIPluginLinks(uriInfoStore.get());
return new UIPluginLinks(scmPathInfoStore.get());
}
static class UIPluginLinks {
private final LinkBuilder uiPluginLinkBuilder;
UIPluginLinks(UriInfo uriInfo) {
uiPluginLinkBuilder = new LinkBuilder(uriInfo, UIRootResource.class, UIPluginResource.class);
UIPluginLinks(ScmPathInfo pathInfo) {
uiPluginLinkBuilder = new LinkBuilder(pathInfo, UIRootResource.class, UIPluginResource.class);
}
String self(String id) {
@@ -415,14 +423,14 @@ class ResourceLinks {
}
public UIPluginCollectionLinks uiPluginCollection() {
return new UIPluginCollectionLinks(uriInfoStore.get());
return new UIPluginCollectionLinks(scmPathInfoStore.get());
}
static class UIPluginCollectionLinks {
private final LinkBuilder uiPluginCollectionLinkBuilder;
UIPluginCollectionLinks(UriInfo uriInfo) {
uiPluginCollectionLinkBuilder = new LinkBuilder(uriInfo, UIRootResource.class, UIPluginResource.class);
UIPluginCollectionLinks(ScmPathInfo pathInfo) {
uiPluginCollectionLinkBuilder = new LinkBuilder(pathInfo, UIRootResource.class, UIPluginResource.class);
}
String self() {

View File

@@ -10,8 +10,6 @@ import java.util.Optional;
import static java.util.Optional.empty;
import static java.util.Optional.of;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper<User, UserDto, UserToUserDtoMapper> {
private final ResourceLinks resourceLinks;

View File

@@ -33,7 +33,7 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
}
@AfterMapping
void appendLinks(User user, @MappingTarget UserDto target) {
protected void appendLinks(User user, @MappingTarget UserDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(target.getName()));
if (UserPermissions.delete(user).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.user().delete(target.getName())));

View File

@@ -37,10 +37,8 @@ package sonia.scm.filter;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import sonia.scm.Priority;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration;
@@ -48,14 +46,15 @@ import sonia.scm.security.SecurityRequests;
import sonia.scm.web.filter.HttpFilter;
import sonia.scm.web.filter.SecurityHttpServletRequestWrapper;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -63,7 +62,8 @@ import javax.servlet.http.HttpServletResponse;
*/
@Priority(Filters.PRIORITY_AUTHORIZATION)
// TODO find a better way for unprotected resources
@WebElement(value = "/api/rest/(?!v2/ui).*", regex = true)
@WebElement(value = REST_API_PATH + "" +
"/(?!v2/ui).*", regex = true)
public class SecurityFilter extends HttpFilter
{

View File

@@ -31,10 +31,7 @@
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.github.sdorra.ssp.PermissionActionCheck;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
@@ -43,7 +40,6 @@ import org.apache.shiro.concurrent.SubjectAwareExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.AlreadyExistsException;
import sonia.scm.ArgumentIsInvalidException;
import sonia.scm.ConfigurationException;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
@@ -54,11 +50,9 @@ import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.KeyGenerator;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.CollectionAppender;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -71,8 +65,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
//~--- JDK imports ------------------------------------------------------------
/**
* Default implementation of {@link RepositoryManager}.
*
@@ -90,7 +82,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private final KeyGenerator keyGenerator;
private final RepositoryDAO repositoryDAO;
private final Set<Type> types;
private RepositoryMatcher repositoryMatcher;
private NamespaceStrategy namespaceStrategy;
private final ManagerDaoAdapter<Repository> managerDaoAdapter;
@@ -99,12 +90,10 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
public DefaultRepositoryManager(ScmConfiguration configuration,
SCMContextProvider contextProvider, KeyGenerator keyGenerator,
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
RepositoryMatcher repositoryMatcher,
NamespaceStrategy namespaceStrategy) {
this.configuration = configuration;
this.keyGenerator = keyGenerator;
this.repositoryDAO = repositoryDAO;
this.repositoryMatcher = repositoryMatcher;
this.namespaceStrategy = namespaceStrategy;
ThreadFactory factory = new ThreadFactoryBuilder()
@@ -317,71 +306,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return validTypes;
}
@Override
public Repository getFromRequest(HttpServletRequest request) {
AssertUtil.assertIsNotNull(request);
return getFromUri(HttpUtil.getStrippedURI(request));
}
@Override
public Repository getFromUri(String uri) {
AssertUtil.assertIsNotEmpty(uri);
if (uri.startsWith(HttpUtil.SEPARATOR_PATH)) {
uri = uri.substring(1);
}
int typeSeparator = uri.indexOf(HttpUtil.SEPARATOR_PATH);
Repository repository = null;
if (typeSeparator > 0) {
String type = uri.substring(0, typeSeparator);
uri = uri.substring(typeSeparator + 1);
repository = getFromTypeAndUri(type, uri);
}
return repository;
}
private Repository getFromTypeAndUri(String type, String uri) {
if (Strings.isNullOrEmpty(type)) {
throw new ArgumentIsInvalidException("argument type is required");
}
if (Strings.isNullOrEmpty(uri)) {
throw new ArgumentIsInvalidException("argument uri is required");
}
// remove ;jsessionid, jetty bug?
uri = HttpUtil.removeMatrixParameter(uri);
Repository repository = null;
if (handlerMap.containsKey(type)) {
Collection<Repository> repositories = repositoryDAO.getAll();
PermissionActionCheck<Repository> check = RepositoryPermissions.read();
for (Repository r : repositories) {
if (repositoryMatcher.matches(r, type, uri)) {
check.check(r);
repository = r.clone();
break;
}
}
}
if ((repository == null) && logger.isDebugEnabled()) {
logger.debug("could not find repository with type {} and uri {}", type,
uri);
}
return repository;
}
@Override
public RepositoryHandler getHandler(String type) {
return handlerMap.get(type);

View File

@@ -33,86 +33,32 @@
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.servlet.RequestScoped;
import sonia.scm.security.ScmSecurityException;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.http.HttpServletRequest;
/**
*
* @author Sebastian Sdorra
*/
@RequestScoped
public class DefaultRepositoryProvider implements RepositoryProvider
{
public class DefaultRepositoryProvider implements RepositoryProvider {
/** Field description */
public static final String ATTRIBUTE_NAME = "scm.request.repository";
//~--- constructors ---------------------------------------------------------
private final Provider<HttpServletRequest> requestProvider;
/**
* Constructs ...
*
*
* @param requestProvider
* @param manager
*/
@Inject
public DefaultRepositoryProvider(
Provider<HttpServletRequest> requestProvider,
RepositoryManager manager)
{
public DefaultRepositoryProvider(Provider<HttpServletRequest> requestProvider) {
this.requestProvider = requestProvider;
this.manager = manager;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*
* @throws ScmSecurityException
*/
@Override
public Repository get() throws ScmSecurityException
{
Repository repository = null;
public Repository get() {
HttpServletRequest request = requestProvider.get();
if (request != null)
{
repository = (Repository) request.getAttribute(ATTRIBUTE_NAME);
if (repository == null)
{
repository = manager.getFromRequest(request);
if (repository != null)
{
request.setAttribute(ATTRIBUTE_NAME, repository);
}
}
if (request != null) {
return (Repository) request.getAttribute(ATTRIBUTE_NAME);
}
return repository;
throw new IllegalStateException("request not found");
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final RepositoryManager manager;
/** Field description */
private final Provider<HttpServletRequest> requestProvider;
}

View File

@@ -61,8 +61,7 @@ public final class HealthChecker {
Repository repository = repositoryManager.get(id);
if (repository == null) {
throw new RepositoryNotFoundException(
"could not find repository with id ".concat(id));
throw new RepositoryNotFoundException(id);
}
doCheck(repository);

View File

@@ -3,12 +3,14 @@ package sonia.scm.security;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Pattern;
import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
/**
* Created by masuewer on 04.07.18.
*/
public final class SecurityRequests {
private static final Pattern URI_LOGIN_PATTERN = Pattern.compile("/api/rest(?:/v2)?/auth/access_token");
private static final Pattern URI_LOGIN_PATTERN = Pattern.compile(REST_API_PATH + "(?:/v2)?/auth/access_token");
private SecurityRequests() {}

View File

@@ -1,99 +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.util;
//~--- non-JDK imports --------------------------------------------------------
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.DecoratorFactory;
/**
*
* @author Sebastian Sdorra
*/
public final class Decorators
{
/**
* the logger for Decorators
*/
private static final Logger logger =
LoggerFactory.getLogger(Decorators.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
private Decorators() {}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param object
* @param decoratorFactories
* @param <T>
*
* @return
*/
public static <T> T decorate(T object,
Iterable<? extends DecoratorFactory<T>> decoratorFactories)
{
if (decoratorFactories != null)
{
for (DecoratorFactory<T> decoratorFactory : decoratorFactories)
{
if (logger.isDebugEnabled())
{
logger.debug("decorate {} with {}", object.getClass(),
decoratorFactory.getClass());
}
object = decoratorFactory.createDecorator(object);
}
}
else if (logger.isDebugEnabled())
{
logger.debug("no decorators found for {}", object.getClass());
}
return object;
}
}

View File

@@ -0,0 +1,79 @@
package sonia.scm.web.protocol;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import sonia.scm.PushStateDispatcher;
import sonia.scm.filter.WebElement;
import sonia.scm.repository.DefaultRepositoryProvider;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.spi.HttpScmProtocol;
import sonia.scm.web.UserAgent;
import sonia.scm.web.UserAgentParser;
import javax.inject.Provider;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
@Singleton
@WebElement(value = HttpProtocolServlet.PATTERN)
@Slf4j
public class HttpProtocolServlet extends HttpServlet {
public static final String PATH = "/repo";
public static final String PATTERN = PATH + "/*";
private final RepositoryServiceFactory serviceFactory;
private final Provider<HttpServletRequest> requestProvider;
private final PushStateDispatcher dispatcher;
private final UserAgentParser userAgentParser;
@Inject
public HttpProtocolServlet(RepositoryServiceFactory serviceFactory, Provider<HttpServletRequest> requestProvider, PushStateDispatcher dispatcher, UserAgentParser userAgentParser) {
this.serviceFactory = serviceFactory;
this.requestProvider = requestProvider;
this.dispatcher = dispatcher;
this.userAgentParser = userAgentParser;
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
UserAgent userAgent = userAgentParser.parse(request);
if (userAgent.isBrowser()) {
log.trace("dispatch browser request for user agent {}", userAgent);
dispatcher.dispatch(request, response, request.getRequestURI());
} else {
String pathInfo = request.getPathInfo();
Optional<NamespaceAndName> namespaceAndName = NamespaceAndNameFromPathExtractor.fromUri(pathInfo);
if (namespaceAndName.isPresent()) {
service(request, response, namespaceAndName.get());
} else {
log.debug("namespace and name not found in request path {}", pathInfo);
response.setStatus(HttpStatus.SC_BAD_REQUEST);
}
}
}
private void service(HttpServletRequest req, HttpServletResponse resp, NamespaceAndName namespaceAndName) throws IOException, ServletException {
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
requestProvider.get().setAttribute(DefaultRepositoryProvider.ATTRIBUTE_NAME, repositoryService.getRepository());
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
protocol.serve(req, resp, getServletConfig());
} catch (RepositoryNotFoundException e) {
log.debug("Repository not found for namespace and name {}", namespaceAndName, e);
resp.setStatus(HttpStatus.SC_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,41 @@
package sonia.scm.web.protocol;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.util.HttpUtil;
import java.util.Optional;
import static java.util.Optional.empty;
import static java.util.Optional.of;
final class NamespaceAndNameFromPathExtractor {
private NamespaceAndNameFromPathExtractor() {}
static Optional<NamespaceAndName> fromUri(String uri) {
if (uri.startsWith(HttpUtil.SEPARATOR_PATH)) {
uri = uri.substring(1);
}
int endOfNamespace = uri.indexOf(HttpUtil.SEPARATOR_PATH);
if (endOfNamespace < 1) {
return empty();
}
String namespace = uri.substring(0, endOfNamespace);
int nameSeparatorIndex = uri.indexOf(HttpUtil.SEPARATOR_PATH, endOfNamespace + 1);
int nameIndex = nameSeparatorIndex > 0 ? nameSeparatorIndex : uri.length();
if (nameIndex == endOfNamespace + 1) {
return empty();
}
String name = uri.substring(endOfNamespace + 1, nameIndex);
int nameDotIndex = name.indexOf('.');
if (nameDotIndex >= 0) {
return of(new NamespaceAndName(namespace, name.substring(0, nameDotIndex)));
} else {
return of(new NamespaceAndName(namespace, name));
}
}
}