Fix REST endpoint for user permissions

This commit is contained in:
René Pfeuffer
2019-01-17 15:29:21 +01:00
parent 783c425b1e
commit aa26a9c0e3
10 changed files with 215 additions and 60 deletions

View File

@@ -0,0 +1,40 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.Context;
import sonia.scm.security.PermissionDescriptor;
import sonia.scm.security.PermissionPermissions;
import javax.inject.Inject;
import java.util.Collection;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
public class PermissionCollectionToDtoMapper {
private final ResourceLinks resourceLinks;
@Inject
public PermissionCollectionToDtoMapper(ResourceLinks resourceLinks) {
this.resourceLinks = resourceLinks;
}
public PermissionListDto map(Collection<PermissionDescriptor> permissions, @Context String userId) {
String[] permissionStrings = permissions
.stream()
.map(PermissionDescriptor::getValue)
.toArray(String[]::new);
PermissionListDto target = new PermissionListDto(permissionStrings);
Links.Builder linksBuilder = linkingTo().self(resourceLinks.userPermissions().permissions(userId));
if (PermissionPermissions.assign().isPermitted()) {
linksBuilder.single(link("overwrite", resourceLinks.userPermissions().overwritePermissions(userId)));
}
target.add(linksBuilder.build());
return target;
}
}

View File

@@ -1,5 +1,7 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -9,7 +11,13 @@ import lombok.Setter;
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class PermissionListDto {
public class PermissionListDto extends HalRepresentation {
private String[] permissions;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -96,6 +96,26 @@ class ResourceLinks {
}
}
UserPermissionLinks userPermissions() {
return new UserPermissionLinks(scmPathInfoStore.get());
}
static class UserPermissionLinks {
private final LinkBuilder userPermissionLinkBuilder;
UserPermissionLinks(ScmPathInfo pathInfo) {
this.userPermissionLinkBuilder = new LinkBuilder(pathInfo, UserRootResource.class, UserResource.class, UserPermissionResource.class);
}
public String permissions(String name) {
return userPermissionLinkBuilder.method("getUserResource").parameters(name).method("permissions").parameters().method("getPermissions").parameters().href();
}
public String overwritePermissions(String name) {
return userPermissionLinkBuilder.method("getUserResource").parameters(name).method("permissions").parameters().method("overwritePermissions").parameters().href();
}
}
MeLinks me() {
return new MeLinks(scmPathInfoStore.get(), this.user());
}

View File

@@ -0,0 +1,79 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
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.Response;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
public class UserPermissionResource {
private final PermissionAssigner permissionAssigner;
private final PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
@Inject
public UserPermissionResource(PermissionAssigner permissionAssigner, PermissionCollectionToDtoMapper permissionCollectionToDtoMapper) {
this.permissionAssigner = permissionAssigner;
this.permissionCollectionToDtoMapper = permissionCollectionToDtoMapper;
}
/**
* Returns permissions for a user.
*
* @param id the id/name of the user
*/
@GET
@Path("")
@Produces(VndMediaType.USER)
@TypeHint(UserDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the user"),
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getPermissions(@PathParam("id") String id) {
Collection<PermissionDescriptor> permissions = permissionAssigner.readPermissionsForUser(id);
return Response.ok(permissionCollectionToDtoMapper.map(permissions, id)).build();
}
/**
* Sets permissions for a user. Overwrites all existing permissions.
*
* @param id id of the user to be modified
* @param newPermissions New list of permissions for the user
*/
@PUT
@Path("")
@Consumes(VndMediaType.PERMISSION_COLLECTION)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 400, condition = "Invalid body"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the correct 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 overwritePermissions(@PathParam("id") String id, PermissionListDto newPermissions) {
Collection<PermissionDescriptor> permissionDescriptors = Arrays.stream(newPermissions.getPermissions())
.map(PermissionDescriptor::new)
.collect(Collectors.toList());
permissionAssigner.setPermissionsForUser(id, permissionDescriptors);
return Response.noContent().build();
}
}

View File

@@ -4,9 +4,6 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.authc.credential.PasswordService;
import sonia.scm.security.AssignedPermission;
import sonia.scm.security.PermissionDescriptor;
import sonia.scm.security.SecuritySystem;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
@@ -30,16 +27,20 @@ public class UserResource {
private final IdResourceManagerAdapter<User, UserDto> adapter;
private final UserManager userManager;
private final PasswordService passwordService;
private final SecuritySystem securitySystem;
private final UserPermissionResource userPermissionResource;
@Inject
public UserResource(UserDtoToUserMapper dtoToUserMapper, UserToUserDtoMapper userToDtoMapper, UserManager manager, PasswordService passwordService, SecuritySystem securitySystem) {
public UserResource(
UserDtoToUserMapper dtoToUserMapper,
UserToUserDtoMapper userToDtoMapper,
UserManager manager,
PasswordService passwordService, UserPermissionResource userPermissionResource) {
this.dtoToUserMapper = dtoToUserMapper;
this.userToDtoMapper = userToDtoMapper;
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
this.userManager = manager;
this.passwordService = passwordService;
this.securitySystem = securitySystem;
this.userPermissionResource = userPermissionResource;
}
/**
@@ -137,51 +138,8 @@ public class UserResource {
return Response.noContent().build();
}
/**
* Returns permissions for a user.
*
* @param id the id/name of the user
*/
@GET
@Path("permissions")
@Produces(VndMediaType.USER)
@TypeHint(UserDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the user"),
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getPermissions(@PathParam("id") String id) {
String[] permissions =
securitySystem.getPermissions(p -> !p.isGroupPermission() && p.getName().equals(id))
.stream()
.map(AssignedPermission::getPermission)
.map(PermissionDescriptor::getValue)
.toArray(String[]::new);
return Response.ok(new PermissionListDto(permissions)).build();
}
/**
* Sets permissions for a user. Overwrites all existing permissions.
*
* @param id id of the user to be modified
* @param newPermissions New list of permissions for the user
*/
@PUT
@Path("permissions")
@Consumes(VndMediaType.PASSWORD_OVERWRITE)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 400, condition = "Invalid body"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the correct 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 overwritePermissions(@PathParam("id") String id, PermissionListDto newPermissions) {
return Response.noContent().build();
public UserPermissionResource permissions() {
return userPermissionResource;
}
}

View File

@@ -5,6 +5,7 @@ import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import sonia.scm.security.PermissionPermissions;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.user.UserPermissions;
@@ -42,6 +43,9 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
}
}
if (PermissionPermissions.read().isPermitted()) {
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(target.getName())));
}
appendLinks(new EdisonLinkAppender(linksBuilder), user);