Skip to content

Commit

Permalink
feat: add endpoint to update roles of a ParticipantContext
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Feb 5, 2024
1 parent 3ab4aa0 commit 9563a06
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ protected Collection<DidDocument> getDidForParticipant(String participantId) {
.build()).getContent();
}

protected ParticipantContext getParticipant(String participantId) {
return getService(ParticipantContextService.class)
.getParticipantContext(participantId)
.orElseThrow(f -> new EdcException(f.getFailureDetail()));
}

protected static ParticipantManifest createNewParticipant() {
var manifest = ParticipantManifest.Builder.newInstance()
.participantId("another-participant")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
import org.eclipse.edc.spi.event.EventRouter;
import org.eclipse.edc.spi.event.EventSubscriber;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;

import java.util.Arrays;

Expand Down Expand Up @@ -83,7 +87,7 @@ void getUserById_notOwner_expect403() {
}

@Test
void createNewUser_principalIsAdmin() {
void createNewUser_principalIsSuperser() {
var subscriber = mock(EventSubscriber.class);
getService(EventRouter.class).registerSync(ParticipantContextCreated.class, subscriber);
var apikey = getSuperUserApiKey();
Expand All @@ -109,7 +113,7 @@ void createNewUser_principalIsAdmin() {


@Test
void createNewUser_principalIsNotAdmin_expect403() {
void createNewUser_principalIsNotSuperser_expect403() {
var subscriber = mock(EventSubscriber.class);
getService(EventRouter.class).registerSync(ParticipantContextCreated.class, subscriber);

Expand Down Expand Up @@ -158,7 +162,7 @@ void createNewUser_principalIsKnown_expect401() {
}

@Test
void activateParticipant_principalIsAdmin() {
void activateParticipant_principalIsSuperser() {
var subscriber = mock(EventSubscriber.class);
getService(EventRouter.class).registerSync(ParticipantContextUpdated.class, subscriber);

Expand Down Expand Up @@ -224,4 +228,37 @@ void regenerateToken() {
.body(notNullValue()));
}

@Test
void updateRoles() {
var participantId = "some-user";
createParticipant(participantId);

RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest()
.header(new Header("x-api-key", getSuperUserApiKey()))
.contentType(ContentType.JSON)
.body(List.of("role1", "role2", "admin"))
.put("/v1/participants/%s/roles".formatted(participantId))
.then()
.log().ifError()
.statusCode(204);

assertThat(getParticipant(participantId).getRoles()).containsExactlyInAnyOrder("role1", "role2", "admin");
}

@ParameterizedTest(name = "Expect 403, role = {0}")
@ValueSource(strings = {"some-role", "admin"})
void updateRoles_whenNotSuperuser(String role) {
var participantId = "some-user";
var userToken = createParticipant(participantId);

RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest()
.header(new Header("x-api-key", userToken))
.contentType(ContentType.JSON)
.body(List.of(role))
.put("/v1/participants/%s/roles".formatted(participantId))
.then()
.log().ifError()
.statusCode(403);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import org.eclipse.edc.identityhub.spi.model.participant.ParticipantManifest;
import org.eclipse.edc.web.spi.ApiErrorDetail;

import java.util.List;

@OpenAPIDefinition(info = @Info(description = "This is the Management API for ParticipantContexts", title = "ParticipantContext Management API", version = "1"))
public interface ParticipantContextApi {

Expand Down Expand Up @@ -109,4 +111,19 @@ public interface ParticipantContextApi {
}
)
void deleteParticipant(String participantId, SecurityContext securityContext);

@Tag(name = "ParticipantContext Management API")
@Operation(description = "Updates a ParticipantContext's roles. Note that this is an absolute update, that means all roles that the Participant should have must be submitted in the body. Requires elevated privileges.",
requestBody = @RequestBody(content = @Content(array = @ArraySchema(schema = @Schema(implementation = List.class)))),
responses = {
@ApiResponse(responseCode = "200", description = "The ParticipantContext was updated successfully"),
@ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")),
@ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")),
@ApiResponse(responseCode = "404", description = "A ParticipantContext with the given ID does not exist.",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json"))
}
)
void updateRoles(String participantId, List<String> roles);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
Expand All @@ -33,6 +34,8 @@
import org.eclipse.edc.identityhub.spi.model.participant.ParticipantManifest;
import org.eclipse.edc.web.spi.exception.ValidationFailureException;

import java.util.List;

import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.edc.identityhub.spi.AuthorizationResultHandler.exceptionMapper;

Expand Down Expand Up @@ -100,4 +103,15 @@ public void deleteParticipant(@PathParam("participantId") String participantId,
.orElseThrow(exceptionMapper(ParticipantContext.class, participantId));
}

@Override
@PUT
@Path("/{participantId}/roles")
@RolesAllowed(ServicePrincipal.ROLE_ADMIN)
public void updateRoles(@PathParam("participantId") String participantId, List<String> roles) {
participantContextService.updateParticipant(participantId, participantContext -> {
participantContext.getRoles().clear();
participantContext.getRoles().addAll(roles);
}).orElseThrow(exceptionMapper(ParticipantContext.class, participantId));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@
import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.util.List;
import java.util.Map;

import static io.restassured.RestAssured.given;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -200,6 +202,38 @@ void delete_notFound() {
verify(participantContextServiceMock).deleteParticipantContext(eq("test-participant"));
}

@Test
void updateRoles() {
when(participantContextServiceMock.updateParticipant(anyString(), any())).thenReturn(ServiceResult.success());

baseRequest()
.body(List.of("role1", "role2"))
.put("/test-participant/roles")
.then()
.log().ifValidationFails()
.statusCode(204);

verify(participantContextServiceMock).updateParticipant(anyString(), argThat(con -> {
var pc = createParticipantContext().build();
con.accept(pc);
return pc.getRoles().containsAll(List.of("role1", "role2"));
}));

}

@Test
void updateRoles_notFound() {
when(participantContextServiceMock.updateParticipant(anyString(), any())).thenReturn(ServiceResult.notFound("foobar"));

baseRequest()
.body(List.of("role1", "role2"))
.put("/test-participant/roles")
.then()
.log().ifValidationFails()
.statusCode(404);
verify(participantContextServiceMock).updateParticipant(anyString(), any());
}

@Override
protected Object controller() {
return new ParticipantContextApiController(new ParticipantManifestValidator(), participantContextServiceMock, authService);
Expand Down

0 comments on commit 9563a06

Please sign in to comment.