Skip to content

Commit

Permalink
added API validator
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Oct 24, 2023
1 parent f46db9f commit 05f8e23
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 8 deletions.
2 changes: 2 additions & 0 deletions core/identity-hub-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ dependencies {
implementation(libs.edc.spi.validator)
implementation(libs.edc.spi.web)
implementation(libs.jakarta.rsApi)
testImplementation(libs.edc.junit)
testImplementation(libs.edc.ext.jsonld)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,29 @@
package org.eclipse.edc.identityservice.api.validation;

import jakarta.json.JsonObject;
import org.eclipse.edc.identityhub.spi.model.PresentationQuery;
import org.eclipse.edc.validator.spi.ValidationResult;
import org.eclipse.edc.validator.spi.Validator;

import static org.eclipse.edc.validator.spi.ValidationResult.failure;
import static org.eclipse.edc.validator.spi.ValidationResult.success;
import static org.eclipse.edc.validator.spi.Violation.violation;

public class PresentationQueryValidator implements Validator<JsonObject> {
@Override
public ValidationResult validate(JsonObject input) {
return null;
var scope = input.get(PresentationQuery.PRESENTATION_QUERY_SCOPE_PROPERTY);

var presentationDef = input.get(PresentationQuery.PRESENTATION_QUERY_DEFINITION_PROPERTY);

if (scope == null && presentationDef == null) {
return failure(violation("Must contain either a 'scope' or a 'presentation_definition' property.", null));
}

if (scope != null && presentationDef != null) {
return failure(violation("Must contain either a 'scope' or a 'presentation_definition', not both.", null));
}

return success();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.identityservice.api.validation;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import org.eclipse.edc.identityhub.spi.model.PresentationQuery;
import org.eclipse.edc.identityhub.spi.model.presentationdefinition.Constraints;
import org.eclipse.edc.identityhub.spi.model.presentationdefinition.Field;
import org.eclipse.edc.identityhub.spi.model.presentationdefinition.InputDescriptor;
import org.eclipse.edc.identityhub.spi.model.presentationdefinition.PresentationDefinition;
import org.eclipse.edc.jsonld.TitaniumJsonLd;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.jsonld.spi.JsonLdKeywords;
import org.eclipse.edc.jsonld.util.JacksonJsonLd;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.UUID;

import static jakarta.json.Json.createArrayBuilder;
import static jakarta.json.Json.createObjectBuilder;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.mockito.Mockito.mock;

class PresentationQueryValidatorTest {
public static final ObjectMapper MAPPER = JacksonJsonLd.createObjectMapper();
private final PresentationQueryValidator validator = new PresentationQueryValidator();
private final JsonLd jsonLd = new TitaniumJsonLd(mock());


@Test
void validate_withScope_success() {
var jo = createObjectBuilder()
.add(PresentationQuery.PRESENTATION_QUERY_SCOPE_PROPERTY, createScopeArray())
.build();

assertThat(validator.validate(jo)).isSucceeded();
}

@Test
void validate_withPresentationDefinition_success() throws JsonProcessingException {
var presDef = PresentationDefinition.Builder.newInstance()
.id(UUID.randomUUID().toString())
.inputDescriptors(List.of(InputDescriptor.Builder.newInstance().id(UUID.randomUUID().toString()).constraints(new Constraints(List.of(Field.Builder.newInstance().build()))).build()))
.build();
var jo = createObjectBuilder()
.add(PresentationQuery.PRESENTATION_QUERY_DEFINITION_PROPERTY, createPresentationDefArray(presDef))
.build();

assertThat(validator.validate(jo)).isSucceeded();
}


@Test
void validate_withNone_fails() {
var jo = createObjectBuilder().build();
assertThat(validator.validate(jo)).isFailed().detail().contains("Must contain either a 'scope' or a 'presentation_definition' property.");
}

@Test
void validate_withBoth_fails() throws JsonProcessingException {
var presDef = PresentationDefinition.Builder.newInstance()
.id(UUID.randomUUID().toString())
.inputDescriptors(List.of(InputDescriptor.Builder.newInstance().id(UUID.randomUUID().toString()).constraints(new Constraints(List.of(Field.Builder.newInstance().build()))).build()))
.build();
var jo = createObjectBuilder()
.add(PresentationQuery.PRESENTATION_QUERY_SCOPE_PROPERTY, createScopeArray())
.add(PresentationQuery.PRESENTATION_QUERY_DEFINITION_PROPERTY, createPresentationDefArray(presDef))
.build();

assertThat(validator.validate(jo)).isFailed().detail().contains("Must contain either a 'scope' or a 'presentation_definition', not both.");
}

private JsonArray createScopeArray() {
return createArrayBuilder()
.add(createObjectBuilder().add(JsonLdKeywords.VALUE, "scope1"))
.add(createObjectBuilder().add(JsonLdKeywords.VALUE, "scope2"))
.build();
}

private JsonArray createPresentationDefArray(PresentationDefinition presDef) throws JsonProcessingException {
var val = MAPPER.writeValueAsString(presDef);
return createArrayBuilder()
.add(createObjectBuilder()
.add(JsonLdKeywords.TYPE, JsonLdKeywords.JSON)
.add(JsonLdKeywords.VALUE, MAPPER.readValue(val, JsonObject.class))
.build())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public JsonObjectToPresentationQueryTransformer(ObjectMapper mapper) {
visitProperties(jsonObject, (k, v) -> {
switch (k) {
case PresentationQuery.PRESENTATION_QUERY_DEFINITION_PROPERTY -> bldr.presentationDefinition(readPresentationDefinition(v));
case PresentationQuery.PRESENTATION_QUERY_SCOPE_PROPERTY -> bldr.scopes(transformArray(v, String.class, context));
case PresentationQuery.PRESENTATION_QUERY_SCOPE_PROPERTY -> transformArrayOrObject(v, Object.class, o -> bldr.scope(o.toString()), context);
default -> context.reportProblem("unknown property '%s'".formatted(k));
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public Builder scopes(List<String> scopes) {
return this;
}

public Builder scope(String scope) {
this.query.scopes.add(scope);
return this;
}

public Builder presentationDefinition(PresentationDefinition presentationDefinition) {
this.query.presentationDefinition = presentationDefinition;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@

package org.eclipse.edc.identityhub.spi.model.presentationdefinition;

import java.util.ArrayList;
import java.util.List;

public class Constraints {
private final List<Field> fields = new ArrayList<>();
public record Constraints(List<Field> fields) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class Field {
private final List<String> paths = new ArrayList<>();
private List<String> paths = new ArrayList<>();
private String id;
private String name;
private String purpose;
private FilterExpression expr;

private Field() {

}

public String getId() {
return id;
}
Expand All @@ -43,4 +48,47 @@ public List<String> getPaths() {
public FilterExpression getExpr() {
return expr;
}


public static final class Builder {
private final Field field;

private Builder() {
field = new Field();
}

public static Builder newInstance() {
return new Builder();
}

public Builder paths(List<String> paths) {
this.field.paths = paths;
return this;
}

public Builder id(String id) {
this.field.id = id;
return this;
}

public Builder name(String name) {
this.field.name = name;
return this;
}

public Builder purpose(String purpose) {
this.field.purpose = purpose;
return this;
}

public Builder expr(FilterExpression expr) {
this.field.expr = expr;
return this;
}

public Field build() {
Objects.requireNonNull(field.paths, "Must contain a paths property.");
return field;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ public Builder constraints(Constraints constraints) {
}

public InputDescriptor build() {
Objects.requireNonNull(descriptor.id, "PresentationDefinition must have an ID.");
Objects.requireNonNull(descriptor.constraints, "PresentationDefinition must have a Constraints object.");
Objects.requireNonNull(descriptor.id, "InputDescriptor must have an ID.");
Objects.requireNonNull(descriptor.constraints, "InputDescriptor must have a Constraints object.");
return descriptor;
}
}
Expand Down

0 comments on commit 05f8e23

Please sign in to comment.