mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-09 15:05:44 +01:00
Fix REST endpoint for user permissions
This commit is contained in:
@@ -6,7 +6,7 @@ import com.github.sdorra.ssp.StaticPermissions;
|
||||
@StaticPermissions(
|
||||
value = "permission",
|
||||
permissions = {},
|
||||
globalPermissions = {"list", "assign"}
|
||||
globalPermissions = {"list", "read", "assign"}
|
||||
)
|
||||
public interface Permission extends PermissionObject {
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public class VndMediaType {
|
||||
public static final String PASSWORD_CHANGE = PREFIX + "passwordChange" + SUFFIX;
|
||||
@SuppressWarnings("squid:S2068")
|
||||
public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX;
|
||||
public static final String PERMISSION_COLLECTION = PREFIX + "permissionCollection" + SUFFIX;
|
||||
public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX;
|
||||
public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ public class ResourceLinksMock {
|
||||
when(resourceLinks.user()).thenReturn(userLinks);
|
||||
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(uriInfo,userLinks));
|
||||
when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(uriInfo));
|
||||
when(resourceLinks.userPermissions()).thenReturn(new ResourceLinks.UserPermissionLinks(uriInfo));
|
||||
when(resourceLinks.autoComplete()).thenReturn(new ResourceLinks.AutoCompleteLinks(uriInfo));
|
||||
when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo));
|
||||
when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo));
|
||||
|
||||
@@ -14,10 +14,12 @@ import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.PageResult;
|
||||
import sonia.scm.security.SecuritySystem;
|
||||
import sonia.scm.security.PermissionAssigner;
|
||||
import sonia.scm.security.PermissionDescriptor;
|
||||
import sonia.scm.user.ChangePasswordNotAllowedException;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
@@ -27,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -61,11 +64,13 @@ public class UserRootResourceTest {
|
||||
@Mock
|
||||
private UserManager userManager;
|
||||
@Mock
|
||||
private SecuritySystem securitySystem;
|
||||
private PermissionAssigner permissionAssigner;
|
||||
@InjectMocks
|
||||
private UserDtoToUserMapperImpl dtoToUserMapper;
|
||||
@InjectMocks
|
||||
private UserToUserDtoMapperImpl userToDtoMapper;
|
||||
@InjectMocks
|
||||
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
||||
|
||||
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||
private User originalUser;
|
||||
@@ -83,7 +88,8 @@ public class UserRootResourceTest {
|
||||
UserCollectionToDtoMapper userCollectionToDtoMapper = new UserCollectionToDtoMapper(userToDtoMapper, resourceLinks);
|
||||
UserCollectionResource userCollectionResource = new UserCollectionResource(userManager, dtoToUserMapper,
|
||||
userCollectionToDtoMapper, resourceLinks, passwordService);
|
||||
UserResource userResource = new UserResource(dtoToUserMapper, userToDtoMapper, userManager, passwordService, securitySystem);
|
||||
UserPermissionResource userPermissionResource = new UserPermissionResource(permissionAssigner, permissionCollectionToDtoMapper);
|
||||
UserResource userResource = new UserResource(dtoToUserMapper, userToDtoMapper, userManager, passwordService, userPermissionResource);
|
||||
UserRootResource userRootResource = new UserRootResource(Providers.of(userCollectionResource),
|
||||
Providers.of(userResource));
|
||||
|
||||
@@ -333,8 +339,6 @@ public class UserRootResourceTest {
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
System.out.println(response.getContentAsString());
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/?page=0"));
|
||||
@@ -351,8 +355,6 @@ public class UserRootResourceTest {
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
System.out.println(response.getContentAsString());
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/?page=1"));
|
||||
@@ -362,6 +364,48 @@ public class UserRootResourceTest {
|
||||
assertTrue(response.getContentAsString().contains("\"last\":{\"href\":\"/v2/users/?page=2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetPermissionLink() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
assertTrue(response.getContentAsString().contains("\"permissions\":{"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetPermissions() throws URISyntaxException {
|
||||
when(permissionAssigner.readPermissionsForUser("Neo")).thenReturn(singletonList(new PermissionDescriptor("something:*")));
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo/permissions");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
assertTrue(response.getContentAsString().contains("\"permissions\":[\"something:*\"]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetPermissions() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.put("/" + UserRootResource.USERS_PATH_V2 + "Neo/permissions")
|
||||
.contentType(VndMediaType.PERMISSION_COLLECTION)
|
||||
.content("{\"permissions\":[\"other:*\"]}".getBytes());
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
ArgumentCaptor<Collection<PermissionDescriptor>> captor = ArgumentCaptor.forClass(Collection.class);
|
||||
doNothing().when(permissionAssigner).setPermissionsForUser(eq("Neo"), captor.capture());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
|
||||
assertEquals("other:*", captor.getValue().iterator().next().getValue());
|
||||
}
|
||||
|
||||
private PageResult<User> createSingletonPageResult(int overallCount) {
|
||||
return new PageResult<>(singletonList(createDummyUser("Neo")), overallCount);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user