Add REST resource for group permissions

This commit is contained in:
René Pfeuffer
2019-01-18 12:06:37 +01:00
parent 5e7405b52d
commit 8000731ab7
9 changed files with 204 additions and 13 deletions

View File

@@ -1,7 +1,7 @@
package sonia.scm.api.v2.resources;
import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor;
import sonia.scm.security.SecuritySystem;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
@@ -13,18 +13,18 @@ import javax.ws.rs.core.Response;
@Path("v2/permissions")
public class GlobalPermissionResource {
private SecuritySystem securitySystem;
private PermissionAssigner permissionAssigner;
@Inject
public GlobalPermissionResource(SecuritySystem securitySystem) {
this.securitySystem = securitySystem;
public GlobalPermissionResource(PermissionAssigner permissionAssigner) {
this.permissionAssigner = permissionAssigner;
}
@GET
@Produces(VndMediaType.PERMISSION_COLLECTION)
@Path("")
public Response getAll() {
String[] permissions = securitySystem.getAvailablePermissions().stream().map(PermissionDescriptor::getValue).toArray(String[]::new);
String[] permissions = permissionAssigner.getAvailablePermissions().stream().map(PermissionDescriptor::getValue).toArray(String[]::new);
return Response.ok(new PermissionListDto(permissions)).build();
}
}

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 GroupPermissionResource {
private final PermissionAssigner permissionAssigner;
private final PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
@Inject
public GroupPermissionResource(PermissionAssigner permissionAssigner, PermissionCollectionToDtoMapper permissionCollectionToDtoMapper) {
this.permissionAssigner = permissionAssigner;
this.permissionCollectionToDtoMapper = permissionCollectionToDtoMapper;
}
/**
* Returns permissions for a group.
*
* @param id the id/name of the group
*/
@GET
@Path("")
@Produces(VndMediaType.PERMISSION_COLLECTION)
@TypeHint(PermissionListDto.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 group"),
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getPermissions(@PathParam("id") String id) {
Collection<PermissionDescriptor> permissions = permissionAssigner.readPermissionsForGroup(id);
return Response.ok(permissionCollectionToDtoMapper.map(permissions, id)).build();
}
/**
* Sets permissions for a group. Overwrites all existing permissions.
*
* @param id id of the group to be modified
* @param newPermissions New list of permissions for the group
*/
@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 group does not have the correct privilege"),
@ResponseCode(code = 404, condition = "not found, no group 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.setPermissionsForGroup(id, permissionDescriptors);
return Response.noContent().build();
}
}

View File

@@ -8,7 +8,6 @@ import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.inject.Named;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -24,13 +23,15 @@ public class GroupResource {
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
private final GroupDtoToGroupMapper dtoToGroupMapper;
private final IdResourceManagerAdapter<Group, GroupDto> adapter;
private final GroupPermissionResource groupPermissionResource;
@Inject
public GroupResource(GroupManager manager, GroupToGroupDtoMapper groupToGroupDtoMapper,
GroupDtoToGroupMapper groupDtoToGroupMapper) {
GroupDtoToGroupMapper groupDtoToGroupMapper, GroupPermissionResource groupPermissionResource) {
this.groupToGroupDtoMapper = groupToGroupDtoMapper;
this.dtoToGroupMapper = groupDtoToGroupMapper;
this.adapter = new IdResourceManagerAdapter<>(manager, Group.class);
this.groupPermissionResource = groupPermissionResource;
}
/**
@@ -100,4 +101,9 @@ public class GroupResource {
public Response update(@PathParam("id") String name, @Valid GroupDto group) {
return adapter.update(name, existing -> dtoToGroupMapper.map(group));
}
@Path("permissions")
public GroupPermissionResource permissions() {
return groupPermissionResource;
}
}

View File

@@ -6,6 +6,7 @@ import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.group.Group;
import sonia.scm.group.GroupPermissions;
import sonia.scm.security.PermissionPermissions;
import javax.inject.Inject;
import java.util.List;
@@ -31,6 +32,9 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto>
if (GroupPermissions.modify(group).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.group().update(target.getName())));
}
if (PermissionPermissions.read().isPermitted()) {
linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(target.getName())));
}
appendLinks(new EdisonLinkAppender(linksBuilder), group);

View File

@@ -116,6 +116,26 @@ class ResourceLinks {
}
}
GroupPermissionLinks groupPermissions() {
return new GroupPermissionLinks(scmPathInfoStore.get());
}
static class GroupPermissionLinks {
private final LinkBuilder groupPermissionLinkBuilder;
GroupPermissionLinks(ScmPathInfo pathInfo) {
this.groupPermissionLinkBuilder = new LinkBuilder(pathInfo, GroupRootResource.class, GroupResource.class, GroupPermissionResource.class);
}
public String permissions(String name) {
return groupPermissionLinkBuilder.method("getGroupResource").parameters(name).method("permissions").parameters().method("getPermissions").parameters().href();
}
public String overwritePermissions(String name) {
return groupPermissionLinkBuilder.method("getGroupResource").parameters(name).method("permissions").parameters().method("overwritePermissions").parameters().href();
}
}
MeLinks me() {
return new MeLinks(scmPathInfoStore.get(), this.user());
}

View File

@@ -37,8 +37,8 @@ public class UserPermissionResource {
*/
@GET
@Path("")
@Produces(VndMediaType.USER)
@TypeHint(UserDto.class)
@Produces(VndMediaType.PERMISSION_COLLECTION)
@TypeHint(PermissionListDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),

View File

@@ -3,6 +3,8 @@ package sonia.scm.security;
import javax.inject.Inject;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PermissionAssigner {
@@ -19,18 +21,46 @@ public class PermissionAssigner {
}
public Collection<PermissionDescriptor> readPermissionsForUser(String id) {
return securitySystem.getPermissions(p -> !p.isGroupPermission() && p.getName().equals(id)).stream().map(AssignedPermission::getPermission).collect(Collectors.toSet());
return readPermissions(filterForUser(id));
}
public Collection<PermissionDescriptor> readPermissionsForGroup(String id) {
return readPermissions(filterForGroup(id));
}
private Predicate<AssignedPermission> filterForUser(String id) {
return p -> !p.isGroupPermission() && p.getName().equals(id);
}
private Predicate<AssignedPermission> filterForGroup(String id) {
return p -> p.isGroupPermission() && p.getName().equals(id);
}
private Set<PermissionDescriptor> readPermissions(Predicate<AssignedPermission> predicate) {
return securitySystem.getPermissions(predicate)
.stream()
.map(AssignedPermission::getPermission)
.collect(Collectors.toSet());
}
public void setPermissionsForUser(String id, Collection<PermissionDescriptor> permissions) {
Collection<AssignedPermission> existingPermissions = securitySystem.getPermissions(p -> !p.isGroupPermission() && p.getName().equals(id));
Collection<AssignedPermission> existingPermissions = securitySystem.getPermissions(filterForUser(id));
adaptPermissions(id, false, permissions, existingPermissions);
}
public void setPermissionsForGroup(String id, Collection<PermissionDescriptor> permissions) {
Collection<AssignedPermission> existingPermissions = securitySystem.getPermissions(filterForGroup(id));
adaptPermissions(id, true, permissions, existingPermissions);
}
private void adaptPermissions(String id, boolean groupPermission, Collection<PermissionDescriptor> permissions, Collection<AssignedPermission> existingPermissions) {
List<AssignedPermission> toRemove = existingPermissions.stream()
.filter(p -> !permissions.contains(p.getPermission()))
.collect(Collectors.toList());
toRemove.forEach(securitySystem::deletePermission);
permissions.stream()
.map(p -> new AssignedPermission(id, false, p))
.map(p -> new AssignedPermission(id, groupPermission, p))
.filter(p -> !existingPermissions.contains(p))
.forEach(securitySystem::addPermission);
}