mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-09 15:05:44 +01:00
#8771 Permission endpoints
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
public class PermissionAlreadyExistsException extends RepositoryException {
|
||||
|
||||
public PermissionAlreadyExistsException(Repository repository, String permissionName) {
|
||||
super(MessageFormat.format("the permission {0} of the repository {1}/{2} is already exists", permissionName, repository.getNamespace(), repository.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
public class PermissionNotFoundException extends RepositoryException{
|
||||
|
||||
|
||||
public PermissionNotFoundException(Repository repository, String permissionName) {
|
||||
super(MessageFormat.format("the permission {0} of the repository {1}/{2} does not exists", permissionName,repository.getNamespace(), repository.getName() ));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@ public class VndMediaType {
|
||||
public static final String USER = PREFIX + "user" + SUFFIX;
|
||||
public static final String GROUP = PREFIX + "group" + SUFFIX;
|
||||
public static final String REPOSITORY = PREFIX + "repository" + SUFFIX;
|
||||
public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
|
||||
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
|
||||
public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX;
|
||||
public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX;
|
||||
|
||||
@@ -276,7 +276,25 @@
|
||||
<version>${jersey-client.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- core plugins -->
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
/*
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -56,14 +56,14 @@ public class StatusExceptionMapper<E extends Throwable>
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(StatusExceptionMapper.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
private final Response.Status status;
|
||||
private final Class<E> type;
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
* Map an Exception to a HTTP Response
|
||||
*
|
||||
*
|
||||
* @param type
|
||||
* @param status
|
||||
* @param type the exception class
|
||||
* @param status the http status to be mapped
|
||||
*/
|
||||
public StatusExceptionMapper(Class<E> type, Response.Status status)
|
||||
{
|
||||
@@ -71,15 +71,12 @@ public class StatusExceptionMapper<E extends Throwable>
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* provide a http responses from an exception
|
||||
*
|
||||
* @param exception the thrown exception
|
||||
*
|
||||
* @param exception
|
||||
*
|
||||
* @return
|
||||
* @return the http response with the exception presentation
|
||||
*/
|
||||
@Override
|
||||
public Response toResponse(E exception)
|
||||
@@ -95,12 +92,4 @@ public class StatusExceptionMapper<E extends Throwable>
|
||||
|
||||
return Response.status(status).build();
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final Response.Status status;
|
||||
|
||||
/** Field description */
|
||||
private final Class<E> type;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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.api.v2.resources;
|
||||
|
||||
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import sonia.scm.api.rest.StatusExceptionMapper;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
/**
|
||||
* @author mkarray
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Provider
|
||||
public class AuthorizationExceptionMapper extends StatusExceptionMapper<AuthorizationException> {
|
||||
|
||||
public AuthorizationExceptionMapper() {
|
||||
super(AuthorizationException.class, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ public class MapperModule extends AbstractModule {
|
||||
bind(RepositoryTypeCollectionToDtoMapper.class);
|
||||
|
||||
bind(BranchToBranchDtoMapper.class).to(Mappers.getMapper(BranchToBranchDtoMapper.class).getClass());
|
||||
bind(PermissionDtoToPermissionMapper.class).to(Mappers.getMapper(PermissionDtoToPermissionMapper.class).getClass());
|
||||
bind(PermissionToPermissionDtoMapper.class).to(Mappers.getMapper(PermissionToPermissionDtoMapper.class).getClass());
|
||||
|
||||
bind(UriInfoStore.class).in(ServletScopes.REQUEST);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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.api.v2.resources;
|
||||
|
||||
|
||||
import sonia.scm.api.rest.StatusExceptionMapper;
|
||||
import sonia.scm.repository.PermissionAlreadyExistsException;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
/**
|
||||
* @author mkarray
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Provider
|
||||
public class PermissionAlreadyExistsExceptionMapper extends StatusExceptionMapper<PermissionAlreadyExistsException> {
|
||||
|
||||
public PermissionAlreadyExistsExceptionMapper() {
|
||||
super(PermissionAlreadyExistsException.class, Response.Status.CONFLICT);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
public class PermissionCollectionResource {
|
||||
@GET
|
||||
@Path("")
|
||||
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
|
||||
@QueryParam("sortBy") String sortBy,
|
||||
@DefaultValue("false") @QueryParam("desc") boolean desc) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,25 @@ import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter @Setter
|
||||
@Getter @Setter @ToString
|
||||
public class PermissionDto extends HalRepresentation {
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private PermissionTypeDto type = PermissionTypeDto.READ;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* the type can be replaced with a dto enum if the mapstruct 1.3.0 is stable
|
||||
* the mapstruct has a Bug on mapping enums in the 1.2.0-Final Version
|
||||
*
|
||||
* see the bug fix: https://github.com/mapstruct/mapstruct/commit/460e87eef6eb71245b387fdb0509c726676a8e19
|
||||
*
|
||||
**/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String type ;
|
||||
|
||||
|
||||
private boolean groupPermission = false;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import sonia.scm.repository.Permission;
|
||||
|
||||
@Mapper
|
||||
public abstract class PermissionDtoToPermissionMapper {
|
||||
|
||||
public abstract Permission map(PermissionDto permissionDto);
|
||||
|
||||
/**
|
||||
* this method is needed to modify an existing permission object
|
||||
*
|
||||
* @param target the target permission
|
||||
* @param permissionDto the source dto
|
||||
* @return the mapped target permission object
|
||||
*/
|
||||
public abstract Permission map(@MappingTarget Permission target, PermissionDto permissionDto);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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.api.v2.resources;
|
||||
|
||||
|
||||
import sonia.scm.api.rest.StatusExceptionMapper;
|
||||
import sonia.scm.repository.PermissionNotFoundException;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
/**
|
||||
* @author mkarray
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Provider
|
||||
public class PermissionNotFoundExceptionMapper extends StatusExceptionMapper<PermissionNotFoundException> {
|
||||
|
||||
public PermissionNotFoundExceptionMapper() {
|
||||
super(PermissionNotFoundException.class, Response.Status.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,234 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.ws.rs.Path;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import sonia.scm.repository.*;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class PermissionRootResource {
|
||||
|
||||
private final Provider<PermissionCollectionResource> permissionCollectionResource;
|
||||
private PermissionDtoToPermissionMapper dtoToModelMapper;
|
||||
private PermissionToPermissionDtoMapper modelToDtoMapper;
|
||||
private ResourceLinks resourceLinks;
|
||||
private final RepositoryManager manager;
|
||||
|
||||
|
||||
@Inject
|
||||
public PermissionRootResource(Provider<PermissionCollectionResource> permissionCollectionResource) {
|
||||
this.permissionCollectionResource = permissionCollectionResource;
|
||||
public PermissionRootResource(PermissionDtoToPermissionMapper dtoToModelMapper, PermissionToPermissionDtoMapper modelToDtoMapper, ResourceLinks resourceLinks, RepositoryManager manager) {
|
||||
this.dtoToModelMapper = dtoToModelMapper;
|
||||
this.modelToDtoMapper = modelToDtoMapper;
|
||||
this.resourceLinks = resourceLinks;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new permission to the user or group managed by the repository
|
||||
*
|
||||
* @param permission permission to add
|
||||
* @return a web response with the status code 201 and the url to GET the added permission
|
||||
*/
|
||||
@POST
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 201, condition = "creates", additionalHeaders = {
|
||||
@ResponseHeader(name = "Location", description = "uri of the created permission")
|
||||
}),
|
||||
@ResponseCode(code = 500, condition = "internal server error"),
|
||||
@ResponseCode(code = 404, condition = "not found"),
|
||||
@ResponseCode(code = 409, condition = "conflict")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PERMISSION)
|
||||
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name, PermissionDto permission) throws RepositoryException {
|
||||
log.info("try to add new permission: {}", permission);
|
||||
Repository repository = checkPermission(namespace, name);
|
||||
checkPermissionAlreadyExists(permission, repository);
|
||||
repository.getPermissions().add(dtoToModelMapper.map(permission));
|
||||
manager.modify(repository);
|
||||
return Response.created(URI.create(resourceLinks.permission().self(namespace,name,permission.getName()))).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the searched permission with permission name related to a repository
|
||||
*
|
||||
* @param namespace the repository namespace
|
||||
* @param name the repository name
|
||||
* @return the http response with a list of permissionDto objects
|
||||
* @throws RepositoryNotFoundException if the repository does not exists
|
||||
*/
|
||||
@GET
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "ok"),
|
||||
@ResponseCode(code = 404, condition = "not found"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces(VndMediaType.PERMISSION)
|
||||
@TypeHint(PermissionDto.class)
|
||||
@Path("{permission-name}")
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) throws RepositoryException {
|
||||
Repository repository = checkPermission(namespace, name);
|
||||
return Response.ok(
|
||||
repository.getPermissions()
|
||||
.stream()
|
||||
.filter(permission -> StringUtils.isNotBlank(permission.getName()) && permission.getName().equals(permissionName))
|
||||
.map(permission -> modelToDtoMapper.map(permission, new NamespaceAndName(repository.getNamespace(),repository.getName())))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new PermissionNotFoundException(repository, permissionName))
|
||||
).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all permissions related to a repository
|
||||
*
|
||||
* @param namespace the repository namespace
|
||||
* @param name the repository name
|
||||
* @return the http response with a list of permissionDto objects
|
||||
* @throws RepositoryNotFoundException if the repository does not exists
|
||||
*/
|
||||
@GET
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "ok"),
|
||||
@ResponseCode(code = 404, condition = "not found"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces(VndMediaType.PERMISSION)
|
||||
@TypeHint(PermissionDto.class)
|
||||
@Path("")
|
||||
public PermissionCollectionResource getPermissionCollectionResource() {
|
||||
return permissionCollectionResource.get();
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws RepositoryNotFoundException {
|
||||
Repository repository = checkPermission(namespace, name);
|
||||
List<PermissionDto> permissionDtoList = repository.getPermissions()
|
||||
.stream()
|
||||
.map(per -> modelToDtoMapper.map(per, new NamespaceAndName(repository.getNamespace(),repository.getName())))
|
||||
.collect(Collectors.toList());
|
||||
return Response.ok(permissionDtoList).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update a permission to the user or group managed by the repository
|
||||
*
|
||||
* @param permission permission to modify
|
||||
* @param permissionName permission to modify
|
||||
* @return a web response with the status code 204
|
||||
*/
|
||||
@PUT
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PERMISSION)
|
||||
@Path("{permission-name}")
|
||||
public Response update(@PathParam("namespace") String namespace,
|
||||
@PathParam("name") String name,
|
||||
@PathParam("permission-name") String permissionName,
|
||||
PermissionDto permission) throws RepositoryException {
|
||||
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
|
||||
Repository repository = checkPermission(namespace, name);
|
||||
repository.getPermissions()
|
||||
.stream()
|
||||
.filter(perm -> StringUtils.isNotBlank(perm.getName()) && perm.getName().equals(permissionName))
|
||||
.findFirst()
|
||||
.map(p -> dtoToModelMapper.map(p, permission))
|
||||
.orElseThrow(() -> new PermissionNotFoundException(repository, permissionName))
|
||||
;
|
||||
manager.modify(repository);
|
||||
log.info("the permission with name: {} is updated.", permissionName);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a permission to the user or group managed by the repository
|
||||
*
|
||||
* @param permissionName permission to delete
|
||||
* @return a web response with the status code 204
|
||||
*/
|
||||
@DELETE
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "delete success or nothing to delete"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Path("{permission-name}")
|
||||
public Response delete(@PathParam("namespace") String namespace,
|
||||
@PathParam("name") String name,
|
||||
@PathParam("permission-name") String permissionName) throws RepositoryException {
|
||||
log.info("try to delete the permission with name: {}.", permissionName);
|
||||
Repository repository = checkPermission(namespace, name);
|
||||
repository.getPermissions()
|
||||
.stream()
|
||||
.filter(perm -> StringUtils.isNotBlank(perm.getName()) && perm.getName().equals(permissionName))
|
||||
.findFirst()
|
||||
.ifPresent(p -> repository.getPermissions().remove(p))
|
||||
;
|
||||
manager.modify(repository);
|
||||
log.info("the permission with name: {} is updated.", permissionName);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* check if the actual user is permitted to manage the repository permissions
|
||||
* return the repository if the user is permitted
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
private Repository checkPermission(@PathParam("namespace") String namespace, @PathParam("name") String name) throws RepositoryNotFoundException {
|
||||
return Optional.ofNullable(manager.get(new NamespaceAndName(namespace, name)))
|
||||
.filter(repository -> {
|
||||
checkUserPermitted(repository);
|
||||
return true;
|
||||
})
|
||||
.orElseThrow(() -> new RepositoryNotFoundException(name));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* throw exception if the user is not permitted
|
||||
* @param repository
|
||||
*/
|
||||
protected void checkUserPermitted(Repository repository) {
|
||||
RepositoryPermissions.modify(repository).check();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if the permission already exists in the repository
|
||||
*
|
||||
* @param permission the searched permission
|
||||
* @param repository the repository to be inspected
|
||||
* @throws PermissionAlreadyExistsException if the permission already exists in the repository
|
||||
*/
|
||||
private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository) throws PermissionAlreadyExistsException {
|
||||
boolean isPermissionAlreadyExist = repository.getPermissions()
|
||||
.stream()
|
||||
.anyMatch(p -> p.getName().equals(permission.getName()));
|
||||
if (isPermissionAlreadyExist) {
|
||||
throw new PermissionAlreadyExistsException(repository, permission.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.*;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Permission;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@Mapper
|
||||
public abstract class PermissionToPermissionDtoMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract PermissionDto map(Permission permission, @Context NamespaceAndName namespaceAndName);
|
||||
|
||||
/**
|
||||
* Add the self, update and delete links.
|
||||
*
|
||||
* @param target the mapped dto
|
||||
* @param namespaceAndName the repository namespace and name
|
||||
*/
|
||||
@AfterMapping
|
||||
void appendLinks(@MappingTarget PermissionDto target, @Context NamespaceAndName namespaceAndName) {
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.self(resourceLinks.permission().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName()));
|
||||
linksBuilder.single(link("update", resourceLinks.permission().update(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.permission().delete(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName())));
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
/**
|
||||
* Type of permissionPrefix for a {@link RepositoryDto}.
|
||||
*
|
||||
* @author mkarray
|
||||
*/
|
||||
|
||||
public enum PermissionTypeDto {
|
||||
|
||||
/**
|
||||
* read permission
|
||||
*/
|
||||
READ,
|
||||
|
||||
/**
|
||||
* read and write permission
|
||||
*/
|
||||
WRITE,
|
||||
|
||||
/**
|
||||
* read, write and manage the properties and permissions
|
||||
*/
|
||||
OWNER
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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.api.v2.resources;
|
||||
|
||||
|
||||
import sonia.scm.api.rest.StatusExceptionMapper;
|
||||
import sonia.scm.repository.RepositoryNotFoundException;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
/**
|
||||
* @author mkarray
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Provider
|
||||
public class RepositoryNotFoundExceptionMapper extends StatusExceptionMapper<RepositoryNotFoundException> {
|
||||
|
||||
public RepositoryNotFoundExceptionMapper() {
|
||||
super(RepositoryNotFoundException.class, Response.Status.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
|
||||
}
|
||||
if (RepositoryPermissions.modify(repository).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.repository().update(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("permissions", resourceLinks.permissionCollection().self(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("permissions", resourceLinks.permission().all(target.getNamespace(), target.getName())));
|
||||
}
|
||||
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
|
||||
if (repositoryService.isSupported(Command.TAGS)) {
|
||||
|
||||
@@ -298,20 +298,35 @@ class ResourceLinks {
|
||||
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("get").parameters(revision).href();
|
||||
}
|
||||
}
|
||||
|
||||
public PermissionCollectionLinks permissionCollection() {
|
||||
return new PermissionCollectionLinks(uriInfoStore.get());
|
||||
public PermissionLinks permission() {
|
||||
return new PermissionLinks(uriInfoStore.get());
|
||||
}
|
||||
|
||||
static class PermissionCollectionLinks {
|
||||
static class PermissionLinks {
|
||||
private final LinkBuilder permissionLinkBuilder;
|
||||
|
||||
PermissionCollectionLinks(UriInfo uriInfo) {
|
||||
permissionLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, PermissionRootResource.class, PermissionCollectionResource.class);
|
||||
PermissionLinks(UriInfo uriInfo) {
|
||||
permissionLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, PermissionRootResource.class);
|
||||
}
|
||||
|
||||
String self(String namespace, String name) {
|
||||
return permissionLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("permissions").parameters().method("getPermissionCollectionResource").parameters().method("getAll").parameters().href();
|
||||
String all(String namespace, String name) {
|
||||
return permissionLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("permissions").parameters().method("getAll").parameters().href();
|
||||
}
|
||||
|
||||
String self(String repositoryNamespace, String repositoryName, String permissionName) {
|
||||
return getLink(repositoryNamespace, repositoryName, permissionName, "get");
|
||||
}
|
||||
|
||||
String update(String repositoryNamespace, String repositoryName, String permissionName) {
|
||||
return getLink(repositoryNamespace, repositoryName, permissionName, "update");
|
||||
}
|
||||
|
||||
String delete(String repositoryNamespace, String repositoryName, String permissionName) {
|
||||
return getLink(repositoryNamespace, repositoryName, permissionName, "delete");
|
||||
}
|
||||
|
||||
private String getLink(String repositoryNamespace, String repositoryName, String permissionName, String methodName) {
|
||||
return permissionLinkBuilder.method("getRepositoryResource").parameters(repositoryNamespace, repositoryName).method("permissions").parameters().method(methodName).parameters(permissionName).href();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
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.jboss.resteasy.spi.HttpRequest;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.*;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
@SubjectAware(
|
||||
username = "trillian",
|
||||
password = "secret",
|
||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||
)
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
@Slf4j
|
||||
public class PermissionRootResourceTest {
|
||||
public static final String REPOSITORY_NAMESPACE = "repo_namespace";
|
||||
public static final String REPOSITORY_NAME = "repo";
|
||||
private static final String PERMISSION_NAME = "perm";
|
||||
|
||||
|
||||
private static final String PATH_OF_ALL_PERMISSIONS = REPOSITORY_NAMESPACE + "/" + REPOSITORY_NAME + "/permissions/";
|
||||
private static final String PATH_OF_ONE_PERMISSION = PATH_OF_ALL_PERMISSIONS + PERMISSION_NAME;
|
||||
private static final String PERMISSION_TEST_PAYLOAD = "{ \"name\" : \"permission_name\", \"type\" : \"READ\" }";
|
||||
private static final ArrayList<Permission> TEST_PERMISSIONS = Lists
|
||||
.newArrayList(
|
||||
new Permission("user_write", PermissionType.WRITE, false),
|
||||
new Permission("user_read", PermissionType.READ, false),
|
||||
new Permission("user_owner", PermissionType.OWNER, false),
|
||||
new Permission("group_read", PermissionType.READ, true),
|
||||
new Permission("group_write", PermissionType.WRITE, true),
|
||||
new Permission("group_owner", PermissionType.OWNER, true)
|
||||
);
|
||||
private final ExpectedRequest requestGETAllPermissions = new ExpectedRequest()
|
||||
.description("GET all permissions")
|
||||
.method("GET")
|
||||
.path(PATH_OF_ALL_PERMISSIONS);
|
||||
private final ExpectedRequest requestPOSTPermission = new ExpectedRequest()
|
||||
.description("create new permission")
|
||||
.method("POST")
|
||||
.content(PERMISSION_TEST_PAYLOAD)
|
||||
.path(PATH_OF_ALL_PERMISSIONS);
|
||||
private final ExpectedRequest requestGETPermission = new ExpectedRequest()
|
||||
.description("GET permission")
|
||||
.method("GET")
|
||||
.path(PATH_OF_ONE_PERMISSION);
|
||||
private final ExpectedRequest requestDELETEPermission = new ExpectedRequest()
|
||||
.description("delete permission")
|
||||
.method("DELETE")
|
||||
.path(PATH_OF_ONE_PERMISSION);
|
||||
private final ExpectedRequest requestPUTPermission = new ExpectedRequest()
|
||||
.description("update permission")
|
||||
.method("PUT")
|
||||
.content(PERMISSION_TEST_PAYLOAD)
|
||||
.path(PATH_OF_ONE_PERMISSION);
|
||||
|
||||
|
||||
private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Mock
|
||||
private RepositoryManager repositoryManager;
|
||||
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||
|
||||
@InjectMocks
|
||||
private PermissionToPermissionDtoMapperImpl permissionToPermissionDtoMapper;
|
||||
|
||||
@InjectMocks
|
||||
private PermissionDtoToPermissionMapperImpl permissionDtoToPermissionMapper;
|
||||
|
||||
|
||||
private PermissionRootResource permissionRootResource;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
initMocks(this);
|
||||
permissionRootResource = spy(new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, resourceLinks, repositoryManager));
|
||||
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider
|
||||
.of(new RepositoryResource(null, null, null, null, null, null, null, MockProvider.of(permissionRootResource))), null);
|
||||
dispatcher.getRegistry().addSingletonResource(repositoryRootResource);
|
||||
dispatcher.getProviderFactory().registerProvider(RepositoryNotFoundExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(PermissionNotFoundExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(PermissionAlreadyExistsExceptionMapper.class);
|
||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldGetAllPermissions() {
|
||||
authorizedUserHasARepositoryWithPermissions(TEST_PERMISSIONS);
|
||||
assertExpectedRequest(requestGETAllPermissions
|
||||
.expectedResponseStatus(200)
|
||||
.responseValidator((response) -> {
|
||||
String body = response.getContentAsString();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
List<PermissionDto> actualPermissionDtos = mapper.readValue(body, new TypeReference<List<PermissionDto>>() {
|
||||
});
|
||||
assertThat(actualPermissionDtos)
|
||||
.as("response payload match permission object models")
|
||||
.hasSize(TEST_PERMISSIONS.size())
|
||||
.usingRecursiveFieldByFieldElementComparator()
|
||||
.containsExactlyInAnyOrder(getExpectedPermissionDtos(TEST_PERMISSIONS))
|
||||
;
|
||||
} catch (IOException e) {
|
||||
fail();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldGetPermissionByName() {
|
||||
authorizedUserHasARepositoryWithPermissions(TEST_PERMISSIONS);
|
||||
Permission expectedPermission = TEST_PERMISSIONS.get(0);
|
||||
assertExpectedRequest(requestGETPermission
|
||||
.expectedResponseStatus(200)
|
||||
.path(PATH_OF_ALL_PERMISSIONS + expectedPermission.getName())
|
||||
.responseValidator((response) -> {
|
||||
String body = response.getContentAsString();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
PermissionDto actualPermissionDto = mapper.readValue(body, PermissionDto.class);
|
||||
assertThat(actualPermissionDto)
|
||||
.as("response payload match permission object model")
|
||||
.isEqualToComparingFieldByFieldRecursively(getExpectedPermissionDto(expectedPermission))
|
||||
;
|
||||
} catch (IOException e) {
|
||||
fail();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldGetCreatedPermissions() {
|
||||
authorizedUserHasARepositoryWithPermissions(TEST_PERMISSIONS);
|
||||
Permission newPermission = new Permission("new_group_perm", PermissionType.WRITE, true);
|
||||
ArrayList<Permission> permissions = Lists.newArrayList(TEST_PERMISSIONS);
|
||||
permissions.add(newPermission);
|
||||
ImmutableList<Permission> expectedPermissions = ImmutableList.copyOf(permissions);
|
||||
assertExpectedRequest(requestPOSTPermission
|
||||
.content("{\"name\" : \"" + newPermission.getName() + "\" , \"type\" : \"WRITE\" , \"groupPermission\" : true}")
|
||||
.expectedResponseStatus(201)
|
||||
.responseValidator(response -> assertThat(response.getContentAsString())
|
||||
.as("POST response has no body")
|
||||
.isBlank())
|
||||
);
|
||||
assertGettingExpectedPermissions(expectedPermissions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAddExistingPermission() {
|
||||
authorizedUserHasARepositoryWithPermissions(TEST_PERMISSIONS);
|
||||
Permission newPermission = TEST_PERMISSIONS.get(0);
|
||||
assertExpectedRequest(requestPOSTPermission
|
||||
.content("{\"name\" : \"" + newPermission.getName() + "\" , \"type\" : \"WRITE\" , \"groupPermission\" : true}")
|
||||
.expectedResponseStatus(409)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldGetUpdatedPermissions() {
|
||||
authorizedUserHasARepositoryWithPermissions(TEST_PERMISSIONS);
|
||||
Permission modifiedPermission = TEST_PERMISSIONS.get(0);
|
||||
// modify the type to owner
|
||||
modifiedPermission.setType(PermissionType.OWNER);
|
||||
ImmutableList<Permission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS);
|
||||
assertExpectedRequest(requestPUTPermission
|
||||
.content("{\"name\" : \"" + modifiedPermission.getName() + "\" , \"type\" : \"OWNER\" , \"groupPermission\" : false}")
|
||||
.path(PATH_OF_ALL_PERMISSIONS + modifiedPermission.getName())
|
||||
.expectedResponseStatus(204)
|
||||
.responseValidator(response -> assertThat(response.getContentAsString())
|
||||
.as("PUT response has no body")
|
||||
.isBlank())
|
||||
);
|
||||
assertGettingExpectedPermissions(expectedPermissions);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDeletePermissions() {
|
||||
authorizedUserHasARepositoryWithPermissions(TEST_PERMISSIONS);
|
||||
Permission deletedPermission = TEST_PERMISSIONS.get(0);
|
||||
ImmutableList<Permission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS.subList(1, TEST_PERMISSIONS.size()));
|
||||
assertExpectedRequest(requestDELETEPermission
|
||||
.path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName())
|
||||
.expectedResponseStatus(204)
|
||||
.responseValidator(response -> assertThat(response.getContentAsString())
|
||||
.as("DELETE response has no body")
|
||||
.isBlank())
|
||||
);
|
||||
assertGettingExpectedPermissions(expectedPermissions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletingNotExistingPermissionShouldProcess() {
|
||||
authorizedUserHasARepositoryWithPermissions(TEST_PERMISSIONS);
|
||||
Permission deletedPermission = TEST_PERMISSIONS.get(0);
|
||||
ImmutableList<Permission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS.subList(1, TEST_PERMISSIONS.size()));
|
||||
assertExpectedRequest(requestDELETEPermission
|
||||
.path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName())
|
||||
.expectedResponseStatus(204)
|
||||
.responseValidator(response -> assertThat(response.getContentAsString())
|
||||
.as("DELETE response has no body")
|
||||
.isBlank())
|
||||
);
|
||||
assertGettingExpectedPermissions(expectedPermissions);
|
||||
assertExpectedRequest(requestDELETEPermission
|
||||
.path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName())
|
||||
.expectedResponseStatus(204)
|
||||
.responseValidator(response -> assertThat(response.getContentAsString())
|
||||
.as("DELETE response has no body")
|
||||
.isBlank())
|
||||
);
|
||||
assertGettingExpectedPermissions(expectedPermissions);
|
||||
}
|
||||
|
||||
private void assertGettingExpectedPermissions(ImmutableList<Permission> expectedPermissions) {
|
||||
assertExpectedRequest(requestGETAllPermissions
|
||||
.expectedResponseStatus(200)
|
||||
.responseValidator((response) -> {
|
||||
String body = response.getContentAsString();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
List<PermissionDto> actualPermissionDtos = mapper.readValue(body, new TypeReference<List<PermissionDto>>() {
|
||||
});
|
||||
assertThat(actualPermissionDtos)
|
||||
.as("response payload match permission object models")
|
||||
.hasSize(expectedPermissions.size())
|
||||
.usingRecursiveFieldByFieldElementComparator()
|
||||
.containsExactlyInAnyOrder(getExpectedPermissionDtos(Lists.newArrayList(expectedPermissions)))
|
||||
;
|
||||
} catch (IOException e) {
|
||||
fail();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private PermissionDto[] getExpectedPermissionDtos(ArrayList<Permission> permissions) {
|
||||
return permissions
|
||||
.stream()
|
||||
.map(this::getExpectedPermissionDto)
|
||||
.toArray(PermissionDto[]::new);
|
||||
}
|
||||
|
||||
private PermissionDto getExpectedPermissionDto(Permission permission) {
|
||||
PermissionDto result = new PermissionDto();
|
||||
result.setName(permission.getName());
|
||||
result.setGroupPermission(permission.isGroupPermission());
|
||||
result.setType(permission.getType().name());
|
||||
String permissionHref = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS + permission.getName();
|
||||
result.add(linkingTo()
|
||||
.self(permissionHref)
|
||||
.single(link("update", permissionHref))
|
||||
.single(link("delete", permissionHref))
|
||||
.build());
|
||||
return result;
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
@DisplayName("test endpoints on missing repository and user is Admin")
|
||||
Stream<DynamicTest> missedRepositoryTestFactory() {
|
||||
return createDynamicTestsToAssertResponses(
|
||||
requestGETAllPermissions.expectedResponseStatus(404),
|
||||
requestGETPermission.expectedResponseStatus(404),
|
||||
requestPOSTPermission.expectedResponseStatus(404),
|
||||
requestDELETEPermission.expectedResponseStatus(404),
|
||||
requestPUTPermission.expectedResponseStatus(404));
|
||||
}
|
||||
|
||||
|
||||
@TestFactory
|
||||
@DisplayName("test endpoints on missing permission and user is Admin")
|
||||
Stream<DynamicTest> missedPermissionTestFactory() {
|
||||
authorizedUserHasARepository();
|
||||
return createDynamicTestsToAssertResponses(
|
||||
requestGETPermission.expectedResponseStatus(404),
|
||||
requestPOSTPermission.expectedResponseStatus(201),
|
||||
requestGETAllPermissions.expectedResponseStatus(200),
|
||||
requestDELETEPermission.expectedResponseStatus(204),
|
||||
requestPUTPermission.expectedResponseStatus(404));
|
||||
}
|
||||
|
||||
private Repository authorizedUserHasARepository() {
|
||||
Repository mockRepository = mock(Repository.class);
|
||||
when(mockRepository.getId()).thenReturn(REPOSITORY_NAME);
|
||||
when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE);
|
||||
when(mockRepository.getName()).thenReturn(REPOSITORY_NAME);
|
||||
doNothing().when(permissionRootResource).checkUserPermitted(mockRepository);
|
||||
when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository);
|
||||
return mockRepository;
|
||||
}
|
||||
|
||||
private void authorizedUserHasARepositoryWithPermissions(ArrayList<Permission> permissions) {
|
||||
when(authorizedUserHasARepository().getPermissions()).thenReturn(permissions);
|
||||
}
|
||||
|
||||
|
||||
@TestFactory
|
||||
@DisplayName("test endpoints on missing permission and user is not Admin")
|
||||
Stream<DynamicTest> missedPermissionUserForbiddenTestFactory() {
|
||||
Repository mockRepository = mock(Repository.class);
|
||||
when(mockRepository.getId()).thenReturn(REPOSITORY_NAME);
|
||||
doThrow(AuthorizationException.class).when(permissionRootResource).checkUserPermitted(mockRepository);
|
||||
when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository);
|
||||
return createDynamicTestsToAssertResponses(
|
||||
requestGETPermission.expectedResponseStatus(401),
|
||||
requestPOSTPermission.expectedResponseStatus(401),
|
||||
requestGETAllPermissions.expectedResponseStatus(401),
|
||||
requestDELETEPermission.expectedResponseStatus(401),
|
||||
requestPUTPermission.expectedResponseStatus(401));
|
||||
}
|
||||
|
||||
|
||||
private Stream<DynamicTest> createDynamicTestsToAssertResponses(ExpectedRequest... expectedRequests) {
|
||||
|
||||
return Stream.of(expectedRequests)
|
||||
.map(entry -> dynamicTest("the endpoint " + entry.description + " should return the status code " + entry.expectedResponseStatus, () -> assertExpectedRequest(entry)));
|
||||
}
|
||||
|
||||
private MockHttpResponse assertExpectedRequest(ExpectedRequest entry) {
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
HttpRequest request = null;
|
||||
try {
|
||||
request = MockHttpRequest
|
||||
.create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path)
|
||||
.content(entry.content)
|
||||
.contentType(VndMediaType.PERMISSION);
|
||||
} catch (URISyntaxException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
dispatcher.invoke(request, response);
|
||||
log.info("Test the Request :{}", entry);
|
||||
assertThat(entry.expectedResponseStatus)
|
||||
.as("assert status code")
|
||||
.isEqualTo(response.getStatus());
|
||||
if (entry.responseValidator != null) {
|
||||
entry.responseValidator.accept(response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@ToString
|
||||
public class ExpectedRequest {
|
||||
private String description;
|
||||
private String method;
|
||||
private String path;
|
||||
private int expectedResponseStatus;
|
||||
private byte[] content = new byte[]{};
|
||||
private Consumer<MockHttpResponse> responseValidator;
|
||||
|
||||
public ExpectedRequest description(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedRequest method(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedRequest path(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedRequest content(String content) {
|
||||
if (content != null) {
|
||||
this.content = content.getBytes();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedRequest expectedResponseStatus(int expectedResponseStatus) {
|
||||
this.expectedResponseStatus = expectedResponseStatus;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedRequest responseValidator(Consumer<MockHttpResponse> responseValidator) {
|
||||
this.responseValidator = responseValidator;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public class ResourceLinksMock {
|
||||
when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(uriInfo));
|
||||
when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(uriInfo));
|
||||
when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(uriInfo));
|
||||
when(resourceLinks.permissionCollection()).thenReturn(new ResourceLinks.PermissionCollectionLinks(uriInfo));
|
||||
when(resourceLinks.permission()).thenReturn(new ResourceLinks.PermissionLinks(uriInfo));
|
||||
when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo));
|
||||
when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(uriInfo));
|
||||
when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo));
|
||||
|
||||
Reference in New Issue
Block a user