Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add endpoint to update roles of a ParticipantContext #257

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,8 +26,11 @@
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.Arrays;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.anyOf;
Expand Down Expand Up @@ -83,7 +86,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 +112,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 +161,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 +227,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
Loading