Skip to content

Commit 7bef529

Browse files
feat: add delete-holder endpoint (#935)
* feat: add delete-holder endpoint * checkstyle * update api version file
1 parent 7a106a4 commit 7bef529

5 files changed

Lines changed: 106 additions & 6 deletions

File tree

e2e-tests/admin-api-tests/src/test/java/org/eclipse/edc/identityhub/tests/HolderApiEndToEndTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,44 @@ void getById_notAuthorized(IssuerService issuer, HolderService service) {
247247

248248
}
249249

250+
@Test
251+
void deleteHolder(IssuerService issuerService, HolderService service) {
252+
var holder = createHolder("test-participant-id", "did:web:foo", "foobar");
253+
service.createHolder(holder);
254+
255+
issuerService.getAdminEndpoint().baseRequest()
256+
.contentType(ContentType.JSON)
257+
.header(authorizeUser(USER, issuerService))
258+
.delete("/v1alpha/participants/%s/holders/%s".formatted(toBase64(USER), "test-participant-id"))
259+
.then()
260+
.statusCode(204);
261+
}
262+
263+
@Test
264+
void deleteHolder_notFound(IssuerService issuerService, HolderService service) {
265+
266+
issuerService.getAdminEndpoint().baseRequest()
267+
.contentType(ContentType.JSON)
268+
.header(authorizeUser(USER, issuerService))
269+
.delete("/v1alpha/participants/%s/holders/%s".formatted(toBase64(USER), "test-participant-id"))
270+
.then()
271+
.statusCode(404);
272+
}
273+
274+
@Test
275+
void deleteHolder_notAuthorized(IssuerService issuerService, HolderService service) {
276+
issuerService.createParticipant(USER);
277+
var holder = createHolder("test-participant-id", "did:web:foo", "foobar");
278+
service.createHolder(holder);
279+
280+
issuerService.getAdminEndpoint().baseRequest()
281+
.contentType(ContentType.JSON)
282+
.header(authorizeUser("anotherUser", issuerService))
283+
.delete("/v1alpha/participants/%s/holders/%s".formatted(toBase64(USER), "test-participant-id"))
284+
.then()
285+
.statusCode(403);
286+
}
287+
250288
protected abstract Header authorizeUser(String participantContextId, IssuerService issuerService);
251289

252290
private Holder createHolder(String id, String did, String name) {

extensions/api/issuer-admin-api/holder-api/src/main/java/org/eclipse/edc/issuerservice/api/admin/holder/v1/unstable/IssuerHolderAdminApi.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ public interface IssuerHolderAdminApi {
5252
)
5353
Response addHolder(String participantContextId, HolderDto holder, SecurityContext context);
5454

55+
@Operation(description = "Delete a holder.",
56+
operationId = "deleteHolder",
57+
responses = {
58+
@ApiResponse(responseCode = "204", description = "The holder was deleted successfully."),
59+
@ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.",
60+
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")),
61+
@ApiResponse(responseCode = "409", description = "Can't add the holder, because a object with the same ID already exists",
62+
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json"))
63+
}
64+
)
65+
Response deleteHolder(String participantContextId, String holderId, SecurityContext context);
66+
5567
@Operation(description = "Updates holder data.",
5668
operationId = "updateHolder",
5769
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = HolderDto.class), mediaType = "application/json")),

extensions/api/issuer-admin-api/holder-api/src/main/java/org/eclipse/edc/issuerservice/api/admin/holder/v1/unstable/IssuerHolderAdminApiController.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import jakarta.annotation.security.RolesAllowed;
1818
import jakarta.ws.rs.Consumes;
19+
import jakarta.ws.rs.DELETE;
1920
import jakarta.ws.rs.GET;
2021
import jakarta.ws.rs.POST;
2122
import jakarta.ws.rs.PUT;
@@ -59,7 +60,7 @@ public IssuerHolderAdminApiController(AuthorizationService authorizationService,
5960

6061
@POST
6162
@RequiredScope("issuer-admin-api:write")
62-
@RolesAllowed({ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PROVISIONER})
63+
@RolesAllowed({ ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PROVISIONER })
6364
@Override
6465
public Response addHolder(@PathParam("participantContextId") String participantContextId, HolderDto holder, @Context SecurityContext context) {
6566
var decodedParticipantContextId = onEncoded(participantContextId).orElseThrow(InvalidRequestException::new);
@@ -69,9 +70,22 @@ public Response addHolder(@PathParam("participantContextId") String participantC
6970
.orElseThrow(exceptionMapper(Holder.class, holder.id()));
7071
}
7172

73+
@DELETE
74+
@Path("/{holderId}")
75+
@RequiredScope("issuer-admin-api:write")
76+
@RolesAllowed({ ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PROVISIONER })
77+
@Override
78+
public Response deleteHolder(@PathParam("participantContextId") String participantContextId, @PathParam("holderId") String holderId, @Context SecurityContext context) {
79+
var decodedParticipantContextId = onEncoded(participantContextId).orElseThrow(InvalidRequestException::new);
80+
return authorizationService.authorize(context, decodedParticipantContextId, holderId, Holder.class)
81+
.compose(u -> holderService.deleteHolder(holderId))
82+
.map(v -> Response.noContent().build())
83+
.orElseThrow(exceptionMapper(Holder.class, holderId));
84+
}
85+
7286
@PUT
7387
@RequiredScope("issuer-admin-api:write")
74-
@RolesAllowed({ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN})
88+
@RolesAllowed({ ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN })
7589
@Override
7690
public Response updateHolder(@PathParam("participantContextId") String participantContextId, HolderDto holder, @Context SecurityContext context) {
7791
var decodedParticipantContextId = onEncoded(participantContextId).orElseThrow(InvalidRequestException::new);
@@ -83,7 +97,7 @@ public Response updateHolder(@PathParam("participantContextId") String participa
8397

8498
@GET
8599
@RequiredScope("issuer-admin-api:read")
86-
@RolesAllowed({ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN})
100+
@RolesAllowed({ ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN })
87101
@Path("/{holderId}")
88102
@Override
89103
public Holder getHolderById(@PathParam("participantContextId") String participantContextId, @PathParam("holderId") String holderId, @Context SecurityContext context) {
@@ -96,7 +110,7 @@ public Holder getHolderById(@PathParam("participantContextId") String participan
96110

97111
@POST
98112
@RequiredScope("issuer-admin-api:read")
99-
@RolesAllowed({ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN})
113+
@RolesAllowed({ ParticipantPrincipal.ROLE_PARTICIPANT, ParticipantPrincipal.ROLE_ADMIN })
100114
@Path("/query")
101115
@Override
102116
public Collection<Holder> queryHolders(@PathParam("participantContextId") String participantContextId, QuerySpec querySpec, @Context SecurityContext context) {

extensions/api/issuer-admin-api/holder-api/src/test/java/org/eclipse/edc/issuerservice/api/admin/holder/v1/unstable/IssuerHolderAdminApiControllerTest.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ void queryHolders() {
150150
.allSatisfy(d -> assertThat(d).usingRecursiveComparison().isEqualTo(test));
151151
}
152152

153-
154153
@Test
155154
void queryHolders_noneFound() {
156155
when(holderService.queryHolders(any())).thenReturn(ServiceResult.success(Set.of()));
@@ -166,6 +165,43 @@ void queryHolders_noneFound() {
166165
assertThat(dto).isEmpty();
167166
}
168167

168+
@Test
169+
void deleteHolder_success() {
170+
when(holderService.deleteHolder(any())).thenReturn(ServiceResult.success());
171+
172+
baseRequest()
173+
.delete("/test-id")
174+
.then()
175+
.log().ifValidationFails()
176+
.statusCode(204)
177+
.body(emptyString());
178+
}
179+
180+
@Test
181+
void deleteHolder_notFound() {
182+
when(holderService.deleteHolder(any())).thenReturn(ServiceResult.notFound("not found"));
183+
184+
baseRequest()
185+
.delete("/test-id")
186+
.then()
187+
.log().ifValidationFails()
188+
.statusCode(404)
189+
.body(notNullValue());
190+
}
191+
192+
@Test
193+
void deleteHolder_whenHasCredentials() {
194+
when(holderService.deleteHolder(any())).thenReturn(ServiceResult.conflict("holder has associated credentials"));
195+
196+
baseRequest()
197+
.delete("/test-id")
198+
.then()
199+
.log().ifValidationFails()
200+
.statusCode(409)
201+
.body(notNullValue());
202+
}
203+
204+
169205
@Override
170206
protected Object controller() {
171207
return new IssuerHolderAdminApiController(authorizationService, holderService);

extensions/api/issuer-admin-api/issuer-admin-api-configuration/src/main/resources/issuer-admin-api-version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{
33
"version": "1.0.0-alpha",
44
"urlPath": "/v1alpha",
5-
"lastUpdated": "2026-02-17T16:00:00Z",
5+
"lastUpdated": "2026-03-04T15:00:00Z",
66
"maturity": null
77
}
88
]

0 commit comments

Comments
 (0)