mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-02-23 06:50:49 +01:00
merge
This commit is contained in:
@@ -30,19 +30,19 @@ public class ManagerDaoAdapter<T extends ModelObject> {
|
||||
|
||||
afterUpdate.handle(notModified);
|
||||
} else {
|
||||
throw new NotFoundException();
|
||||
throw new NotFoundException(object.getClass(), object.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate) throws AlreadyExistsException {
|
||||
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate) {
|
||||
return create(newObject, permissionCheck, beforeCreate, afterCreate, dao::contains);
|
||||
}
|
||||
|
||||
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate, Predicate<T> existsCheck) throws AlreadyExistsException {
|
||||
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate, Predicate<T> existsCheck) {
|
||||
permissionCheck.get().check();
|
||||
AssertUtil.assertIsValid(newObject);
|
||||
if (existsCheck.test(newObject)) {
|
||||
throw new AlreadyExistsException();
|
||||
throw new AlreadyExistsException(newObject);
|
||||
}
|
||||
newObject.setCreationDate(System.currentTimeMillis());
|
||||
beforeCreate.handle(newObject);
|
||||
@@ -58,7 +58,7 @@ public class ManagerDaoAdapter<T extends ModelObject> {
|
||||
dao.delete(toDelete);
|
||||
afterDelete.handle(toDelete);
|
||||
} else {
|
||||
throw new NotFoundException();
|
||||
throw new NotFoundException(toDelete.getClass(), toDelete.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
scm-webapp/src/main/java/sonia/scm/ResteasyModule.java
Normal file
20
scm-webapp/src/main/java/sonia/scm/ResteasyModule.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package sonia.scm;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;
|
||||
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Map;
|
||||
|
||||
public class ResteasyModule extends ServletModule {
|
||||
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
bind(HttpServletDispatcher.class).in(Singleton.class);
|
||||
|
||||
Map<String, String> initParams = ImmutableMap.of(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, "/api");
|
||||
serve("/api/*").with(HttpServletDispatcher.class, initParams);
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,7 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
|
||||
ClassOverrides overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader());
|
||||
List<Module> moduleList = Lists.newArrayList();
|
||||
|
||||
moduleList.add(new ResteasyModule());
|
||||
moduleList.add(new ScmInitializerModule());
|
||||
moduleList.add(new ScmEventBusModule());
|
||||
moduleList.add(new EagerSingletonModule());
|
||||
|
||||
@@ -57,5 +57,9 @@ public class TemplatingPushStateDispatcher implements PushStateDispatcher {
|
||||
return request.getContextPath();
|
||||
}
|
||||
|
||||
public String getLiveReloadURL() {
|
||||
return System.getProperty("livereload.url");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package sonia.scm.api;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
import sonia.scm.api.v2.resources.ErrorDto;
|
||||
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
import java.util.Collections;
|
||||
|
||||
@Provider
|
||||
public class FallbackExceptionMapper implements ExceptionMapper<Exception> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FallbackExceptionMapper.class);
|
||||
|
||||
private static final String ERROR_CODE = "CmR8GCJb31";
|
||||
|
||||
private final ExceptionWithContextToErrorDtoMapper mapper;
|
||||
|
||||
@Inject
|
||||
public FallbackExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response toResponse(Exception exception) {
|
||||
logger.debug("map {} to status code 500", exception);
|
||||
ErrorDto errorDto = new ErrorDto();
|
||||
errorDto.setMessage("internal server error");
|
||||
errorDto.setContext(Collections.emptyList());
|
||||
errorDto.setErrorCode(ERROR_CODE);
|
||||
errorDto.setTransactionId(MDC.get("transaction_id"));
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(errorDto)
|
||||
.type(VndMediaType.ERROR_TYPE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
package sonia.scm.api.rest;
|
||||
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class AlreadyExistsExceptionMapper implements ExceptionMapper<AlreadyExistsException> {
|
||||
@Override
|
||||
public Response toResponse(AlreadyExistsException exception) {
|
||||
return Response.status(Status.CONFLICT)
|
||||
.entity(exception.getMessage())
|
||||
.build();
|
||||
public class AlreadyExistsExceptionMapper extends ContextualExceptionMapper<AlreadyExistsException> {
|
||||
@Inject
|
||||
public AlreadyExistsExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(AlreadyExistsException.class, Status.CONFLICT, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package sonia.scm.api.rest;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class AuthenticationExceptionMapper extends StatusExceptionMapper<AuthenticationException> {
|
||||
public AuthenticationExceptionMapper() {
|
||||
super(AuthenticationException.class, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
package sonia.scm.api.rest;
|
||||
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class ConcurrentModificationExceptionMapper implements ExceptionMapper<ConcurrentModificationException> {
|
||||
@Override
|
||||
public Response toResponse(ConcurrentModificationException exception) {
|
||||
return Response.status(Response.Status.CONFLICT).build();
|
||||
public class ConcurrentModificationExceptionMapper extends ContextualExceptionMapper<ConcurrentModificationException> {
|
||||
@Inject
|
||||
public ConcurrentModificationExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(ConcurrentModificationException.class, Response.Status.CONFLICT, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package sonia.scm.api.rest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ExceptionWithContext;
|
||||
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
|
||||
public class ContextualExceptionMapper<E extends ExceptionWithContext> implements ExceptionMapper<E> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContextualExceptionMapper.class);
|
||||
|
||||
private final ExceptionWithContextToErrorDtoMapper mapper;
|
||||
|
||||
private final Response.Status status;
|
||||
private final Class<E> type;
|
||||
|
||||
public ContextualExceptionMapper(Class<E> type, Response.Status status, ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
this.type = type;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response toResponse(E exception) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("map {} to status code {}", type.getSimpleName(), status.getStatusCode(), exception);
|
||||
} else {
|
||||
logger.debug("map {} to status code {} with message '{}'", type.getSimpleName(), status.getStatusCode(), exception.getMessage());
|
||||
}
|
||||
return Response.status(status)
|
||||
.entity(mapper.map(exception))
|
||||
.type(VndMediaType.ERROR_TYPE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,68 +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.api.rest;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.36
|
||||
*/
|
||||
@Provider
|
||||
@Slf4j
|
||||
public class IllegalArgumentExceptionMapper
|
||||
implements ExceptionMapper<IllegalArgumentException>
|
||||
{
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param exception
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Response toResponse(IllegalArgumentException exception)
|
||||
{
|
||||
log.info("caught IllegalArgumentException -- mapping to bad request", exception);
|
||||
return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package sonia.scm.api.rest;
|
||||
|
||||
import javax.ws.rs.NotAuthorizedException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class NotAuthorizedExceptionMapper extends StatusExceptionMapper<NotAuthorizedException> {
|
||||
public NotAuthorizedExceptionMapper()
|
||||
{
|
||||
super(NotAuthorizedException.class, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package sonia.scm.api.rest.resources;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.PathNotFoundException;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
import sonia.scm.repository.api.CatCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.util.IOUtil;
|
||||
@@ -34,18 +32,6 @@ public class BrowserStreamingOutput implements StreamingOutput {
|
||||
public void write(OutputStream output) throws IOException {
|
||||
try {
|
||||
builder.retriveContent(output, path);
|
||||
} catch (PathNotFoundException ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("could not find path {}", ex.getPath());
|
||||
}
|
||||
|
||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
||||
} catch (RevisionNotFoundException ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("could not find revision {}", ex.getRevision());
|
||||
}
|
||||
|
||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
||||
} finally {
|
||||
IOUtil.close(repositoryService);
|
||||
}
|
||||
|
||||
@@ -44,8 +44,6 @@ import org.apache.shiro.authc.credential.PasswordService;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.api.rest.RestActionResult;
|
||||
import sonia.scm.security.Role;
|
||||
import sonia.scm.security.ScmSecurityException;
|
||||
|
||||
@@ -1,148 +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.api.rest.resources;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.security.Role;
|
||||
import sonia.scm.security.ScmSecurityException;
|
||||
import sonia.scm.util.ScmConfigurationUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
@Path("config")
|
||||
public class ConfigurationResource
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param configuration
|
||||
* @param securityContextProvider
|
||||
*/
|
||||
@Inject
|
||||
public ConfigurationResource(ScmConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Response getConfiguration()
|
||||
{
|
||||
Response response = null;
|
||||
|
||||
if (SecurityUtils.getSubject().hasRole(Role.ADMIN))
|
||||
{
|
||||
response = Response.ok(configuration).build();
|
||||
}
|
||||
else
|
||||
{
|
||||
response = Response.status(Response.Status.FORBIDDEN).build();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
//~--- set methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param uriInfo
|
||||
* @param newConfig
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Response setConfig(@Context UriInfo uriInfo,
|
||||
ScmConfiguration newConfig)
|
||||
{
|
||||
|
||||
// TODO replace by checkRole
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
|
||||
if (!subject.hasRole(Role.ADMIN))
|
||||
{
|
||||
throw new ScmSecurityException("admin privileges required");
|
||||
}
|
||||
|
||||
configuration.load(newConfig);
|
||||
|
||||
synchronized (ScmConfiguration.class)
|
||||
{
|
||||
ScmConfigurationUtil.getInstance().store(configuration);
|
||||
}
|
||||
|
||||
return Response.created(uriInfo.getRequestUri()).build();
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
public ScmConfiguration configuration;
|
||||
}
|
||||
@@ -37,7 +37,6 @@ package sonia.scm.api.rest.resources;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
import sonia.scm.repository.api.DiffCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.util.IOUtil;
|
||||
@@ -93,24 +92,8 @@ public class DiffStreamingOutput implements StreamingOutput
|
||||
public void write(OutputStream output) throws IOException {
|
||||
try
|
||||
{
|
||||
builder.retriveContent(output);
|
||||
builder.retrieveContent(output);
|
||||
}
|
||||
catch (RevisionNotFoundException ex)
|
||||
{
|
||||
if (logger.isWarnEnabled())
|
||||
{
|
||||
logger.warn("could not find revision {}", ex.getRevision());
|
||||
}
|
||||
|
||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
||||
}
|
||||
// catch (RepositoryException ex)
|
||||
// {
|
||||
// logger.error("could not write content to page", ex);
|
||||
//
|
||||
// throw new WebApplicationException(ex,
|
||||
// Response.Status.INTERNAL_SERVER_ERROR);
|
||||
// }
|
||||
finally
|
||||
{
|
||||
IOUtil.close(repositoryService);
|
||||
|
||||
@@ -1,248 +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.api.rest.resources;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.GroupManager;
|
||||
import sonia.scm.security.Role;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.GenericEntity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Request;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Collection;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage groups and their members.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Path("groups")
|
||||
@Singleton
|
||||
public class GroupResource extends AbstractManagerResource<Group> {
|
||||
|
||||
/** Field description */
|
||||
public static final String PATH_PART = "groups";
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
@Inject
|
||||
public GroupResource(GroupManager groupManager)
|
||||
{
|
||||
super(groupManager, Group.class);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a new group. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param uriInfo current uri informations
|
||||
* @param group the group to be created
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 201, condition = "create success", additionalHeaders = {
|
||||
@ResponseHeader(name = "Location", description = "uri to the created group")
|
||||
}),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Override
|
||||
public Response create(@Context UriInfo uriInfo, Group group)
|
||||
{
|
||||
return super.create(uriInfo, group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a group. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param name the name of the group to delete.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@DELETE
|
||||
@Path("{id}")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "delete success"),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Override
|
||||
public Response delete(@PathParam("id") String name)
|
||||
{
|
||||
return super.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the given group. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param name name of the group to be modified
|
||||
* @param group group object to modify
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PUT
|
||||
@Path("{id}")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Override
|
||||
public Response update(@PathParam("id") String name, Group group)
|
||||
{
|
||||
return super.update(name, group);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetches a group by its name or id. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param id the id/name of the group
|
||||
*
|
||||
* @return the {@link Group} with the specified id
|
||||
*/
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@TypeHint(Group.class)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Override
|
||||
public Response get(@Context Request request, @PathParam("id") String id)
|
||||
{
|
||||
Response response = null;
|
||||
|
||||
if (SecurityUtils.getSubject().hasRole(Role.ADMIN))
|
||||
{
|
||||
response = super.get(request, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = Response.status(Response.Status.FORBIDDEN).build();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all groups. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param start the start value for paging
|
||||
* @param limit the limit value for paging
|
||||
* @param sortby sort parameter
|
||||
* @param desc sort direction desc or aesc
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@TypeHint(Group[].class)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Override
|
||||
public Response getAll(@Context Request request, @DefaultValue("0")
|
||||
@QueryParam("start") int start, @DefaultValue("-1")
|
||||
@QueryParam("limit") int limit, @QueryParam("sortby") String sortby,
|
||||
@DefaultValue("false")
|
||||
@QueryParam("desc") boolean desc)
|
||||
{
|
||||
return super.getAll(request, start, limit, sortby, desc);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected GenericEntity<Collection<Group>> createGenericEntity(
|
||||
Collection<Group> items)
|
||||
{
|
||||
return new GenericEntity<Collection<Group>>(items) {}
|
||||
;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected String getId(Group group)
|
||||
{
|
||||
return group.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPart()
|
||||
{
|
||||
return PATH_PART;
|
||||
}
|
||||
}
|
||||
@@ -1,362 +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.api.rest.resources;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.api.rest.RestActionResult;
|
||||
import sonia.scm.api.rest.RestActionUploadResult;
|
||||
import sonia.scm.plugin.OverviewPluginPredicate;
|
||||
import sonia.scm.plugin.PluginConditionFailedException;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.PluginInformationComparator;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Endpoint to manage plugins.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
@Path("plugins")
|
||||
public class PluginResource
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for PluginResource
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(PluginResource.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param pluginManager
|
||||
*/
|
||||
@Inject
|
||||
public PluginResource(PluginManager pluginManager)
|
||||
{
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Installs a plugin from a package.
|
||||
*
|
||||
* @param uploadedInputStream
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@POST
|
||||
@Path("install-package")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 412, condition = "precondition failed"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Response install(
|
||||
/*@FormParam("package")*/ InputStream uploadedInputStream)
|
||||
throws IOException
|
||||
{
|
||||
Response response = null;
|
||||
|
||||
try
|
||||
{
|
||||
pluginManager.installPackage(uploadedInputStream);
|
||||
response = Response.ok(new RestActionUploadResult(true)).build();
|
||||
}
|
||||
catch (PluginConditionFailedException ex)
|
||||
{
|
||||
logger.warn(
|
||||
"could not install plugin package, because the condition failed", ex);
|
||||
response = Response.status(Status.PRECONDITION_FAILED).entity(
|
||||
new RestActionResult(false)).build();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.warn("plugin installation failed", ex);
|
||||
response =
|
||||
Response.serverError().entity(new RestActionResult(false)).build();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a plugin.
|
||||
*
|
||||
* @param id id of the plugin to be installed
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Path("install/{id}")
|
||||
public Response install(@PathParam("id") String id)
|
||||
{
|
||||
pluginManager.install(id);
|
||||
|
||||
// TODO should return 204 no content
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a plugin from a package. This method is a workaround for ExtJS
|
||||
* file upload, which requires text/html as content-type.
|
||||
*
|
||||
* @param uploadedInputStream
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@POST
|
||||
@Path("install-package.html")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 412, condition = "precondition failed"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
public Response installFromUI(
|
||||
/*@FormParam("package")*/ InputStream uploadedInputStream)
|
||||
throws IOException
|
||||
{
|
||||
return install(uploadedInputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls a plugin.
|
||||
*
|
||||
* @param id id of the plugin to be uninstalled
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Path("uninstall/{id}")
|
||||
public Response uninstall(@PathParam("id") String id)
|
||||
{
|
||||
pluginManager.uninstall(id);
|
||||
|
||||
// TODO should return 204 content
|
||||
// consider to do a uninstall with a delete
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a plugin.
|
||||
*
|
||||
* @param id id of the plugin to be updated
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Path("update/{id}")
|
||||
public Response update(@PathParam("id") String id)
|
||||
{
|
||||
pluginManager.update(id);
|
||||
|
||||
// TODO should return 204 content
|
||||
// consider to do an update with a put
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns all plugins.
|
||||
*
|
||||
* @return all plugins
|
||||
*/
|
||||
@GET
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Collection<PluginInformation> getAll()
|
||||
{
|
||||
return pluginManager.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available plugins.
|
||||
*
|
||||
* @return all available plugins
|
||||
*/
|
||||
@GET
|
||||
@Path("available")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Collection<PluginInformation> getAvailable()
|
||||
{
|
||||
return pluginManager.getAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all plugins which are available for update.
|
||||
*
|
||||
* @return all plugins which are available for update
|
||||
*/
|
||||
@GET
|
||||
@Path("updates")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Collection<PluginInformation> getAvailableUpdates()
|
||||
{
|
||||
return pluginManager.getAvailableUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all installed plugins.
|
||||
*
|
||||
* @return all installed plugins
|
||||
*/
|
||||
@GET
|
||||
@Path("installed")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
|
||||
public Collection<PluginInformation> getInstalled()
|
||||
{
|
||||
return pluginManager.getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all plugins for the overview.
|
||||
*
|
||||
* @return all plugins for the overview
|
||||
*/
|
||||
@GET
|
||||
@Path("overview")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Collection<PluginInformation> getOverview()
|
||||
{
|
||||
//J-
|
||||
List<PluginInformation> plugins = Lists.newArrayList(
|
||||
pluginManager.get(OverviewPluginPredicate.INSTANCE)
|
||||
);
|
||||
//J+
|
||||
|
||||
Collections.sort(plugins, PluginInformationComparator.INSTANCE);
|
||||
|
||||
Iterator<PluginInformation> it = plugins.iterator();
|
||||
String last = null;
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
PluginInformation pi = it.next();
|
||||
String id = pi.getId(false);
|
||||
|
||||
if ((last != null) && id.equals(last))
|
||||
{
|
||||
it.remove();
|
||||
}
|
||||
|
||||
last = id;
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** plugin manager */
|
||||
private final PluginManager pluginManager;
|
||||
}
|
||||
@@ -45,11 +45,11 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.NotSupportedFeatuerException;
|
||||
import sonia.scm.NotSupportedFeatureException;
|
||||
import sonia.scm.Type;
|
||||
import sonia.scm.api.rest.RestActionUploadResult;
|
||||
import sonia.scm.api.v2.resources.RepositoryResource;
|
||||
import sonia.scm.repository.*;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
@@ -394,7 +394,7 @@ public class RepositoryImportResource
|
||||
|
||||
response = Response.ok(result).build();
|
||||
}
|
||||
catch (NotSupportedFeatuerException ex)
|
||||
catch (NotSupportedFeatureException ex)
|
||||
{
|
||||
logger
|
||||
.warn(
|
||||
@@ -515,13 +515,6 @@ public class RepositoryImportResource
|
||||
// repository = new Repository(null, type, name);
|
||||
manager.create(repository);
|
||||
}
|
||||
catch (AlreadyExistsException ex)
|
||||
{
|
||||
logger.warn("a {} repository with the name {} already exists", type,
|
||||
name);
|
||||
|
||||
throw new WebApplicationException(Response.Status.CONFLICT);
|
||||
}
|
||||
catch (InternalRepositoryException ex)
|
||||
{
|
||||
handleGenericCreationFailure(ex, type, name);
|
||||
@@ -616,7 +609,7 @@ public class RepositoryImportResource
|
||||
types.add(t);
|
||||
}
|
||||
}
|
||||
catch (NotSupportedFeatuerException ex)
|
||||
catch (NotSupportedFeatureException ex)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
@@ -718,7 +711,7 @@ public class RepositoryImportResource
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NotSupportedFeatuerException ex)
|
||||
catch (NotSupportedFeatureException ex)
|
||||
{
|
||||
throw new WebApplicationException(ex, Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,319 +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.api.rest.resources;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import sonia.scm.security.Role;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.util.AssertUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.GenericEntity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Request;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Collection;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage users.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
@Path("users")
|
||||
public class UserResource extends AbstractManagerResource<User>
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final String DUMMY_PASSWORT = "__dummypassword__";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATH_PART = "users";
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param userManager
|
||||
* @param passwordService
|
||||
*/
|
||||
@Inject
|
||||
public UserResource(UserManager userManager, PasswordService passwordService)
|
||||
{
|
||||
super(userManager, User.class);
|
||||
this.passwordService = passwordService;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a new user. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param uriInfo current uri informations
|
||||
* @param user the user to be created
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 201, condition = "create success", additionalHeaders = {
|
||||
@ResponseHeader(name = "Location", description = "uri to the created group")
|
||||
}),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Override
|
||||
public Response create(@Context UriInfo uriInfo, User user)
|
||||
{
|
||||
return super.create(uriInfo, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a user. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param name the name of the user to delete.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@DELETE
|
||||
@Path("{id}")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "delete success"),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Override
|
||||
public Response delete(@PathParam("id") String name)
|
||||
{
|
||||
return super.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the given user. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param name name of the user to be modified
|
||||
* @param user user object to modify
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PUT
|
||||
@Path("{id}")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Override
|
||||
public Response update(@PathParam("id") String name, User user)
|
||||
{
|
||||
return super.update(name, user);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a user. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param id the id/name of the user
|
||||
*
|
||||
* @return the {@link User} with the specified id
|
||||
*/
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@TypeHint(User.class)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Override
|
||||
public Response get(@Context Request request, @PathParam("id") String id)
|
||||
{
|
||||
Response response = null;
|
||||
|
||||
if (SecurityUtils.getSubject().hasRole(Role.ADMIN))
|
||||
{
|
||||
response = super.get(request, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = Response.status(Response.Status.FORBIDDEN).build();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all users. <strong>Note:</strong> This method requires admin privileges.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param start the start value for paging
|
||||
* @param limit the limit value for paging
|
||||
* @param sortby sort parameter
|
||||
* @param desc sort direction desc or aesc
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@TypeHint(User[].class)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Override
|
||||
public Response getAll(@Context Request request, @DefaultValue("0")
|
||||
@QueryParam("start") int start, @DefaultValue("-1")
|
||||
@QueryParam("limit") int limit, @QueryParam("sortby") String sortby,
|
||||
@DefaultValue("false")
|
||||
@QueryParam("desc") boolean desc)
|
||||
{
|
||||
return super.getAll(request, start, limit, sortby, desc);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected GenericEntity<Collection<User>> createGenericEntity(
|
||||
Collection<User> items)
|
||||
{
|
||||
return new GenericEntity<Collection<User>>(items) {}
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preCreate(User user)
|
||||
{
|
||||
encryptPassword(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preUpdate(User user)
|
||||
{
|
||||
if (DUMMY_PASSWORT.equals(user.getPassword()))
|
||||
{
|
||||
User o = manager.get(user.getName());
|
||||
|
||||
AssertUtil.assertIsNotNull(o);
|
||||
user.setPassword(o.getPassword());
|
||||
}
|
||||
else
|
||||
{
|
||||
encryptPassword(user);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<User> prepareForReturn(Collection<User> users)
|
||||
{
|
||||
if (Util.isNotEmpty(users))
|
||||
{
|
||||
for (User u : users)
|
||||
{
|
||||
u.setPassword(DUMMY_PASSWORT);
|
||||
}
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User prepareForReturn(User user)
|
||||
{
|
||||
user.setPassword(DUMMY_PASSWORT);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getId(User user)
|
||||
{
|
||||
return user.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPart()
|
||||
{
|
||||
return PATH_PART;
|
||||
}
|
||||
|
||||
private void encryptPassword(User user)
|
||||
{
|
||||
String password = user.getPassword();
|
||||
|
||||
if (Util.isNotEmpty(password))
|
||||
{
|
||||
user.setPassword(passwordService.encryptPassword(password));
|
||||
}
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private PasswordService passwordService;
|
||||
}
|
||||
@@ -28,12 +28,14 @@
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
package sonia.scm.api.v2;
|
||||
|
||||
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.api.rest.StatusExceptionMapper;
|
||||
import sonia.scm.api.rest.ContextualExceptionMapper;
|
||||
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@@ -41,9 +43,9 @@ import javax.ws.rs.ext.Provider;
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Provider
|
||||
public class NotFoundExceptionMapper extends StatusExceptionMapper<NotFoundException> {
|
||||
|
||||
public NotFoundExceptionMapper() {
|
||||
super(NotFoundException.class, Response.Status.NOT_FOUND);
|
||||
public class NotFoundExceptionMapper extends ContextualExceptionMapper<NotFoundException> {
|
||||
@Inject
|
||||
public NotFoundExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(NotFoundException.class, Response.Status.NOT_FOUND, mapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package sonia.scm.api.v2;
|
||||
|
||||
import sonia.scm.NotSupportedFeatureException;
|
||||
import sonia.scm.api.rest.ContextualExceptionMapper;
|
||||
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class NotSupportedFeatureExceptionMapper extends ContextualExceptionMapper<NotSupportedFeatureException> {
|
||||
@Inject
|
||||
public NotSupportedFeatureExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(NotSupportedFeatureException.class, Response.Status.BAD_REQUEST, mapper);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,30 @@
|
||||
package sonia.scm.api.v2;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jboss.resteasy.api.validation.ResteasyViolationException;
|
||||
import sonia.scm.api.v2.resources.ViolationExceptionToErrorDtoMapper;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlElementWrapper;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Provider
|
||||
public class ValidationExceptionMapper implements ExceptionMapper<ResteasyViolationException> {
|
||||
|
||||
private final ViolationExceptionToErrorDtoMapper mapper;
|
||||
|
||||
@Inject
|
||||
public ValidationExceptionMapper(ViolationExceptionToErrorDtoMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response toResponse(ResteasyViolationException exception) {
|
||||
|
||||
List<ConstraintViolationBean> violations =
|
||||
exception.getConstraintViolations()
|
||||
.stream()
|
||||
.map(ConstraintViolationBean::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response
|
||||
.status(Response.Status.BAD_REQUEST)
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.entity(new ValidationError(violations))
|
||||
.entity(mapper.map(exception))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class ValidationError {
|
||||
@XmlElement(name = "violation")
|
||||
@XmlElementWrapper(name = "violations")
|
||||
private List<ConstraintViolationBean> violations;
|
||||
|
||||
public ValidationError(List<ConstraintViolationBean> violations) {
|
||||
this.violations = violations;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "violation")
|
||||
@Getter
|
||||
public static class ConstraintViolationBean {
|
||||
private String path;
|
||||
private String message;
|
||||
|
||||
public ConstraintViolationBean(ConstraintViolation<?> violation) {
|
||||
message = violation.getMessage();
|
||||
path = violation.getPropertyPath().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
@Path(AuthenticationResource.PATH)
|
||||
@AllowAnonymousAccess
|
||||
public class AuthenticationResource {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationResource.class);
|
||||
|
||||
@@ -5,12 +5,12 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.PageResult;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.Branches;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
@@ -26,6 +26,10 @@ import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
public class BranchRootResource {
|
||||
|
||||
@@ -77,7 +81,7 @@ public class BranchRootResource {
|
||||
.build();
|
||||
} catch (CommandNotSupportedException ex) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).build();
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
@@ -97,7 +101,7 @@ public class BranchRootResource {
|
||||
@PathParam("name") String name,
|
||||
@PathParam("branch") String branchName,
|
||||
@DefaultValue("0") @QueryParam("page") int page,
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws Exception {
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
boolean branchExists = repositoryService.getBranchesCommand()
|
||||
.getBranches()
|
||||
@@ -105,7 +109,7 @@ public class BranchRootResource {
|
||||
.stream()
|
||||
.anyMatch(branch -> branchName.equals(branch.getName()));
|
||||
if (!branchExists){
|
||||
throw new NotFoundException("branch", branchName);
|
||||
throw notFound(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name));
|
||||
}
|
||||
Repository repository = repositoryService.getRepository();
|
||||
RepositoryPermissions.read(repository).check();
|
||||
@@ -124,6 +128,49 @@ public class BranchRootResource {
|
||||
}
|
||||
}
|
||||
|
||||
@Path("{branch}/diffchangesets/{otherBranchName}")
|
||||
@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 changeset"),
|
||||
@ResponseCode(code = 404, condition = "not found, no changesets available in the repository"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces(VndMediaType.CHANGESET_COLLECTION)
|
||||
@TypeHint(CollectionDto.class)
|
||||
public Response changesetDiff(@PathParam("namespace") String namespace,
|
||||
@PathParam("name") String name,
|
||||
@PathParam("branch") String branchName,
|
||||
@PathParam("otherBranchName") String otherBranchName,
|
||||
@DefaultValue("0") @QueryParam("page") int page,
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws Exception {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
List<Branch> allBranches = repositoryService.getBranchesCommand().getBranches().getBranches();
|
||||
if (allBranches.stream().noneMatch(branch -> branchName.equals(branch.getName()))) {
|
||||
throw new NotFoundException("branch", branchName);
|
||||
}
|
||||
if (allBranches.stream().noneMatch(branch -> otherBranchName.equals(branch.getName()))) {
|
||||
throw new NotFoundException("branch", otherBranchName);
|
||||
}
|
||||
Repository repository = repositoryService.getRepository();
|
||||
RepositoryPermissions.read(repository).check();
|
||||
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
|
||||
.page(page)
|
||||
.pageSize(pageSize)
|
||||
.create()
|
||||
.setBranch(branchName)
|
||||
.setAncestorChangeset(otherBranchName)
|
||||
.getChangesets();
|
||||
if (changesets != null && changesets.getChangesets() != null) {
|
||||
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
|
||||
return Response.ok(branchChangesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository, branchName)).build();
|
||||
} else {
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the branches for a repository.
|
||||
*
|
||||
@@ -150,8 +197,6 @@ public class BranchRootResource {
|
||||
return Response.ok(branchCollectionToDtoMapper.map(namespace, name, branches.getBranches())).build();
|
||||
} catch (CommandNotSupportedException ex) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).build();
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public abstract class BranchToBranchDtoMapper {
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.self(resourceLinks.branch().self(namespaceAndName, target.getName()))
|
||||
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build())
|
||||
.single(linkBuilder("changesetDiff", resourceLinks.branch().changesetDiff(namespaceAndName, target.getName())).build())
|
||||
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build())
|
||||
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build());
|
||||
target.add(linksBuilder.build());
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.api.rest.ContextualExceptionMapper;
|
||||
import sonia.scm.user.ChangePasswordNotAllowedException;
|
||||
import sonia.scm.user.InvalidPasswordException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class ChangePasswordNotAllowedExceptionMapper implements ExceptionMapper<ChangePasswordNotAllowedException> {
|
||||
@Override
|
||||
public Response toResponse(ChangePasswordNotAllowedException exception) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(exception.getMessage())
|
||||
.build();
|
||||
public class ChangePasswordNotAllowedExceptionMapper extends ContextualExceptionMapper<ChangePasswordNotAllowedException> {
|
||||
@Inject
|
||||
public ChangePasswordNotAllowedExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(ChangePasswordNotAllowedException.class, Response.Status.BAD_REQUEST, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,7 @@ import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
import sonia.scm.repository.api.LogCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
@@ -26,6 +23,7 @@ import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@@ -56,7 +54,7 @@ public class ChangesetRootResource {
|
||||
@Produces(VndMediaType.CHANGESET_COLLECTION)
|
||||
@TypeHint(CollectionDto.class)
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @DefaultValue("0") @QueryParam("page") int page,
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RevisionNotFoundException, RepositoryNotFoundException {
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
Repository repository = repositoryService.getRepository();
|
||||
RepositoryPermissions.read(repository).check();
|
||||
@@ -89,7 +87,7 @@ public class ChangesetRootResource {
|
||||
@Produces(VndMediaType.CHANGESET)
|
||||
@TypeHint(ChangesetDto.class)
|
||||
@Path("{id}")
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException, RevisionNotFoundException, RepositoryNotFoundException {
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
Repository repository = repositoryService.getRepository();
|
||||
RepositoryPermissions.read(repository).check();
|
||||
@@ -97,8 +95,12 @@ public class ChangesetRootResource {
|
||||
.setStartChangeset(id)
|
||||
.setEndChangeset(id)
|
||||
.getChangesets();
|
||||
if (changesets != null && changesets.getChangesets() != null && changesets.getChangesets().size() == 1) {
|
||||
return Response.ok(changesetToChangesetDtoMapper.map(changesets.getChangesets().get(0), repository)).build();
|
||||
if (changesets != null && changesets.getChangesets() != null && !changesets.getChangesets().isEmpty()) {
|
||||
Optional<Changeset> changeset = changesets.getChangesets().stream().filter(ch -> ch.getId().equals(id)).findFirst();
|
||||
if (!changeset.isPresent()) {
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
return Response.ok(changesetToChangesetDtoMapper.map(changeset.get(), repository)).build();
|
||||
} else {
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.Manager;
|
||||
import sonia.scm.ModelObject;
|
||||
import sonia.scm.PageResult;
|
||||
@@ -47,7 +46,7 @@ class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
* Creates a model object for the given dto and returns a corresponding http response.
|
||||
* This handles all corner cases, eg. no conflicts or missing privileges.
|
||||
*/
|
||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws AlreadyExistsException {
|
||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) {
|
||||
if (dto == null) {
|
||||
return Response.status(BAD_REQUEST).build();
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.PathNotFoundException;
|
||||
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.util.IOUtil;
|
||||
@@ -64,8 +62,8 @@ public class ContentResource {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
Response.ResponseBuilder responseBuilder = Response.ok(stream);
|
||||
return createContentHeader(namespace, name, revision, path, repositoryService, responseBuilder);
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
|
||||
} catch (NotFoundException e) {
|
||||
LOG.debug(e.getMessage());
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
@@ -75,14 +73,8 @@ public class ContentResource {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
repositoryService.getCatCommand().setRevision(revision).retriveContent(os, path);
|
||||
os.close();
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
LOG.debug("repository {}/{} not found", path, namespace, name, e);
|
||||
throw new WebApplicationException(Status.NOT_FOUND);
|
||||
} catch (PathNotFoundException e) {
|
||||
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
|
||||
throw new WebApplicationException(Status.NOT_FOUND);
|
||||
} catch (RevisionNotFoundException e) {
|
||||
LOG.debug("revision '{}' not found in repository {}/{}", revision, namespace, name, e);
|
||||
} catch (NotFoundException e) {
|
||||
LOG.debug(e.getMessage());
|
||||
throw new WebApplicationException(Status.NOT_FOUND);
|
||||
}
|
||||
};
|
||||
@@ -111,8 +103,8 @@ public class ContentResource {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
Response.ResponseBuilder responseBuilder = Response.ok();
|
||||
return createContentHeader(namespace, name, revision, path, repositoryService, responseBuilder);
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
|
||||
} catch (NotFoundException e) {
|
||||
LOG.debug(e.getMessage());
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
@@ -120,12 +112,6 @@ public class ContentResource {
|
||||
private Response createContentHeader(String namespace, String name, String revision, String path, RepositoryService repositoryService, Response.ResponseBuilder responseBuilder) {
|
||||
try {
|
||||
appendContentHeader(path, getHead(revision, path, repositoryService), responseBuilder);
|
||||
} catch (PathNotFoundException e) {
|
||||
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
} catch (RevisionNotFoundException e) {
|
||||
LOG.debug("revision '{}' not found in repository {}/{}", revision, namespace, name, e);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
} catch (IOException e) {
|
||||
LOG.info("error reading repository resource {} from {}/{}", path, namespace, name, e);
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
|
||||
@@ -136,10 +122,10 @@ public class ContentResource {
|
||||
private void appendContentHeader(String path, byte[] head, Response.ResponseBuilder responseBuilder) {
|
||||
ContentType contentType = ContentTypes.detect(path, head);
|
||||
responseBuilder.header("Content-Type", contentType.getRaw());
|
||||
contentType.getLanguage().ifPresent(language -> responseBuilder.header("Language", language));
|
||||
contentType.getLanguage().ifPresent(language -> responseBuilder.header("X-Programming-Language", language));
|
||||
}
|
||||
|
||||
private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException, PathNotFoundException, RevisionNotFoundException {
|
||||
private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException {
|
||||
InputStream stream = repositoryService.getCatCommand().setRevision(revision).getStream(path);
|
||||
try {
|
||||
byte[] buffer = new byte[HEAD_BUFFER_SIZE];
|
||||
|
||||
@@ -4,24 +4,29 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
import sonia.scm.repository.api.DiffFormat;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
|
||||
public class DiffRootResource {
|
||||
|
||||
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
||||
|
||||
private static final String DIFF_FORMAT_VALUES_REGEX = "NATIVE|GIT|UNIFIED";
|
||||
|
||||
private final RepositoryServiceFactory serviceFactory;
|
||||
|
||||
@Inject
|
||||
@@ -50,17 +55,15 @@ public class DiffRootResource {
|
||||
@ResponseCode(code = 404, condition = "not found, no revision with the specified param for the repository available or repository not found"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision){
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision , @Pattern(regexp = DIFF_FORMAT_VALUES_REGEX) @DefaultValue("NATIVE") @QueryParam("format") String format ){
|
||||
HttpUtil.checkForCRLFInjection(revision);
|
||||
DiffFormat diffFormat = DiffFormat.valueOf(format);
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
StreamingOutput responseEntry = output -> {
|
||||
try {
|
||||
repositoryService.getDiffCommand()
|
||||
.setRevision(revision)
|
||||
.retriveContent(output);
|
||||
} catch (RevisionNotFoundException e) {
|
||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
||||
}
|
||||
repositoryService.getDiffCommand()
|
||||
.setRevision(revision)
|
||||
.setFormat(diffFormat)
|
||||
.retrieveContent(output);
|
||||
};
|
||||
return Response.ok(responseEntry)
|
||||
.header(HEADER_CONTENT_DISPOSITION, HttpUtil.createContentDispositionAttachmentHeader(String.format("%s-%s.diff", name, revision)))
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import sonia.scm.ContextEntry;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlElementWrapper;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
|
||||
@Getter @Setter
|
||||
public class ErrorDto {
|
||||
private String transactionId;
|
||||
private String errorCode;
|
||||
private List<ContextEntry> context;
|
||||
private String message;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@XmlElementWrapper(name = "violations")
|
||||
private List<ConstraintViolationDto> violations;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String url;
|
||||
|
||||
@XmlRootElement(name = "violation")
|
||||
@Getter @Setter
|
||||
public static class ConstraintViolationDto {
|
||||
private String path;
|
||||
private String message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.slf4j.MDC;
|
||||
import sonia.scm.ExceptionWithContext;
|
||||
|
||||
@Mapper
|
||||
public abstract class ExceptionWithContextToErrorDtoMapper {
|
||||
|
||||
@Mapping(target = "errorCode", source = "code")
|
||||
@Mapping(target = "transactionId", ignore = true)
|
||||
@Mapping(target = "violations", ignore = true)
|
||||
@Mapping(target = "url", ignore = true)
|
||||
public abstract ErrorDto map(ExceptionWithContext exception);
|
||||
|
||||
@AfterMapping
|
||||
void setTransactionId(@MappingTarget ErrorDto dto) {
|
||||
dto.setTransactionId(MDC.get("transaction_id"));
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.PageResult;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
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;
|
||||
@@ -26,6 +23,9 @@ import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
@Slf4j
|
||||
public class FileHistoryRootResource {
|
||||
|
||||
@@ -51,8 +51,6 @@ public class FileHistoryRootResource {
|
||||
* @param pageSize pagination
|
||||
* @return all changesets related to the given file starting with the given revision
|
||||
* @throws IOException on io error
|
||||
* @throws RevisionNotFoundException on missing revision
|
||||
* @throws RepositoryNotFoundException on missing repository
|
||||
*/
|
||||
@GET
|
||||
@Path("{revision}/{path: .*}")
|
||||
@@ -69,8 +67,9 @@ public class FileHistoryRootResource {
|
||||
@PathParam("revision") String revision,
|
||||
@PathParam("path") String path,
|
||||
@DefaultValue("0") @QueryParam("page") int page,
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RevisionNotFoundException, RepositoryNotFoundException {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||
log.info("Get changesets of the file {} and revision {}", path, revision);
|
||||
Repository repository = repositoryService.getRepository();
|
||||
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
|
||||
@@ -84,9 +83,9 @@ public class FileHistoryRootResource {
|
||||
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
|
||||
return Response.ok(fileHistoryCollectionToDtoMapper.map(page, pageSize, pageResult, repository, revision, path)).build();
|
||||
} else {
|
||||
String message = String.format("for the revision %s and the file %s there is no changesets", revision, path);
|
||||
String message = String.format("for the revision %s and the file %s there are no changesets", revision, path);
|
||||
log.error(message);
|
||||
throw new InternalRepositoryException(message);
|
||||
throw notFound(entity("path", path).in("revision", revision).in(namespaceAndName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.GroupManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
@@ -71,7 +71,7 @@ public class GroupCollectionResource {
|
||||
|
||||
/**
|
||||
* Creates a new group.
|
||||
* @param groupDto The group to be created.
|
||||
* @param group The group to be created.
|
||||
* @return A response with the link to the new group (if created successfully).
|
||||
*/
|
||||
@POST
|
||||
@@ -86,9 +86,9 @@ public class GroupCollectionResource {
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created group"))
|
||||
public Response create(@Valid GroupDto groupDto) throws AlreadyExistsException {
|
||||
return adapter.create(groupDto,
|
||||
() -> dtoToGroupMapper.map(groupDto),
|
||||
group -> resourceLinks.group().self(group.getName()));
|
||||
public Response create(@Valid GroupDto group) {
|
||||
return adapter.create(group,
|
||||
() -> dtoToGroupMapper.map(group),
|
||||
g -> resourceLinks.group().self(g.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@ 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.ConcurrentModificationException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.GroupManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
@@ -84,7 +83,7 @@ public class GroupResource {
|
||||
* <strong>Note:</strong> This method requires "group" privilege.
|
||||
*
|
||||
* @param name name of the group to be modified
|
||||
* @param groupDto group object to modify
|
||||
* @param group group object to modify
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@@ -98,7 +97,7 @@ public class GroupResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(@PathParam("id") String name, @Valid GroupDto groupDto) throws ConcurrentModificationException {
|
||||
return adapter.update(name, existing -> dtoToGroupMapper.map(groupDto));
|
||||
public Response update(@PathParam("id") String name, @Valid GroupDto group) {
|
||||
return adapter.update(name, existing -> dtoToGroupMapper.map(group));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.Manager;
|
||||
import sonia.scm.ModelObject;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.PageResult;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
@@ -22,6 +21,7 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
DTO extends HalRepresentation> {
|
||||
|
||||
private final Manager<MODEL_OBJECT> manager;
|
||||
private final String type;
|
||||
|
||||
private final SingleResourceManagerAdapter<MODEL_OBJECT, DTO> singleAdapter;
|
||||
private final CollectionResourceManagerAdapter<MODEL_OBJECT, DTO> collectionAdapter;
|
||||
@@ -30,13 +30,14 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
this.manager = manager;
|
||||
singleAdapter = new SingleResourceManagerAdapter<>(manager, type);
|
||||
collectionAdapter = new CollectionResourceManagerAdapter<>(manager, type);
|
||||
this.type = type.getSimpleName();
|
||||
}
|
||||
|
||||
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) {
|
||||
return singleAdapter.get(loadBy(id), mapToDto);
|
||||
}
|
||||
|
||||
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) throws ConcurrentModificationException {
|
||||
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) {
|
||||
return singleAdapter.update(
|
||||
loadBy(id),
|
||||
applyChanges,
|
||||
@@ -48,7 +49,7 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
return collectionAdapter.getAll(page, pageSize, sortBy, desc, mapToDto);
|
||||
}
|
||||
|
||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws AlreadyExistsException {
|
||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) {
|
||||
return collectionAdapter.create(dto, modelObjectSupplier, uriCreator);
|
||||
}
|
||||
|
||||
@@ -56,8 +57,8 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
return singleAdapter.delete(id);
|
||||
}
|
||||
|
||||
private Supplier<Optional<MODEL_OBJECT>> loadBy(String id) {
|
||||
return () -> Optional.ofNullable(manager.get(id));
|
||||
private Supplier<MODEL_OBJECT> loadBy(String id) {
|
||||
return () -> Optional.ofNullable(manager.get(id)).orElseThrow(() -> new NotFoundException(type, id));
|
||||
}
|
||||
|
||||
private Predicate<MODEL_OBJECT> idStaysTheSame(String id) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.security.AllowAnonymousAccess;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -9,6 +10,7 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
|
||||
@Path(IndexResource.INDEX_PATH_V2)
|
||||
@AllowAnonymousAccess
|
||||
public class IndexResource {
|
||||
public static final String INDEX_PATH_V2 = "v2/";
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.api.rest.StatusExceptionMapper;
|
||||
import sonia.scm.api.rest.ContextualExceptionMapper;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class InternalRepositoryExceptionMapper extends StatusExceptionMapper<InternalRepositoryException> {
|
||||
public class InternalRepositoryExceptionMapper extends ContextualExceptionMapper<InternalRepositoryException> {
|
||||
|
||||
public InternalRepositoryExceptionMapper() {
|
||||
super(InternalRepositoryException.class, Response.Status.INTERNAL_SERVER_ERROR);
|
||||
@Inject
|
||||
public InternalRepositoryExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(InternalRepositoryException.class, Response.Status.INTERNAL_SERVER_ERROR, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.api.rest.ContextualExceptionMapper;
|
||||
import sonia.scm.user.InvalidPasswordException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class InvalidPasswordExceptionMapper implements ExceptionMapper<InvalidPasswordException> {
|
||||
@Override
|
||||
public Response toResponse(InvalidPasswordException exception) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(exception.getMessage())
|
||||
.build();
|
||||
public class InvalidPasswordExceptionMapper extends ContextualExceptionMapper<InvalidPasswordException> {
|
||||
|
||||
@Inject
|
||||
public InvalidPasswordExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(InvalidPasswordException.class, Response.Status.BAD_REQUEST, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ public class MapperModule extends AbstractModule {
|
||||
|
||||
bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapper(ReducedObjectModelToDtoMapper.class).getClass());
|
||||
|
||||
bind(ViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ViolationExceptionToErrorDtoMapper.class).getClass());
|
||||
bind(ExceptionWithContextToErrorDtoMapper.class).to(Mappers.getMapper(ExceptionWithContextToErrorDtoMapper.class).getClass());
|
||||
|
||||
// no mapstruct required
|
||||
bind(UIPluginDtoMapper.class);
|
||||
bind(UIPluginDtoCollectionMapper.class);
|
||||
|
||||
@@ -10,6 +10,7 @@ import sonia.scm.user.UserManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
@@ -73,8 +74,8 @@ public class MeResource {
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
||||
public Response changePassword(@Valid PasswordChangeDto passwordChangeDto) {
|
||||
userManager.changePasswordForLoggedInUser(passwordService.encryptPassword(passwordChangeDto.getOldPassword()), passwordService.encryptPassword(passwordChangeDto.getNewPassword()));
|
||||
public Response changePassword(@Valid PasswordChangeDto passwordChange) {
|
||||
userManager.changePasswordForLoggedInUser(passwordService.encryptPassword(passwordChange.getOldPassword()), passwordService.encryptPassword(passwordChange.getNewPassword()));
|
||||
return Response.noContent().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,8 @@ 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;
|
||||
@@ -46,7 +43,7 @@ public class ModificationsRootResource {
|
||||
@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 {
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
Modifications modifications = repositoryService.getModificationsCommand()
|
||||
.revision(revision)
|
||||
|
||||
@@ -11,11 +11,11 @@ import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Permission;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
@@ -30,6 +30,9 @@ import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static sonia.scm.AlreadyExistsException.alreadyExists;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
import static sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX;
|
||||
|
||||
@Slf4j
|
||||
@@ -71,12 +74,12 @@ public class PermissionRootResource {
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PERMISSION)
|
||||
@Path("")
|
||||
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid PermissionDto permission) throws AlreadyExistsException, NotFoundException {
|
||||
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid PermissionDto permission) {
|
||||
log.info("try to add new permission: {}", permission);
|
||||
Repository repository = load(namespace, name);
|
||||
RepositoryPermissions.permissionWrite(repository).check();
|
||||
checkPermissionAlreadyExists(permission, repository);
|
||||
repository.getPermissions().add(dtoToModelMapper.map(permission));
|
||||
repository.addPermission(dtoToModelMapper.map(permission));
|
||||
manager.modify(repository);
|
||||
String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission);
|
||||
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, urlPermissionName))).build();
|
||||
@@ -109,7 +112,7 @@ public class PermissionRootResource {
|
||||
.filter(filterPermission(permissionName))
|
||||
.map(permission -> modelToDtoMapper.map(permission, repository))
|
||||
.findFirst()
|
||||
.orElseThrow(NotFoundException::new)
|
||||
.orElseThrow(() -> notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name)))
|
||||
).build();
|
||||
}
|
||||
|
||||
@@ -131,7 +134,7 @@ public class PermissionRootResource {
|
||||
@Produces(VndMediaType.PERMISSION)
|
||||
@TypeHint(PermissionDto.class)
|
||||
@Path("")
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws RepositoryNotFoundException {
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) {
|
||||
Repository repository = load(namespace, name);
|
||||
RepositoryPermissions.permissionRead(repository).check();
|
||||
return Response.ok(permissionCollectionToDtoMapper.map(repository)).build();
|
||||
@@ -158,23 +161,23 @@ public class PermissionRootResource {
|
||||
public Response update(@PathParam("namespace") String namespace,
|
||||
@PathParam("name") String name,
|
||||
@PathParam("permission-name") String permissionName,
|
||||
@Valid PermissionDto permission) throws AlreadyExistsException {
|
||||
@Valid PermissionDto permission) {
|
||||
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
|
||||
Repository repository = load(namespace, name);
|
||||
RepositoryPermissions.permissionWrite(repository).check();
|
||||
String extractedPermissionName = getPermissionName(permissionName);
|
||||
if (!isPermissionExist(new PermissionDto(extractedPermissionName, isGroupPermission(permissionName)), repository)) {
|
||||
throw new NotFoundException("permission", extractedPermissionName);
|
||||
throw notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name));
|
||||
}
|
||||
permission.setGroupPermission(isGroupPermission(permissionName));
|
||||
if (!extractedPermissionName.equals(permission.getName())) {
|
||||
checkPermissionAlreadyExists(permission, repository, "target permission " + permission.getName() + " already exists");
|
||||
checkPermissionAlreadyExists(permission, repository);
|
||||
}
|
||||
Permission existingPermission = repository.getPermissions()
|
||||
.stream()
|
||||
.filter(filterPermission(permissionName))
|
||||
.findFirst()
|
||||
.orElseThrow(NotFoundException::new);
|
||||
.orElseThrow(() -> notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name)));
|
||||
dtoToModelMapper.modify(existingPermission, permission);
|
||||
manager.modify(repository);
|
||||
log.info("the permission with name: {} is updated.", permissionName);
|
||||
@@ -206,7 +209,7 @@ public class PermissionRootResource {
|
||||
.stream()
|
||||
.filter(filterPermission(permissionName))
|
||||
.findFirst()
|
||||
.ifPresent(p -> repository.getPermissions().remove(p))
|
||||
.ifPresent(repository::removePermission)
|
||||
;
|
||||
manager.modify(repository);
|
||||
log.info("the permission with name: {} is updated.", permissionName);
|
||||
@@ -237,12 +240,12 @@ public class PermissionRootResource {
|
||||
* @param namespace the repository namespace
|
||||
* @param name the repository name
|
||||
* @return the repository if the user is permitted
|
||||
* @throws RepositoryNotFoundException if the repository does not exists
|
||||
* @throws NotFoundException if the repository does not exists
|
||||
*/
|
||||
private Repository load(String namespace, String name) throws RepositoryNotFoundException {
|
||||
private Repository load(String namespace, String name) {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
return Optional.ofNullable(manager.get(namespaceAndName))
|
||||
.orElseThrow(() -> new RepositoryNotFoundException(namespaceAndName));
|
||||
.orElseThrow(() -> notFound(entity(namespaceAndName)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,12 +253,11 @@ public class PermissionRootResource {
|
||||
*
|
||||
* @param permission the searched permission
|
||||
* @param repository the repository to be inspected
|
||||
* @param errorMessage error message
|
||||
* @throws AlreadyExistsException if the permission already exists in the repository
|
||||
*/
|
||||
private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository, String errorMessage) throws AlreadyExistsException {
|
||||
private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository) {
|
||||
if (isPermissionExist(permission, repository)) {
|
||||
throw new AlreadyExistsException(errorMessage);
|
||||
throw alreadyExists(entity("permission", permission.getName()).in(repository));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,10 +266,6 @@ public class PermissionRootResource {
|
||||
.stream()
|
||||
.anyMatch(p -> p.getName().equals(permission.getName()) && p.isGroupPermission() == permission.isGroupPermission());
|
||||
}
|
||||
|
||||
private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository) throws AlreadyExistsException {
|
||||
checkPermissionAlreadyExists(permission, repository, "the permission " + permission.getName() + " already exist.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,12 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import sonia.scm.repository.Permission;
|
||||
import sonia.scm.repository.PermissionType;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -21,6 +24,8 @@ import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
public class RepositoryCollectionResource {
|
||||
|
||||
private static final int DEFAULT_PAGE_SIZE = 10;
|
||||
@@ -72,7 +77,7 @@ public class RepositoryCollectionResource {
|
||||
* <strong>Note:</strong> This method requires "repository" privilege. The namespace of the given repository will
|
||||
* be ignored and set by the configured namespace strategy.
|
||||
*
|
||||
* @param repositoryDto The repository to be created.
|
||||
* @param repository The repository to be created.
|
||||
* @return A response with the link to the new repository (if created successfully).
|
||||
*/
|
||||
@POST
|
||||
@@ -87,9 +92,19 @@ public class RepositoryCollectionResource {
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
|
||||
public Response create(@Valid RepositoryDto repositoryDto) throws AlreadyExistsException {
|
||||
return adapter.create(repositoryDto,
|
||||
() -> dtoToRepositoryMapper.map(repositoryDto, null),
|
||||
repository -> resourceLinks.repository().self(repository.getNamespace(), repository.getName()));
|
||||
public Response create(@Valid RepositoryDto repository) {
|
||||
return adapter.create(repository,
|
||||
() -> createModelObjectFromDto(repository),
|
||||
r -> resourceLinks.repository().self(r.getNamespace(), r.getName()));
|
||||
}
|
||||
|
||||
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
|
||||
Repository repository = dtoToRepositoryMapper.map(repositoryDto, null);
|
||||
repository.setPermissions(singletonList(new Permission(currentUser(), PermissionType.OWNER)));
|
||||
return repository;
|
||||
}
|
||||
|
||||
private String currentUser() {
|
||||
return SecurityUtils.getSubject().getPrincipals().oneByType(User.class).getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ 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.ConcurrentModificationException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryIsNotArchivedException;
|
||||
@@ -12,6 +10,7 @@ import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
@@ -26,6 +25,9 @@ import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
public class RepositoryResource {
|
||||
|
||||
private final RepositoryToRepositoryDtoMapper repositoryToDtoMapper;
|
||||
@@ -124,7 +126,7 @@ public class RepositoryResource {
|
||||
*
|
||||
* @param namespace the namespace of the repository to be modified
|
||||
* @param name the name of the repository to be modified
|
||||
* @param repositoryDto repository object to modify
|
||||
* @param repository repository object to modify
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@@ -138,10 +140,10 @@ public class RepositoryResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repositoryDto) throws ConcurrentModificationException {
|
||||
public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repository) {
|
||||
return adapter.update(
|
||||
loadBy(namespace, name),
|
||||
existing -> processUpdate(repositoryDto, existing),
|
||||
existing -> processUpdate(repository, existing),
|
||||
nameAndNamespaceStaysTheSame(namespace, name)
|
||||
);
|
||||
}
|
||||
@@ -203,8 +205,9 @@ public class RepositoryResource {
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<Optional<Repository>> loadBy(String namespace, String name) {
|
||||
return () -> Optional.ofNullable(manager.get(new NamespaceAndName(namespace, name)));
|
||||
private Supplier<Repository> loadBy(String namespace, String name) {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
return () -> Optional.ofNullable(manager.get(namespaceAndName)).orElseThrow(() -> notFound(entity(namespaceAndName)));
|
||||
}
|
||||
|
||||
private Predicate<Repository> nameAndNamespaceStaysTheSame(String namespace, String name) {
|
||||
|
||||
@@ -322,6 +322,10 @@ class ResourceLinks {
|
||||
public String history(NamespaceAndName namespaceAndName, String branch) {
|
||||
return branchLinkBuilder.method("getRepositoryResource").parameters(namespaceAndName.getNamespace(), namespaceAndName.getName()).method("branches").parameters().method("history").parameters(branch).href();
|
||||
}
|
||||
|
||||
public String changesetDiff(NamespaceAndName namespaceAndName, String branch) {
|
||||
return branchLinkBuilder.method("getRepositoryResource").parameters(namespaceAndName.getNamespace(), namespaceAndName.getName()).method("branches").parameters().method("changesetDiff").parameters(branch, "").href() + "{otherBranch}";
|
||||
}
|
||||
}
|
||||
|
||||
public BranchCollectionLinks branchCollection() {
|
||||
|
||||
@@ -11,7 +11,6 @@ import javax.ws.rs.core.GenericEntity;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
@@ -33,45 +32,41 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
DTO extends HalRepresentation> extends AbstractManagerResource<MODEL_OBJECT> {
|
||||
|
||||
private final Function<Throwable, Optional<Response>> errorHandler;
|
||||
private final Class<MODEL_OBJECT> type;
|
||||
|
||||
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
|
||||
this(manager, type, e -> Optional.empty());
|
||||
}
|
||||
|
||||
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type, Function<Throwable, Optional<Response>> errorHandler) {
|
||||
SingleResourceManagerAdapter(
|
||||
Manager<MODEL_OBJECT> manager,
|
||||
Class<MODEL_OBJECT> type,
|
||||
Function<Throwable, Optional<Response>> errorHandler) {
|
||||
super(manager, type);
|
||||
this.errorHandler = errorHandler;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the model object for the given id, transforms it to a dto and returns a corresponding http response.
|
||||
* This handles all corner cases, eg. no matching object for the id or missing privileges.
|
||||
*/
|
||||
Response get(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, DTO> mapToDto) {
|
||||
return reader.get()
|
||||
.map(mapToDto)
|
||||
.map(Response::ok)
|
||||
.map(Response.ResponseBuilder::build)
|
||||
.orElseThrow(NotFoundException::new);
|
||||
}
|
||||
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey, Consumer<MODEL_OBJECT> checker) throws NotFoundException, ConcurrentModificationException {
|
||||
MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new);
|
||||
checker.accept(existingModelObject);
|
||||
return update(reader,applyChanges,hasSameKey);
|
||||
Response get(Supplier<MODEL_OBJECT> reader, Function<MODEL_OBJECT, DTO> mapToDto) {
|
||||
return Response.ok(mapToDto.apply(reader.get())).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model object for the given id according to the given function and returns a corresponding http response.
|
||||
* This handles all corner cases, eg. no matching object for the id or missing privileges.
|
||||
*/
|
||||
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey) throws NotFoundException, ConcurrentModificationException {
|
||||
MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new);
|
||||
Response update(Supplier<MODEL_OBJECT> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey) {
|
||||
MODEL_OBJECT existingModelObject = reader.get();
|
||||
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
|
||||
if (!hasSameKey.test(changedModelObject)) {
|
||||
return Response.status(BAD_REQUEST).entity("illegal change of id").build();
|
||||
}
|
||||
else if (modelObjectWasModifiedConcurrently(existingModelObject, changedModelObject)) {
|
||||
throw new ConcurrentModificationException();
|
||||
throw new ConcurrentModificationException(type, existingModelObject.getId());
|
||||
}
|
||||
return update(getId(existingModelObject), changedModelObject);
|
||||
}
|
||||
@@ -81,11 +76,13 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
&& (updated.getLastModified() == null || existing.getLastModified() > updated.getLastModified());
|
||||
}
|
||||
|
||||
public Response delete(Supplier<Optional<MODEL_OBJECT>> reader) {
|
||||
return reader.get()
|
||||
.map(MODEL_OBJECT::getId)
|
||||
.map(this::delete)
|
||||
.orElse(null);
|
||||
public Response delete(Supplier<MODEL_OBJECT> reader) {
|
||||
try {
|
||||
return delete(reader.get().getId());
|
||||
} catch (NotFoundException e) {
|
||||
// due to idempotency of delete this does not matter here.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.BrowserResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.api.BrowseCommandBuilder;
|
||||
@@ -31,14 +30,14 @@ public class SourceRootResource {
|
||||
@GET
|
||||
@Produces(VndMediaType.SOURCE)
|
||||
@Path("")
|
||||
public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws NotFoundException, IOException {
|
||||
public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException {
|
||||
return getSource(namespace, name, "/", null);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(VndMediaType.SOURCE)
|
||||
@Path("{revision}")
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws NotFoundException, IOException {
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException {
|
||||
return getSource(namespace, name, "/", revision);
|
||||
}
|
||||
|
||||
@@ -49,7 +48,7 @@ public class SourceRootResource {
|
||||
return getSource(namespace, name, path, revision);
|
||||
}
|
||||
|
||||
private Response getSource(String namespace, String repoName, String path, String revision) throws IOException, NotFoundException {
|
||||
private Response getSource(String namespace, String repoName, String path, String revision) throws IOException {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, repoName);
|
||||
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||
BrowseCommandBuilder browseCommand = repositoryService.getBrowseCommand();
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.NotFoundException;
|
||||
|
||||
public class TagNotFoundException extends NotFoundException {
|
||||
|
||||
}
|
||||
@@ -3,9 +3,9 @@ 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.NotFoundException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.Tags;
|
||||
@@ -21,6 +21,9 @@ import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
public class TagRootResource {
|
||||
|
||||
private final RepositoryServiceFactory serviceFactory;
|
||||
@@ -47,7 +50,7 @@ public class TagRootResource {
|
||||
})
|
||||
@Produces(VndMediaType.TAG_COLLECTION)
|
||||
@TypeHint(CollectionDto.class)
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException, RepositoryNotFoundException {
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
Tags tags = getTags(repositoryService);
|
||||
if (tags != null && tags.getTags() != null) {
|
||||
@@ -72,7 +75,7 @@ public class TagRootResource {
|
||||
@Produces(VndMediaType.TAG)
|
||||
@TypeHint(TagDto.class)
|
||||
@Path("{tagName}")
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("tagName") String tagName) throws IOException, RepositoryNotFoundException, TagNotFoundException {
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("tagName") String tagName) throws IOException {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||
Tags tags = getTags(repositoryService);
|
||||
@@ -80,7 +83,7 @@ public class TagRootResource {
|
||||
Tag tag = tags.getTags().stream()
|
||||
.filter(t -> tagName.equals(t.getName()))
|
||||
.findFirst()
|
||||
.orElseThrow(TagNotFoundException::new);
|
||||
.orElseThrow(() -> createNotFoundException(namespace, name, tagName));
|
||||
return Response.ok(tagToTagDtoMapper.map(tag, namespaceAndName)).build();
|
||||
} else {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
@@ -90,6 +93,10 @@ public class TagRootResource {
|
||||
}
|
||||
}
|
||||
|
||||
private NotFoundException createNotFoundException(String namespace, String name, String tagName) {
|
||||
return notFound(entity("Tag", tagName).in("Repository", namespace + "/" + name));
|
||||
}
|
||||
|
||||
private Tags getTags(RepositoryService repositoryService) throws IOException {
|
||||
Repository repository = repositoryService.getRepository();
|
||||
RepositoryPermissions.read(repository).check();
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.security.AllowAnonymousAccess;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -17,6 +18,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@AllowAnonymousAccess
|
||||
public class UIPluginResource {
|
||||
|
||||
private final PluginLoader pluginLoader;
|
||||
|
||||
@@ -6,12 +6,12 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
@@ -76,7 +76,7 @@ public class UserCollectionResource {
|
||||
*
|
||||
* <strong>Note:</strong> This method requires "user" privilege.
|
||||
*
|
||||
* @param userDto The user to be created.
|
||||
* @param user The user to be created.
|
||||
* @return A response with the link to the new user (if created successfully).
|
||||
*/
|
||||
@POST
|
||||
@@ -91,7 +91,7 @@ public class UserCollectionResource {
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
|
||||
public Response create(@Valid UserDto userDto) throws AlreadyExistsException {
|
||||
return adapter.create(userDto, () -> dtoToUserMapper.map(userDto, passwordService.encryptPassword(userDto.getPassword())), user -> resourceLinks.user().self(user.getName()));
|
||||
public Response create(@Valid UserDto user) {
|
||||
return adapter.create(user, () -> dtoToUserMapper.map(user, passwordService.encryptPassword(user.getPassword())), u -> resourceLinks.user().self(u.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
@@ -87,7 +87,7 @@ public class UserResource {
|
||||
* <strong>Note:</strong> This method requires "user" privilege.
|
||||
*
|
||||
* @param name name of the user to be modified
|
||||
* @param userDto user object to modify
|
||||
* @param user user object to modify
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@@ -101,8 +101,8 @@ public class UserResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(@PathParam("id") String name, @Valid UserDto userDto) throws ConcurrentModificationException {
|
||||
return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
|
||||
public Response update(@PathParam("id") String name, @Valid UserDto user) {
|
||||
return adapter.update(name, existing -> dtoToUserMapper.map(user, existing.getPassword()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +114,7 @@ public class UserResource {
|
||||
* <strong>Note:</strong> This method requires "user:changeOwnPassword" privilege to modify the own password.
|
||||
*
|
||||
* @param name name of the user to be modified
|
||||
* @param passwordOverwriteDto change password object to modify password. the old password is here not required
|
||||
* @param passwordOverwrite change password object to modify password. the old password is here not required
|
||||
*/
|
||||
@PUT
|
||||
@Path("password")
|
||||
@@ -128,8 +128,8 @@ public class UserResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response overwritePassword(@PathParam("id") String name, @Valid PasswordOverwriteDto passwordOverwriteDto) {
|
||||
userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwriteDto.getNewPassword()));
|
||||
public Response overwritePassword(@PathParam("id") String name, @Valid PasswordOverwriteDto passwordOverwrite) {
|
||||
userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwrite.getNewPassword()));
|
||||
return Response.noContent().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.jboss.resteasy.api.validation.ResteasyViolationException;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper
|
||||
public abstract class ViolationExceptionToErrorDtoMapper {
|
||||
|
||||
@Mapping(target = "errorCode", ignore = true)
|
||||
@Mapping(target = "transactionId", ignore = true)
|
||||
@Mapping(target = "context", ignore = true)
|
||||
@Mapping(target = "url", ignore = true)
|
||||
public abstract ErrorDto map(ResteasyViolationException exception);
|
||||
|
||||
@AfterMapping
|
||||
void setTransactionId(@MappingTarget ErrorDto dto) {
|
||||
dto.setTransactionId(MDC.get("transaction_id"));
|
||||
}
|
||||
|
||||
@AfterMapping
|
||||
void mapViolations(ResteasyViolationException exception, @MappingTarget ErrorDto dto) {
|
||||
List<ErrorDto.ConstraintViolationDto> violations =
|
||||
exception.getConstraintViolations()
|
||||
.stream()
|
||||
.map(this::createViolationDto)
|
||||
.collect(Collectors.toList());
|
||||
dto.setViolations(violations);
|
||||
}
|
||||
|
||||
private ErrorDto.ConstraintViolationDto createViolationDto(ConstraintViolation<?> violation) {
|
||||
ErrorDto.ConstraintViolationDto constraintViolationDto = new ErrorDto.ConstraintViolationDto();
|
||||
constraintViolationDto.setMessage(violation.getMessage());
|
||||
constraintViolationDto.setPath(violation.getPropertyPath().toString());
|
||||
return constraintViolationDto;
|
||||
}
|
||||
|
||||
@AfterMapping
|
||||
void setErrorCode(@MappingTarget ErrorDto dto) {
|
||||
dto.setErrorCode("1wR7ZBe7H1");
|
||||
}
|
||||
|
||||
@AfterMapping
|
||||
void setMessage(@MappingTarget ErrorDto dto) {
|
||||
dto.setMessage("input violates conditions (see violation list)");
|
||||
}
|
||||
}
|
||||
@@ -34,22 +34,19 @@ package sonia.scm.boot;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.github.legman.Subscribe;
|
||||
|
||||
import com.google.inject.servlet.GuiceFilter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.Stage;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -65,6 +62,8 @@ public class BootstrapContextFilter extends GuiceFilter
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
private final BootstrapContextListener listener = new BootstrapContextListener();
|
||||
|
||||
/**
|
||||
* Restart the whole webapp context.
|
||||
*
|
||||
@@ -85,29 +84,20 @@ public class BootstrapContextFilter extends GuiceFilter
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
logger.warn(
|
||||
"destroy filter pipeline, because of a received restart event");
|
||||
logger.warn("destroy filter pipeline, because of a received restart event");
|
||||
destroy();
|
||||
logger.warn(
|
||||
"reinitialize filter pipeline, because of a received restart event");
|
||||
super.init(filterConfig);
|
||||
|
||||
logger.warn("reinitialize filter pipeline, because of a received restart event");
|
||||
initGuice();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param filterConfig
|
||||
*
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException
|
||||
{
|
||||
this.filterConfig = filterConfig;
|
||||
super.init(filterConfig);
|
||||
|
||||
initGuice();
|
||||
|
||||
if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT)
|
||||
{
|
||||
@@ -116,6 +106,19 @@ public class BootstrapContextFilter extends GuiceFilter
|
||||
}
|
||||
}
|
||||
|
||||
public void initGuice() throws ServletException {
|
||||
super.init(filterConfig);
|
||||
|
||||
listener.contextInitialized(new ServletContextEvent(filterConfig.getServletContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
listener.contextDestroyed(new ServletContextEvent(filterConfig.getServletContext()));
|
||||
ServletContextCleaner.cleanup(filterConfig.getServletContext());
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
|
||||
@@ -148,13 +148,15 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
{
|
||||
context = sce.getServletContext();
|
||||
|
||||
PluginIndex index = readCorePluginIndex(context);
|
||||
|
||||
File pluginDirectory = getPluginDirectory();
|
||||
|
||||
try
|
||||
{
|
||||
extractCorePlugins(context, pluginDirectory, index);
|
||||
if (!isCorePluginExtractionDisabled()) {
|
||||
extractCorePlugins(context, pluginDirectory);
|
||||
} else {
|
||||
logger.info("core plugin extraction is disabled");
|
||||
}
|
||||
|
||||
ClassLoader cl =
|
||||
ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
|
||||
@@ -181,31 +183,8 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the whole webapp context.
|
||||
*
|
||||
*
|
||||
* @param event restart event
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleRestartEvent(RestartEvent event)
|
||||
{
|
||||
logger.warn("received restart event from {} with reason: {}",
|
||||
event.getCause(), event.getReason());
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
logger.error("context is null, scm-manager is not initialized");
|
||||
}
|
||||
else
|
||||
{
|
||||
ServletContextEvent sce = new ServletContextEvent(context);
|
||||
|
||||
logger.warn("destroy context, because of a received restart event");
|
||||
contextDestroyed(sce);
|
||||
logger.warn("reinitialize context, because of a received restart event");
|
||||
contextInitialized(sce);
|
||||
}
|
||||
private boolean isCorePluginExtractionDisabled() {
|
||||
return Boolean.getBoolean("sonia.scm.boot.disable-core-plugin-extraction");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +193,6 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
*
|
||||
* @param context
|
||||
* @param pluginDirectory
|
||||
* @param name
|
||||
* @param entry
|
||||
*
|
||||
* @throws IOException
|
||||
@@ -269,17 +247,15 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
*
|
||||
* @param context
|
||||
* @param pluginDirectory
|
||||
* @param lines
|
||||
* @param index
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void extractCorePlugins(ServletContext context, File pluginDirectory,
|
||||
PluginIndex index)
|
||||
throws IOException
|
||||
private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException
|
||||
{
|
||||
IOUtil.mkdirs(pluginDirectory);
|
||||
|
||||
PluginIndex index = readCorePluginIndex(context);
|
||||
|
||||
for (PluginIndexEntry entry : index)
|
||||
{
|
||||
extractCorePlugin(context, pluginDirectory, entry);
|
||||
|
||||
97
scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java
Normal file
97
scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.Stage;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.filter.WebElement;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* This servlet sends a {@link RestartEvent} to the {@link ScmEventBus} which causes scm-manager to restart the context.
|
||||
* The {@link RestartServlet} can be used for reloading java code or for installing plugins without a complete restart.
|
||||
* At the moment the Servlet accepts only request, if scm-manager was started in the {@link Stage#DEVELOPMENT} stage.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Priority(0)
|
||||
@WebElement("/restart")
|
||||
public class RestartServlet extends HttpServlet {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RestartServlet.class);
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final AtomicBoolean restarting = new AtomicBoolean();
|
||||
|
||||
private final ScmEventBus eventBus;
|
||||
private final Stage stage;
|
||||
|
||||
@Inject
|
||||
public RestartServlet() {
|
||||
this(ScmEventBus.getInstance(), SCMContext.getContext().getStage());
|
||||
}
|
||||
|
||||
RestartServlet(ScmEventBus eventBus, Stage stage) {
|
||||
this.eventBus = eventBus;
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
|
||||
LOG.info("received sendRestartEvent request");
|
||||
|
||||
if (isRestartAllowed()) {
|
||||
|
||||
try (InputStream requestInput = req.getInputStream()) {
|
||||
Reason reason = objectMapper.readValue(requestInput, Reason.class);
|
||||
sendRestartEvent(resp, reason);
|
||||
} catch (IOException ex) {
|
||||
LOG.warn("failed to trigger sendRestartEvent event", ex);
|
||||
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG.debug("received restart event in non development stage");
|
||||
resp.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRestartAllowed() {
|
||||
return stage == Stage.DEVELOPMENT;
|
||||
}
|
||||
|
||||
private void sendRestartEvent(HttpServletResponse response, Reason reason) {
|
||||
if ( restarting.compareAndSet(false, true) ) {
|
||||
LOG.info("trigger sendRestartEvent, because of {}", reason.getMessage());
|
||||
eventBus.post(new RestartEvent(RestartServlet.class, reason.getMessage()));
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_ACCEPTED);
|
||||
} else {
|
||||
LOG.warn("scm-manager restarts already");
|
||||
response.setStatus(HttpServletResponse.SC_CONFLICT);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Reason {
|
||||
|
||||
private String message;
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Remove cached resources from {@link ServletContext} to allow a clean restart of scm-manager without stale or
|
||||
* duplicated data.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
final class ServletContextCleaner {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ServletContextCleaner.class);
|
||||
|
||||
private static final Set<String> REMOVE_PREFIX = ImmutableSet.of(
|
||||
"org.jboss.resteasy",
|
||||
"resteasy",
|
||||
"org.apache.shiro",
|
||||
"sonia.scm"
|
||||
);
|
||||
|
||||
private ServletContextCleaner() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove cached attributes from {@link ServletContext}.
|
||||
*
|
||||
* @param servletContext servlet context
|
||||
*/
|
||||
static void cleanup(ServletContext servletContext) {
|
||||
LOG.info("remove cached attributes from context");
|
||||
|
||||
Enumeration<String> attributeNames = servletContext.getAttributeNames();
|
||||
while( attributeNames.hasMoreElements()) {
|
||||
String name = attributeNames.nextElement();
|
||||
if (shouldRemove(name)) {
|
||||
LOG.info("remove attribute {} from servlet context", name);
|
||||
servletContext.removeAttribute(name);
|
||||
} else {
|
||||
LOG.info("keep attribute {} in servlet context", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldRemove(String name) {
|
||||
for (String prefix : REMOVE_PREFIX) {
|
||||
if (name.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +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.filter;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.shiro.subject.Subject;
|
||||
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.security.Role;
|
||||
|
||||
/**
|
||||
* Security filter which allow only administrators to access the underlying
|
||||
* resources.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
// TODO before releasing v2, delete this filter (we use Permission objects now)
|
||||
@WebElement(
|
||||
value = Filters.PATTERN_CONFIG,
|
||||
morePatterns = {
|
||||
Filters.PATTERN_USERS,
|
||||
Filters.PATTERN_GROUPS,
|
||||
Filters.PATTERN_PLUGINS
|
||||
}
|
||||
)
|
||||
@Priority(Filters.PRIORITY_AUTHORIZATION + 1)
|
||||
public class AdminSecurityFilter extends SecurityFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param configuration scm-manager main configuration
|
||||
*/
|
||||
@Inject
|
||||
public AdminSecurityFilter(ScmConfiguration configuration)
|
||||
{
|
||||
super(configuration);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the subject has the admin role.
|
||||
*
|
||||
* @param subject subject
|
||||
*
|
||||
* @return {@code true} if the subject has the admin role
|
||||
*/
|
||||
@Override
|
||||
protected boolean hasPermission(Subject subject)
|
||||
{
|
||||
return subject.hasRole(Role.ADMIN);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ package sonia.scm.filter;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
@@ -42,6 +41,8 @@ import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.security.DefaultKeyGenerator;
|
||||
import sonia.scm.security.KeyGenerator;
|
||||
import sonia.scm.web.filter.HttpFilter;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
@@ -62,27 +63,26 @@ import sonia.scm.Priority;
|
||||
@WebElement(Filters.PATTERN_ALL)
|
||||
public class MDCFilter extends HttpFilter
|
||||
{
|
||||
private static final DefaultKeyGenerator TRANSACTION_KEY_GENERATOR = new DefaultKeyGenerator();
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final String MDC_CLIEN_HOST = "client_host";
|
||||
static final String MDC_CLIENT_HOST = "client_host";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final String MDC_CLIEN_IP = "client_ip";
|
||||
|
||||
/** url of the current request */
|
||||
static final String MDC_CLIENT_IP = "client_ip";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String MDC_REQUEST_URI = "request_uri";
|
||||
|
||||
/** request method */
|
||||
|
||||
@VisibleForTesting
|
||||
static final String MDC_REQUEST_METHOD = "request_method";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final String MDC_USERNAME = "username";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String MDC_TRANSACTION_ID = "transaction_id";
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -102,10 +102,11 @@ public class MDCFilter extends HttpFilter
|
||||
throws IOException, ServletException
|
||||
{
|
||||
MDC.put(MDC_USERNAME, getUsername());
|
||||
MDC.put(MDC_CLIEN_IP, request.getRemoteAddr());
|
||||
MDC.put(MDC_CLIEN_HOST, request.getRemoteHost());
|
||||
MDC.put(MDC_CLIENT_IP, request.getRemoteAddr());
|
||||
MDC.put(MDC_CLIENT_HOST, request.getRemoteHost());
|
||||
MDC.put(MDC_REQUEST_METHOD, request.getMethod());
|
||||
MDC.put(MDC_REQUEST_URI, request.getRequestURI());
|
||||
MDC.put(MDC_TRANSACTION_ID, TRANSACTION_KEY_GENERATOR.createKey());
|
||||
|
||||
try
|
||||
{
|
||||
@@ -114,10 +115,11 @@ public class MDCFilter extends HttpFilter
|
||||
finally
|
||||
{
|
||||
MDC.remove(MDC_USERNAME);
|
||||
MDC.remove(MDC_CLIEN_IP);
|
||||
MDC.remove(MDC_CLIEN_HOST);
|
||||
MDC.remove(MDC_CLIENT_IP);
|
||||
MDC.remove(MDC_CLIENT_HOST);
|
||||
MDC.remove(MDC_REQUEST_METHOD);
|
||||
MDC.remove(MDC_REQUEST_URI);
|
||||
MDC.remove(MDC_TRANSACTION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,8 @@ import org.apache.shiro.subject.Subject;
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.security.SecurityRequests;
|
||||
import sonia.scm.web.filter.HttpFilter;
|
||||
import sonia.scm.web.filter.SecurityHttpServletRequestWrapper;
|
||||
import sonia.scm.web.filter.PropagatePrincipleServletRequestWrapper;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
@@ -61,10 +60,7 @@ import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Priority(Filters.PRIORITY_AUTHORIZATION)
|
||||
// TODO find a better way for unprotected resources
|
||||
@WebElement(value = REST_API_PATH + "" +
|
||||
"/(?!v2/ui).*", regex = true)
|
||||
public class SecurityFilter extends HttpFilter
|
||||
public class PropagatePrincipleFilter extends HttpFilter
|
||||
{
|
||||
|
||||
/** name of request attribute for the primary principal */
|
||||
@@ -74,7 +70,7 @@ public class SecurityFilter extends HttpFilter
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
@Inject
|
||||
public SecurityFilter(ScmConfiguration configuration)
|
||||
public PropagatePrincipleFilter(ScmConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
@@ -84,31 +80,16 @@ public class SecurityFilter extends HttpFilter
|
||||
HttpServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
if (!SecurityRequests.isAuthenticationRequest(request) && !SecurityRequests.isIndexRequest(request))
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
if (hasPermission(subject))
|
||||
{
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
if (hasPermission(subject))
|
||||
{
|
||||
// add primary principal as request attribute
|
||||
// see https://goo.gl/JRjNmf
|
||||
String username = getUsername(subject);
|
||||
request.setAttribute(ATTRIBUTE_REMOTE_USER, username);
|
||||
// add primary principal as request attribute
|
||||
// see https://goo.gl/JRjNmf
|
||||
String username = getUsername(subject);
|
||||
request.setAttribute(ATTRIBUTE_REMOTE_USER, username);
|
||||
|
||||
// wrap servlet request to provide authentication informations
|
||||
chain.doFilter(new SecurityHttpServletRequestWrapper(request, username), response);
|
||||
}
|
||||
else if (subject.isAuthenticated() || subject.isRemembered())
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
else if (configuration.isAnonymousAccessEnabled())
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
// wrap servlet request to provide authentication information
|
||||
chain.doFilter(new PropagatePrincipleServletRequestWrapper(request, username), response);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -116,7 +97,7 @@ public class SecurityFilter extends HttpFilter
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasPermission(Subject subject)
|
||||
private boolean hasPermission(Subject subject)
|
||||
{
|
||||
return ((configuration != null)
|
||||
&& configuration.isAnonymousAccessEnabled()) || subject.isAuthenticated()
|
||||
@@ -139,5 +120,4 @@ public class SecurityFilter extends HttpFilter
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,7 +42,6 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.HandlerEventType;
|
||||
import sonia.scm.ManagerDaoAdapter;
|
||||
import sonia.scm.NotFoundException;
|
||||
@@ -106,7 +105,7 @@ public class DefaultGroupManager extends AbstractGroupManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group create(Group group) throws AlreadyExistsException {
|
||||
public Group create(Group group) {
|
||||
String type = group.getType();
|
||||
if (Util.isEmpty(type)) {
|
||||
group.setType(groupDAO.getType());
|
||||
@@ -172,7 +171,7 @@ public class DefaultGroupManager extends AbstractGroupManager
|
||||
|
||||
if (fresh == null)
|
||||
{
|
||||
throw new NotFoundException("group", group.getId());
|
||||
throw new NotFoundException(Group.class, group.getId());
|
||||
}
|
||||
|
||||
fresh.copyProperties(group);
|
||||
|
||||
@@ -41,6 +41,8 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.Stage;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import java.net.MalformedURLException;
|
||||
@@ -69,19 +71,21 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param servletContext
|
||||
* @param plugins
|
||||
*/
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext,
|
||||
Iterable<PluginWrapper> plugins)
|
||||
{
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<PluginWrapper> plugins) {
|
||||
this(servletContext, plugins, SCMContext.getContext().getStage());
|
||||
}
|
||||
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<PluginWrapper> plugins, Stage stage) {
|
||||
this.servletContext = servletContext;
|
||||
this.plugins = plugins;
|
||||
this.cache = CacheBuilder.newBuilder().build();
|
||||
this.cache = createCache(stage);
|
||||
}
|
||||
|
||||
private Cache<String, URL> createCache(Stage stage) {
|
||||
if (stage == Stage.DEVELOPMENT) {
|
||||
return CacheBuilder.newBuilder().maximumSize(0).build(); // Disable caching
|
||||
}
|
||||
return CacheBuilder.newBuilder().build();
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
@@ -97,7 +101,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
@Override
|
||||
public URL getResource(String path)
|
||||
{
|
||||
URL resource = cache.getIfPresent(path);
|
||||
URL resource = getFromCache(path);
|
||||
|
||||
if (resource == null)
|
||||
{
|
||||
@@ -105,7 +109,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
|
||||
if (resource != null)
|
||||
{
|
||||
cache.put(path, resource);
|
||||
addToCache(path, resource);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -116,6 +120,14 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
return resource;
|
||||
}
|
||||
|
||||
private URL getFromCache(String path) {
|
||||
return cache.getIfPresent(path);
|
||||
}
|
||||
|
||||
private void addToCache(String path, URL url) {
|
||||
cache.put(path, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -65,6 +65,9 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link RepositoryManager}.
|
||||
*
|
||||
@@ -122,11 +125,11 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository create(Repository repository) throws AlreadyExistsException {
|
||||
public Repository create(Repository repository) {
|
||||
return create(repository, true);
|
||||
}
|
||||
|
||||
public Repository create(Repository repository, boolean initRepository) throws AlreadyExistsException {
|
||||
public Repository create(Repository repository, boolean initRepository) {
|
||||
repository.setId(keyGenerator.createKey());
|
||||
repository.setNamespace(namespaceStrategy.createNamespace(repository));
|
||||
|
||||
@@ -140,7 +143,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
try {
|
||||
getHandler(newRepository).create(newRepository);
|
||||
} catch (AlreadyExistsException e) {
|
||||
throw new InternalRepositoryException("directory for repository does already exist", e);
|
||||
throw new InternalRepositoryException(repository, "directory for repository does already exist", e);
|
||||
}
|
||||
}
|
||||
fireEvent(HandlerEventType.BEFORE_CREATE, newRepository);
|
||||
@@ -170,7 +173,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importRepository(Repository repository) throws AlreadyExistsException {
|
||||
public void importRepository(Repository repository) {
|
||||
create(repository, false);
|
||||
}
|
||||
|
||||
@@ -198,7 +201,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(Repository repository) throws RepositoryNotFoundException {
|
||||
public void refresh(Repository repository) {
|
||||
AssertUtil.assertIsNotNull(repository);
|
||||
RepositoryPermissions.read(repository).check();
|
||||
|
||||
@@ -207,7 +210,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
if (fresh != null) {
|
||||
fresh.copyProperties(repository);
|
||||
} else {
|
||||
throw new RepositoryNotFoundException(repository);
|
||||
throw notFound(entity(repository));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,9 +353,9 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
RepositoryHandler handler = handlerMap.get(type);
|
||||
|
||||
if (handler == null) {
|
||||
throw new InternalRepositoryException("could not find handler for " + type);
|
||||
throw new InternalRepositoryException(entity(repository), "could not find handler for " + type);
|
||||
} else if (!handler.isConfigured()) {
|
||||
throw new InternalRepositoryException("handler is not configured for type " + type);
|
||||
throw new InternalRepositoryException(entity(repository), "handler is not configured for type " + type);
|
||||
}
|
||||
|
||||
return handler;
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Inject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.NotFoundException;
|
||||
|
||||
import java.util.Set;
|
||||
@@ -61,7 +60,7 @@ public final class HealthChecker {
|
||||
Repository repository = repositoryManager.get(id);
|
||||
|
||||
if (repository == null) {
|
||||
throw new RepositoryNotFoundException(id);
|
||||
throw new NotFoundException(Repository.class, id);
|
||||
}
|
||||
|
||||
doCheck(repository);
|
||||
|
||||
@@ -167,7 +167,7 @@ public class AuthorizationChangedEventProducer {
|
||||
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
|
||||
return repository.isArchived() != beforeModification.isArchived()
|
||||
|| repository.isPublicReadable() != beforeModification.isPublicReadable()
|
||||
|| ! repository.getPermissions().equals(beforeModification.getPermissions());
|
||||
|| !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
|
||||
}
|
||||
|
||||
private void fireEventForEveryUser() {
|
||||
|
||||
@@ -54,13 +54,14 @@ import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.group.GroupNames;
|
||||
import sonia.scm.group.GroupPermissions;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.Permission;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryDAO;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserPermissions;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -198,7 +199,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private void collectRepositoryPermissions(Builder<String> builder,
|
||||
Repository repository, User user, GroupNames groups)
|
||||
{
|
||||
List<sonia.scm.repository.Permission> repositoryPermissions
|
||||
Collection<Permission> repositoryPermissions
|
||||
= repository.getPermissions();
|
||||
|
||||
if (Util.isNotEmpty(repositoryPermissions))
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.ContainerRequestFilter;
|
||||
import javax.ws.rs.container.ResourceInfo;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@Provider
|
||||
public class SecurityRequestFilter implements ContainerRequestFilter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SecurityRequestFilter.class);
|
||||
|
||||
@Context
|
||||
private ResourceInfo resourceInfo;
|
||||
|
||||
@Override
|
||||
public void filter(ContainerRequestContext requestContext) {
|
||||
Method resourceMethod = resourceInfo.getResourceMethod();
|
||||
if (hasPermission() || anonymousAccessIsAllowed(resourceMethod)) {
|
||||
LOG.debug("allowed unauthenticated request to method {}", resourceMethod);
|
||||
// nothing further to do
|
||||
} else {
|
||||
LOG.debug("blocked unauthenticated request to method {}", resourceMethod);
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean anonymousAccessIsAllowed(Method method) {
|
||||
return method.isAnnotationPresent(AllowAnonymousAccess.class)
|
||||
|| method.getDeclaringClass().isAnnotationPresent(AllowAnonymousAccess.class);
|
||||
}
|
||||
|
||||
private boolean hasPermission() {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
return subject.isAuthenticated() || subject.isRemembered();
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ import com.google.inject.Singleton;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.EagerSingleton;
|
||||
import sonia.scm.HandlerEventType;
|
||||
import sonia.scm.ManagerDaoAdapter;
|
||||
@@ -137,7 +137,7 @@ public class DefaultUserManager extends AbstractUserManager
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public User create(User user) throws AlreadyExistsException {
|
||||
public User create(User user) {
|
||||
String type = user.getType();
|
||||
if (Util.isEmpty(type)) {
|
||||
user.setType(userDAO.getType());
|
||||
@@ -219,7 +219,7 @@ public class DefaultUserManager extends AbstractUserManager
|
||||
|
||||
if (fresh == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
throw new NotFoundException(User.class, user.getName());
|
||||
}
|
||||
|
||||
fresh.copyProperties(user);
|
||||
@@ -403,7 +403,7 @@ public class DefaultUserManager extends AbstractUserManager
|
||||
User user = get((String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal());
|
||||
|
||||
if (!user.getPassword().equals(oldPassword)) {
|
||||
throw new InvalidPasswordException();
|
||||
throw new InvalidPasswordException(ContextEntry.ContextBuilder.entity("passwordChange", "-").in(User.class, user.getName()));
|
||||
}
|
||||
|
||||
user.setPassword(newPassword);
|
||||
@@ -419,10 +419,10 @@ public class DefaultUserManager extends AbstractUserManager
|
||||
public void overwritePassword(String userId, String newPassword) {
|
||||
User user = get(userId);
|
||||
if (user == null) {
|
||||
throw new NotFoundException();
|
||||
throw new NotFoundException(User.class, userId);
|
||||
}
|
||||
if (!isTypeDefault(user)) {
|
||||
throw new ChangePasswordNotAllowedException(user.getType());
|
||||
throw new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("passwordChange", "-").in(User.class, user.getName()), user.getType());
|
||||
}
|
||||
user.setPassword(newPassword);
|
||||
this.modify(user);
|
||||
|
||||
@@ -30,6 +30,9 @@ import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
|
||||
/**
|
||||
* Collect the plugin translations.
|
||||
@@ -69,7 +72,7 @@ public class I18nServlet extends HttpServlet {
|
||||
createdFile.ifPresent(map -> createdJsonFileConsumer.accept(path, map));
|
||||
return createdFile.orElse(null);
|
||||
}
|
||||
)).orElseThrow(NotFoundException::new);
|
||||
)).orElseThrow(() -> notFound(entity("jsonprovider", path)));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -4,11 +4,11 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpStatus;
|
||||
import sonia.scm.NotFoundException;
|
||||
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;
|
||||
@@ -71,8 +71,8 @@ public class HttpProtocolServlet extends HttpServlet {
|
||||
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);
|
||||
} catch (NotFoundException e) {
|
||||
log.debug(e.getMessage());
|
||||
resp.setStatus(HttpStatus.SC_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
9
scm-webapp/src/main/resources/META-INF/validation.xml
Normal file
9
scm-webapp/src/main/resources/META-INF/validation.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<validation-config
|
||||
xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration"
|
||||
version="1.1">
|
||||
|
||||
<parameter-name-provider>org.hibernate.validator.parameternameprovider.ReflectionParameterNameProvider</parameter-name-provider>
|
||||
|
||||
</validation-config>
|
||||
@@ -46,7 +46,7 @@
|
||||
<!-- encoders are by default assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-10X{transaction_id}] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
@@ -65,9 +65,6 @@
|
||||
<logger name="sonia.scm.event.LegmanScmEventBus" level="DEBUG" />
|
||||
<logger name="sonia.scm.plugin.ext.DefaultAnnotationScanner" level="INFO" />
|
||||
<logger name="sonia.scm.security.ConfigurableLoginAttemptHandler" level="DEBUG" />
|
||||
|
||||
<!-- event bus -->
|
||||
<logger name="sonia.scm.event.LegmanScmEventBus" level="INFO" />
|
||||
|
||||
<!-- cgi -->
|
||||
<logger name="sonia.scm.web.cgi.DefaultCGIExecutor" level="DEBUG" />
|
||||
@@ -93,7 +90,9 @@
|
||||
<logger name="net.sf.ehcache" level="DEBUG" />
|
||||
-->
|
||||
|
||||
<logger name="org.jboss.resteasy" level="DEBUG" />
|
||||
<logger name="org.jboss.resteasy" level="INFO" />
|
||||
|
||||
<logger name="sonia.scm.boot.RestartServlet" level="TRACE" />
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT" />
|
||||
|
||||
@@ -61,14 +61,14 @@
|
||||
|
||||
<append>true</append>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-10X{transaction_id}] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-10X{transaction_id}] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
|
||||
</appender>
|
||||
|
||||
@@ -41,10 +41,6 @@
|
||||
|
||||
<!-- bootstraping -->
|
||||
|
||||
<listener>
|
||||
<listener-class>sonia.scm.boot.BootstrapContextListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<filter>
|
||||
<filter-name>BootstrapFilter</filter-name>
|
||||
<filter-class>sonia.scm.boot.BootstrapContextFilter</filter-class>
|
||||
@@ -55,25 +51,6 @@
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
<!-- rest -->
|
||||
|
||||
<context-param>
|
||||
<param-name>resteasy.servlet.mapping.prefix</param-name>
|
||||
<param-value>/api</param-value>
|
||||
</context-param>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Resteasy</servlet-name>
|
||||
<servlet-class>
|
||||
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Resteasy</servlet-name>
|
||||
<url-pattern>/api/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- capture sessions -->
|
||||
<!--
|
||||
TODO remove, we need no longer a session
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -45,6 +46,23 @@ public class TemplatingPushStateDispatcherTest {
|
||||
|
||||
@Test
|
||||
public void testDispatch() throws IOException {
|
||||
TemplatingPushStateDispatcher.IndexHtmlModel model = dispatch();
|
||||
assertEquals("/scm", model.getContextPath());
|
||||
assertNull(model.getLiveReloadURL());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispatchWithLiveReloadURL() throws IOException {
|
||||
System.setProperty("livereload.url", "/livereload.js");
|
||||
try {
|
||||
TemplatingPushStateDispatcher.IndexHtmlModel model = dispatch();
|
||||
assertEquals("/livereload.js", model.getLiveReloadURL());
|
||||
} finally {
|
||||
System.clearProperty("livereload.url");
|
||||
}
|
||||
}
|
||||
|
||||
private TemplatingPushStateDispatcher.IndexHtmlModel dispatch() throws IOException {
|
||||
when(request.getContextPath()).thenReturn("/scm");
|
||||
when(templateEngine.getTemplate(TemplatingPushStateDispatcher.TEMPLATE)).thenReturn(template);
|
||||
|
||||
@@ -59,8 +77,7 @@ public class TemplatingPushStateDispatcherTest {
|
||||
|
||||
verify(template).execute(any(Writer.class), captor.capture());
|
||||
|
||||
TemplatingPushStateDispatcher.IndexHtmlModel model = (TemplatingPushStateDispatcher.IndexHtmlModel) captor.getValue();
|
||||
assertEquals("/scm", model.getContextPath());
|
||||
return (TemplatingPushStateDispatcher.IndexHtmlModel) captor.getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.apache.shiro.util.ThreadContext;
|
||||
import org.apache.shiro.util.ThreadState;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.jboss.resteasy.core.Dispatcher;
|
||||
import org.jboss.resteasy.mock.MockDispatcherFactory;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.junit.After;
|
||||
@@ -19,7 +18,6 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
@@ -38,7 +36,6 @@ import java.util.List;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -50,7 +47,8 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
public static final String CHANGESET_PATH = "space/repo/changesets/";
|
||||
public static final String CHANGESET_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + CHANGESET_PATH;
|
||||
private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||
@@ -77,16 +75,14 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() throws Exception {
|
||||
public void prepareEnvironment() {
|
||||
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||
changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper);
|
||||
super.changesetRootResource = Providers.of(changesetRootResource);
|
||||
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||
dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
||||
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||
dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||
when(repositoryService.getLogCommand()).thenReturn(logCommandBuilder);
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
@@ -169,7 +165,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
when(logCommandBuilder.setStartChangeset(anyString())).thenReturn(logCommandBuilder);
|
||||
when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult);
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(CHANGESET_URL + "id")
|
||||
.get(CHANGESET_URL + id)
|
||||
.accept(VndMediaType.CHANGESET);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
@@ -8,9 +8,8 @@ import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.PathNotFoundException;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.api.CatCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
@@ -58,8 +57,8 @@ public class ContentResourceTest {
|
||||
when(catCommand.setRevision(REV)).thenReturn(catCommand);
|
||||
|
||||
// defaults for unknown things
|
||||
doThrow(new RepositoryNotFoundException("x")).when(repositoryServiceFactory).create(not(eq(existingNamespaceAndName)));
|
||||
doThrow(new PathNotFoundException("x")).when(catCommand).getStream(any());
|
||||
doThrow(new NotFoundException("Test", "r")).when(repositoryServiceFactory).create(not(eq(existingNamespaceAndName)));
|
||||
doThrow(new NotFoundException("Test", "X")).when(catCommand).getStream(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -93,7 +92,7 @@ public class ContentResourceTest {
|
||||
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "SomeGoCode.go");
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
assertEquals("GO", response.getHeaderString("Language"));
|
||||
assertEquals("GO", response.getHeaderString("X-Programming-Language"));
|
||||
assertEquals("text/x-go", response.getHeaderString("Content-Type"));
|
||||
}
|
||||
|
||||
@@ -104,7 +103,7 @@ public class ContentResourceTest {
|
||||
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "Dockerfile");
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
assertEquals("DOCKERFILE", response.getHeaderString("Language"));
|
||||
assertEquals("DOCKERFILE", response.getHeaderString("X-Programming-Language"));
|
||||
assertEquals("text/plain", response.getHeaderString("Content-Type"));
|
||||
}
|
||||
|
||||
@@ -175,7 +174,7 @@ public class ContentResourceTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
public void close() {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,20 +17,23 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||
import sonia.scm.api.v2.NotFoundExceptionMapper;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
import sonia.scm.repository.api.DiffCommandBuilder;
|
||||
import sonia.scm.repository.api.DiffFormat;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -62,14 +65,15 @@ public class DiffResourceTest extends RepositoryTestBase {
|
||||
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() throws Exception {
|
||||
public void prepareEnvironment() {
|
||||
diffRootResource = new DiffRootResource(serviceFactory);
|
||||
super.diffRootResource = Providers.of(diffRootResource);
|
||||
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||
dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class);
|
||||
ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl();
|
||||
dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper));
|
||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class);
|
||||
when(service.getDiffCommand()).thenReturn(diffCommandBuilder);
|
||||
@@ -86,19 +90,17 @@ public class DiffResourceTest extends RepositoryTestBase {
|
||||
@Test
|
||||
public void shouldGetDiffs() throws Exception {
|
||||
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.retriveContent(any())).thenReturn(diffCommandBuilder);
|
||||
|
||||
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.retrieveContent(any())).thenReturn(diffCommandBuilder);
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(DIFF_URL + "revision")
|
||||
.accept(VndMediaType.DIFF);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
assertEquals(200, response.getStatus());
|
||||
log.info("Response :{}", response.getContentAsString());
|
||||
|
||||
assertThat(response.getStatus())
|
||||
.isEqualTo(200);
|
||||
assertThat(response.getContentAsString())
|
||||
.isNotNull();
|
||||
String expectedHeader = "Content-Disposition";
|
||||
String expectedValue = "attachment; filename=\"repo-revision.diff\"; filename*=utf-8''repo-revision.diff";
|
||||
assertThat(response.getOutputHeaders().containsKey(expectedHeader)).isTrue();
|
||||
@@ -107,8 +109,8 @@ public class DiffResourceTest extends RepositoryTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGet404OnMissingRepository() throws URISyntaxException, RepositoryNotFoundException {
|
||||
when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(RepositoryNotFoundException.class);
|
||||
public void shouldGet404OnMissingRepository() throws URISyntaxException {
|
||||
when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(new NotFoundException("Text", "x"));
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(DIFF_URL + "revision")
|
||||
.accept(VndMediaType.DIFF);
|
||||
@@ -120,20 +122,24 @@ public class DiffResourceTest extends RepositoryTestBase {
|
||||
@Test
|
||||
public void shouldGet404OnMissingRevision() throws Exception {
|
||||
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class);
|
||||
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.retrieveContent(any())).thenThrow(new NotFoundException("Text", "x"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(DIFF_URL + "revision")
|
||||
.accept(VndMediaType.DIFF);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(404, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGet400OnCrlfInjection() throws Exception {
|
||||
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class);
|
||||
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.retrieveContent(any())).thenThrow(new NotFoundException("Text", "x"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(DIFF_URL + "ny%0D%0ASet-cookie:%20Tamper=3079675143472450634")
|
||||
@@ -143,6 +149,47 @@ public class DiffResourceTest extends RepositoryTestBase {
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGet400OnUnknownFormat() throws Exception {
|
||||
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.retrieveContent(any())).thenThrow(new NotFoundException("Test", "test"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(DIFF_URL + "revision?format=Unknown")
|
||||
.accept(VndMediaType.DIFF);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAcceptDiffFormats() throws Exception {
|
||||
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
|
||||
when(diffCommandBuilder.retrieveContent(any())).thenReturn(diffCommandBuilder);
|
||||
|
||||
Arrays.stream(DiffFormat.values()).map(DiffFormat::name).forEach(
|
||||
this::assertRequestOk
|
||||
);
|
||||
}
|
||||
|
||||
private void assertRequestOk(String format) {
|
||||
MockHttpRequest request = null;
|
||||
try {
|
||||
request = MockHttpRequest
|
||||
.get(DIFF_URL + "revision?format=" + format)
|
||||
.accept(VndMediaType.DIFF);
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
fail("got exception: " + e);
|
||||
}
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertThat(response.getStatus())
|
||||
.withFailMessage("diff format from DiffFormat enum must be accepted: " + format)
|
||||
.isEqualTo(200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,20 +5,22 @@ import org.jboss.resteasy.mock.MockDispatcherFactory;
|
||||
import sonia.scm.api.rest.AlreadyExistsExceptionMapper;
|
||||
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||
import sonia.scm.api.rest.ConcurrentModificationExceptionMapper;
|
||||
import sonia.scm.api.rest.IllegalArgumentExceptionMapper;
|
||||
import sonia.scm.api.v2.NotFoundExceptionMapper;
|
||||
import sonia.scm.api.v2.NotSupportedFeatureExceptionMapper;
|
||||
|
||||
public class DispatcherMock {
|
||||
public static Dispatcher createDispatcher(Object resource) {
|
||||
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
dispatcher.getRegistry().addSingletonResource(resource);
|
||||
dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(AlreadyExistsExceptionMapper.class);
|
||||
ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl();
|
||||
dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper));
|
||||
dispatcher.getProviderFactory().register(new AlreadyExistsExceptionMapper(mapper));
|
||||
dispatcher.getProviderFactory().register(new ConcurrentModificationExceptionMapper(mapper));
|
||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(ConcurrentModificationExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(ChangePasswordNotAllowedExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(InvalidPasswordExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(IllegalArgumentExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().register(new InternalRepositoryExceptionMapper(mapper));
|
||||
dispatcher.getProviderFactory().register(new ChangePasswordNotAllowedExceptionMapper(mapper));
|
||||
dispatcher.getProviderFactory().register(new InvalidPasswordExceptionMapper(mapper));
|
||||
dispatcher.getProviderFactory().register(new NotSupportedFeatureExceptionMapper(mapper));
|
||||
return dispatcher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.apache.shiro.util.ThreadContext;
|
||||
import org.apache.shiro.util.ThreadState;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.jboss.resteasy.core.Dispatcher;
|
||||
import org.jboss.resteasy.mock.MockDispatcherFactory;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.junit.After;
|
||||
@@ -18,15 +17,14 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
import sonia.scm.repository.api.LogCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
@@ -52,7 +50,6 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
|
||||
public static final String FILE_HISTORY_PATH = "space/repo/history/";
|
||||
public static final String FILE_HISTORY_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + FILE_HISTORY_PATH;
|
||||
private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||
@@ -73,23 +70,21 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
|
||||
private FileHistoryRootResource fileHistoryRootResource;
|
||||
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() throws Exception {
|
||||
public void prepareEnvironment() {
|
||||
fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||
fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper);
|
||||
super.fileHistoryRootResource = Providers.of(fileHistoryRootResource);
|
||||
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||
dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||
dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class);
|
||||
ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl();
|
||||
when(service.getLogCommand()).thenReturn(logCommandBuilder);
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
@@ -133,8 +128,8 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldGet404OnMissingRepository() throws URISyntaxException, RepositoryNotFoundException {
|
||||
when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(RepositoryNotFoundException.class);
|
||||
public void shouldGet404OnMissingRepository() throws URISyntaxException {
|
||||
when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(new NotFoundException("Text", "x"));
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(FILE_HISTORY_URL + "revision/a.txt")
|
||||
.accept(VndMediaType.CHANGESET_COLLECTION);
|
||||
@@ -152,7 +147,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder);
|
||||
when(logCommandBuilder.setStartChangeset(eq(id))).thenReturn(logCommandBuilder);
|
||||
when(logCommandBuilder.setPath(eq(path))).thenReturn(logCommandBuilder);
|
||||
when(logCommandBuilder.getChangesets()).thenThrow(RevisionNotFoundException.class);
|
||||
when(logCommandBuilder.getChangesets()).thenThrow(new NotFoundException("Text", "x"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(FILE_HISTORY_URL + id + "/" + path)
|
||||
@@ -171,7 +166,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder);
|
||||
when(logCommandBuilder.setStartChangeset(eq(id))).thenReturn(logCommandBuilder);
|
||||
when(logCommandBuilder.setPath(eq(path))).thenReturn(logCommandBuilder);
|
||||
when(logCommandBuilder.getChangesets()).thenThrow(InternalRepositoryException.class);
|
||||
when(logCommandBuilder.getChangesets()).thenThrow(new InternalRepositoryException(ContextEntry.ContextBuilder.noContext(), "", new RuntimeException()));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(FILE_HISTORY_URL + id + "/" + path)
|
||||
@@ -182,7 +177,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGet500OnNullChangesets() throws Exception {
|
||||
public void shouldGet404OnNullChangesets() throws Exception {
|
||||
String id = "revision_123";
|
||||
String path = "root_dir/sub_dir/file-to-inspect.txt";
|
||||
|
||||
@@ -197,6 +192,6 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
.accept(VndMediaType.CHANGESET_COLLECTION);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
assertEquals(500, response.getStatus());
|
||||
assertEquals(404, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class GroupRootResourceTest {
|
||||
private ArgumentCaptor<Group> groupCaptor = ArgumentCaptor.forClass(Group.class);
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() throws Exception {
|
||||
public void prepareEnvironment() {
|
||||
initMocks(this);
|
||||
when(groupManager.create(groupCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
|
||||
doNothing().when(groupManager).modify(groupCaptor.capture());
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.user.InvalidPasswordException;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
@@ -168,7 +169,8 @@ public class MeResourceTest {
|
||||
.content(content.getBytes());
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
doThrow(InvalidPasswordException.class).when(userManager).changePasswordForLoggedInUser(any(), any());
|
||||
doThrow(new InvalidPasswordException(ContextEntry.ContextBuilder.entity("passwortChange", "-")))
|
||||
.when(userManager).changePasswordForLoggedInUser(any(), any());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.apache.shiro.util.ThreadContext;
|
||||
import org.apache.shiro.util.ThreadState;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.jboss.resteasy.core.Dispatcher;
|
||||
import org.jboss.resteasy.mock.MockDispatcherFactory;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.junit.After;
|
||||
@@ -18,7 +17,6 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
@@ -37,6 +35,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.noContext;
|
||||
|
||||
@Slf4j
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
@@ -45,7 +44,8 @@ public class ModificationsResourceTest extends RepositoryTestBase {
|
||||
|
||||
public static final String MODIFICATIONS_PATH = "space/repo/modifications/";
|
||||
public static final String MODIFICATIONS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + MODIFICATIONS_PATH;
|
||||
private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||
@@ -70,16 +70,13 @@ public class ModificationsResourceTest extends RepositoryTestBase {
|
||||
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() throws Exception {
|
||||
public void prepareEnvironment() {
|
||||
modificationsRootResource = new ModificationsRootResource(serviceFactory, modificationsToDtoMapper);
|
||||
super.modificationsRootResource = Providers.of(modificationsRootResource);
|
||||
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||
dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
||||
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||
dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class);
|
||||
when(repositoryService.getModificationsCommand()).thenReturn(modificationsCommandBuilder);
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
@@ -107,7 +104,7 @@ public class ModificationsResourceTest extends RepositoryTestBase {
|
||||
@Test
|
||||
public void shouldGet500OnModificationsCommandError() throws Exception {
|
||||
when(modificationsCommandBuilder.revision(any())).thenReturn(modificationsCommandBuilder);
|
||||
when(modificationsCommandBuilder.getModifications()).thenThrow(InternalRepositoryException.class);
|
||||
when(modificationsCommandBuilder.getModifications()).thenThrow(new InternalRepositoryException(noContext(), "", new RuntimeException()));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get(MODIFICATIONS_URL + "revision")
|
||||
|
||||
@@ -164,10 +164,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
||||
@TestFactory
|
||||
@DisplayName("test endpoints on missing permissions and user is Admin")
|
||||
Stream<DynamicTest> missedPermissionTestFactory() {
|
||||
Repository mockRepository = mock(Repository.class);
|
||||
when(mockRepository.getId()).thenReturn(REPOSITORY_NAME);
|
||||
when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE);
|
||||
when(mockRepository.getName()).thenReturn(REPOSITORY_NAME);
|
||||
Repository mockRepository = new Repository(REPOSITORY_NAME, "git", REPOSITORY_NAMESPACE, REPOSITORY_NAME);
|
||||
when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository);
|
||||
return createDynamicTestsToAssertResponses(
|
||||
requestGETPermission.expectedResponseStatus(404),
|
||||
@@ -180,10 +177,6 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
||||
@TestFactory
|
||||
@DisplayName("test endpoints on missing permissions and user is not Admin")
|
||||
Stream<DynamicTest> missedPermissionUserForbiddenTestFactory() {
|
||||
Repository mockRepository = mock(Repository.class);
|
||||
when(mockRepository.getId()).thenReturn(REPOSITORY_NAME);
|
||||
when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE);
|
||||
when(mockRepository.getName()).thenReturn(REPOSITORY_NAME);
|
||||
doThrow(AuthorizationException.class).when(repositoryManager).get(any(NamespaceAndName.class));
|
||||
return createDynamicTestsToAssertResponses(
|
||||
requestGETPermission.expectedResponseStatus(403),
|
||||
@@ -409,17 +402,17 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
||||
}
|
||||
|
||||
private Repository createUserWithRepository(String userPermission) {
|
||||
Repository mockRepository = mock(Repository.class);
|
||||
when(mockRepository.getId()).thenReturn(REPOSITORY_NAME);
|
||||
when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE);
|
||||
when(mockRepository.getName()).thenReturn(REPOSITORY_NAME);
|
||||
Repository mockRepository = new Repository();
|
||||
mockRepository.setId(REPOSITORY_NAME);
|
||||
mockRepository.setNamespace(REPOSITORY_NAMESPACE);
|
||||
mockRepository.setName(REPOSITORY_NAME);
|
||||
when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository);
|
||||
when(subject.isPermitted(userPermission != null ? eq(userPermission) : any(String.class))).thenReturn(true);
|
||||
return mockRepository;
|
||||
}
|
||||
|
||||
private void createUserWithRepositoryAndPermissions(ArrayList<Permission> permissions, String userPermission) {
|
||||
when(createUserWithRepository(userPermission).getPermissions()).thenReturn(permissions);
|
||||
createUserWithRepository(userPermission).setPermissions(permissions);
|
||||
}
|
||||
|
||||
private Stream<DynamicTest> createDynamicTestsToAssertResponses(ExpectedRequest... expectedRequests) {
|
||||
@@ -427,10 +420,9 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
||||
.map(entry -> dynamicTest("the endpoint " + entry.description + " should return the status code " + entry.expectedResponseStatus, () -> assertExpectedRequest(entry)));
|
||||
}
|
||||
|
||||
private MockHttpResponse assertExpectedRequest(ExpectedRequest entry) throws URISyntaxException {
|
||||
private void assertExpectedRequest(ExpectedRequest entry) throws URISyntaxException {
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
HttpRequest request = null;
|
||||
request = MockHttpRequest
|
||||
HttpRequest request = MockHttpRequest
|
||||
.create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path)
|
||||
.content(entry.content)
|
||||
.contentType(VndMediaType.PERMISSION);
|
||||
@@ -442,7 +434,6 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
||||
if (entry.responseValidator != null) {
|
||||
entry.responseValidator.accept(response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@ToString
|
||||
@@ -476,12 +467,12 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedRequest expectedResponseStatus(int expectedResponseStatus) {
|
||||
ExpectedRequest expectedResponseStatus(int expectedResponseStatus) {
|
||||
this.expectedResponseStatus = expectedResponseStatus;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedRequest responseValidator(Consumer<MockHttpResponse> responseValidator) {
|
||||
ExpectedRequest responseValidator(Consumer<MockHttpResponse> responseValidator) {
|
||||
this.responseValidator = responseValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.inject.util.Providers;
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.jboss.resteasy.core.Dispatcher;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
@@ -22,6 +25,7 @@ import sonia.scm.repository.RepositoryIsNotArchivedException;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -37,6 +41,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -59,6 +64,8 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
||||
)
|
||||
public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
private static final String REALM = "AdminRealm";
|
||||
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
@Rule
|
||||
@@ -96,6 +103,13 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
||||
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
|
||||
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
|
||||
trillian.add(new User("trillian"), REALM);
|
||||
shiro.setSubject(
|
||||
new Subject.Builder()
|
||||
.principals(trillian)
|
||||
.authenticated(true)
|
||||
.buildSubject());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -257,6 +271,34 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||
verify(repositoryManager).create(any(Repository.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetCurrentUserAsOwner() throws Exception {
|
||||
ArgumentCaptor<Repository> createCaptor = ArgumentCaptor.forClass(Repository.class);
|
||||
when(repositoryManager.create(createCaptor.capture())).thenAnswer(invocation -> {
|
||||
Repository repository = (Repository) invocation.getArguments()[0];
|
||||
repository.setNamespace("otherspace");
|
||||
return repository;
|
||||
});
|
||||
|
||||
URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json");
|
||||
byte[] repositoryJson = Resources.toByteArray(url);
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
|
||||
.contentType(VndMediaType.REPOSITORY)
|
||||
.content(repositoryJson);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
Assertions.assertThat(createCaptor.getValue().getPermissions())
|
||||
.hasSize(1)
|
||||
.allSatisfy(p -> {
|
||||
assertThat(p.getName()).isEqualTo("trillian");
|
||||
assertThat(p.getType()).isEqualTo(PermissionType.OWNER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotOverwriteExistingPermissionsOnUpdate() throws Exception {
|
||||
Repository existingRepository = mockRepository("space", "repo");
|
||||
|
||||
@@ -14,7 +14,6 @@ import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.BrowserResult;
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.api.BrowseCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
@@ -60,7 +59,7 @@ public class SourceRootResourceTest extends RepositoryTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnSources() throws URISyntaxException, IOException, NotFoundException {
|
||||
public void shouldReturnSources() throws URISyntaxException, IOException {
|
||||
BrowserResult result = createBrowserResult();
|
||||
when(browseCommandBuilder.getBrowserResult()).thenReturn(result);
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources");
|
||||
@@ -74,8 +73,8 @@ public class SourceRootResourceTest extends RepositoryTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturn404IfRepoNotFound() throws URISyntaxException, RepositoryNotFoundException {
|
||||
when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(RepositoryNotFoundException.class);
|
||||
public void shouldReturn404IfRepoNotFound() throws URISyntaxException {
|
||||
when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(new NotFoundException("Test", "a"));
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "idont/exist/sources");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
@@ -84,7 +83,7 @@ public class SourceRootResourceTest extends RepositoryTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetResultForSingleFile() throws URISyntaxException, IOException, NotFoundException {
|
||||
public void shouldGetResultForSingleFile() throws URISyntaxException, IOException {
|
||||
FileObject fileObject = new FileObject();
|
||||
fileObject.setName("File Object!");
|
||||
fileObject.setPath("/");
|
||||
@@ -100,8 +99,8 @@ public class SourceRootResourceTest extends RepositoryTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGet404ForSingleFileIfRepoNotFound() throws URISyntaxException, RepositoryNotFoundException {
|
||||
when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(RepositoryNotFoundException.class);
|
||||
public void shouldGet404ForSingleFileIfRepoNotFound() throws URISyntaxException {
|
||||
when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(new NotFoundException("Test", "a"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "idont/exist/sources/revision/fileabc");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||
import sonia.scm.api.v2.NotFoundExceptionMapper;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.Tag;
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.PageResult;
|
||||
import sonia.scm.user.ChangePasswordNotAllowedException;
|
||||
@@ -102,7 +103,7 @@ public class UserRootResourceTest {
|
||||
@Test
|
||||
public void shouldGet400OnCreatingNewUserWithNotAllowedCharacters() throws URISyntaxException {
|
||||
// the @ character at the begin of the name is not allowed
|
||||
String userJson = "{ \"name\": \"@user\", \"type\": \"db\" }";
|
||||
String userJson = "{ \"name\": \"@user\",\"active\": true,\"admin\": false,\"displayName\": \"someone\",\"mail\": \"x@example.com\",\"type\": \"db\" }";
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + UserRootResource.USERS_PATH_V2)
|
||||
.contentType(VndMediaType.USER)
|
||||
@@ -114,7 +115,7 @@ public class UserRootResourceTest {
|
||||
assertEquals(400, response.getStatus());
|
||||
|
||||
// the whitespace at the begin opf the name is not allowed
|
||||
userJson = "{ \"name\": \" user\", \"type\": \"db\" }";
|
||||
userJson = "{ \"name\": \" user\",\"active\": true,\"admin\": false,\"displayName\": \"someone\",\"mail\": \"x@example.com\",\"type\": \"db\" }";
|
||||
request = MockHttpRequest
|
||||
.post("/" + UserRootResource.USERS_PATH_V2)
|
||||
.contentType(VndMediaType.USER)
|
||||
@@ -167,7 +168,7 @@ public class UserRootResourceTest {
|
||||
.content(content.getBytes());
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
doThrow(ChangePasswordNotAllowedException.class).when(userManager).overwritePassword(any(), any());
|
||||
doThrow(new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("passwordChange", "-"), "xml")).when(userManager).overwritePassword(any(), any());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
@@ -185,7 +186,7 @@ public class UserRootResourceTest {
|
||||
.content(content.getBytes());
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
doThrow(NotFoundException.class).when(userManager).overwritePassword(any(), any());
|
||||
doThrow(new NotFoundException("Test", "x")).when(userManager).overwritePassword(any(), any());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
|
||||
133
scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java
Normal file
133
scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java
Normal file
@@ -0,0 +1,133 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.github.legman.Subscribe;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.Stage;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.event.ScmTestEventBus;
|
||||
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RestartServletTest {
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
|
||||
private RestartServlet restartServlet;
|
||||
|
||||
private EventListener listener;
|
||||
|
||||
private void setUpObjectUnderTest(Stage stage) {
|
||||
listener = new EventListener();
|
||||
ScmEventBus eventBus = ScmTestEventBus.getInstance();
|
||||
eventBus.register(listener);
|
||||
|
||||
restartServlet = new RestartServlet(eventBus, stage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestart() throws IOException {
|
||||
setUpObjectUnderTest(Stage.DEVELOPMENT);
|
||||
setRequestInputReason("something changed");
|
||||
|
||||
restartServlet.doPost(request, response);
|
||||
|
||||
verify(response).setStatus(HttpServletResponse.SC_ACCEPTED);
|
||||
|
||||
RestartEvent restartEvent = listener.restartEvent;
|
||||
assertThat(restartEvent).isNotNull();
|
||||
assertThat(restartEvent.getCause()).isEqualTo(RestartServlet.class);
|
||||
assertThat(restartEvent.getReason()).isEqualTo("something changed");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestartCalledTwice() throws IOException {
|
||||
setUpObjectUnderTest(Stage.DEVELOPMENT);
|
||||
|
||||
setRequestInputReason("initial change");
|
||||
restartServlet.doPost(request, response);
|
||||
verify(response).setStatus(HttpServletResponse.SC_ACCEPTED);
|
||||
|
||||
setRequestInputReason("changed again");
|
||||
restartServlet.doPost(request, response);
|
||||
verify(response).setStatus(HttpServletResponse.SC_CONFLICT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestartWithInvalidContent() throws IOException {
|
||||
setUpObjectUnderTest(Stage.DEVELOPMENT);
|
||||
|
||||
setRequestInputContent("invalid json");
|
||||
|
||||
restartServlet.doPost(request, response);
|
||||
|
||||
verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestartInProductionStage() throws IOException {
|
||||
setUpObjectUnderTest(Stage.PRODUCTION);
|
||||
|
||||
setRequestInputReason("initial change");
|
||||
|
||||
restartServlet.doPost(request, response);
|
||||
|
||||
verify(response).setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
|
||||
}
|
||||
|
||||
private void setRequestInputReason(String message) throws IOException {
|
||||
String content = createReason(message);
|
||||
setRequestInputContent(content);
|
||||
}
|
||||
|
||||
private void setRequestInputContent(String content) throws IOException {
|
||||
InputStream input = createReasonAsInputStream(content);
|
||||
when(request.getInputStream()).thenReturn(createServletInputStream(input));
|
||||
}
|
||||
|
||||
private ServletInputStream createServletInputStream(final InputStream inputStream) {
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return inputStream.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private InputStream createReasonAsInputStream(String content) {
|
||||
return new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
private String createReason(String message) {
|
||||
return String.format("{\"message\": \"%s\"}", message);
|
||||
}
|
||||
|
||||
public static class EventListener {
|
||||
|
||||
private RestartEvent restartEvent;
|
||||
|
||||
@Subscribe(async = false)
|
||||
public void store(RestartEvent restartEvent) {
|
||||
this.restartEvent = restartEvent;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ServletContextCleanerTest {
|
||||
|
||||
@Mock
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Test
|
||||
public void testCleanup() {
|
||||
Set<String> names = ImmutableSet.of(
|
||||
"org.jboss.resteasy.Dispatcher",
|
||||
"resteasy.Deployment",
|
||||
"sonia.scm.Context",
|
||||
"org.eclipse.jetty.HttpServer",
|
||||
"javax.servlet.Context",
|
||||
"org.apache.shiro.SecurityManager"
|
||||
);
|
||||
|
||||
when(servletContext.getAttributeNames()).thenReturn(toEnumeration(names));
|
||||
|
||||
ServletContextCleaner.cleanup(servletContext);
|
||||
|
||||
verify(servletContext).removeAttribute("org.jboss.resteasy.Dispatcher");
|
||||
verify(servletContext).removeAttribute("resteasy.Deployment");
|
||||
verify(servletContext).removeAttribute("sonia.scm.Context");
|
||||
verify(servletContext, never()).removeAttribute("org.eclipse.jetty.HttpServer");
|
||||
verify(servletContext, never()).removeAttribute("javax.servlet.Context");
|
||||
verify(servletContext).removeAttribute("org.apache.shiro.SecurityManager");
|
||||
}
|
||||
|
||||
private <T> Enumeration<T> toEnumeration(Collection<T> collection) {
|
||||
return new Vector<>(collection).elements();
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2014, 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.filter;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AdminSecurityFilter}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
|
||||
public class AdminSecurityFilterTest {
|
||||
|
||||
private AdminSecurityFilter securityFilter;
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
/**
|
||||
* Prepare object under test and mocks.
|
||||
*/
|
||||
@Before
|
||||
public void setUp(){
|
||||
this.securityFilter = new AdminSecurityFilter(new ScmConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AdminSecurityFilter#hasPermission(org.apache.shiro.subject.Subject)} as administrator.
|
||||
*/
|
||||
@Test
|
||||
@SubjectAware(username = "dent", password = "secret")
|
||||
public void testHasPermissionAsAdministrator() {
|
||||
assertTrue(securityFilter.hasPermission(SecurityUtils.getSubject()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AdminSecurityFilter#hasPermission(org.apache.shiro.subject.Subject)} as user.
|
||||
*/
|
||||
@Test
|
||||
@SubjectAware(username = "trillian", password = "secret")
|
||||
public void testHasPermissionAsUser() {
|
||||
assertFalse(securityFilter.hasPermission(SecurityUtils.getSubject()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -98,9 +98,10 @@ public class MDCFilterTest extends AbstractTestBase {
|
||||
assertNotNull(chain.ctx);
|
||||
assertEquals("trillian", chain.ctx.get(MDCFilter.MDC_USERNAME));
|
||||
assertEquals("api/v1/repositories", chain.ctx.get(MDCFilter.MDC_REQUEST_URI));
|
||||
assertEquals("127.0.0.1", chain.ctx.get(MDCFilter.MDC_CLIEN_IP));
|
||||
assertEquals("localhost", chain.ctx.get(MDCFilter.MDC_CLIEN_HOST));
|
||||
assertEquals("127.0.0.1", chain.ctx.get(MDCFilter.MDC_CLIENT_IP));
|
||||
assertEquals("localhost", chain.ctx.get(MDCFilter.MDC_CLIENT_HOST));
|
||||
assertEquals("GET", chain.ctx.get(MDCFilter.MDC_REQUEST_METHOD));
|
||||
assertNotNull(chain.ctx.get(MDCFilter.MDC_TRANSACTION_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,38 +33,38 @@ package sonia.scm.filter;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import java.io.IOException;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import static org.mockito.Mockito.*;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserTestData;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SecurityFilter}.
|
||||
* Unit tests for {@link PropagatePrincipleFilter}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
|
||||
public class SecurityFilterTest {
|
||||
public class PropagatePrincipleFilterTest {
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
@@ -83,7 +83,7 @@ public class SecurityFilterTest {
|
||||
|
||||
private ScmConfiguration configuration;
|
||||
|
||||
private SecurityFilter securityFilter;
|
||||
private PropagatePrincipleFilter propagatePrincipleFilter;
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
@@ -94,38 +94,7 @@ public class SecurityFilterTest {
|
||||
@Before
|
||||
public void setUp(){
|
||||
this.configuration = new ScmConfiguration();
|
||||
this.securityFilter = new SecurityFilter(configuration);
|
||||
|
||||
when(request.getContextPath()).thenReturn("/scm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests filter on authentication endpoint v1.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Test
|
||||
public void testDoOnAuthenticationUrlV1() throws IOException, ServletException {
|
||||
checkIfAuthenticationUrlIsPassedThrough("/scm/api/auth/access_token");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests filter on authentication endpoint v2.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Test
|
||||
public void testDoOnAuthenticationUrlV2() throws IOException, ServletException {
|
||||
checkIfAuthenticationUrlIsPassedThrough("/scm/api/v2/auth/access_token");
|
||||
}
|
||||
|
||||
private void checkIfAuthenticationUrlIsPassedThrough(String uri) throws IOException, ServletException {
|
||||
when(request.getRequestURI()).thenReturn(uri);
|
||||
securityFilter.doFilter(request, response, chain);
|
||||
verify(request, never()).setAttribute(Mockito.anyString(), Mockito.any());
|
||||
verify(chain).doFilter(request, response);
|
||||
this.propagatePrincipleFilter = new PropagatePrincipleFilter(configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,8 +105,7 @@ public class SecurityFilterTest {
|
||||
*/
|
||||
@Test
|
||||
public void testAnonymous() throws IOException, ServletException {
|
||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
||||
securityFilter.doFilter(request, response, chain);
|
||||
propagatePrincipleFilter.doFilter(request, response, chain);
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
@@ -149,14 +117,13 @@ public class SecurityFilterTest {
|
||||
*/
|
||||
@Test
|
||||
public void testAnonymousWithAccessEnabled() throws IOException, ServletException {
|
||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
||||
configuration.setAnonymousAccessEnabled(true);
|
||||
|
||||
// execute
|
||||
securityFilter.doFilter(request, response, chain);
|
||||
propagatePrincipleFilter.doFilter(request, response, chain);
|
||||
|
||||
// verify and capture
|
||||
verify(request).setAttribute(SecurityFilter.ATTRIBUTE_REMOTE_USER, SCMContext.USER_ANONYMOUS);
|
||||
verify(request).setAttribute(PropagatePrincipleFilter.ATTRIBUTE_REMOTE_USER, SCMContext.USER_ANONYMOUS);
|
||||
verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture());
|
||||
|
||||
// assert
|
||||
@@ -173,13 +140,12 @@ public class SecurityFilterTest {
|
||||
@Test
|
||||
public void testAuthenticated() throws IOException, ServletException {
|
||||
authenticateUser(UserTestData.createTrillian());
|
||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
||||
|
||||
|
||||
// execute
|
||||
securityFilter.doFilter(request, response, chain);
|
||||
propagatePrincipleFilter.doFilter(request, response, chain);
|
||||
|
||||
// verify and capture
|
||||
verify(request).setAttribute(SecurityFilter.ATTRIBUTE_REMOTE_USER, "trillian");
|
||||
verify(request).setAttribute(PropagatePrincipleFilter.ATTRIBUTE_REMOTE_USER, "trillian");
|
||||
verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture());
|
||||
|
||||
// assert
|
||||
@@ -187,42 +153,6 @@ public class SecurityFilterTest {
|
||||
assertEquals("trillian", captured.getRemoteUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests filter without permissions.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Test
|
||||
public void testForbidden() throws IOException, ServletException {
|
||||
authenticateUser(UserTestData.createTrillian());
|
||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
||||
|
||||
// execute
|
||||
securityFilter = new AccessForbiddenSecurityFilter(configuration);
|
||||
securityFilter.doFilter(request, response, chain);
|
||||
|
||||
// assert
|
||||
verify(response).sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests filter unauthenticated and without permissions.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Test
|
||||
public void testUnauthorized() throws IOException, ServletException {
|
||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
||||
|
||||
// execute
|
||||
securityFilter = new AccessForbiddenSecurityFilter(configuration);
|
||||
securityFilter.doFilter(request, response, chain);
|
||||
|
||||
// assert
|
||||
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
private void authenticateUser(User user) {
|
||||
SimplePrincipalCollection spc = new SimplePrincipalCollection();
|
||||
@@ -236,18 +166,4 @@ public class SecurityFilterTest {
|
||||
|
||||
shiro.setSubject(subject);
|
||||
}
|
||||
|
||||
private static class AccessForbiddenSecurityFilter extends SecurityFilter {
|
||||
|
||||
private AccessForbiddenSecurityFilter(ScmConfiguration configuration) {
|
||||
super(configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasPermission(Subject subject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.Stage;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import java.io.File;
|
||||
@@ -96,14 +97,12 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
@Test
|
||||
public void testGetResourceFromCache() throws MalformedURLException
|
||||
{
|
||||
public void testGetResourceFromCache() {
|
||||
DefaultUberWebResourceLoader resourceLoader =
|
||||
new DefaultUberWebResourceLoader(servletContext,
|
||||
new ArrayList<PluginWrapper>());
|
||||
new ArrayList<PluginWrapper>(), Stage.PRODUCTION);
|
||||
|
||||
resourceLoader.getCache().put("/myresource", GITHUB);
|
||||
|
||||
@@ -112,6 +111,15 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
assertSame(GITHUB, resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetResourceCacheIsDisableInStageDevelopment() throws MalformedURLException {
|
||||
DefaultUberWebResourceLoader resourceLoader = new DefaultUberWebResourceLoader(servletContext, new ArrayList<>(), Stage.DEVELOPMENT);
|
||||
when(servletContext.getResource("/scm")).thenAnswer(invocation -> new URL("https://scm-manager.org"));
|
||||
URL url = resourceLoader.getResource("/scm");
|
||||
URL secondUrl = resourceLoader.getResource("/scm");
|
||||
assertNotSame(url, secondUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -184,7 +184,7 @@ private long calculateAverage(List<Long> times) {
|
||||
|
||||
private Repository createTestRepository(int number) {
|
||||
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number);
|
||||
repository.getPermissions().add(new Permission("trillian", PermissionType.READ));
|
||||
repository.addPermission(new Permission("trillian", PermissionType.READ));
|
||||
return repository;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
private String mockedNamespace = "default_namespace";
|
||||
|
||||
@Test
|
||||
public void testCreate() throws AlreadyExistsException {
|
||||
public void testCreate() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
Repository dbRepo = manager.get(heartOfGold.getId());
|
||||
|
||||
@@ -123,18 +123,18 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
username = "unpriv"
|
||||
)
|
||||
@Test(expected = UnauthorizedException.class)
|
||||
public void testCreateWithoutPrivileges() throws AlreadyExistsException {
|
||||
public void testCreateWithoutPrivileges() {
|
||||
createTestRepository();
|
||||
}
|
||||
|
||||
@Test(expected = AlreadyExistsException.class)
|
||||
public void testCreateExisting() throws AlreadyExistsException {
|
||||
public void testCreateExisting() {
|
||||
createTestRepository();
|
||||
createTestRepository();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() throws Exception {
|
||||
public void testDelete() {
|
||||
delete(manager, createTestRepository());
|
||||
}
|
||||
|
||||
@@ -142,12 +142,12 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
username = "unpriv"
|
||||
)
|
||||
@Test(expected = UnauthorizedException.class)
|
||||
public void testDeleteWithoutPrivileges() throws Exception {
|
||||
public void testDeleteWithoutPrivileges() {
|
||||
delete(manager, createTestRepository());
|
||||
}
|
||||
|
||||
@Test(expected = RepositoryIsNotArchivedException.class)
|
||||
public void testDeleteNonArchived() throws Exception {
|
||||
public void testDeleteNonArchived() {
|
||||
configuration.setEnableRepositoryArchive(true);
|
||||
delete(manager, createTestRepository());
|
||||
}
|
||||
@@ -158,7 +158,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteWithEnabledArchive() throws Exception {
|
||||
public void testDeleteWithEnabledArchive() {
|
||||
Repository repository = createTestRepository();
|
||||
|
||||
repository.setArchived(true);
|
||||
@@ -168,7 +168,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() throws AlreadyExistsException {
|
||||
public void testGet() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
String id = heartOfGold.getId();
|
||||
String description = heartOfGold.getDescription();
|
||||
@@ -186,7 +186,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
@SubjectAware(
|
||||
username = "crato"
|
||||
)
|
||||
public void testGetWithoutRequiredPrivileges() throws AlreadyExistsException {
|
||||
public void testGetWithoutRequiredPrivileges() {
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
manager.create(heartOfGold);
|
||||
|
||||
@@ -195,7 +195,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAll() throws AlreadyExistsException {
|
||||
public void testGetAll() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
Repository happyVerticalPeopleTransporter = createSecondTestRepository();
|
||||
boolean foundHeart = false;
|
||||
@@ -233,7 +233,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@SubjectAware(username = "dent")
|
||||
public void testGetAllWithPermissionsForTwoOrThreeRepos() throws AlreadyExistsException {
|
||||
public void testGetAllWithPermissionsForTwoOrThreeRepos() {
|
||||
// mock key generator
|
||||
KeyGenerator keyGenerator = mock(KeyGenerator.class);
|
||||
Stack<String> keys = new Stack<>();
|
||||
@@ -274,7 +274,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvents() throws Exception {
|
||||
public void testEvents() {
|
||||
RepositoryManager repoManager = createRepositoryManager(false);
|
||||
repoManager.init(contextProvider);
|
||||
TestListener listener = new TestListener();
|
||||
@@ -305,7 +305,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModify() throws AlreadyExistsException {
|
||||
public void testModify() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
|
||||
heartOfGold.setDescription("prototype ship");
|
||||
@@ -319,7 +319,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "crato")
|
||||
public void testModifyWithoutRequiredPermissions() throws AlreadyExistsException, NotFoundException {
|
||||
public void testModifyWithoutRequiredPermissions() {
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
manager.create(heartOfGold);
|
||||
heartOfGold.setDescription("prototype ship");
|
||||
@@ -334,7 +334,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh() throws AlreadyExistsException {
|
||||
public void testRefresh() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
String description = heartOfGold.getDescription();
|
||||
|
||||
@@ -345,7 +345,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "crato")
|
||||
public void testRefreshWithoutRequiredPermissions() throws AlreadyExistsException, NotFoundException {
|
||||
public void testRefreshWithoutRequiredPermissions() {
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
manager.create(heartOfGold);
|
||||
heartOfGold.setDescription("prototype ship");
|
||||
@@ -354,13 +354,13 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
manager.refresh(heartOfGold);
|
||||
}
|
||||
|
||||
@Test(expected = RepositoryNotFoundException.class)
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void testRefreshNotFound(){
|
||||
manager.refresh(createRepositoryWithId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepositoryHook() throws AlreadyExistsException {
|
||||
public void testRepositoryHook() {
|
||||
CountingReceiveHook hook = new CountingReceiveHook();
|
||||
RepositoryManager repoManager = createRepositoryManager(false);
|
||||
|
||||
@@ -380,23 +380,23 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamespaceSet() throws Exception {
|
||||
public void testNamespaceSet() {
|
||||
RepositoryManager repoManager = createRepositoryManager(false);
|
||||
Repository repository = spy(createTestRepository());
|
||||
repository.setName("Testrepo");
|
||||
((DefaultRepositoryManager) repoManager).create(repository);
|
||||
repoManager.create(repository);
|
||||
assertEquals("default_namespace", repository.getNamespace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetNamespace() throws AlreadyExistsException {
|
||||
public void shouldSetNamespace() {
|
||||
Repository repository = new Repository(null, "hg", null, "scm");
|
||||
manager.create(repository);
|
||||
assertNotNull(repository.getId());
|
||||
assertNotNull(repository.getNamespace());
|
||||
}
|
||||
|
||||
private void createUriTestRepositories(RepositoryManager m) throws AlreadyExistsException {
|
||||
private void createUriTestRepositories(RepositoryManager m) {
|
||||
mockedNamespace = "namespace";
|
||||
createRepository(m, new Repository("1", "hg", "namespace", "scm"));
|
||||
createRepository(m, new Repository("2", "hg", "namespace", "scm-test"));
|
||||
@@ -452,7 +452,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
keyGenerator, repositoryDAO, handlerSet, namespaceStrategy);
|
||||
}
|
||||
|
||||
private void createRepository(RepositoryManager m, Repository repository) throws AlreadyExistsException {
|
||||
private void createRepository(RepositoryManager m, Repository repository) {
|
||||
m.create(repository);
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
assertEquals(repo.getLastModified(), other.getLastModified());
|
||||
}
|
||||
|
||||
private Repository createRepository(Repository repository) throws AlreadyExistsException {
|
||||
private Repository createRepository(Repository repository) {
|
||||
manager.create(repository);
|
||||
assertNotNull(repository.getId());
|
||||
assertNotNull(manager.get(repository.getId()));
|
||||
@@ -490,12 +490,12 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
return repository;
|
||||
}
|
||||
|
||||
private Repository createSecondTestRepository() throws AlreadyExistsException {
|
||||
private Repository createSecondTestRepository() {
|
||||
return createRepository(
|
||||
RepositoryTestData.createHappyVerticalPeopleTransporter());
|
||||
}
|
||||
|
||||
private Repository createTestRepository() throws AlreadyExistsException {
|
||||
private Repository createTestRepository() {
|
||||
return createRepository(RepositoryTestData.createHeartOfGold());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.ResourceInfo;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
|
||||
public class SecurityRequestFilterTest {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiroRule = new ShiroRule();
|
||||
|
||||
@Mock
|
||||
private ResourceInfo resourceInfo;
|
||||
@Mock
|
||||
private ContainerRequestContext context;
|
||||
@InjectMocks
|
||||
private SecurityRequestFilter securityRequestFilter;
|
||||
|
||||
@Test
|
||||
public void shouldAllowUnauthenticatedAccessForAnnotatedMethod() throws NoSuchMethodException {
|
||||
when(resourceInfo.getResourceMethod()).thenReturn(SecurityTestClass.class.getMethod("anonymousAccessAllowed"));
|
||||
|
||||
securityRequestFilter.filter(context);
|
||||
}
|
||||
|
||||
@Test(expected = AuthenticationException.class)
|
||||
public void shouldRejectUnauthenticatedAccessForAnnotatedMethod() throws NoSuchMethodException {
|
||||
when(resourceInfo.getResourceMethod()).thenReturn(SecurityTestClass.class.getMethod("loginRequired"));
|
||||
|
||||
securityRequestFilter.filter(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "trillian", password = "secret")
|
||||
public void shouldAllowAuthenticatedAccessForMethodWithoutAnnotation() throws NoSuchMethodException {
|
||||
when(resourceInfo.getResourceMethod()).thenReturn(SecurityTestClass.class.getMethod("loginRequired"));
|
||||
|
||||
securityRequestFilter.filter(context);
|
||||
}
|
||||
|
||||
private static class SecurityTestClass {
|
||||
@AllowAnonymousAccess
|
||||
public void anonymousAccessAllowed() {
|
||||
}
|
||||
|
||||
public void loginRequired() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.selenium;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import sonia.scm.selenium.page.Pages;
|
||||
import sonia.scm.selenium.page.MainPage;
|
||||
import sonia.scm.selenium.page.LoginPage;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Authentication related selenium integration tests.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class AuthenticationITCase extends SeleniumITCaseBase {
|
||||
|
||||
/**
|
||||
* Authenticates an user and call logout function.
|
||||
*/
|
||||
@Test
|
||||
public void testAuthentication() {
|
||||
MainPage main = Pages.get(driver, LoginPage.class).login("scmadmin", "scmadmin");
|
||||
assertEquals("scmadmin", main.getUserInfo());
|
||||
main.logout();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user