mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 08:55:44 +01:00
Merge changes
This commit is contained in:
7
Jenkinsfile
vendored
7
Jenkinsfile
vendored
@@ -11,7 +11,7 @@ node() { // No specific label
|
|||||||
disableConcurrentBuilds()
|
disableConcurrentBuilds()
|
||||||
])
|
])
|
||||||
|
|
||||||
String defaultEmailRecipients = env.EMAIL_RECIPIENTS
|
String defaultEmailRecipients = env.EMAIL_SCM_RECIPIENTS
|
||||||
|
|
||||||
catchError {
|
catchError {
|
||||||
|
|
||||||
@@ -40,5 +40,8 @@ node() { // No specific label
|
|||||||
// Archive Unit and integration test results, if any
|
// Archive Unit and integration test results, if any
|
||||||
junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml,**/target/jest-reports/TEST-*.xml'
|
junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml,**/target/jest-reports/TEST-*.xml'
|
||||||
|
|
||||||
mailIfStatusChanged(findEmailRecipients(defaultEmailRecipients))
|
// Find maven warnings and visualize in job
|
||||||
|
warnings consoleParsers: [[parserName: 'Maven']], canRunOnFailed: true
|
||||||
|
|
||||||
|
mailIfStatusChanged(defaultEmailRecipients)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package sonia.scm.api.v2.resources;
|
|||||||
import de.otto.edison.hal.Embedded;
|
import de.otto.edison.hal.Embedded;
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import lombok.Data;
|
import lombok.Getter;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.Setter;
|
||||||
|
|
||||||
@Data @EqualsAndHashCode(callSuper = false)
|
@Getter @Setter
|
||||||
class CollectionDto extends HalRepresentation {
|
class CollectionDto extends HalRepresentation {
|
||||||
|
|
||||||
private int page;
|
private int page;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sonia.scm.api.v2.resources;
|
|||||||
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
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.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
@@ -25,9 +26,9 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import static sonia.scm.api.v2.resources.ResourceLinks.group;
|
import static sonia.scm.api.v2.resources.ResourceLinks.group;
|
||||||
|
|
||||||
@Produces(VndMediaType.GROUP_COLLECTION)
|
|
||||||
public class GroupCollectionResource {
|
public class GroupCollectionResource {
|
||||||
public static final int DEFAULT_PAGE_SIZE = 10;
|
|
||||||
|
private static final int DEFAULT_PAGE_SIZE = 10;
|
||||||
private final GroupDtoToGroupMapper dtoToGroupMapper;
|
private final GroupDtoToGroupMapper dtoToGroupMapper;
|
||||||
private final GroupCollectionToDtoMapper groupCollectionToDtoMapper;
|
private final GroupCollectionToDtoMapper groupCollectionToDtoMapper;
|
||||||
|
|
||||||
@@ -42,21 +43,23 @@ public class GroupCollectionResource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all groups for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
|
* Returns all groups for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
|
||||||
* <strong>Note:</strong> This method requires admin privileges.
|
*
|
||||||
|
* <strong>Note:</strong> This method requires "group" privilege.
|
||||||
*
|
*
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
* @param page the number of the requested page
|
* @param page the number of the requested page
|
||||||
* @param pageSize the page size (default page size is {@value DEFAULT_PAGE_SIZE})
|
* @param pageSize the page size (default page size is {@value DEFAULT_PAGE_SIZE})
|
||||||
* @param sortBy sort parameter
|
* @param sortBy sort parameter
|
||||||
* @param desc sort direction desc or aesc
|
* @param desc sort direction desc or aesc
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("")
|
@Path("")
|
||||||
|
@Produces(VndMediaType.GROUP_COLLECTION)
|
||||||
@TypeHint(GroupDto[].class)
|
@TypeHint(GroupDto[].class)
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public Response getAll(@Context Request request,
|
public Response getAll(@Context Request request,
|
||||||
@@ -65,7 +68,8 @@ public class GroupCollectionResource {
|
|||||||
@QueryParam("sortby") String sortBy,
|
@QueryParam("sortby") String sortBy,
|
||||||
@DefaultValue("false")
|
@DefaultValue("false")
|
||||||
@QueryParam("desc") boolean desc) {
|
@QueryParam("desc") boolean desc) {
|
||||||
return adapter.getAll(page, pageSize, sortBy, desc, pageResult -> groupCollectionToDtoMapper.map(page, pageSize, pageResult));
|
return adapter.getAll(page, pageSize, sortBy, desc,
|
||||||
|
pageResult -> groupCollectionToDtoMapper.map(page, pageSize, pageResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,17 +79,19 @@ public class GroupCollectionResource {
|
|||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@Path("")
|
@Path("")
|
||||||
|
@Consumes(VndMediaType.GROUP)
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 201, condition = "create success", additionalHeaders = {
|
@ResponseCode(code = 201, condition = "create success"),
|
||||||
@ResponseHeader(name = "Location", description = "uri to the created group")
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
}),
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 409, condition = "conflict, a group with this name already exists"),
|
@ResponseCode(code = 409, condition = "conflict, a group with this name already exists"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@Consumes(VndMediaType.GROUP)
|
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created group"))
|
||||||
public Response create(@Context UriInfo uriInfo, GroupDto groupDto) throws IOException, GroupException {
|
public Response create(@Context UriInfo uriInfo, GroupDto groupDto) throws IOException, GroupException {
|
||||||
return adapter.create(groupDto, () -> dtoToGroupMapper.map(groupDto), group -> group(uriInfo).self(group.getName()));
|
return adapter.create(groupDto,
|
||||||
|
() -> dtoToGroupMapper.map(groupDto),
|
||||||
|
group -> group(uriInfo).self(group.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import lombok.Data;
|
import lombok.Getter;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true)
|
@Getter @Setter @NoArgsConstructor
|
||||||
public class GroupDto extends HalRepresentation {
|
public class GroupDto extends HalRepresentation {
|
||||||
|
|
||||||
private Instant creationDate;
|
private Instant creationDate;
|
||||||
|
|||||||
@@ -9,13 +9,18 @@ import sonia.scm.group.GroupManager;
|
|||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.DELETE;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.PUT;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.Request;
|
import javax.ws.rs.core.Request;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
@Produces(VndMediaType.GROUP)
|
|
||||||
public class GroupResource {
|
public class GroupResource {
|
||||||
|
|
||||||
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
|
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
|
||||||
@@ -30,12 +35,23 @@ public class GroupResource {
|
|||||||
this.adapter = new ResourceManagerAdapter<>(manager);
|
this.adapter = new ResourceManagerAdapter<>(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("")
|
/**
|
||||||
|
* Returns a group.
|
||||||
|
*
|
||||||
|
* <strong>Note:</strong> This method requires "group" privilege.
|
||||||
|
*
|
||||||
|
* @param request the current request
|
||||||
|
* @param id the id/name of the group
|
||||||
|
*
|
||||||
|
*/
|
||||||
@GET
|
@GET
|
||||||
|
@Path("")
|
||||||
|
@Produces(VndMediaType.GROUP)
|
||||||
@TypeHint(GroupDto.class)
|
@TypeHint(GroupDto.class)
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no privileges to read the group"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the group"),
|
||||||
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@@ -43,26 +59,43 @@ public class GroupResource {
|
|||||||
return adapter.get(id, groupToGroupDtoMapper::map);
|
return adapter.get(id, groupToGroupDtoMapper::map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("")
|
/**
|
||||||
|
* Deletes a group.
|
||||||
|
*
|
||||||
|
* <strong>Note:</strong> This method requires "group" privilege.
|
||||||
|
*
|
||||||
|
* @param name the name of the group to delete.
|
||||||
|
*
|
||||||
|
*/
|
||||||
@DELETE
|
@DELETE
|
||||||
public Response delete(@PathParam("id") String id) {
|
@Path("")
|
||||||
throw new RuntimeException();
|
@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, the current user does not have the \"group\" privilege"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
|
public Response delete(@PathParam("id") String name) {
|
||||||
|
throw new UnsupportedOperationException("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifies the given group.
|
* Modifies the given group.
|
||||||
*
|
*
|
||||||
* <strong>Note:</strong> This method requires "group" privileges.
|
* <strong>Note:</strong> This method requires "group" privilege.
|
||||||
*
|
*
|
||||||
* @param name name of the group to be modified
|
* @param name name of the group to be modified
|
||||||
* @param groupDto group object to modify
|
* @param groupDto group object to modify
|
||||||
*/
|
*/
|
||||||
@PUT
|
@PUT
|
||||||
@Path("")
|
@Path("")
|
||||||
|
@Consumes(VndMediaType.GROUP)
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 204, condition = "update success"),
|
@ResponseCode(code = 204, condition = "update success"),
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"group\" privilege"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
|
||||||
|
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RESTful Web Service Resource to manage groups and their members.
|
||||||
|
*/
|
||||||
@Path(GroupRootResource.GROUPS_PATH_V2)
|
@Path(GroupRootResource.GROUPS_PATH_V2)
|
||||||
public class GroupRootResource {
|
public class GroupRootResource {
|
||||||
|
|
||||||
public static final String GROUPS_PATH_V2 = "v2/groups/";
|
static final String GROUPS_PATH_V2 = "v2/groups/";
|
||||||
|
|
||||||
private final Provider<GroupCollectionResource> groupCollectionResource;
|
private final Provider<GroupCollectionResource> groupCollectionResource;
|
||||||
private final Provider<GroupResource> groupResource;
|
private final Provider<GroupResource> groupResource;
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ package sonia.scm.api.v2.resources;
|
|||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Getter;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
@NoArgsConstructor @AllArgsConstructor
|
@NoArgsConstructor @AllArgsConstructor @Getter @Setter
|
||||||
@Data @EqualsAndHashCode(callSuper = false)
|
|
||||||
public class MemberDto extends HalRepresentation {
|
public class MemberDto extends HalRepresentation {
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,21 @@ import java.util.Collection;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter from resource http endpoints to managers.
|
* Adapter from resource http endpoints to managers.
|
||||||
|
*
|
||||||
|
* Provides common CRUD operations and DTO to Model Object mapping to keep Resources more DRY.
|
||||||
|
*
|
||||||
* @param <MODEL_OBJECT> The type of the model object, eg. {@link sonia.scm.user.User}.
|
* @param <MODEL_OBJECT> The type of the model object, eg. {@link sonia.scm.user.User}.
|
||||||
* @param <DTO> The corresponding transport object, eg. {@link UserDto}.
|
* @param <DTO> The corresponding transport object, eg. {@link UserDto}.
|
||||||
* @param <EXCEPTION> The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
|
* @param <EXCEPTION> The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
|
||||||
*/
|
*/
|
||||||
class ResourceManagerAdapter<MODEL_OBJECT extends ModelObject, DTO extends HalRepresentation, EXCEPTION extends Exception> extends AbstractManagerResource<MODEL_OBJECT, EXCEPTION> {
|
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
|
||||||
|
class ResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||||
|
DTO extends HalRepresentation,
|
||||||
|
EXCEPTION extends Exception> extends AbstractManagerResource<MODEL_OBJECT, EXCEPTION> {
|
||||||
|
|
||||||
ResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager) {
|
ResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager) {
|
||||||
super(manager);
|
super(manager);
|
||||||
@@ -49,6 +57,9 @@ class ResourceManagerAdapter<MODEL_OBJECT extends ModelObject, DTO extends HalRe
|
|||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
|
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
|
||||||
|
if (!id.equals(changedModelObject.getId())) {
|
||||||
|
return Response.status(BAD_REQUEST).entity("illegal change of id").build();
|
||||||
|
}
|
||||||
return update(id, changedModelObject);
|
return update(id, changedModelObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +78,7 @@ class ResourceManagerAdapter<MODEL_OBJECT extends ModelObject, DTO extends HalRe
|
|||||||
*/
|
*/
|
||||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws IOException, EXCEPTION {
|
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws IOException, EXCEPTION {
|
||||||
if (dto == null) {
|
if (dto == null) {
|
||||||
return Response.status(400).build();
|
return Response.status(BAD_REQUEST).build();
|
||||||
}
|
}
|
||||||
MODEL_OBJECT modelObject = modelObjectSupplier.get();
|
MODEL_OBJECT modelObject = modelObjectSupplier.get();
|
||||||
manager.create(modelObject);
|
manager.create(modelObject);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import sonia.scm.user.UserManager;
|
|||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DefaultValue;
|
import javax.ws.rs.DefaultValue;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
@@ -25,7 +26,6 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import static sonia.scm.api.v2.resources.ResourceLinks.user;
|
import static sonia.scm.api.v2.resources.ResourceLinks.user;
|
||||||
|
|
||||||
@Produces(VndMediaType.USER_COLLECTION)
|
|
||||||
public class UserCollectionResource {
|
public class UserCollectionResource {
|
||||||
|
|
||||||
private static final int DEFAULT_PAGE_SIZE = 10;
|
private static final int DEFAULT_PAGE_SIZE = 10;
|
||||||
@@ -45,7 +45,7 @@ public class UserCollectionResource {
|
|||||||
/**
|
/**
|
||||||
* Returns all users for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
|
* Returns all users for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
|
||||||
*
|
*
|
||||||
* <strong>Note:</strong> This method requires "user" privileges.
|
* <strong>Note:</strong> This method requires "user" privilege.
|
||||||
*
|
*
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
* @param page the number of the requested page
|
* @param page the number of the requested page
|
||||||
@@ -55,10 +55,12 @@ public class UserCollectionResource {
|
|||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("")
|
@Path("")
|
||||||
|
@Produces(VndMediaType.USER_COLLECTION)
|
||||||
@TypeHint(UserDto[].class)
|
@TypeHint(UserDto[].class)
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public Response getAll(@Context Request request,
|
public Response getAll(@Context Request request,
|
||||||
@@ -66,30 +68,33 @@ public class UserCollectionResource {
|
|||||||
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
|
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
|
||||||
@QueryParam("sortby") String sortBy,
|
@QueryParam("sortby") String sortBy,
|
||||||
@DefaultValue("false") @QueryParam("desc") boolean desc) {
|
@DefaultValue("false") @QueryParam("desc") boolean desc) {
|
||||||
return adapter.getAll(page, pageSize, sortBy, desc, pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
|
return adapter.getAll(page, pageSize, sortBy, desc,
|
||||||
|
pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new user.
|
* Creates a new user.
|
||||||
*
|
*
|
||||||
* <strong>Note:</strong> This method requires "user" privileges.
|
* <strong>Note:</strong> This method requires "user" privilege.
|
||||||
*
|
*
|
||||||
* @param userDto The user to be created.
|
* @param userDto The user to be created.
|
||||||
* @return A response with the link to the new user (if created successfully).
|
* @return A response with the link to the new user (if created successfully).
|
||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@Path("")
|
@Path("")
|
||||||
|
@Consumes(VndMediaType.USER)
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 201, condition = "create success", additionalHeaders = {
|
@ResponseCode(code = 201, condition = "create success"),
|
||||||
@ResponseHeader(name = "Location", description = "uri to the created user")
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
}),
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
|
|
||||||
@ResponseCode(code = 409, condition = "conflict, a user with this name already exists"),
|
@ResponseCode(code = 409, condition = "conflict, a user with this name already exists"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
|
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
|
||||||
public Response create(@Context UriInfo uriInfo, UserDto userDto) throws IOException, UserException {
|
public Response create(@Context UriInfo uriInfo, UserDto userDto) throws IOException, UserException {
|
||||||
return adapter.create(userDto, () -> dtoToUserMapper.map(userDto, ""), user -> user(uriInfo).self(user.getName()));
|
return adapter.create(userDto,
|
||||||
|
() -> dtoToUserMapper.map(userDto, ""),
|
||||||
|
user -> user(uriInfo).self(user.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,15 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.Getter;
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@AllArgsConstructor @NoArgsConstructor
|
@NoArgsConstructor @Getter @Setter
|
||||||
@Data @EqualsAndHashCode(callSuper = false)
|
|
||||||
public class UserDto extends HalRepresentation {
|
public class UserDto extends HalRepresentation {
|
||||||
private boolean active;
|
private boolean active;
|
||||||
private boolean admin;
|
private boolean admin;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import sonia.scm.user.UserManager;
|
|||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.PUT;
|
import javax.ws.rs.PUT;
|
||||||
@@ -20,7 +21,6 @@ import javax.ws.rs.core.Request;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
@Produces(VndMediaType.USER)
|
|
||||||
public class UserResource {
|
public class UserResource {
|
||||||
|
|
||||||
private final UserDtoToUserMapper dtoToUserMapper;
|
private final UserDtoToUserMapper dtoToUserMapper;
|
||||||
@@ -38,7 +38,7 @@ public class UserResource {
|
|||||||
/**
|
/**
|
||||||
* Returns a user.
|
* Returns a user.
|
||||||
*
|
*
|
||||||
* <strong>Note:</strong> This method requires "user" privileges.
|
* <strong>Note:</strong> This method requires "user" privilege.
|
||||||
*
|
*
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
* @param id the id/name of the user
|
* @param id the id/name of the user
|
||||||
@@ -46,10 +46,12 @@ public class UserResource {
|
|||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("")
|
@Path("")
|
||||||
|
@Produces(VndMediaType.USER)
|
||||||
@TypeHint(UserDto.class)
|
@TypeHint(UserDto.class)
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no privileges to read the user"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the user"),
|
||||||
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
|
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@@ -57,31 +59,10 @@ public class UserResource {
|
|||||||
return adapter.get(id, userToDtoMapper::map);
|
return adapter.get(id, userToDtoMapper::map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the given user.
|
|
||||||
*
|
|
||||||
* <strong>Note:</strong> This method requires "user" privileges.
|
|
||||||
*
|
|
||||||
* @param name name of the user to be modified
|
|
||||||
* @param userDto user object to modify
|
|
||||||
*/
|
|
||||||
@PUT
|
|
||||||
@Path("")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "update success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
|
|
||||||
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
public Response update(@Context UriInfo uriInfo, @PathParam("id") String name, UserDto userDto) {
|
|
||||||
return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a user.
|
* Deletes a user.
|
||||||
*
|
*
|
||||||
* <strong>Note:</strong> This method requires "user" privileges.
|
* <strong>Note:</strong> This method requires "user" privilege.
|
||||||
*
|
*
|
||||||
* @param name the name of the user to delete.
|
* @param name the name of the user to delete.
|
||||||
*
|
*
|
||||||
@@ -89,12 +70,36 @@ public class UserResource {
|
|||||||
@DELETE
|
@DELETE
|
||||||
@Path("")
|
@Path("")
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 204, condition = "delete success"),
|
@ResponseCode(code = 204, condition = "delete success or nothing to delete"),
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
public Response delete(@PathParam("id") String name) {
|
public Response delete(@PathParam("id") String name) {
|
||||||
return adapter.delete(name);
|
return adapter.delete(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the given user.
|
||||||
|
*
|
||||||
|
* <strong>Note:</strong> This method requires "user" privilege.
|
||||||
|
*
|
||||||
|
* @param name name of the user to be modified
|
||||||
|
* @param userDto user object to modify
|
||||||
|
*/
|
||||||
|
@PUT
|
||||||
|
@Path("")
|
||||||
|
@Consumes(VndMediaType.USER)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 204, condition = "update success"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
|
||||||
|
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
|
public Response update(@Context UriInfo uriInfo, @PathParam("id") String name, UserDto userDto) {
|
||||||
|
return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import javax.ws.rs.Path;
|
|||||||
public class UserRootResource {
|
public class UserRootResource {
|
||||||
|
|
||||||
static final String USERS_PATH_V2 = "v2/users/";
|
static final String USERS_PATH_V2 = "v2/users/";
|
||||||
|
|
||||||
private final Provider<UserCollectionResource> userCollectionResource;
|
private final Provider<UserCollectionResource> userCollectionResource;
|
||||||
private final Provider<UserResource> userResource;
|
private final Provider<UserResource> userResource;
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import static org.junit.Assert.assertTrue;
|
|||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
@@ -69,9 +70,8 @@ public class UserRootResourceTest {
|
|||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() throws IOException, UserException {
|
public void prepareEnvironment() throws IOException, UserException {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
User dummyUser = createDummyUser();
|
User dummyUser = createDummyUser("Neo");
|
||||||
when(userManager.getPage(any(), eq(0), eq(10))).thenReturn(new PageResult<>(singletonList(dummyUser), 1));
|
when(userManager.getPage(any(), eq(0), eq(10))).thenReturn(new PageResult<>(singletonList(dummyUser), 1));
|
||||||
when(userManager.get("Neo")).thenReturn(dummyUser);
|
|
||||||
doNothing().when(userManager).create(userCaptor.capture());
|
doNothing().when(userManager).create(userCaptor.capture());
|
||||||
doNothing().when(userManager).modify(userCaptor.capture());
|
doNothing().when(userManager).modify(userCaptor.capture());
|
||||||
doNothing().when(userManager).delete(userCaptor.capture());
|
doNothing().when(userManager).delete(userCaptor.capture());
|
||||||
@@ -192,11 +192,48 @@ public class UserRootResourceTest {
|
|||||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
private User createDummyUser() {
|
@Test
|
||||||
|
public void shouldFailUpdateForDifferentIds() throws IOException, URISyntaxException, UserException {
|
||||||
|
URL url = Resources.getResource("sonia/scm/api/v2/user-test-update.json");
|
||||||
|
byte[] userJson = Resources.toByteArray(url);
|
||||||
|
createDummyUser("Other");
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + UserRootResource.USERS_PATH_V2 + "Other")
|
||||||
|
.contentType(VndMediaType.USER)
|
||||||
|
.content(userJson);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||||
|
verify(userManager, never()).modify(any(User.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldFailUpdateForUnknownEntity() throws IOException, URISyntaxException, UserException {
|
||||||
|
URL url = Resources.getResource("sonia/scm/api/v2/user-test-update.json");
|
||||||
|
byte[] userJson = Resources.toByteArray(url);
|
||||||
|
when(userManager.get("Neo")).thenReturn(null);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + UserRootResource.USERS_PATH_V2 + "Neo")
|
||||||
|
.contentType(VndMediaType.USER)
|
||||||
|
.content(userJson);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_NOT_FOUND, response.getStatus());
|
||||||
|
verify(userManager, never()).modify(any(User.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private User createDummyUser(String name) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setName("Neo");
|
user.setName(name);
|
||||||
user.setPassword("redpill");
|
user.setPassword("redpill");
|
||||||
user.setCreationDate(System.currentTimeMillis());
|
user.setCreationDate(System.currentTimeMillis());
|
||||||
|
when(userManager.get(name)).thenReturn(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user