Skip to content

Commit

Permalink
fix(api): gracefully handle missing type in query credential by type (#…
Browse files Browse the repository at this point in the history
…490)

* fix(api): gracefully handle missing type in query credential by type

* checkstyle

* added parameter description [skip ci]
  • Loading branch information
paullatzelsperger authored Nov 13, 2024
1 parent f10c1f1 commit cdb8379
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,46 @@ void delete(IdentityHubEndToEndTestContext context) {
});
}

@Test
void queryByType(IdentityHubEndToEndTestContext context) {
var superUserKey = context.createSuperUser();
var user = "user1";
var token = context.createParticipant(user);

var credential = context.createCredential();
context.storeCredential(credential, user);

assertThat(Arrays.asList(token, superUserKey))
.allSatisfy(t -> context.getIdentityApiEndpoint().baseRequest()
.contentType(JSON)
.header(new Header("x-api-key", t))
.get("/v1alpha/participants/%s/credentials?type=%s".formatted(toBase64(user), credential.getType().get(0)))
.then()
.log().ifValidationFails()
.statusCode(200)
.body(notNullValue()));
}

@Test
void queryByTyp_noTypeSpecified(IdentityHubEndToEndTestContext context) {
var superUserKey = context.createSuperUser();
var user = "user1";
var token = context.createParticipant(user);

var credential = context.createCredential();
context.storeCredential(credential, user);

assertThat(Arrays.asList(token, superUserKey))
.allSatisfy(t -> context.getIdentityApiEndpoint().baseRequest()
.contentType(JSON)
.header(new Header("x-api-key", t))
.get("/v1alpha/participants/%s/credentials".formatted(toBase64(user)))
.then()
.log().ifValidationFails()
.statusCode(200)
.body(notNullValue()));
}

private String toBase64(String s) {
return Base64.getUrlEncoder().encodeToString(s.getBytes());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.junit.jupiter.api.extension.RegisterExtension;

import java.util.HashMap;
import java.util.Map;

import static io.restassured.RestAssured.given;
import static java.lang.String.valueOf;
Expand Down Expand Up @@ -58,17 +57,23 @@ class IdentityHub extends SmokeTest {
@RegisterExtension
protected RuntimeExtension runtime =
new RuntimePerMethodExtension(new EmbeddedRuntime("identityhub-bom",
Map.of(
"web.http.port", DEFAULT_PORT,
"web.http.path", DEFAULT_PATH,
"edc.ih.iam.id", "did:web:test",
"edc.ih.iam.publickey.path", "/some/path/to/key.pem",
"web.http.presentation.port", valueOf(getFreePort()),
"web.http.presentation.path", "/api/resolution",
"web.http.identity.port", valueOf(getFreePort()),
"web.http.identity.path", "/api/identity",
"edc.sts.account.api.url", "https://sts.com/accounts",
"edc.sts.accounts.api.auth.header.value", "password"),
new HashMap<>() {

{
put("web.http.port", DEFAULT_PORT);
put("web.http.path", DEFAULT_PATH);
put("edc.ih.iam.id", "did:web:test");
put("edc.ih.iam.publickey.path", "/some/path/to/key.pem");
put("web.http.presentation.port", valueOf(getFreePort()));
put("web.http.presentation.path", "/api/resolution");
put("web.http.identity.port", valueOf(getFreePort()));
put("web.http.identity.path", "/api/identity");
put("web.http.version.port", valueOf(getFreePort()));
put("web.http.version.path", "/api/version");
put("edc.sts.account.api.url", "https://sts.com/accounts");
put("edc.sts.accounts.api.auth.header.value", "password");
}
},
":dist:bom:identityhub-bom"
));
}
Expand All @@ -92,6 +97,8 @@ class IdentityHubWithSts extends SmokeTest {
put("web.http.identity.path", "/api/identity");
put("web.http.accounts.port", valueOf(getFreePort()));
put("web.http.accounts.path", "/api/accounts");
put("web.http.version.port", valueOf(getFreePort()));
put("web.http.version.path", "/api/version");
put("edc.api.accounts.key", "password");
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ public interface VerifiableCredentialsApi {
@Operation(description = "Query VerifiableCredentials by type.",
operationId = "queryCredentialsByType",
parameters = {
@Parameter(name = "participantId", description = "Base64-Url encode Participant Context ID", required = true, in = ParameterIn.PATH)
@Parameter(name = "participantId", description = "Base64-Url encode Participant Context ID", required = true, in = ParameterIn.PATH),
@Parameter(name = "type", description = "Credential type. If omitted, all credentials are returned (limited to 50 elements).")
},
responses = {
@ApiResponse(responseCode = "200", description = "The list of VerifiableCredentials.",
Expand All @@ -114,7 +115,7 @@ public interface VerifiableCredentialsApi {
@Parameter(name = "participantId", description = "Base64-Url encode Participant Context ID", required = true, in = ParameterIn.PATH),
},
responses = {
@ApiResponse(responseCode = "200", description = "The VerifiableCredential was deleted successfully", content = {@Content(schema = @Schema(implementation = String.class))}),
@ApiResponse(responseCode = "200", description = "The VerifiableCredential was deleted successfully", content = { @Content(schema = @Schema(implementation = String.class)) }),
@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 = "403", description = "The request could not be completed, because either the authentication was missing or was not valid.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.eclipse.edc.util.string.StringUtils;
import org.eclipse.edc.web.spi.exception.InvalidRequestException;
import org.eclipse.edc.web.spi.exception.ObjectNotFoundException;
import org.eclipse.edc.web.spi.exception.ValidationFailureException;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;

Expand Down Expand Up @@ -106,12 +108,14 @@ public void updateCredential(VerifiableCredentialManifest manifest, @Context Sec

@GET
@Override
public Collection<VerifiableCredentialResource> queryCredentialsByType(@QueryParam("type") String type, @Context SecurityContext securityContext) {
var query = QuerySpec.Builder.newInstance()
.filter(new Criterion("verifiableCredential.credential.types", "contains", type))
.build();
public Collection<VerifiableCredentialResource> queryCredentialsByType(@Nullable @QueryParam("type") String type, @Context SecurityContext securityContext) {
var query = QuerySpec.Builder.newInstance();

return credentialStore.query(query)
if (!StringUtils.isNullOrEmpty(type)) {
query.filter(new Criterion("verifiableCredential.credential.types", "contains", type));
}

return credentialStore.query(query.build())
.orElseThrow(InvalidRequestException::new)
.stream()
.filter(vcr -> authorizationService.isAuthorized(securityContext, vcr.getId(), VerifiableCredentialResource.class).succeeded())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,24 @@ void notAuthorized_returns403() {
verifyNoMoreInteractions(credentialStore);
}

@Test
void typeNotSpecified_returnsAllCredentials() {
var credential1 = createCredentialResource("test-type").build();
var credential2 = createCredentialResource("test-type").build();
when(credentialStore.query(any())).thenReturn(StoreResult.success(List.of(credential1, credential2)));

var result = baseRequest()
.get()
.then()
.log().ifValidationFails()
.statusCode(200)
.extract().body().as(VerifiableCredentialResource[].class);

assertThat(result).usingRecursiveFieldByFieldElementComparatorIgnoringFields("clock").containsExactlyInAnyOrder(credential1, credential2);
verify(credentialStore).query(any());
verifyNoMoreInteractions(credentialStore);
}

@Test
void emptyResult() {
when(credentialStore.query(any())).thenReturn(StoreResult.success(List.of()));
Expand Down

0 comments on commit cdb8379

Please sign in to comment.