diff --git a/backend/README.md b/backend/README.md index 6cb456e46..103027a35 100644 --- a/backend/README.md +++ b/backend/README.md @@ -32,7 +32,7 @@ https://docs.micronaut.io/latest/guide/index.html#ideSetup 1. CLI: Build the UI -```s +``` mvn clean package -Pbuild-frontend ``` @@ -41,7 +41,9 @@ mvn clean package -Pbuild-frontend Depending on the docker version the .env file either needs to reside in the root directory (older versions) or in the script's directory (newer versions) [See .env file set up](https://github.com/hyperledger-labs/business-partner-agent/blob/master/scripts/README.md) - +``` +scripts/.env-example -> scripts/.env +``` 3. Start dependent services ```s # e.g. run from the scripts directory @@ -73,4 +75,13 @@ If you want to run in web only mode you also have to set: 5. Access the UI Swagger UI: http://localhost:8080/swagger-ui -Frontend: http://localhost:8080 \ No newline at end of file +Frontend: http://localhost:8080 + +##FAQ +I get a "Micronaut - Error starting Micronaut server: Switching from web only mode to aries is not supported" error? +The schema for web and aries mode differ and the database has to be reset. +` +docker-compose -f scripts/docker-compose.yml down && +docker volume rm scripts_bpa-wallet-db1 && +docker-compose -f scripts/docker-compose.yml up bpa-agent1 bpa-wallet-db1 +` \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/exception/ProofTemplateException.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/exception/ProofTemplateException.java new file mode 100644 index 000000000..e5f1fc375 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/exception/ProofTemplateException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.api.exception; + +public class ProofTemplateException extends RuntimeException { + public ProofTemplateException() { + } + + public ProofTemplateException(String message) { + super(message); + } + + public ProofTemplateException(String message, Throwable cause) { + super(message, cause); + } + + public ProofTemplateException(Throwable cause) { + super(cause); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ActivityLogConfig.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ActivityLogConfig.java index 4c1775960..1af45ea2a 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ActivityLogConfig.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ActivityLogConfig.java @@ -44,9 +44,11 @@ public class ActivityLogConfig { ConnectionState.PING_RESPONSE, ConnectionState.PING_NO_RESPONSE); - private static List PRESENTATION_EXCHANGE_STATES_TASKS = List.of(PresentationExchangeState.REQUEST_RECEIVED); + private static List PRESENTATION_EXCHANGE_STATES_TASKS = List + .of(PresentationExchangeState.REQUEST_RECEIVED); - private static List PRESENTATION_EXCHANGE_STATES_COMPLETED = List.of(PresentationExchangeState.VERIFIED, + private static List PRESENTATION_EXCHANGE_STATES_COMPLETED = List.of( + PresentationExchangeState.VERIFIED, PresentationExchangeState.PRESENTATION_ACKED); private List connectionStatesForActivities; @@ -63,7 +65,8 @@ public class ActivityLogConfig { this.acaPyConfig = acaPyConfig; // 1. set the tasks lists first as they depend on aca py configuration connectionStatesForTasks = this.isConnectionRequestTask() ? CONNECTION_STATES_TASKS : List.of(); - presentationExchangeStatesForTasks = this.isPresentationExchangeTask() ? PRESENTATION_EXCHANGE_STATES_TASKS : List.of(); + presentationExchangeStatesForTasks = this.isPresentationExchangeTask() ? PRESENTATION_EXCHANGE_STATES_TASKS + : List.of(); // 2. set the completed state lists connectionStatesForCompleted = CONNECTION_STATES_COMPLETED; diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ClockFactory.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ClockFactory.java new file mode 100644 index 000000000..8f455e1a5 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ClockFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.config; + +import io.micronaut.context.annotation.Factory; + +import javax.inject.Singleton; +import java.time.Clock; + +@Factory +public class ClockFactory { + + @Singleton + Clock systemClock() { + return Clock.systemUTC(); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/TypeConverters.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/TypeConverters.java new file mode 100644 index 000000000..906786c53 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/TypeConverters.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.micronaut.context.annotation.Factory; +import io.micronaut.core.convert.TypeConverter; +import lombok.extern.slf4j.Slf4j; +import org.hyperledger.bpa.model.prooftemplate.BPAAttributeGroup; +import org.hyperledger.bpa.model.prooftemplate.BPAAttributeGroups; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.List; +import java.util.Optional; + +@Slf4j +@Factory +public class TypeConverters { + + public static final TypeReference> ATTR_REF = new TypeReference<>() { + }; + + @Inject + ObjectMapper mapper; + + @Singleton + TypeConverter attrsToString() { + return (object, targetType, context) -> Optional.ofNullable(attributeToString(object)); + } + + @Singleton + TypeConverter stringToAttrs() { + return (object, targetType, context) -> Optional.ofNullable(stringToAttribute(object)); + } + + private String attributeToString(BPAAttributeGroups f) { + String res = null; + try { + res = mapper.writeValueAsString(f); + } catch (JsonProcessingException e) { + log.error("could not convert to json: ", e); + } + return res; + } + + private BPAAttributeGroups stringToAttribute(String f) { + BPAAttributeGroups res = null; + try { + res = mapper.readValue(f, BPAAttributeGroups.class); + } catch (JsonProcessingException e) { + log.error("could not convert from json: {}", f, e); + } + return res; + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/ProofTemplateController.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/ProofTemplateController.java new file mode 100644 index 000000000..25729d2db --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/ProofTemplateController.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.controller; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.*; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.validation.Validated; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.hyperledger.bpa.controller.api.prooftemplates.ProofTemplate; +import org.hyperledger.bpa.impl.ProofTemplateManager; +import org.hyperledger.bpa.impl.verification.ValidUUID; +import org.hyperledger.bpa.model.BPAProofTemplate; + +import javax.inject.Inject; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.*; +import java.util.stream.Collectors; + +@Controller("/api/proof-templates") +@Tag(name = "Proof Template Management") +@Validated +@Secured(SecurityRule.IS_AUTHENTICATED) +@ExecuteOn(TaskExecutors.IO) +public class ProofTemplateController { + + @Inject + private ProofTemplateManager proofTemplateManager; + + @Get + public HttpResponse> listProofTemplates() { + return HttpResponse.ok( + proofTemplateManager.listProofTemplates() + .map(BPAProofTemplate::toRepresentation) + .collect(Collectors.toList())); + } + + @Post + public HttpResponse addProofTemplate(@Valid @Body ProofTemplate template) { + if (template.getId() == null) { + BPAProofTemplate persistedTemplate = proofTemplateManager + .addProofTemplate(BPAProofTemplate.fromRepresentation(template)); + return HttpResponse.created(persistedTemplate.toRepresentation()); + } else { + return HttpResponse.badRequest(template); + } + } + + // TODO add possibility to update a template, because we might refer to + // templates via FK, updates have to create new entities. + + @Get("/known-condition-operators") + public HttpResponse> listKnownConditionOperators() { + return HttpResponse.ok(proofTemplateManager.getKnownConditionOperators()); + } + + @Put("/{id}/proof-request/{partnerId}") + public HttpResponse invokeProofRequestByTemplate( + @PathVariable @ValidUUID @NotNull String id, + @PathVariable @ValidUUID @NotNull String partnerId) { + proofTemplateManager.invokeProofRequestByTemplate(UUID.fromString(id), UUID.fromString(partnerId)); + return HttpResponse.ok(); + } + + @Delete("/{id}") + public HttpResponse removeProofTemplate(@PathVariable @ValidUUID @NotNull String id) { + proofTemplateManager.removeProofTemplate(UUID.fromString(id)); + return HttpResponse.ok(); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/Attribute.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/Attribute.java new file mode 100644 index 000000000..b443c4ce5 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/Attribute.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.controller.api.prooftemplates; + +import io.micronaut.core.annotation.Introspected; +import lombok.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +public class Attribute { + @NotNull + @NotEmpty + String name; + @Valid + @Singular + @NotNull + List conditions; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/AttributeGroup.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/AttributeGroup.java new file mode 100644 index 000000000..721a52cd8 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/AttributeGroup.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.controller.api.prooftemplates; + +import io.micronaut.core.annotation.Introspected; +import lombok.*; +import org.hyperledger.bpa.impl.verification.ValidUUID; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +public class AttributeGroup { + @NotNull + @ValidUUID + String schemaId; + + @Singular + @Valid + @NotNull + List attributes; + + @NotNull + @Builder.Default + Boolean nonRevoked = Boolean.FALSE; + @NotNull + @Builder.Default + @Valid + SchemaRestrictions schemaLevelRestrictions = SchemaRestrictions.builder().build(); +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/ProofTemplate.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/ProofTemplate.java new file mode 100644 index 000000000..8426ea1e4 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/ProofTemplate.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.controller.api.prooftemplates; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; +import lombok.*; +import org.hyperledger.bpa.impl.verification.ValidUUID; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import java.time.Instant; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +public class ProofTemplate { + @Nullable + @ValidUUID + String id; + @Nullable + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone = "UTC") + private Instant createdAt; + + @NotEmpty + String name; + @NotEmpty + @Valid + @Singular + List attributeGroups; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/SchemaRestrictions.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/SchemaRestrictions.java new file mode 100644 index 000000000..c8c81a412 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/SchemaRestrictions.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.controller.api.prooftemplates; + +import io.micronaut.core.annotation.Nullable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.impl.verification.ValidUUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SchemaRestrictions { + + @Nullable + @ValidUUID + private String schemaId; + @Nullable + private String schemaName; + @Nullable + private String schemaVersion; + @Nullable + private String schemaIssuerDid; + @Nullable + private String credentialDefinitionId; + @Nullable + private String issuerDid; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/ValueCondition.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/ValueCondition.java new file mode 100644 index 000000000..e6ff1347e --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/prooftemplates/ValueCondition.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.controller.api.prooftemplates; + +import io.micronaut.core.annotation.Introspected; +import lombok.*; +import org.hyperledger.bpa.model.prooftemplate.ValueOperators; + +import javax.validation.constraints.NotNull; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +public class ValueCondition { + @NotNull + ValueOperators operator; + @NonNull + String value; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java index b1f887810..c66b6a983 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java @@ -114,23 +114,23 @@ public void completePartnerRequestTask(@NonNull Partner partner) { activityRepository.findByLinkIdAndTypeAndRole(partner.getId(), ActivityType.CONNECTION_REQUEST, ActivityRole.CONNECTION_REQUEST_RECIPIENT).ifPresentOrElse(activity -> { - // set to completed and mark accepted - activity.setState(ActivityState.CONNECTION_REQUEST_ACCEPTED); - activity.setCompleted(true); - activityRepository.update(activity); - eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); - }, () -> { - // add in a completed activity - Activity a = Activity.builder() - .linkId(partner.getId()) - .partner(partner) - .type(ActivityType.CONNECTION_REQUEST) - .role(ActivityRole.CONNECTION_REQUEST_RECIPIENT) - .state(ActivityState.CONNECTION_REQUEST_ACCEPTED) - .completed(true) - .build(); - activityRepository.save(a); - }); + // set to completed and mark accepted + activity.setState(ActivityState.CONNECTION_REQUEST_ACCEPTED); + activity.setCompleted(true); + activityRepository.update(activity); + eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); + }, () -> { + // add in a completed activity + Activity a = Activity.builder() + .linkId(partner.getId()) + .partner(partner) + .type(ActivityType.CONNECTION_REQUEST) + .role(ActivityRole.CONNECTION_REQUEST_RECIPIENT) + .state(ActivityState.CONNECTION_REQUEST_ACCEPTED) + .completed(true) + .build(); + activityRepository.save(a); + }); } public void deletePartnerActivities(@NonNull Partner partner) { @@ -158,21 +158,21 @@ public void addPartnerAcceptedActivity(@NonNull Partner partner) { activityRepository.findByLinkIdAndTypeAndRole(partner.getId(), ActivityType.CONNECTION_REQUEST, ActivityRole.CONNECTION_REQUEST_SENDER).ifPresentOrElse(activity -> { - activity.setState(ActivityState.CONNECTION_REQUEST_ACCEPTED); - activity.setCompleted(true); - activityRepository.update(activity); - }, () -> { - // add in a completed activity - Activity a = Activity.builder() - .linkId(partner.getId()) - .partner(partner) - .type(ActivityType.CONNECTION_REQUEST) - .role(ActivityRole.CONNECTION_REQUEST_SENDER) - .state(ActivityState.CONNECTION_REQUEST_ACCEPTED) - .completed(true) - .build(); - activityRepository.save(a); - }); + activity.setState(ActivityState.CONNECTION_REQUEST_ACCEPTED); + activity.setCompleted(true); + activityRepository.update(activity); + }, () -> { + // add in a completed activity + Activity a = Activity.builder() + .linkId(partner.getId()) + .partner(partner) + .type(ActivityType.CONNECTION_REQUEST) + .role(ActivityRole.CONNECTION_REQUEST_SENDER) + .state(ActivityState.CONNECTION_REQUEST_ACCEPTED) + .completed(true) + .build(); + activityRepository.save(a); + }); } public void addPresentationExchangeTask(@NonNull PartnerProof partnerProof) { @@ -213,24 +213,24 @@ public void completePresentationExchangeTask(@NonNull PartnerProof partnerProof) activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), ActivityType.PRESENTATION_EXCHANGE, role).ifPresentOrElse(activity -> { - // set to completed and mark accepted - activity.setState(state); - activity.setCompleted(true); - activityRepository.update(activity); + // set to completed and mark accepted + activity.setState(state); + activity.setCompleted(true); + activityRepository.update(activity); - eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); - }, () -> { - // add in a completed activity - Activity a = Activity.builder() - .linkId(partnerProof.getId()) - .partner(partner) - .type(ActivityType.PRESENTATION_EXCHANGE) - .role(role) - .state(state) - .completed(true) - .build(); - activityRepository.save(a); - }); + eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); + }, () -> { + // add in a completed activity + Activity a = Activity.builder() + .linkId(partnerProof.getId()) + .partner(partner) + .type(ActivityType.PRESENTATION_EXCHANGE) + .role(role) + .state(state) + .completed(true) + .build(); + activityRepository.save(a); + }); }); } @@ -242,11 +242,11 @@ public void declinePresentationExchangeTask(@NonNull PartnerProof partnerProof) activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), ActivityType.PRESENTATION_EXCHANGE, role).ifPresent(activity -> { - activity.setState(ActivityState.PRESENTATION_EXCHANGE_DECLINED); - activity.setCompleted(true); - activityRepository.update(activity); - eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); - }); + activity.setState(ActivityState.PRESENTATION_EXCHANGE_DECLINED); + activity.setCompleted(true); + activityRepository.update(activity); + eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); + }); }); } @@ -255,9 +255,9 @@ public void deletePresentationExchangeTask(@NonNull PartnerProof partnerProof) { activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), ActivityType.PRESENTATION_EXCHANGE, role).ifPresent(activity -> { - activityRepository.delete(activity); - eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); - }); + activityRepository.delete(activity); + eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); + }); } private ActivityItem convert(Activity activity) { @@ -275,23 +275,23 @@ private ActivityItem convert(Activity activity) { private ActivityState getPresentationExchangeState(PartnerProof partnerProof) { switch (partnerProof.getState()) { - case VERIFIED: - case PRESENTATION_ACKED: - return ActivityState.PRESENTATION_EXCHANGE_ACCEPTED; - case REQUEST_SENT: - case PRESENTATIONS_SENT: - return ActivityState.PRESENTATION_EXCHANGE_SENT; - case REQUEST_RECEIVED: - case PRESENTATION_RECEIVED: + case VERIFIED: + case PRESENTATION_ACKED: + return ActivityState.PRESENTATION_EXCHANGE_ACCEPTED; + case REQUEST_SENT: + case PRESENTATIONS_SENT: + return ActivityState.PRESENTATION_EXCHANGE_SENT; + case REQUEST_RECEIVED: + case PRESENTATION_RECEIVED: + return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; + default: + switch (partnerProof.getRole()) { + case VERIFIER: return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; + case PROVER: default: - switch (partnerProof.getRole()) { - case VERIFIER: - return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; - case PROVER: - default: - return ActivityState.PRESENTATION_EXCHANGE_SENT; - } + return ActivityState.PRESENTATION_EXCHANGE_SENT; + } } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ProofTemplateManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ProofTemplateManager.java new file mode 100644 index 000000000..1e48a10b2 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ProofTemplateManager.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl; + +import lombok.AllArgsConstructor; +import org.hyperledger.bpa.api.exception.ProofTemplateException; +import org.hyperledger.bpa.impl.aries.ProofManager; +import org.hyperledger.bpa.model.BPAProofTemplate; +import org.hyperledger.bpa.model.prooftemplate.ValueOperators; +import org.hyperledger.bpa.repository.BPAProofTemplateRepository; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +@AllArgsConstructor +@Singleton +public class ProofTemplateManager { + + @Inject + private final BPAProofTemplateRepository repo; + + @Inject + private final ProofManager proofManager; + + public void invokeProofRequestByTemplate(@NotNull UUID id, @NotNull UUID partnerId) { + Optional proofTemplate = repo.findById(id); + proofTemplate.ifPresent(t -> proofManager.sendPresentProofRequest(partnerId, t)); + proofTemplate.orElseThrow(() -> new ProofTemplateException("No proof template found for: " + id)); + } + + public BPAProofTemplate addProofTemplate(BPAProofTemplate template) { + return repo.save(template); + } + + public Stream listProofTemplates() { + return StreamSupport.stream(repo.findAll().spliterator(), false); + } + + public void removeProofTemplate(UUID templateId) { + repo.deleteById(templateId); + } + + public Set getKnownConditionOperators() { + return Arrays.stream(ValueOperators.values()).map(ValueOperators::getValue).collect(Collectors.toSet()); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java index 126d16881..881965775 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java @@ -41,9 +41,11 @@ import org.hyperledger.bpa.impl.aries.config.SchemaService; import org.hyperledger.bpa.impl.notification.PresentationRequestDeletedEvent; import org.hyperledger.bpa.impl.notification.PresentationRequestSentEvent; +import org.hyperledger.bpa.impl.prooftemplates.ProofTemplateConversion; import org.hyperledger.bpa.impl.util.AriesStringUtil; import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.impl.util.TimeUtil; +import org.hyperledger.bpa.model.BPAProofTemplate; import org.hyperledger.bpa.model.Partner; import org.hyperledger.bpa.model.PartnerProof; import org.hyperledger.bpa.repository.MyCredentialRepository; @@ -52,12 +54,14 @@ import javax.inject.Inject; import javax.inject.Singleton; +import javax.validation.Valid; import javax.validation.constraints.NotNull; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.function.Consumer; +import java.util.stream.Collectors; @Slf4j @Singleton @@ -95,67 +99,72 @@ public class ProofManager { @Inject ApplicationEventPublisher eventPublisher; + @Inject + ProofTemplateConversion proofTemplateConversion; + + public void sendPresentProofRequest(@NonNull UUID partnerId, @NonNull @Valid BPAProofTemplate proofTemplate) { + try { + PresentProofRequest proofRequest = proofTemplateConversion.proofRequestViaVisitorFrom(partnerId, + proofTemplate); + // the proofTemplate does not contain the proof request Non-Revocation value, if + // that was not part of the template and set during proof request creation. + ac.presentProofSendRequest(proofRequest).ifPresent( + // using null for issuerId and schemaId because the template could have multiple + // of each. + persistProof(partnerId, null, null, proofTemplate)); + } catch (IOException e) { + throw new NetworkException(ACA_PY_ERROR_MSG, e); + } + } + // request proof from partner public void sendPresentProofRequest(@NonNull UUID partnerId, @NonNull RequestProofRequest req) { try { - final Optional p = partnerRepo.findById(partnerId); - if (p.isPresent()) { - // only when aries partner - if (p.get().hasConnectionId()) { - if (req.isRequestBySchema()) { - final Optional schema = ac.schemasGetById(req.getRequestBySchema().getSchemaId()); - if (schema.isPresent()) { - PresentProofRequest proofRequest = PresentProofRequestHelper - .buildForAllAttributes(p.get().getConnectionId(), - schema.get().getAttrNames(), req.buildRestrictions()); - ac.presentProofSendRequest(proofRequest).ifPresent(proof -> { - final PartnerProof pp = PartnerProof - .builder() - .partnerId(partnerId) - .state(proof.getState()) - .presentationExchangeId(proof.getPresentationExchangeId()) - .role(proof.getRole()) - .schemaId(schema.get().getId()) - .threadId(proof.getThreadId()) - .issuer(req.getFirstIssuerDid()) - .build(); - pProofRepo.save(pp); - - eventPublisher.publishEventAsync(PresentationRequestSentEvent.builder() - .partnerProof(pp) - .build()); - }); - } else { - throw new PartnerException("Could not find any schema on the ledger for id: " - + req.getRequestBySchema().getSchemaId()); - } - } else { - ac.presentProofSendRequest(req.getRequestRaw().toString()).ifPresent(exchange -> { - final PartnerProof pp = PartnerProof - .builder() - .partnerId(partnerId) - .state(exchange.getState()) - .presentationExchangeId(exchange.getPresentationExchangeId()) - .role(exchange.getRole()) - .build(); - pProofRepo.save(pp); - - eventPublisher.publishEventAsync(PresentationRequestSentEvent.builder() - .partnerProof(pp) - .build()); - }); - } - } else { - throw new PartnerException("Partner has no aca-py connection"); - } + final Partner partner = partnerRepo.findById(partnerId) + .orElseThrow(() -> new PartnerException("Partner not found")); + if (!partner.hasConnectionId()) { + throw new PartnerException("Partner has no aca-py connection"); + } + if (req.isRequestBySchema()) { + String schemaId = req.getRequestBySchema().getSchemaId(); + final Schema schema = ac.schemasGetById(schemaId) + .orElseThrow(() -> new PartnerException( + "Could not find any schema on the ledger for id: " + schemaId)); + PresentProofRequest proofRequest = PresentProofRequestHelper + .buildForAllAttributes(partner.getConnectionId(), + schema.getAttrNames(), req.buildRestrictions()); + ac.presentProofSendRequest(proofRequest).ifPresent( + persistProof(partnerId, req.getFirstIssuerDid(), schema.getId(), null)); } else { - throw new PartnerException("Partner not found"); + ac.presentProofSendRequest(req.getRequestRaw().toString()).ifPresent( + persistProof(partnerId, null, null, null)); } } catch (IOException e) { throw new NetworkException(ACA_PY_ERROR_MSG, e); } } + private Consumer persistProof(@NonNull UUID partnerId, @Nullable String issuerId, + @Nullable String schemaId, @Nullable BPAProofTemplate proofTemplate) { + return exchange -> { + final PartnerProof pp = PartnerProof + .builder() + .partnerId(partnerId) + .state(exchange.getState()) + .presentationExchangeId(exchange.getPresentationExchangeId()) + .role(exchange.getRole()) + .threadId(exchange.getThreadId()) + .schemaId(schemaId) + .proofTemplate(proofTemplate) + .issuer(issuerId) + .build(); + pProofRepo.save(pp); + eventPublisher.publishEventAsync(PresentationRequestSentEvent.builder() + .partnerProof(pp) + .build()); + }; + } + public void declinePresentProofRequest(@NotNull PartnerProof proofEx, String explainString) { if (PresentationExchangeState.REQUEST_RECEIVED.equals(proofEx.getState())) { try { @@ -268,18 +277,13 @@ public void sendProofProposal(@NonNull UUID partnerId, @NonNull UUID myCredentia } public List listPartnerProofs(@NonNull UUID partnerId) { - List result = new ArrayList<>(); - pProofRepo.findByPartnerIdOrderByRole(partnerId).forEach(p -> result.add(conv.toAPIObject(p))); - return result; + return pProofRepo.findByPartnerIdOrderByRole(partnerId).stream() + .map(conv::toAPIObject) + .collect(Collectors.toList()); } public Optional getPartnerProofById(@NonNull UUID id) { - Optional result = Optional.empty(); - final Optional proof = pProofRepo.findById(id); - if (proof.isPresent()) { - result = Optional.of(conv.toAPIObject(proof.get())); - } - return result; + return pProofRepo.findById(id).map(conv::toAPIObject); } public void deletePartnerProof(@NonNull UUID id) { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/Attributes.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/Attributes.java new file mode 100644 index 000000000..d4d6970a8 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/Attributes.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.prooftemplates; + +import lombok.Builder; +import lombok.Singular; +import org.hyperledger.aries.api.present_proof.PresentProofRequest; +import org.hyperledger.bpa.model.prooftemplate.BPASchemaRestrictions; + +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +@Builder +class Attributes { + String schemaId; + NonRevocationApplicator revocationApplicator; + BPASchemaRestrictions schemaRestrictions; + @Singular + List names; + @Singular + Map equals; + + public void addToBuilder( + BiConsumer builderSink) { + PresentProofRequest.ProofRequest.ProofRestrictions.ProofRestrictionsBuilder restrictionsBuilder = ProofTemplateElementVisitor + .asProofRestrictionsBuilder( + schemaRestrictions); + equals.forEach(restrictionsBuilder::addAttributeValueRestriction); + + PresentProofRequest.ProofRequest.ProofRequestedAttributes.ProofRequestedAttributesBuilder builder = PresentProofRequest.ProofRequest.ProofRequestedAttributes + .builder() + .names(names) + .restriction(restrictionsBuilder.schemaId(schemaId).build().toJsonObject()); + + builderSink.accept(schemaId, revocationApplicator.applyOn(builder).build()); + + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/NonRevocationApplicator.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/NonRevocationApplicator.java new file mode 100644 index 000000000..82c5fd40f --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/NonRevocationApplicator.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.prooftemplates; + +import io.micronaut.core.annotation.Nullable; +import lombok.Builder; +import lombok.NonNull; +import org.hyperledger.aries.api.present_proof.PresentProofRequest; + +import java.util.Objects; + +public class NonRevocationApplicator { + @Builder.Default + Boolean applyNonRevocation = false; + + PresentProofRequest.ProofRequest.ProofNonRevoked nonRevocation; + + @Builder + public NonRevocationApplicator(@NonNull Boolean applyNonRevocation, + @Nullable RevocationTimeStampProvider revocationTimeStampProvider) { + this.applyNonRevocation = applyNonRevocation; + if (applyNonRevocation) { + Long longValue = Objects.requireNonNull(revocationTimeStampProvider).get(); + nonRevocation = PresentProofRequest.ProofRequest.ProofNonRevoked.builder() + .from(longValue) + .to(longValue) + .build(); + } + } + + public PresentProofRequest.ProofRequest.ProofRequestedAttributes.ProofRequestedAttributesBuilder applyOn( + PresentProofRequest.ProofRequest.ProofRequestedAttributes.ProofRequestedAttributesBuilder attributes) { + if (applyNonRevocation) { + return attributes.nonRevoked(nonRevocation); + } + return attributes; + } + + public PresentProofRequest.ProofRequest.ProofRequestedPredicates.ProofRequestedPredicatesBuilder applyOn( + PresentProofRequest.ProofRequest.ProofRequestedPredicates.ProofRequestedPredicatesBuilder predicates) { + if (applyNonRevocation) { + return predicates.nonRevoked(nonRevocation); + } + return predicates; + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/Predicate.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/Predicate.java new file mode 100644 index 000000000..ba33ba9c2 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/Predicate.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.prooftemplates; + +import lombok.Builder; +import org.hyperledger.acy_py.generated.model.IndyProofReqPredSpec; +import org.hyperledger.aries.api.present_proof.PresentProofRequest; +import org.hyperledger.bpa.model.prooftemplate.BPASchemaRestrictions; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + +@Builder +class Predicate { + String schemaId; + AtomicInteger sameSchemaCounter; + NonRevocationApplicator revocationApplicator; + BPASchemaRestrictions schemaRestrictions; + String name; + IndyProofReqPredSpec.PTypeEnum operator; + Integer value; + + public void addToBuilder( + BiConsumer builderSink) { + PresentProofRequest.ProofRequest.ProofRestrictions.ProofRestrictionsBuilder restrictionsBuilder = ProofTemplateElementVisitor + .asProofRestrictionsBuilder( + schemaRestrictions); + PresentProofRequest.ProofRequest.ProofRequestedPredicates.ProofRequestedPredicatesBuilder builder = PresentProofRequest.ProofRequest.ProofRequestedPredicates + .builder() + .name(name) + .pType(operator) + .pValue(value) + .restriction(restrictionsBuilder.schemaId(schemaId).build().toJsonObject()); + String predicateName = schemaId + sameSchemaCounter.incrementAndGet(); + builderSink.accept(predicateName, revocationApplicator.applyOn(builder).build()); + + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/ProofTemplateConversion.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/ProofTemplateConversion.java new file mode 100644 index 000000000..5078e2ae3 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/ProofTemplateConversion.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.prooftemplates; + +import io.micronaut.core.annotation.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.hyperledger.aries.api.present_proof.PresentProofRequest; +import org.hyperledger.bpa.api.aries.SchemaAPI; +import org.hyperledger.bpa.api.exception.PartnerException; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.model.*; +import org.hyperledger.bpa.model.prooftemplate.BPAAttribute; +import org.hyperledger.bpa.model.prooftemplate.BPAAttributeGroup; +import org.hyperledger.bpa.repository.PartnerRepository; +import org.hyperledger.bpa.util.Pair; +import org.jetbrains.annotations.NotNull; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.validation.Valid; +import java.time.Clock; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +@Slf4j +@Singleton +public class ProofTemplateConversion { + + @Inject + PartnerRepository partnerRepo; + + @Inject + Clock clock; + + @Inject + SchemaService schemaService; + + @NonNull + public PresentProofRequest proofRequestViaVisitorFrom(@NonNull UUID partnerId, + @NonNull @Valid BPAProofTemplate proofTemplate) { + final Partner partner = partnerRepo.findById(partnerId) + .orElseThrow(() -> new PartnerException("Partner not found")); + if (!partner.hasConnectionId()) { + throw new PartnerException("Partner has no aca-py connection"); + } + + ProofTemplateElementVisitor proofTemplateElementVisitor = new ProofTemplateElementVisitor( + this::resolveLedgerSchemaId, + new RevocationTimeStampProvider(clock)); + + proofTemplateElementVisitor.visit(proofTemplate); + proofTemplate.streamAttributeGroups() + .forEach(proofTemplateElementVisitor::visit); + proofTemplate.streamAttributeGroups() + .flatMap(this::pairSchemaIdWithAttributes) + .forEach(proofTemplateElementVisitor::visit); + + return PresentProofRequest.builder() + .proofRequest(proofTemplateElementVisitor.getResult()) + .connectionId(partner.getConnectionId()) + .build(); + } + + public Optional resolveLedgerSchemaId(String databaseSchemaId) { + return schemaService.getSchema(UUID.fromString(databaseSchemaId)).map(SchemaAPI::getSchemaId); + } + + @NotNull + private Stream> pairSchemaIdWithAttributes(@NonNull BPAAttributeGroup ag) { + Optional> pairBuilder = resolveLedgerSchemaId(ag.getSchemaId()) + .map(Pair.builder()::left); + return pairBuilder.map( + pair -> ag.getAttributes().stream() + .map(pair::right) + .map(Pair.PairBuilder::build)) + .orElse(Stream.empty()); + } + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/ProofTemplateElementVisitor.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/ProofTemplateElementVisitor.java new file mode 100644 index 000000000..1facbe447 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/ProofTemplateElementVisitor.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.prooftemplates; + +import org.hyperledger.aries.api.present_proof.PresentProofRequest; +import org.hyperledger.bpa.model.*; +import org.hyperledger.bpa.model.prooftemplate.*; +import org.hyperledger.bpa.util.Pair; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.*; +import java.util.stream.Stream; + +/** + * This class constructs a {@link PresentProofRequest.ProofRequest} from a + * {@link BPAProofTemplate} by visiting the template's parts + * {@link BPAAttributeGroup}s and {@link BPAAttribute}s. The visit methods + * accept the different parts and store them in a context conserving way, to + * build the {@link PresentProofRequest.ProofRequest} with {@link #getResult()}. + *

+ * The call order of the visit methods does not matter.

+ * These methods are not thread safe.

+ * Use one instance per {@link BPAProofTemplate} + * + * @see ProofTemplateConversion#proofRequestViaVisitorFrom for an example on how to use this class + */ +class ProofTemplateElementVisitor { + + private Function> resolveLedgerSchemaId; + private final RevocationTimeStampProvider revocationTimeStampProvider; + private static final NonRevocationApplicator DEFAULT_NON_REVOCATION = new NonRevocationApplicator(false, null); + + private final Map attributesBySchemaId = new HashMap<>(); + private final Map> predicatesBySchemaId = new HashMap<>(); + private final Map schemaRestrictions = new HashMap<>(); + private final Map nonRevocationApplicatorMap = new HashMap<>(); + private final Map sameSchemaCounters = new HashMap<>(); + private String templateName; + + static PresentProofRequest.ProofRequest.ProofRestrictions.ProofRestrictionsBuilder asProofRestrictionsBuilder( + BPASchemaRestrictions schemaRestrictions) { + return PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId(schemaRestrictions.getSchemaId()) + .schemaName(schemaRestrictions.getSchemaName()) + .schemaVersion(schemaRestrictions.getSchemaVersion()) + .schemaIssuerDid(schemaRestrictions.getSchemaIssuerDid()) + .credentialDefinitionId(schemaRestrictions.getCredentialDefinitionId()) + .issuerDid(schemaRestrictions.getIssuerDid()); + } + + private NonRevocationApplicator getRevocationApplicator(Pair schemaAndAttributesBuilder) { + return nonRevocationApplicatorMap.computeIfAbsent(schemaAndAttributesBuilder.getLeft(), + schemaId -> DEFAULT_NON_REVOCATION); + } + + private BPASchemaRestrictions getSchemaRestrictions(Pair schemaAndAttributesBuilder) { + return schemaRestrictions.computeIfAbsent( + schemaAndAttributesBuilder.getLeft(), + schemaId -> BPASchemaRestrictions.builder().build()); + } + + private AtomicInteger getSameSchemaCounter(Pair schemaAndAttributesBuilder) { + return sameSchemaCounters.computeIfAbsent(schemaAndAttributesBuilder.getLeft(), s -> new AtomicInteger(0)); + } + + ProofTemplateElementVisitor( + Function> resolveLedgerSchemaId, + RevocationTimeStampProvider revocationTimeStampProvider) { + this.resolveLedgerSchemaId = resolveLedgerSchemaId; + this.revocationTimeStampProvider = revocationTimeStampProvider; + } + + /** + * Collects general information of the given {@link BPAProofTemplate}, e.g. the + * template's name. + * + * @param bpaProofTemplate contains general information to create a + * {@link PresentProofRequest.ProofRequest}. + */ + void visit(BPAProofTemplate bpaProofTemplate) { + templateName = bpaProofTemplate.getName(); + } + + /** + * Collects the information of the given {@link BPAAttributeGroup} + * + * @param bpaAttributeGroup contains all information for a + * {@link PresentProofRequest.ProofRequest} related to + * a certain schema. + */ + void visit(BPAAttributeGroup bpaAttributeGroup) { + resolveLedgerSchemaId.apply(bpaAttributeGroup.getSchemaId()).ifPresent(ledgerSchemaId -> { + nonRevocationApplicatorMap.put(ledgerSchemaId, NonRevocationApplicator.builder() + .applyNonRevocation(bpaAttributeGroup.getNonRevoked()) + .revocationTimeStampProvider(revocationTimeStampProvider) + .build()); + schemaRestrictions.put(ledgerSchemaId, bpaAttributeGroup.getSchemaLevelRestrictions()); + }); + } + + /** + * Collects the information of the given {@link BPAAttribute} in context of its + * {@link BPAAttributeGroup}'s schemaId + * + * @param schemaIdAndBpaAttribute is the schemaId of the + * {@link BPAAttributeGroup} containing + * {@link BPAAttribute} + */ + void visit(Pair schemaIdAndBpaAttribute) { + String schemaId = schemaIdAndBpaAttribute.getLeft(); + BPAAttribute attribute = schemaIdAndBpaAttribute.getRight(); + if (shouldAddAsAttribute(attribute)) { + attributesBySchemaId.compute(schemaId, addAttribute(attribute)); + } + if (shouldAddAsPredicate(attribute)) { + predicatesBySchemaId.compute(schemaId, addPredicates(attribute)); + } + } + + /** + * Adds one attribute for the passed {@link BPAAttribute} to the given + * attributesBuilder. + * + * @param attribute part of a {@link BPAProofTemplate}'s + * {@link BPAAttributeGroup}, representing a field in a + * verifiable credential. + * @return The lambda for {@link Map#compute(Object, BiFunction)}. + * @see ProofTemplateElementVisitor#addAttribute(String, + * Attributes.AttributesBuilder, BPAAttribute) + */ + private BiFunction addAttribute( + BPAAttribute attribute) { + return (schemaId, attributesBuilder) -> addAttribute(schemaId, attributesBuilder, attribute); + } + + /** + * Adds one attribute for the passed {@link BPAAttribute} to the given + * attributesBuilder. + * + * @param schemaId of the {@link BPAAttributeGroup} the given + * {@link BPAAttribute} is contained in. + * @param attributesBuilder is the builder containing already processed + * {@link BPAAttribute}s for a + * {@link BPAAttributeGroup}. + * @param attribute is part of a {@link BPAProofTemplate}'s + * {@link BPAAttributeGroup}, representing a field in a + * verifiable credential. + * @return the attributesBuilder extended with the + * {@link BPAAttribute} processed during this invocation. + */ + private Attributes.AttributesBuilder addAttribute(String schemaId, + Attributes.AttributesBuilder attributesBuilder, + BPAAttribute attribute) { + Optional equalsValue = attribute.getConditions().stream() + .map(Pair.with(BPACondition::getValue, BPACondition::getOperator)) + .filter(Pair.filterRight(ValueOperators.EQUALS::equals)) + .findAny() + .map(Pair::getLeft); + + Attributes.AttributesBuilder result = Optional.ofNullable(attributesBuilder).orElseGet(Attributes::builder) + .schemaId(schemaId) + .name(attribute.getName()); + equalsValue.ifPresent(value -> result.equal(attribute.getName(), value)); + return result; + } + + /** + * Adds one predicate for each predicate constraint at the passed + * {@link BPAAttribute} to the given predicateBuilderList. + * + * @param attribute part of a {@link BPAProofTemplate}'s + * {@link BPAAttributeGroup}, representing a field in a + * verifiable credential. + * @return The lambda for {@link Map#compute(Object, BiFunction)}. + * @see ProofTemplateElementVisitor#addPredicates(String, List, BPAAttribute) + */ + private BiFunction, List> addPredicates( + BPAAttribute attribute) { + return (schemaId, predicateBuilderList) -> addPredicates(schemaId, predicateBuilderList, attribute); + } + + /** + * Adds one predicate for each predicate constraint at the passed + * {@link BPAAttribute} to the given predicateBuilderList. + * + * @param schemaId of the {@link BPAAttributeGroup} the given + * {@link BPAAttribute} is contained in. + * @param predicateBuilderList is the list of already created + * {@link Predicate.PredicateBuilder}s for a + * {@link BPAAttributeGroup}. + * @param attribute is part of a {@link BPAProofTemplate}'s + * {@link BPAAttributeGroup}, representing a field + * in a verifiable credential. + * @return the predicateBuilderList extended with the + * {@link Predicate.PredicateBuilder}s created during this invocation. + */ + private List addPredicates(String schemaId, + List predicateBuilderList, + BPAAttribute attribute) { + List buildersList = Optional.ofNullable(predicateBuilderList) + .orElseGet(ArrayList::new); + attribute.getConditions().stream() + .map(Pair.with(BPACondition::getValue, BPACondition::getOperator)) + .flatMap(Pair.streamMapRight(o -> o.getPredicateOperator().stream())) + .flatMap(Pair.streamMapLeft(this::mapValueToInteger)) + .map(valueAndOperator -> Predicate.builder() + .schemaId(schemaId) + .name(attribute.getName()) + .operator(valueAndOperator.getRight()) + .value(valueAndOperator.getLeft())) + .forEach(buildersList::add); + return buildersList; + } + + /** + * If an attribute is handled as a predicate the value has to be an integer for + * comparison. + * + * @param value to compare the attribute's actual value to. + * @return a stream containing the value as an integer or an empty stream, if a + * conversion was not successful. + */ + private Stream mapValueToInteger(String value) { + try { + return Optional.ofNullable(value).map(Integer::parseInt).stream(); + } catch (NumberFormatException e) { + // TODO log error? + return Stream.empty(); + } + } + + /** + * Attribute declaration with no constraint or the {@link ValueOperators#EQUALS} + * should be added as attribute. + * + * @param attribute is part of a {@link BPAProofTemplate}'s + * {@link BPAAttributeGroup}, representing a field in a + * verifiable credential. + * @return true if there is no constraint or the operator is + * {@link ValueOperators#EQUALS} + */ + private boolean shouldAddAsAttribute(BPAAttribute attribute) { + return attribute.getConditions().isEmpty() || attribute.getConditions().stream() + .map(BPACondition::getOperator).anyMatch(ValueOperators.EQUALS::equals); + } + + /** + * Attribute declaration with a relation constraint should be added as + * predicate. + * + * @param attribute is part of a {@link BPAProofTemplate}'s + * {@link BPAAttributeGroup}, representing a field in a + * verifiable credential. + * @return true if the {@link ValueOperators#handleAsPredicate()} + * returns true + */ + private boolean shouldAddAsPredicate(BPAAttribute attribute) { + return attribute.getConditions() + .stream() + .map(BPACondition::getOperator) + .anyMatch(ValueOperators::handleAsPredicate); + } + + /** + * Creates a {@link PresentProofRequest.ProofRequest} with the information + * collected by the visit methods. + * + * @return an applicable {@link PresentProofRequest.ProofRequest} + */ + PresentProofRequest.ProofRequest getResult() { + PresentProofRequest.ProofRequest.ProofRequestBuilder proofRequestBuilder = PresentProofRequest.ProofRequest + .builder() + .name(templateName); + + addAttributesTo(proofRequestBuilder); + addPredicates(proofRequestBuilder); + return proofRequestBuilder.build(); + } + + private void addAttributesTo(PresentProofRequest.ProofRequest.ProofRequestBuilder proofRequestBuilder) { + attributesBySchemaId.entrySet().stream() + .map(Pair::new) + .map(Pair.lookUpAndSetOnRight(this::getRevocationApplicator, builder -> builder::revocationApplicator)) + .map(Pair.lookUpAndSetOnRight(this::getSchemaRestrictions, builder -> builder::schemaRestrictions)) + .map(Pair::getRight) + .map(Attributes.AttributesBuilder::build) + .forEach(attributes -> attributes.addToBuilder(proofRequestBuilder::requestedAttribute)); + } + + private void addPredicates(PresentProofRequest.ProofRequest.ProofRequestBuilder proofRequestBuilder) { + predicatesBySchemaId.entrySet().stream() + .map(Pair::new) + .flatMap(Pair.streamMapRight(List::stream)) + .map(Pair.lookUpAndSetOnRight(this::getRevocationApplicator, builder -> builder::revocationApplicator)) + .map(Pair.lookUpAndSetOnRight(this::getSchemaRestrictions, builder -> builder::schemaRestrictions)) + .map(Pair.lookUpAndSetOnRight(this::getSameSchemaCounter, builder -> builder::sameSchemaCounter)) + .map(Pair::getRight) + .map(Predicate.PredicateBuilder::build) + .forEach(attributes -> attributes.addToBuilder(proofRequestBuilder::requestedPredicate)); + } + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/RevocationTimeStampProvider.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/RevocationTimeStampProvider.java new file mode 100644 index 000000000..b43b57089 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/prooftemplates/RevocationTimeStampProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.prooftemplates; + +import io.micronaut.core.annotation.NonNull; + +import java.time.Clock; +import java.util.function.Supplier; + +public class RevocationTimeStampProvider implements Supplier { + @NonNull + private final Long value; + + public RevocationTimeStampProvider(@NonNull Clock clock) { + this.value = Math.floorDiv(clock.millis(), 1000); + } + + @Override + @NonNull + public Long get() { + return value; + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/ValidUUID.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/ValidUUID.java new file mode 100644 index 000000000..671db4231 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/ValidUUID.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.verification; + +import javax.validation.Constraint; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = {}) +public @interface ValidUUID { + String message() default "Not a valid UUID: {validatedValue}"; +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/ValidatorFactory.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/ValidatorFactory.java new file mode 100644 index 000000000..bbb7aa278 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/ValidatorFactory.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.verification; + +import io.micronaut.context.annotation.Factory; +import io.micronaut.validation.validator.constraints.ConstraintValidator; +import org.apache.commons.lang3.StringUtils; +import org.hyperledger.bpa.api.aries.SchemaAPI; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.impl.verification.prooftemplates.*; +import org.hyperledger.bpa.model.prooftemplate.BPAAttribute; +import org.hyperledger.bpa.model.prooftemplate.BPAAttributeGroup; +import org.hyperledger.bpa.model.prooftemplate.BPACondition; +import org.hyperledger.bpa.util.Pair; + +import javax.inject.Singleton; +import java.util.*; +import java.util.function.Predicate; + +@Factory +public class ValidatorFactory { + + @Singleton + ConstraintValidator uuidValidator() { + return (value, annotationMetadata, context) -> { + // treat an empty id as valid. + if (StringUtils.isBlank(value)) { + return true; + } + return isUUID(value); + }; + } + + private boolean isUUID(CharSequence value) { + try { + UUID.fromString(String.valueOf(value)); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + @Singleton + ConstraintValidator schemaIdValidator(SchemaService schemaService) { + return (value, annotationMetadata, context) -> Optional.ofNullable(value) + .map(String::valueOf) + .filter(this::isUUID) + .map(UUID::fromString) + .flatMap(schemaService::getSchema) + .isPresent(); + } + + // TODO find a way to validate single list entries in isolation. This shows the + // whole list as invalid, even if it's only one entry. + @Singleton + ConstraintValidator> attributeGroupsValidator( + SchemaService schemaService) { + return (value, annotationMetadata, context) -> Objects.requireNonNull(value) + .stream() + .allMatch(attributeGroup -> attributeGroupValidator(schemaService).isValid(attributeGroup, + annotationMetadata, context)); + + } + + @Singleton + ConstraintValidator attributeGroupValidator(SchemaService schemaService) { + return (value, annotationMetadata, context) -> { + boolean valid = false; + if (value != null) { + Optional> attributeInSchema = Optional.ofNullable(value) + .map(BPAAttributeGroup::getSchemaId) + .filter(this::isUUID) + .map(UUID::fromString) + .flatMap(schemaService::getSchema) + .map(SchemaAPI::getSchemaId) + .map(schemaId -> schemaService + .getSchemaAttributeNames(schemaId)::contains); + List attributes = value.getAttributes(); + if (attributes != null && attributeInSchema.isPresent()) { + // an empty AttributeGroup is treated as valid + valid = attributes.isEmpty() || attributes.stream() + .map(BPAAttribute::getName) + .allMatch(attributeInSchema.get()); + } else if (attributeInSchema.isEmpty()) { + // treat attributes in an attribute group for a not existing schema as valid, + // because these would be a follow error. + valid = true; + } + } + return valid; + }; + } + + @Singleton + ConstraintValidator> distinctAttributeNamesValidator() { + return (value, annotationMetadata, context) -> { + Set seenAttributes = new HashSet<>(); + Predicate findDuplicates = Predicate.not(seenAttributes::add); + if (value != null && !value.isEmpty()) { + return value.stream() + .map(BPAAttribute::getName) + .noneMatch(findDuplicates); + } + return true; + }; + } + + // TODO find a way to validate single list entries in isolation. This shows the + // whole list as invalid, even if it's only one entry. + @Singleton + ConstraintValidator> attributeConditionsOperatorAndValueValidator() { + return (value, annotationMetadata, context) -> Objects.requireNonNull(value) + .stream() + .allMatch(condition -> attributeConditionOperatorAndValueValidator() + .isValid(condition, annotationMetadata, context)); + } + + @Singleton + ConstraintValidator attributeConditionOperatorAndValueValidator() { + return (value, annotationMetadata, context) -> Optional.ofNullable(value) + .map(condition -> new Pair<>(condition.getValue(), condition.getOperator())) + .filter(pair -> pair.getRight().conditionValueIsValid(pair.getLeft())) + .isPresent(); + + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/DistinctAttributeNames.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/DistinctAttributeNames.java new file mode 100644 index 000000000..5f5bab5e6 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/DistinctAttributeNames.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.verification.prooftemplates; + +import javax.validation.Constraint; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE_USE; + +@Target({ TYPE_USE, FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = {}) +public @interface DistinctAttributeNames { + String message() default "Contains duplicate attribute names: {validatedValue}"; +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidAttributeCondition.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidAttributeCondition.java new file mode 100644 index 000000000..1d3e2a6ba --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidAttributeCondition.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.verification.prooftemplates; + +import javax.validation.Constraint; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = {}) +public @interface ValidAttributeCondition { + String message() default "{validatedValue} is an unsupported condition operator for proof template attributes or misses a required value."; + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidAttributeGroup.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidAttributeGroup.java new file mode 100644 index 000000000..e2614b49c --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidAttributeGroup.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.verification.prooftemplates; + +import javax.validation.Constraint; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE_USE; + +@Target({ TYPE_USE, FIELD, }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = {}) +public @interface ValidAttributeGroup { + String MESSAGE_TEMPLATE = "Attribute group contains attributes, which are not in the schema: {validatedValue}"; + + String message() default MESSAGE_TEMPLATE; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidBPASchemaId.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidBPASchemaId.java new file mode 100644 index 000000000..5ac16cc5f --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/verification/prooftemplates/ValidBPASchemaId.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.verification.prooftemplates; + +import javax.validation.Constraint; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = {}) +public @interface ValidBPASchemaId { + String MESSAGE_TEMPLATE = "Schema id invalid: '{validatedValue}' You have to use the schema.id instead of the schema.schemaId"; + + String message() default MESSAGE_TEMPLATE; +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/BPAProofTemplate.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/BPAProofTemplate.java new file mode 100644 index 000000000..3d07e0529 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/BPAProofTemplate.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.model; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.annotation.AutoPopulated; +import io.micronaut.data.annotation.DateCreated; +import io.micronaut.data.annotation.TypeDef; +import io.micronaut.data.model.DataType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.controller.api.prooftemplates.ProofTemplate; +import org.hyperledger.bpa.model.prooftemplate.BPAAttributeGroup; +import org.hyperledger.bpa.model.prooftemplate.BPAAttributeGroups; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import java.time.Instant; +import java.util.UUID; +import java.util.stream.Stream; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +@Entity +@Table(name = "bpa_proof_template") +public class BPAProofTemplate { + @Id + @AutoPopulated + UUID id; + + @Nullable + @DateCreated + private Instant createdAt; + + @NotEmpty + String name; + + @NotEmpty + @Valid + @Column(name = "attribute_groups_json") + @TypeDef(type = DataType.JSON) + // using a concrete class instead of a generic list does not unmarshal correctly + // see https://github.com/micronaut-projects/micronaut-data/issues/1064 + BPAAttributeGroups attributeGroups; + + public Stream streamAttributeGroups() { + return attributeGroups.getAttributeGroups().stream(); + } + + public ProofTemplate toRepresentation() { + return new ProofTemplate( + id.toString(), + createdAt, + name, + attributeGroups.toRepresentation()); + } + + public static BPAProofTemplate fromRepresentation(ProofTemplate proofTemplate) { + UUID id = null; + if (proofTemplate.getId() != null) { + id = UUID.fromString(proofTemplate.getId()); + } + return new BPAProofTemplate( + id, + proofTemplate.getCreatedAt(), + proofTemplate.getName(), + BPAAttributeGroups.fromRepresentation(proofTemplate.getAttributeGroups())); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/BPASchema.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/BPASchema.java index 6ab9a6b7d..1b5993218 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/BPASchema.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/BPASchema.java @@ -21,10 +21,7 @@ import io.micronaut.data.annotation.DateCreated; import io.micronaut.data.annotation.TypeDef; import io.micronaut.data.model.DataType; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; import io.micronaut.core.annotation.Nullable; import javax.persistence.*; @@ -53,6 +50,7 @@ public class BPASchema { private String schemaId; @TypeDef(type = DataType.JSON) + @Singular private Set schemaAttributeNames; @Nullable diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/PartnerProof.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/PartnerProof.java index 9f3975f4a..49db7be3f 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/PartnerProof.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/PartnerProof.java @@ -32,10 +32,7 @@ import org.hyperledger.aries.api.present_proof.PresentationExchangeState; import org.hyperledger.aries.api.present_proof.PresentProofRequest; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; +import javax.persistence.*; import java.time.Instant; import java.util.Map; import java.util.UUID; @@ -98,4 +95,7 @@ public class PartnerProof { @TypeDef(type = DataType.JSON) private PresentProofRequest.ProofRequest proofRequest; + @Nullable + @ManyToOne(fetch = FetchType.LAZY) + private BPAProofTemplate proofTemplate; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttribute.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttribute.java new file mode 100644 index 000000000..c478c45af --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttribute.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.model.prooftemplate; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.micronaut.core.annotation.Introspected; +import lombok.*; +import org.hyperledger.bpa.controller.api.prooftemplates.Attribute; +import org.hyperledger.bpa.impl.verification.prooftemplates.ValidAttributeCondition; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import java.util.List; +import java.util.stream.Collectors; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +public class BPAAttribute { + @NotEmpty + private String name; + @Valid + @Singular + @ValidAttributeCondition + private List conditions; + + public Attribute toRepresentation() { + return new Attribute( + name, + conditions.stream() + .map(BPACondition::toRepresentation) + .collect(Collectors.toList())); + } + + public static BPAAttribute fromRepresentation(Attribute attribute) { + return new BPAAttribute( + attribute.getName(), + attribute.getConditions().stream() + .map(BPACondition::fromRepresentation) + .collect(Collectors.toList())); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttributeGroup.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttributeGroup.java new file mode 100644 index 000000000..3ca858f2e --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttributeGroup.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.model.prooftemplate; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.micronaut.core.annotation.Introspected; +import lombok.*; +import org.hyperledger.bpa.controller.api.prooftemplates.AttributeGroup; +import org.hyperledger.bpa.impl.verification.prooftemplates.DistinctAttributeNames; +import org.hyperledger.bpa.impl.verification.prooftemplates.ValidAttributeCondition; +import org.hyperledger.bpa.impl.verification.prooftemplates.ValidAttributeGroup; +import org.hyperledger.bpa.impl.verification.prooftemplates.ValidBPASchemaId; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.stream.Collectors; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +@ValidAttributeGroup +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +// using a concrete class instead of a generic list does not unmarshal correctly +// see https://github.com/micronaut-projects/micronaut-data/issues/1064 +public class BPAAttributeGroup { + @NotNull + @ValidBPASchemaId + String schemaId; + @NotNull + @Singular + @Valid + @DistinctAttributeNames + List attributes; + @NotNull + @Builder.Default + Boolean nonRevoked = Boolean.FALSE; + @NotNull + @Builder.Default + @Valid + @ValidAttributeCondition + BPASchemaRestrictions schemaLevelRestrictions = BPASchemaRestrictions.builder().build(); + + public AttributeGroup toRepresentation() { + return new AttributeGroup( + schemaId, + attributes.stream() + .map(BPAAttribute::toRepresentation) + .collect(Collectors.toList()), + nonRevoked, + schemaLevelRestrictions.toRepresentation()); + } + + public static BPAAttributeGroup fromRepresentation(AttributeGroup attributeGroup) { + return BPAAttributeGroup.builder() + .schemaId(attributeGroup.getSchemaId()) + .attributes(attributeGroup.getAttributes().stream() + .map(BPAAttribute::fromRepresentation) + .collect(Collectors.toList())) + .nonRevoked(attributeGroup.getNonRevoked()) + .schemaLevelRestrictions( + BPASchemaRestrictions.fromRepresentation( + attributeGroup.getSchemaLevelRestrictions())) + .build(); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttributeGroups.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttributeGroups.java new file mode 100644 index 000000000..821e79dd4 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPAAttributeGroups.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.model.prooftemplate; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.micronaut.core.annotation.Introspected; +import lombok.*; +import org.hyperledger.bpa.controller.api.prooftemplates.AttributeGroup; +import org.hyperledger.bpa.impl.verification.prooftemplates.ValidAttributeGroup; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.stream.Collectors; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +// using a concrete class instead of a generic list does not unmarshal correctly +// see https://github.com/micronaut-projects/micronaut-data/issues/1064 +public class BPAAttributeGroups { + @Singular + @Valid + @NotNull + @ValidAttributeGroup + List attributeGroups; + + public List toRepresentation() { + return attributeGroups.stream() + .map(BPAAttributeGroup::toRepresentation) + .collect(Collectors.toList()); + } + + public static BPAAttributeGroups fromRepresentation(List proofTemplate) { + return new BPAAttributeGroups(proofTemplate.stream() + .map(BPAAttributeGroup::fromRepresentation) + .collect(Collectors.toList())); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPACondition.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPACondition.java new file mode 100644 index 000000000..23260d9b9 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPACondition.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.model.prooftemplate; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.micronaut.core.annotation.Introspected; +import lombok.*; +import org.hyperledger.bpa.controller.api.prooftemplates.ValueCondition; +import org.hyperledger.bpa.impl.verification.prooftemplates.ValidAttributeCondition; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@ValidAttributeCondition +public class BPACondition { + @NonNull + private ValueOperators operator; + @NonNull + private String value; + + public ValueCondition toRepresentation() { + return new ValueCondition(operator, value); + } + + public static BPACondition fromRepresentation(ValueCondition condition) { + return new BPACondition(condition.getOperator(), condition.getValue()); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPASchemaRestrictions.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPASchemaRestrictions.java new file mode 100644 index 000000000..4fa7243f4 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/BPASchemaRestrictions.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.model.prooftemplate; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.controller.api.prooftemplates.SchemaRestrictions; +import org.hyperledger.bpa.impl.verification.ValidUUID; + +import java.util.Optional; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Introspected +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +public class BPASchemaRestrictions { + @Nullable + @ValidUUID + private String schemaId; + @Nullable + private String schemaName; + @Nullable + private String schemaVersion; + @Nullable + private String schemaIssuerDid; + @Nullable + private String credentialDefinitionId; + @Nullable + private String issuerDid; + + public SchemaRestrictions toRepresentation() { + return SchemaRestrictions.builder() + .schemaId(getSchemaId()) + .schemaName(getSchemaName()) + .schemaVersion(getSchemaVersion()) + .schemaIssuerDid(getSchemaIssuerDid()) + .credentialDefinitionId(getCredentialDefinitionId()) + .issuerDid(getIssuerDid()) + .build(); + } + + public static BPASchemaRestrictions fromRepresentation(SchemaRestrictions schemaRestrictions) { + + return Optional.ofNullable(schemaRestrictions) + .map(other -> BPASchemaRestrictions.builder() + .schemaId(other.getSchemaId()) + .schemaName(other.getSchemaName()) + .schemaVersion(other.getSchemaVersion()) + .schemaIssuerDid(other.getSchemaIssuerDid()) + .credentialDefinitionId(other.getCredentialDefinitionId()) + .issuerDid(other.getIssuerDid()) + .build()) + .orElse(null); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/ValueOperators.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/ValueOperators.java new file mode 100644 index 000000000..697854a3f --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/prooftemplate/ValueOperators.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.model.prooftemplate; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.hyperledger.acy_py.generated.model.IndyProofReqPredSpec; + +import java.util.Optional; +import java.util.function.Predicate; + +class ParseableAsInteger implements Predicate { + @Override + public boolean test(String value) { + try { + Integer.parseInt(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } +} + +public enum ValueOperators { + @JsonProperty("==") + EQUALS("==", + "Compares the attributes value with the given value for equality. This reveals the value.", s -> true), + @JsonProperty("<") + LESS_THAN(IndyProofReqPredSpec.PTypeEnum.LESS_THAN, + "True, if the attributes value is less than given. Do not reveal the value.", + new ParseableAsInteger()), + @JsonProperty("<=") + LESS_THAN_OR_EQUAL_TO(IndyProofReqPredSpec.PTypeEnum.LESS_THAN_OR_EQUAL_TO, + "True, if the attributes value is less than or equal given. Do not reveal the value.", + new ParseableAsInteger()), + @JsonProperty(">") + GREATER_THAN(IndyProofReqPredSpec.PTypeEnum.GREATER_THAN, + "True, if the attributes value is greater than given. Do not reveal the value.", + new ParseableAsInteger()), + @JsonProperty(">=") + GREATER_THAN_OR_EQUAL_TO(IndyProofReqPredSpec.PTypeEnum.GREATER_THAN_OR_EQUAL_TO, + "True, if the attributes value is greater than or equal given. Do not reveal the value.", + new ParseableAsInteger()); + + private final IndyProofReqPredSpec.PTypeEnum predicateOperator; + private final String value; + private final String description; + private Predicate conditionValueValidator; + + ValueOperators(String value, String description, Predicate conditionValueValidator) { + this.predicateOperator = null; + this.value = value; + this.description = description; + this.conditionValueValidator = conditionValueValidator; + + } + + ValueOperators(IndyProofReqPredSpec.PTypeEnum value, String description, + Predicate conditionValueValidator) { + this.predicateOperator = value; + this.value = value.getValue(); + this.description = description; + this.conditionValueValidator = conditionValueValidator; + } + + public boolean handleAsPredicate() { + return predicateOperator != null; + } + + public Optional getPredicateOperator() { + return Optional.ofNullable(predicateOperator); + } + + public boolean conditionValueIsValid(String conditionValue) { + return this.conditionValueValidator.test(conditionValue); + } + + public String getValue() { + return value; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return value + " - " + description; + } + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/BPAProofTemplateRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/BPAProofTemplateRepository.java new file mode 100644 index 000000000..a4976d8aa --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/BPAProofTemplateRepository.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.repository; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; +import org.hyperledger.bpa.model.BPAProofTemplate; + +import java.util.UUID; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface BPAProofTemplateRepository extends CrudRepository { + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/PartnerProofRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/PartnerProofRepository.java index 449381607..d5633f10e 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/PartnerProofRepository.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/PartnerProofRepository.java @@ -17,7 +17,9 @@ */ package org.hyperledger.bpa.repository; +import io.micronaut.core.annotation.NonNull; import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.Join; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.repository.CrudRepository; @@ -34,12 +36,30 @@ @JdbcRepository(dialect = Dialect.POSTGRES) public interface PartnerProofRepository extends CrudRepository { + @Override + @NonNull + @Join(value = "proofTemplate", type = Join.Type.LEFT_FETCH) + Optional findById(@NonNull UUID id); + + @Override + @NonNull + @Join(value = "proofTemplate", type = Join.Type.LEFT_FETCH) + Iterable findAll(); + + @NonNull + @Join(value = "proofTemplate", type = Join.Type.LEFT_FETCH) Optional findByPresentationExchangeId(String presentationExchangeId); + @NonNull + @Join(value = "proofTemplate", type = Join.Type.LEFT_FETCH) Optional findByThreadId(String threadId); + @NonNull + @Join(value = "proofTemplate", type = Join.Type.LEFT_FETCH) List findByPartnerId(UUID partnerId); + @NonNull + @Join(value = "proofTemplate", type = Join.Type.LEFT_FETCH) List findByPartnerIdOrderByRole(UUID partnerId); void updateState(@Id UUID id, PresentationExchangeState state); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/PartnerRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/PartnerRepository.java index 413dc1dd5..f6b89201f 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/PartnerRepository.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/PartnerRepository.java @@ -38,8 +38,9 @@ public interface PartnerRepository extends CrudRepository { @Override + @NonNull @Join(value = "tags", type = Join.Type.LEFT_FETCH) - Optional findById(UUID id); + Optional findById(@NonNull UUID id); @Override @NonNull diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/util/Pair.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/util/Pair.java new file mode 100644 index 000000000..63e143caf --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/util/Pair.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.util; + +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +@RequiredArgsConstructor +@Data +@Builder +public class Pair implements Map.Entry { + @NonNull + private final L left; + @NonNull + private final R right; + + public Pair(@NonNull Map.Entry entry) { + this.left = entry.getKey(); + this.right = entry.getValue(); + } + + @NonNull + @Override + public L getKey() { + return left; + } + + @NonNull + @Override + public R getValue() { + return right; + } + + @Override + public R setValue(R value) { + throw new UnsupportedOperationException(); + } + + @NonNull + public Pair withRight(@NonNull T newRight) { + return new Pair<>(this.left, newRight); + } + + @NonNull + public Pair withLeft(@NonNull T newLeft) { + return new Pair<>(newLeft, this.right); + } + + @NonNull + public Pair flip() { + return new Pair<>(right, left); + } + + @NonNull + public Pair withRight(@NonNull Function mapper) { + return withRight(mapper.apply(right)); + } + + @NonNull + public Pair withLeft(@NonNull Function mapper) { + return withLeft(mapper.apply(left)); + } + + @NonNull + public static Function, Pair> mapRight(@NonNull Function mapper) { + return p -> p.withRight(mapper); + } + + @NonNull + public static Function, Pair> mapLeft(@NonNull Function mapper) { + return p -> p.withLeft(mapper); + } + + @NonNull + public static Function, Optional>> optionalMapLeft( + @NonNull Function> mapper) { + return p -> mapper.apply(p.left).map(p::withLeft); + } + + @NonNull + public static Function> with(@NonNull Function getLeft, + @NonNull Function getRight) { + return o -> new Pair<>(getLeft.apply(o), getRight.apply(o)); + } + + @NonNull + public static Function, Stream>> streamMapLeft( + @NonNull Function> mapper) { + return p -> mapper.apply(p.left).map(p::withLeft); + } + + @NonNull + public static Function, Optional>> optionalMapRight( + @NonNull Function> mapper) { + return p -> mapper.apply(p.right).map(p::withRight); + } + + @NonNull + public static Function, Stream>> streamMapRight( + @NonNull Function> mapper) { + return p -> mapper.apply(p.right).map(p::withRight); + } + + @NonNull + public static Predicate> filterLeft(@NonNull Predicate predicate) { + return p -> predicate.test(p.left); + } + + @NonNull + public static Predicate> filterRight(@NonNull Predicate predicate) { + return p -> predicate.test(p.right); + } + + @NonNull + public static UnaryOperator> lookUpAndSetOnRight( + @NonNull BiFunction valueLookupFunction, + @NonNull Function> setter) { + return lookUpAndSetOnRight(pair -> valueLookupFunction.apply(pair.getLeft(), pair.getRight()), setter); + } + + @NonNull + public static UnaryOperator> lookUpAndSetOnRight( + @NonNull Function, T> valueLookupFunction, + @NonNull Function> setter) { + return target -> set(valueLookupFunction, setter.apply(target.getRight()).andThen(target::withRight)) + .apply(target); + } + + @NonNull + public static UnaryOperator> lookUpAndSetOnLeft( + @NonNull BiFunction valueLookupFunction, + @NonNull Function> setter) { + return lookUpAndSetOnLeft(pair -> valueLookupFunction.apply(pair.getLeft(), pair.getRight()), setter); + } + + @NonNull + public static UnaryOperator> lookUpAndSetOnLeft( + @NonNull Function, T> valueLookupFunction, + @NonNull Function> setter) { + return target -> set(valueLookupFunction, setter.apply(target.getLeft()).andThen(target::withLeft)) + .apply(target); + } + + @NonNull + private static Function, Pair> set( + @NonNull Function, T> valueSupplier, + @NonNull Function> valueSink) { + return pair -> valueSupplier.andThen(valueSink).apply(pair); + } + +} diff --git a/backend/business-partner-agent/src/main/resources/databasemigrations/V1.18__add-proof-template-table.sql b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.18__add-proof-template-table.sql new file mode 100644 index 000000000..03756c9b5 --- /dev/null +++ b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.18__add-proof-template-table.sql @@ -0,0 +1,13 @@ +CREATE TABLE bpa_proof_template ( + id uuid PRIMARY KEY, + created_at timestamp without time zone NOT NULL, + name character varying(255) NOT NULL, + attribute_groups_json jsonb +); + +ALTER TABLE partner_proof ADD COLUMN proof_template_id uuid; -- field name plus _id + +ALTER TABLE partner_proof + ADD CONSTRAINT partner_proof_template_fk_1 + FOREIGN KEY (proof_template_id) + REFERENCES bpa_proof_template(id); diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/client/DidDocClientTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/client/DidDocClientTest.java index 8784bbff6..e6bf95a68 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/client/DidDocClientTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/client/DidDocClientTest.java @@ -45,7 +45,6 @@ void testGetDidDocument() { } @Test - @Disabled void testGetMasterdata() { String url = "https://ipfs.test.evan.network/ipfs/QmQGGjnY88gwvD6xSKXKbxz6n1Vdk9Q5WAMtar3WdEXmun"; // String url2 = "https://acme.iil.network/md.json"; diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/controller/ProofTemplateControllerTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/controller/ProofTemplateControllerTest.java new file mode 100644 index 000000000..7bbb8400f --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/controller/ProofTemplateControllerTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.controller; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.test.annotation.MockBean; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.hyperledger.bpa.controller.api.prooftemplates.Attribute; +import org.hyperledger.bpa.controller.api.prooftemplates.AttributeGroup; +import org.hyperledger.bpa.controller.api.prooftemplates.ProofTemplate; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.repository.BPAProofTemplateRepository; +import org.hyperledger.bpa.util.SchemaMockFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.inject.Inject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@MicronautTest() +class ProofTemplateControllerTest { + @Inject + @Client("/api/proof-templates") + HttpClient client; + + @MockBean(SchemaService.class) + SchemaService schemaService() { + return Mockito.mock(SchemaService.class); + } + + @Inject + SchemaMockFactory.SchemaMock schemaMock; + + @Inject + BPAProofTemplateRepository repository; + + @BeforeEach + public void setup() { + repository.deleteAll(); + } + + @Test + void testAddProofTemplateRequestsAreHandledCorrectly() { + UUID schemaId = schemaMock.prepareSchemaWithAttributes("mySchemaId", "myAttribute"); + + HttpResponse addedTemplate = client.toBlocking().exchange( + HttpRequest.POST("", + ProofTemplate.builder() + .name("aTemplate") + .attributeGroup( + AttributeGroup.builder() + .schemaId(schemaId.toString()) + .attribute( + Attribute.builder() + .name("myAttribute") + .build()) + .build()) + .build()), + ProofTemplate.class); + Assertions.assertEquals(HttpStatus.CREATED, addedTemplate.getStatus()); + Assertions.assertTrue(addedTemplate.getBody().isPresent()); + Assertions.assertTrue(addedTemplate.getBody().map(ProofTemplate::getId).isPresent()); + Assertions.assertTrue(addedTemplate.getBody() + .flatMap(p -> p.getAttributeGroups().stream().findAny()) + .flatMap(ag -> ag.getAttributes().stream() + .map(Attribute::getName) + .filter("myAttribute"::equals) + .findAny()) + .isPresent()); + Assertions.assertEquals(1, repository.count()); + } + + @Test + void testThatListProofTemplatesReturnTheCorrectDateFormat() { + UUID schemaId = schemaMock.prepareSchemaWithAttributes("mySchemaId", "myAttribute"); + Assertions.assertEquals(0, repository.count()); + client.toBlocking().exchange( + HttpRequest.POST("", + ProofTemplate.builder() + .name("aTemplate") + .attributeGroup( + AttributeGroup.builder() + .schemaId(schemaId.toString()) + .attribute( + Attribute.builder() + .name("myAttribute") + .build()) + .build()) + .build()), + ProofTemplate.class); + Assertions.assertEquals(1, repository.count()); + + Consumer assertDateFormat = (dateString) -> { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + try { + Assertions.assertNotNull(sdf.parse(dateString)); + + } catch (ParseException e) { + Assertions.fail(dateString + " does not match the pattern \"yyyy-MM-dd'T'HH:mm:ss.SSSZ\""); + } + }; + HttpResponse addedTemplate = client.toBlocking().exchange(HttpRequest.GET(""), String.class); + Pattern dataExtractionPattern = Pattern.compile("\"createdAt\"\\s*:\\s*\"([^,]+)\","); + addedTemplate.getBody() + .map(dataExtractionPattern::matcher) + .filter(Matcher::find) + .map(m -> m.group(1)) + .ifPresent(assertDateFormat); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/ProofTemplateManagerTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/ProofTemplateManagerTest.java new file mode 100644 index 000000000..aa8ea0e52 --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/ProofTemplateManagerTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl; + +import io.micronaut.test.annotation.MockBean; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.hyperledger.bpa.api.exception.ProofTemplateException; +import org.hyperledger.bpa.impl.aries.ProofManager; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.model.prooftemplate.BPAAttributeGroups; +import org.hyperledger.bpa.model.BPAProofTemplate; +import org.hyperledger.bpa.repository.BPAProofTemplateRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.inject.Inject; +import javax.validation.ConstraintViolationException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@MicronautTest +class ProofTemplateManagerTest { + @Inject + private BPAProofTemplateRepository repo; + + @Inject + ProofManager proofManager; + + @MockBean(ProofManager.class) + ProofManager proofManager() { + return Mockito.mock(ProofManager.class); + } + + @Inject + SchemaService schemaService; + + @MockBean(SchemaService.class) + SchemaService schemaService() { + return Mockito.mock(SchemaService.class); + } + + @Test + void testThatProofManagerIsInvokeWithPartnerIdAndProofTemplate() { + UUID partnerId = UUID.randomUUID(); + BPAProofTemplate template = repo.save( + BPAProofTemplate.builder() + .name("myTemplate") + .attributeGroups( + BPAAttributeGroups.builder() + .build()) + .build()); + // reset created at with value from db., because Java's value is more detailed + // that the database's. + repo.findById(template.getId()).map(BPAProofTemplate::getCreatedAt).ifPresent(template::setCreatedAt); + doNothing().when(proofManager).sendPresentProofRequest(eq(partnerId), eq(template)); + + ProofTemplateManager sut = new ProofTemplateManager(repo, proofManager); + sut.invokeProofRequestByTemplate(template.getId(), partnerId); + + verify(proofManager, times(1)).sendPresentProofRequest(partnerId, template); + } + + @Test + void testThatProofManagerIsNotInvokedIfProofTemplateDoesNotExist() { + ProofTemplateManager sut = new ProofTemplateManager(repo, proofManager); + + Assertions.assertThrows( + ProofTemplateException.class, + () -> sut.invokeProofRequestByTemplate(UUID.randomUUID(), UUID.randomUUID()), + "Expected a ProofTemplateException if there is ProofTemplate with the given id."); + verify(proofManager, never()).sendPresentProofRequest(any(UUID.class), any(BPAProofTemplate.class)); + } + + @Test + void testAddProofTemplate() { + BPAProofTemplate template = BPAProofTemplate.builder() + .name("myTemplate") + .attributeGroups( + BPAAttributeGroups.builder() + .build()) + .build(); + + ProofTemplateManager sut = new ProofTemplateManager(repo, proofManager); + + Assertions.assertEquals(0, repo.count(), "There should be no templates initially."); + BPAProofTemplate expected = sut.addProofTemplate(template); + Assertions.assertEquals(1, repo.count(), "There should be one template."); + Optional actual = repo.findById(expected.getId()); + assertTrue(actual.isPresent()); + assertTrue(actual.map(BPAProofTemplate::getCreatedAt).isPresent()); + // equalize the time stamp, because Java's value is more detailed that the + // database's. + actual.ifPresent(t -> t.setCreatedAt(expected.getCreatedAt())); + Assertions.assertEquals(expected, actual.get(), "The passed proof template should be persisted with an id."); + } + + @Test + void testThatAddProofTemplateRejectInvalidProofTemplates() { + BPAProofTemplate template = BPAProofTemplate.builder().build(); + + Assertions.assertThrows( + ConstraintViolationException.class, + () -> new ProofTemplateManager(repo, proofManager).addProofTemplate(template), + "ProofTemplateManager#addProofTemplate should reject invalid templates with a ConstraintViolationException"); + Assertions.assertEquals(0, repo.count(), "There should be no templates persisted."); + } + + @Test + void listProofTemplates() { + BPAProofTemplate.BPAProofTemplateBuilder templateBuilder = BPAProofTemplate.builder() + .name("myFirstTemplate") + .attributeGroups( + BPAAttributeGroups.builder() + .build()); + repo.save(templateBuilder + .name("myFirstTemplate") + .build()); + repo.save(templateBuilder + .name("mySecondTemplate") + .build()); + + ProofTemplateManager sut = new ProofTemplateManager(repo, proofManager); + + List allTemplates = sut.listProofTemplates().map(BPAProofTemplate::getName) + .collect(Collectors.toList()); + assertEquals(2, allTemplates.size(), "Expected exactly 2 persisted proof templates."); + assertTrue(allTemplates.contains("myFirstTemplate"), "Expected myFirstTemplate in the listed proof templates"); + assertTrue(allTemplates.contains("mySecondTemplate"), + "Expected mySecondTemplate in the listed proof templates"); + } + + @Test + void removeProofTemplate() { + UUID templateId = repo.save( + BPAProofTemplate.builder() + .name("myTemplate") + .attributeGroups( + BPAAttributeGroups.builder() + .build()) + .build()) + .getId(); + + ProofTemplateManager sut = new ProofTemplateManager(repo, proofManager); + assertTrue(repo.findById(templateId).isPresent(), "The to-be-removed proof template should exist."); + sut.removeProofTemplate(templateId); + assertTrue(repo.findById(templateId).isEmpty(), "The proof template was not removed."); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/prooftemplate/aries/ProofTemplateConversionTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/prooftemplate/aries/ProofTemplateConversionTest.java new file mode 100644 index 000000000..b870c182c --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/prooftemplate/aries/ProofTemplateConversionTest.java @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.impl.prooftemplate.aries; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.hyperledger.acy_py.generated.model.IndyProofReqPredSpec; +import org.hyperledger.aries.api.present_proof.PresentProofRequest; +import org.hyperledger.bpa.model.*; +import org.hyperledger.bpa.model.prooftemplate.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.*; + +@MicronautTest +public class ProofTemplateConversionTest extends ProofTemplateConversionTestBase { + + @Test + public void testThatATemplateWithOneAttributeFromOneSchemaIsConvertedCorrectly() { + UUID schemaId = prepareSchemaWithAttributes("mySchemaId", "name"); + prepareConnectionId("myConnectionId"); + BPAProofTemplate template = BPAProofTemplate.builder() + .id(UUID.randomUUID()) + .name("MyTestTemplate") + .attributeGroups(BPAAttributeGroups.builder() + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(schemaId.toString()) + .attribute(BPAAttribute.builder() + .name("name") + .build()) + .build()) + .build()) + .build(); + PresentProofRequest expected = PresentProofRequest.builder() + .connectionId("myConnectionId") + .proofRequest(PresentProofRequest.ProofRequest.builder() + .name("MyTestTemplate") + .requestedAttribute("mySchemaId", + PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() + .names(List.of("name")) + .restriction( + PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId("mySchemaId") + .build() + .toJsonObject()) + .build()) + .build()) + .build(); + PresentProofRequest actual = proofTemplateConversion.proofRequestViaVisitorFrom(UUID.randomUUID(), template); + assertWithAcapy(actual); + assertEqualAttributesInProofRequests(expected, actual); + } + + @Test + public void testThatATemplateWithSeveralAttributesFromTwoSchemaIsConvertedCorrectly() { + UUID schemaId1 = prepareSchemaWithAttributes("schema1", "name1", "name2"); + UUID schemaId2 = prepareSchemaWithAttributes("schema2", "name1", "name2", "name3"); + prepareConnectionId("myConnectionId"); + + BPAProofTemplate template = BPAProofTemplate.builder() + .id(UUID.randomUUID()) + .name("MyTestTemplate") + .attributeGroups(BPAAttributeGroups.builder() + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(schemaId1.toString()) + .attribute(BPAAttribute.builder() + .name("name1") + .build()) + .attribute(BPAAttribute.builder() + .name("name2") + .build()) + .build()) + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(schemaId2.toString()) + .attribute(BPAAttribute.builder() + .name("name1") + .build()) + .attribute(BPAAttribute.builder() + .name("name2") + .build()) + .attribute(BPAAttribute.builder() + .name("name3") + .build()) + .build()) + .build()) + .build(); + + PresentProofRequest expected = PresentProofRequest.builder() + .connectionId("myConnectionId") + .proofRequest(PresentProofRequest.ProofRequest.builder() + .name("MyTestTemplate") + .requestedAttribute("schema1", + PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() + .names(List.of("name1", "name2")) + .restriction( + PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId("schema1") + .build() + .toJsonObject()) + .build()) + .requestedAttribute("schema2", + PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() + .names(List.of("name1", "name2", "name3")) + .restriction( + PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId("schema2") + .build() + .toJsonObject()) + .build()) + .build()) + .build(); + PresentProofRequest actual = proofTemplateConversion.proofRequestViaVisitorFrom(UUID.randomUUID(), template); + assertWithAcapy(actual); + assertEqualAttributesInProofRequests(expected, actual); + } + + @Test + public void testOneAttributeGroupsWithRevocation() { + UUID schemaId = prepareSchemaWithAttributes("mySchemaId", "name1", "name2"); + prepareConnectionId("myConnectionId"); + + BPAProofTemplate template = BPAProofTemplate.builder() + .id(UUID.randomUUID()) + .name("MyTestTemplate") + .attributeGroups(BPAAttributeGroups.builder() + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(schemaId.toString()) + .nonRevoked(true) + .attribute(BPAAttribute.builder() + .name("name1") + .build()) + .attribute(BPAAttribute.builder() + .name("name2") + .build()) + .build()) + .build()) + .build(); + + long epochMillis = clock.millis(); + long epochSeconds = epochMillis / 1000L; + + PresentProofRequest expected = PresentProofRequest.builder() + .connectionId("myConnectionId") + .proofRequest(PresentProofRequest.ProofRequest.builder() + .requestedAttribute("mySchemaId", + PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() + .names(List.of("name1", "name2")) + .nonRevoked(PresentProofRequest.ProofRequest.ProofNonRevoked.builder() + .to(epochSeconds) + .from(epochSeconds) + .build()) + .restriction( + PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId("mySchemaId") + .build() + .toJsonObject()) + .build()) + .build()) + .build(); + PresentProofRequest actual = proofTemplateConversion.proofRequestViaVisitorFrom(UUID.randomUUID(), template); + assertWithAcapy(actual); + actual.getProofRequest().getRequestedAttributes().values().forEach( + attributes -> Assertions.assertEquals(epochSeconds, attributes.getNonRevoked().getFrom().longValue())); + assertEqualAttributesInProofRequests(expected, actual); + } + + @Test + public void testTwoAttributeGroupsWithRevocation() { + UUID schemaId1 = prepareSchemaWithAttributes("mySchemaId1", "name1", "name2"); + UUID schemaId2 = prepareSchemaWithAttributes("mySchemaId2", "name3", "name4"); + prepareConnectionId("myConnectionId"); + + BPAProofTemplate template = BPAProofTemplate.builder() + .id(UUID.randomUUID()) + .name("MyTestTemplate") + .attributeGroups(BPAAttributeGroups.builder() + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(schemaId1.toString()) + .nonRevoked(true) + .attribute(BPAAttribute.builder() + .name("name1") + .build()) + .attribute(BPAAttribute.builder() + .name("name2") + .build()) + .build()) + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(schemaId2.toString()) + .nonRevoked(true) + .attribute(BPAAttribute.builder() + .name("name3") + .build()) + .attribute(BPAAttribute.builder() + .name("name4") + .build()) + .build()) + .build()) + .build(); + + long epochMillis = clock.millis(); + long epochSeconds = epochMillis / 1000L; + + PresentProofRequest expected = PresentProofRequest.builder() + .connectionId("myConnectionId") + .proofRequest(PresentProofRequest.ProofRequest.builder() + .requestedAttribute("mySchemaId1", + PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() + .names(List.of("name1", "name2")) + .nonRevoked(PresentProofRequest.ProofRequest.ProofNonRevoked.builder() + .to(epochSeconds) + .from(epochSeconds) + .build()) + .restriction( + PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId("mySchemaId1") + .build() + .toJsonObject()) + .build()) + .requestedAttribute("mySchemaId2", + PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() + .names(List.of("name3", "name4")) + .nonRevoked(PresentProofRequest.ProofRequest.ProofNonRevoked.builder() + .to(epochSeconds) + .from(epochSeconds) + .build()) + .restriction( + PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId("mySchemaId2") + .build() + .toJsonObject()) + .build()) + .build()) + .build(); + PresentProofRequest actual = proofTemplateConversion.proofRequestViaVisitorFrom(UUID.randomUUID(), template); + assertWithAcapy(actual); + actual.getProofRequest().getRequestedAttributes().values().forEach( + attributes -> Assertions.assertEquals(epochSeconds, attributes.getNonRevoked().getFrom().longValue())); + assertEqualAttributesInProofRequests(expected, actual); + } + + @Test + public void testOneAttributeGroupsAndOnePredicateWithRevocation() { + UUID schemaId = prepareSchemaWithAttributes("mySchemaId1", "name1", "name2", "secret1"); + prepareConnectionId("myConnectionId"); + + BPAProofTemplate template = BPAProofTemplate.builder() + .id(UUID.randomUUID()) + .name("MyTestTemplate") + .attributeGroups(BPAAttributeGroups.builder() + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(schemaId.toString()) + .nonRevoked(true) + .attribute(BPAAttribute.builder() + .name("name1") + .build()) + .attribute(BPAAttribute.builder() + .name("name2") + .build()) + .attribute(BPAAttribute.builder() + .name("secret1") + .condition(BPACondition.builder() + .operator(ValueOperators.GREATER_THAN_OR_EQUAL_TO) + .value("10") + .build()) + .build()) + .build()) + .build()) + .build(); + + long epochMillis = clock.millis(); + long epochSeconds = epochMillis / 1000L; + + PresentProofRequest expected = PresentProofRequest.builder() + .connectionId("myConnectionId") + .proofRequest(PresentProofRequest.ProofRequest.builder() + .requestedAttribute("mySchemaId1", + PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() + .names(List.of("name1", "name2")) + .nonRevoked(PresentProofRequest.ProofRequest.ProofNonRevoked.builder() + .to(epochSeconds) + .from(epochSeconds) + .build()) + .restriction( + PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId("mySchemaId1") + .build() + .toJsonObject()) + .build()) + .requestedPredicate("mySchemaId1", + PresentProofRequest.ProofRequest.ProofRequestedPredicates.builder() + .name("secret1") + .nonRevoked(PresentProofRequest.ProofRequest.ProofNonRevoked.builder() + .to(epochSeconds) + .from(epochSeconds) + .build()) + .pType(IndyProofReqPredSpec.PTypeEnum.GREATER_THAN_OR_EQUAL_TO) + .pValue(10) + .build()) + .build()) + .build(); + PresentProofRequest actual = proofTemplateConversion.proofRequestViaVisitorFrom(UUID.randomUUID(), template); + assertWithAcapy(actual); + actual.getProofRequest().getRequestedAttributes().values().forEach( + attributes -> Assertions.assertEquals(epochSeconds, attributes.getNonRevoked().getFrom().longValue())); + assertEqualAttributesInProofRequests(expected, actual); + } + + @Test + public void testThatATemplateWithOneAttributeFromOneSchemaAndIssuedByIsConvertedCorrectly() { + UUID schemaId = prepareSchemaWithAttributes("mySchemaId", "name"); + prepareConnectionId("myConnectionId"); + BPAProofTemplate template = BPAProofTemplate.builder() + .id(UUID.randomUUID()) + .name("MyTestTemplate") + .attributeGroups(BPAAttributeGroups.builder() + .attributeGroup(BPAAttributeGroup.builder() + .schemaLevelRestrictions(BPASchemaRestrictions.builder() + .issuerDid("issuerDid") + .build()) + .schemaId(schemaId.toString()) + .attribute(BPAAttribute.builder() + .name("name") + .build()) + .build()) + .build()) + .build(); + PresentProofRequest expected = PresentProofRequest.builder() + .connectionId("myConnectionId") + .proofRequest(PresentProofRequest.ProofRequest.builder() + .name("MyTestTemplate") + .requestedAttribute("mySchemaId", + PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() + .names(List.of("name")) + .restriction( + PresentProofRequest.ProofRequest.ProofRestrictions.builder() + .schemaId("mySchemaId") + .issuerDid("issuerDid") + .build() + .toJsonObject()) + .build()) + .build()) + .build(); + PresentProofRequest actual = proofTemplateConversion.proofRequestViaVisitorFrom(UUID.randomUUID(), template); + assertWithAcapy(actual); + assertEqualAttributesInProofRequests(expected, actual); + } +} diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/prooftemplate/aries/ProofTemplateConversionTestBase.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/prooftemplate/aries/ProofTemplateConversionTestBase.java new file mode 100644 index 000000000..b5ee173ef --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/prooftemplate/aries/ProofTemplateConversionTestBase.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.prooftemplate.aries; + +import io.micronaut.test.annotation.MockBean; +import org.hyperledger.aries.api.present_proof.PresentProofRequest; +import org.hyperledger.bpa.RunWithAries; +import org.hyperledger.bpa.api.aries.SchemaAPI; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.impl.prooftemplates.ProofTemplateConversion; +import org.hyperledger.bpa.model.BPASchema; +import org.hyperledger.bpa.model.Partner; +import org.hyperledger.bpa.repository.PartnerRepository; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.mockito.Mockito; + +import javax.inject.Inject; +import java.io.IOException; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.*; +import java.util.stream.Collectors; + +public class ProofTemplateConversionTestBase extends RunWithAries { + @Inject + SchemaService schemaService; + @Inject + PartnerRepository partnerRepo; + @Inject + ProofTemplateConversion proofTemplateConversion; + @Inject + Clock clock; + + @MockBean(SchemaService.class) + SchemaService schemaService() { + return Mockito.mock(SchemaService.class); + } + + @MockBean(PartnerRepository.class) + PartnerRepository partnerRepository() { + return Mockito.mock(PartnerRepository.class); + } + + @MockBean(Clock.class) + Clock testClock() { + return Clock.fixed(Instant.now(), ZoneId.systemDefault()); + } + + protected UUID prepareSchemaWithAttributes(String ledgerSchemaId, String... attributes) { + UUID schemaId = UUID.randomUUID(); + Mockito.when(schemaService.getSchema(schemaId)) + .thenReturn(Optional.of(SchemaAPI.builder().schemaId(ledgerSchemaId).id(schemaId).build())); + Mockito.when(schemaService.getSchemaFor(ledgerSchemaId)) + .thenReturn(Optional.of(BPASchema.builder().schemaId(ledgerSchemaId).id(schemaId).build())); + Mockito.when(schemaService.getSchemaAttributeNames(ledgerSchemaId)) + .thenReturn(Set.of(attributes)); + return schemaId; + } + + protected void prepareConnectionId(String connectionId) { + Mockito.when(partnerRepo.findById(Mockito.any(UUID.class))) + .thenReturn(Optional.of(Partner.builder() + .connectionId(connectionId) + .build())); + } + + protected void assertWithAcapy(PresentProofRequest proofRequest) { + try { + ac.presentProofCreateRequest(proofRequest); + } catch (IOException e) { + Assertions.fail("aca-py cannot process the request " + proofRequest, e); + } + } + + protected void assertEqualAttributesInProofRequests(PresentProofRequest expected, PresentProofRequest actual) { + Assertions.assertEquals(expected.getConnectionId(), actual.getConnectionId()); + Map actualAttributes = new HashMap<>(); + + actual.getProofRequest().getRequestedAttributes().forEach( + actualAttributes::put); + expected.getProofRequest().getRequestedAttributes() + .forEach((key, value) -> assertEqualRequestedAttributes(key, value, actualAttributes.get(key))); + } + + private void assertEqualRequestedAttributes(String groupName, + PresentProofRequest.ProofRequest.ProofRequestedAttributes expected, + PresentProofRequest.ProofRequest.ProofRequestedAttributes actual) { + if (actual == null) { + if (expected != null) { + Assertions + .fail(String.format("The ProofRequestedAttributes with the name %s does not exist", groupName)); + } else { + Assertions.fail("expected and actual are not set."); + } + } + Assertions.assertEquals(expected.getNonRevoked(), actual.getNonRevoked(), String.format( + "The non-revocation does not match for ProofRequestedAttributes with the name %s", groupName)); + Assertions.assertEquals(sortedAttributeNames(expected), sortedAttributeNames(actual), String.format( + "The attribute names do not match for ProofRequestedAttributes with the name %s", groupName)); + Assertions.assertEquals(flattenRestrictions(expected), flattenRestrictions(actual), + String.format("The restrictions objects do not match for ProofRequestedAttributes with the name %s", + expected.getName())); + } + + @NotNull + private List sortedAttributeNames(PresentProofRequest.ProofRequest.ProofRequestedAttributes expected) { + return expected.getNames().stream().sorted().collect(Collectors.toList()); + } + + private List flattenRestrictions(PresentProofRequest.ProofRequest.ProofRequestedAttributes expected) { + return expected.getRestrictions().stream() + .map(json -> json.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue().toString()) + .sorted().collect(Collectors.joining("\n"))) + .sorted().collect(Collectors.toList()); + } +} diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAAttributeGroupTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAAttributeGroupTest.java new file mode 100644 index 000000000..b09ca22e6 --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAAttributeGroupTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.model; + +import io.micronaut.test.annotation.MockBean; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.validation.validator.Validator; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.model.prooftemplate.BPAAttribute; +import org.hyperledger.bpa.model.prooftemplate.BPAAttributeGroup; +import org.hyperledger.bpa.model.prooftemplate.BPACondition; +import org.hyperledger.bpa.model.prooftemplate.ValueOperators; +import org.hyperledger.bpa.util.SchemaMockFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +@MicronautTest +class BPAAttributeGroupTest { + @Inject + Validator validator; + + @Inject + SchemaService schemaService; + + @MockBean(SchemaService.class) + SchemaService schemaService() { + return Mockito.mock(SchemaService.class); + } + + @Inject + SchemaMockFactory.SchemaMock schemaMock; + + @Test + void testThatSchemaIdIsCheckedForExistenceInSchemaService() { + UUID schemaId = schemaMock.prepareSchemaWithAttributes("mySchemaId"); + BPAAttributeGroup sut = BPAAttributeGroup.builder().schemaId(schemaId.toString()).build(); + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(1, constraintViolations.size()); + Assertions.assertEquals(schemaId.toString(), + constraintViolations.stream().findFirst().map(ConstraintViolation::getInvalidValue).orElse(null)); + } + + @Test + void testThatAttributeNamesAreCheckedAgainstSchemaFromSchemaService() { + UUID schemaId = schemaMock.prepareSchemaWithAttributes("mySchemaId", "surname", "lastname"); + BPAAttributeGroup sut = BPAAttributeGroup.builder() + .schemaId(schemaId.toString()) + .attribute(BPAAttribute.builder() + .name("fullname") + .build()) + .attribute(BPAAttribute.builder() + .name("surname") + .build()) + .attribute(BPAAttribute.builder() + .name("lastname") + .build()) + .build(); + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(1, constraintViolations.size()); + Assertions.assertEquals(sut, + constraintViolations.stream().findFirst().map(ConstraintViolation::getInvalidValue).orElse(null)); + } + + @Test + void testThatAttributesNamesAreDistinct() { + UUID schemaId = schemaMock.prepareSchemaWithAttributes("mySchemaId", "fullname"); + BPAAttributeGroup sut = BPAAttributeGroup.builder() + .schemaId(schemaId.toString()) + .attribute(BPAAttribute.builder() + .name("fullname") + .build()) + .attribute(BPAAttribute.builder() + .name("fullname") + .build()) + .build(); + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(1, constraintViolations.size()); + List expected = List.of( + BPAAttribute.builder() + .name("fullname") + .build(), + BPAAttribute.builder() + .name("fullname") + .build()); + Assertions.assertEquals(expected, + constraintViolations.stream().findFirst().map(ConstraintViolation::getInvalidValue).orElse(null)); + } + + @Test + void testThatAttributeConditionsAreVerified() { + UUID schemaId = schemaMock.prepareSchemaWithAttributes("mySchemaId", "myAttributeName"); + BPACondition invalidCondition = BPACondition.builder() + .value("any") + .operator(ValueOperators.GREATER_THAN) + .build(); + BPAAttributeGroup sut = BPAAttributeGroup.builder() + .schemaId(schemaId.toString()) + .attribute(BPAAttribute.builder() + .name("myAttributeName") + .condition(invalidCondition) + .build()) + .build(); + + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(1, constraintViolations.size()); + Assertions.assertEquals(List.of(invalidCondition), + constraintViolations.stream().findFirst().map(ConstraintViolation::getInvalidValue).orElse(null)); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAConditionTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAConditionTest.java new file mode 100644 index 000000000..79eac5630 --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAConditionTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.model; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.validation.validator.Validator; +import org.hyperledger.bpa.model.prooftemplate.BPACondition; +import org.hyperledger.bpa.model.prooftemplate.ValueOperators; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import java.util.Set; + +@MicronautTest() +class BPAConditionTest { + + @Inject + Validator validator; + + @Test + void testThatLessThenConditionIsValid() { + BPACondition sut = BPACondition.builder().value("11").operator(ValueOperators.LESS_THAN).build(); + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(0, constraintViolations.size()); + } + + @Test + void testThatLessOrEqualsConditionIsValid() { + BPACondition sut = BPACondition.builder().value("2").operator(ValueOperators.LESS_THAN_OR_EQUAL_TO).build(); + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(0, constraintViolations.size()); + } + + @Test + void testThatGreaterOrEqualsConditionIsValid() { + BPACondition sut = BPACondition.builder().value("5").operator(ValueOperators.GREATER_THAN_OR_EQUAL_TO) + .build(); + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(0, constraintViolations.size()); + } + + @Test + void testThatGreaterThanConditionIsValid() { + BPACondition sut = BPACondition.builder().value("3").operator(ValueOperators.GREATER_THAN).build(); + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(0, constraintViolations.size()); + } + + @Test + void testThatEqualsConditionIsValid() { + BPACondition sut = BPACondition.builder().value("any") + .operator(ValueOperators.EQUALS).build(); + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(0, constraintViolations.size()); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAProofTemplateTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAProofTemplateTest.java new file mode 100644 index 000000000..5aab82c23 --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/model/BPAProofTemplateTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.model; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.micronaut.test.annotation.MockBean; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.validation.validator.Validator; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.impl.verification.prooftemplates.ValidAttributeGroup; +import org.hyperledger.bpa.impl.verification.prooftemplates.ValidBPASchemaId; +import org.hyperledger.bpa.model.prooftemplate.*; +import org.hyperledger.bpa.util.SchemaMockFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@MicronautTest +class BPAProofTemplateTest { + @Inject + Validator validator; + @Inject + ObjectMapper om; + + @MockBean(SchemaService.class) + public SchemaService schemaService() { + return Mockito.mock(SchemaService.class); + } + + @Inject + SchemaMockFactory.SchemaMock schemaMock; + + @Test + void testThatAttributeGroupsAreVerified() { + UUID schemaId = schemaMock.prepareSchemaWithAttributes("mySchemaId", "anotherAttributeName"); + UUID notExistingSchema = UUID.randomUUID(); + BPAProofTemplate sut = BPAProofTemplate.builder() + .id(UUID.randomUUID()) + .name("MyTestTemplate") + .attributeGroups( + BPAAttributeGroups.builder() + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(notExistingSchema.toString()) + .attribute(BPAAttribute.builder() + .name("myAttributeName") + .build()) + .build()) + .attributeGroup(BPAAttributeGroup.builder() + .schemaId(schemaId.toString()) + .attribute(BPAAttribute.builder() + .name("notASchemaAttribute") + .build()) + .build()) + .build()) + .build(); + + Set> constraintViolations = validator.validate(sut); + Assertions.assertEquals(2, constraintViolations.size()); + List violatingValues = constraintViolations.stream().map(ConstraintViolation::getMessageTemplate) + .collect(Collectors.toList()); + Assertions.assertTrue(violatingValues.contains(ValidBPASchemaId.MESSAGE_TEMPLATE)); + Assertions.assertTrue(violatingValues.contains(ValidAttributeGroup.MESSAGE_TEMPLATE)); + } + + @Test + void testThatSerializationWorksBothWays() throws JsonProcessingException { + BPAProofTemplate proofTemplate = BPAProofTemplate.builder() + .id(UUID.randomUUID()) + .name("MyTestTemplate") + .attributeGroups( + BPAAttributeGroups.builder() + .attributeGroup(BPAAttributeGroup.builder() + .schemaId("mySchemaId") + .attribute(BPAAttribute.builder() + .name("myAttributeName") + .condition(BPACondition.builder() + .value("any") + .operator(ValueOperators.LESS_THAN) + .build()) + .build()) + .build()) + .build()) + .build(); + String string = om.writeValueAsString(proofTemplate); + BPAProofTemplate result = om.readValue(string, BPAProofTemplate.class); + Assertions.assertEquals(proofTemplate, result); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/repository/BPAProofTemplateRepositoryTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/repository/BPAProofTemplateRepositoryTest.java new file mode 100644 index 000000000..b552319a4 --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/repository/BPAProofTemplateRepositoryTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.repository; + +import io.micronaut.data.exceptions.DataAccessException; +import io.micronaut.test.annotation.MockBean; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.model.*; +import org.hyperledger.bpa.model.prooftemplate.*; +import org.hyperledger.bpa.util.SchemaMockFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.inject.Inject; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@MicronautTest(transactional = false) +class BPAProofTemplateRepositoryTest { + + @Inject + BPAProofTemplateRepository repo; + + @Inject + PartnerProofRepository proofRepository; + + @MockBean(SchemaService.class) + SchemaService schemaService() { + return Mockito.mock(SchemaService.class); + } + + @Inject + SchemaMockFactory.SchemaMock schemaMock; + + @BeforeEach + public void setup() { + proofRepository.deleteAll(); + repo.deleteAll(); + } + + @Test + void testSavingAnEntity() { + BPAProofTemplate.BPAProofTemplateBuilder proofTemplateBuilder = getBpaProofTemplateBuilder(); + BPAProofTemplate proofTemplateToSave = proofTemplateBuilder + .build(); + + UUID newEntityId = repo.save(proofTemplateToSave).getId(); + + Optional savedProofTemplate = repo.findById(newEntityId); + assertTrue(savedProofTemplate.isPresent()); + assertTrue(savedProofTemplate.map(BPAProofTemplate::getCreatedAt).isPresent()); + BPAProofTemplate expectedProofTemplate = proofTemplateBuilder + .id(newEntityId) + // copy database generated time stamp + .createdAt(savedProofTemplate.get().getCreatedAt()) + .build(); + assertEquals(expectedProofTemplate, savedProofTemplate.get()); + } + + @Test + void testThatCreatedAtIsOverwrittenByDB() { + Instant givenCreatedAt = Instant.now().minus(Duration.ofMillis(1000)); + BPAProofTemplate.BPAProofTemplateBuilder proofTemplateBuilder = getBpaProofTemplateBuilder() + .createdAt(givenCreatedAt); + BPAProofTemplate proofTemplateToSave = proofTemplateBuilder + .build(); + + UUID newEntityId = repo.save(proofTemplateToSave).getId(); + + Optional savedProofTemplate = repo.findById(newEntityId); + assertTrue(savedProofTemplate.isPresent()); + assertTrue(savedProofTemplate.map(BPAProofTemplate::getCreatedAt).isPresent()); + assertNotEquals(givenCreatedAt, savedProofTemplate.get().getCreatedAt()); + assertTrue(givenCreatedAt.isBefore(savedProofTemplate.get().getCreatedAt())); + BPAProofTemplate expectedProofTemplate = proofTemplateBuilder + .id(newEntityId) + // copy database generated time stamp + .createdAt(savedProofTemplate.get().getCreatedAt()) + .build(); + assertEquals(expectedProofTemplate, savedProofTemplate.get()); + } + + @Test + void testThatCreatedAtIsNotOverwrittenOnUpdatingByDB() throws InterruptedException { + Instant givenCreatedAt = Instant.now().minus(Duration.ofMillis(1000)); + BPAProofTemplate.BPAProofTemplateBuilder proofTemplateBuilder = getBpaProofTemplateBuilder() + .createdAt(givenCreatedAt); + BPAProofTemplate proofTemplateToSave = proofTemplateBuilder + .build(); + + UUID newEntityId = repo.save(proofTemplateToSave).getId(); + + Optional savedProofTemplate = repo.findById(newEntityId); + assertTrue(savedProofTemplate.isPresent()); + assertTrue(savedProofTemplate.map(BPAProofTemplate::getCreatedAt).isPresent()); + Instant firstCreatedAt = savedProofTemplate.get().getCreatedAt(); + Thread.sleep(50); + proofTemplateToSave.setName("update to save"); + // modified timestamp from cliet + proofTemplateToSave.setCreatedAt(Instant.now()); + repo.save(proofTemplateToSave); + savedProofTemplate = repo.findById(newEntityId); + assertTrue(savedProofTemplate.isPresent()); + assertTrue(savedProofTemplate.map(BPAProofTemplate::getCreatedAt).isPresent()); + Instant secondCreatedAt = savedProofTemplate.get().getCreatedAt(); + assertEquals(firstCreatedAt, secondCreatedAt); + } + + @Test + void testThatPartnerProofResolvesItsTemplate() { + BPAProofTemplate.BPAProofTemplateBuilder proofTemplateBuilder = getBpaProofTemplateBuilder(); + BPAProofTemplate proofTemplateToSave = proofTemplateBuilder + .build(); + + BPAProofTemplate savedTemplate = repo.save(proofTemplateToSave); + System.out.println(savedTemplate); + PartnerProof proof = proofRepository.save(PartnerProof.builder() + .partnerId(UUID.randomUUID()) + .presentationExchangeId("presentationExchangeId") + .proofTemplate(savedTemplate) + .build()); + + assertTrue(proofRepository.findById(proof.getId()).map(PartnerProof::getProofTemplate).isPresent()); + } + + @Test + void testThatDeletionIsConstrainedToUnusedTemplates() { + BPAProofTemplate.BPAProofTemplateBuilder proofTemplateBuilder = getBpaProofTemplateBuilder(); + BPAProofTemplate proofTemplateToSave = proofTemplateBuilder + .build(); + + BPAProofTemplate savedTemplate = repo.save(proofTemplateToSave); + System.out.println(savedTemplate); + PartnerProof proof = proofRepository.save(PartnerProof.builder() + .partnerId(UUID.randomUUID()) + .presentationExchangeId("presentationExchangeId") + .proofTemplate(savedTemplate) + .build()); + + assertThrows(DataAccessException.class, () -> repo.deleteById(savedTemplate.getId())); + proofRepository.deleteById(proof.getId()); + assertTrue(proofRepository.findById(proof.getId()).isEmpty()); + assertTrue(repo.findById(proofTemplateToSave.getId()).isPresent()); + repo.deleteById(savedTemplate.getId()); + assertEquals(0, repo.count()); + } + + private BPAProofTemplate.BPAProofTemplateBuilder getBpaProofTemplateBuilder() { + UUID schemaId = schemaMock.prepareSchemaWithAttributes("mySchemaId", "myAttribute"); + return BPAProofTemplate.builder() + .name("myProofTemplate") + .attributeGroups( + BPAAttributeGroups.builder() + .attributeGroup( + BPAAttributeGroup.builder() + .schemaId(schemaId.toString()) + .attribute( + BPAAttribute.builder() + .name("myAttribute") + .condition( + BPACondition.builder() + .value("113") + .operator(ValueOperators.LESS_THAN) + .build()) + .build()) + .build()) + .build()); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/util/SchemaMockFactory.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/util/SchemaMockFactory.java new file mode 100644 index 000000000..f0456b72a --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/util/SchemaMockFactory.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hyperledger.bpa.util; + +import io.micronaut.context.annotation.Bean; +import io.micronaut.context.annotation.Factory; +import org.hyperledger.bpa.api.aries.SchemaAPI; +import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.model.BPASchema; +import org.mockito.Mockito; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +@Factory +public class SchemaMockFactory { + + @Bean + public SchemaMock prepareSchemaWithAttributes(SchemaService schemaService) { + return (ledgerSchemaId, attributes) -> { + UUID schemaId = UUID.randomUUID(); + if (attributes.length > 0) { + Mockito.when(schemaService.getSchema(schemaId)) + .thenReturn(Optional.of(SchemaAPI.builder().schemaId(ledgerSchemaId).id(schemaId).build())); + Mockito.when(schemaService.getSchemaFor(ledgerSchemaId)) + .thenReturn(Optional.of(BPASchema.builder().schemaId(ledgerSchemaId).id(schemaId).build())); + Mockito.when(schemaService.getSchemaAttributeNames(ledgerSchemaId)) + .thenReturn(Set.of(attributes)); + } else { + Mockito.when(schemaService.getSchema(schemaId)) + .thenReturn(Optional.empty()); + Mockito.when(schemaService.getSchemaFor(ledgerSchemaId)) + .thenReturn(Optional.empty()); + Mockito.when(schemaService.getSchemaAttributeNames(ledgerSchemaId)) + .thenReturn(Set.of()); + } + return schemaId; + }; + } + + public interface SchemaMock { + /** + * Mocks {@link SchemaService#getSchema(UUID)} , + * {@link SchemaService#getSchemaFor(String)} and + * {@link SchemaService#getSchemaAttributeNames(String)} + * + * @param ledgerSchemaId the ledger schemaId of the schema to be mocked. + * @param attributes if no attribute names are given, the schema is mocked + * as not existent. + * @return the database schemaId of the mocked schema. + */ + UUID prepareSchemaWithAttributes(String ledgerSchemaId, String... attributes); + } +} diff --git a/backend/business-partner-agent/src/test/resources/spotbugs-exclude.xml b/backend/business-partner-agent/src/test/resources/spotbugs-exclude.xml index 1dd7ad555..fb46906e9 100644 --- a/backend/business-partner-agent/src/test/resources/spotbugs-exclude.xml +++ b/backend/business-partner-agent/src/test/resources/spotbugs-exclude.xml @@ -10,6 +10,18 @@ pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE,RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" /> + + + + + + + + + + + + true + + org.projectlombok + lombok-maven-plugin + ${lombok.version} + + false + ${project.basedir}/src/main/java + ${project.build.directory}/generated-sources/delombok + + + + generate-sources + + delombok + + + + org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 + 3.3.0 ${java.home}/bin/javadoc + ${project.build.directory}/generated-sources/delombok diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 014b67839..b4fc1bbba 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -116,6 +116,18 @@ }} + + + + $vuetify.icons.proofTemplates + + + {{ + $t("nav.proofTemplates") + }} + + + + + + + diff --git a/frontend/src/constants.js b/frontend/src/constants.js index be1081883..3fc3781ad 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -29,6 +29,7 @@ export const ApiRoutes = Object.freeze({ ADMIN: "/admin", ISSUER: "/issuer", PARTNERS: "/partners", + PROOF_TEMPLATES: "/proof-templates", ACTIVITIES: "/activities", }); diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 01242036e..6272a0f8d 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -28,7 +28,8 @@ "settings": "Settings", "notifications": "Notifications", "about": "About", - "signout": "Sign out" + "signout": "Sign out", + "proofTemplates": "Proof Templates" }, "button" : { "save": "Save", diff --git a/frontend/src/main.js b/frontend/src/main.js index e3f16f87a..2450e0bb5 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -108,6 +108,7 @@ Vue.prototype.$config = { store.dispatch("loadSchemas"); store.dispatch("loadPartners"); store.dispatch("loadTags"); + store.dispatch("loadProofTemplates"); console.log("Create the Vue application"); new Vue({ diff --git a/frontend/src/plugins/vuetify.js b/frontend/src/plugins/vuetify.js index 675fe3675..96f348dee 100644 --- a/frontend/src/plugins/vuetify.js +++ b/frontend/src/plugins/vuetify.js @@ -74,6 +74,7 @@ export default new Vuetify({ newMessage: mdiShapePolygonPlus, qrCode: mdiQrcode, credentialManagement: "fas fa-file-signature", + proofTemplates: "fas fa-clone", proofRequests: "fas fa-exchange-alt", connectionAlert: mdiAlertCircleOutline, connectionWaiting: mdiClockTimeThreeOutline, diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index d170cbf1a..8e3ac8929 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -32,6 +32,9 @@ import About from "../views/About.vue"; import CredentialManagement from "@/views/issuer/CredentialManagement.vue"; import Notifications from "@/views/Notifications.vue"; import PresentationRequestDetails from "@/views/PresentationRequestDetails.vue"; +import ProofTemplates from "@/views/ProofTemplates"; +import ProofTemplateCreate from "@/views/ProofTemplateCreate"; +import ProofTemplateView from "@/views/ProofTemplateView"; Vue.use(VueRouter); @@ -173,6 +176,23 @@ const routes = [ name: "Notifications", component: Notifications, }, + { + path: "/app/proofTemplates", + name: "ProofTemplates", + component: ProofTemplates, + }, + { + path: "/app/proofTemplate", + name: "ProofTemplateCreate", + component: ProofTemplateCreate, + props: true, + }, + { + path: "/app/proofTemplate/:id", + name: "ProofTemplateView", + component: ProofTemplateView, + props: true, + }, ]; const router = new VueRouter({ diff --git a/frontend/src/services/index.js b/frontend/src/services/index.js index 7c0f12df9..12a374f68 100644 --- a/frontend/src/services/index.js +++ b/frontend/src/services/index.js @@ -8,3 +8,4 @@ export { default as adminService } from "./adminService"; export { default as issuerService } from "./issuerService"; export { default as partnerService } from "./partnerService"; +export { default as proofTemplateService } from "./proofTemplateService"; diff --git a/frontend/src/services/proofTemplateService.js b/frontend/src/services/proofTemplateService.js new file mode 100644 index 000000000..b4ef84232 --- /dev/null +++ b/frontend/src/services/proofTemplateService.js @@ -0,0 +1,22 @@ +/* + Copyright (c) 2021 - for information on the respective copyright owner + see the NOTICE file and/or the repository at + https://github.com/hyperledger-labs/organizational-agent + + SPDX-License-Identifier: Apache-2.0 +*/ + +import { appAxios } from "@/services/interceptors"; +import { ApiRoutes } from "@/constants"; + +export default { + getProofTemplates() { + return appAxios().get(`${ApiRoutes.PROOF_TEMPLATES}`); + }, + createProofTemplate(data) { + return appAxios().post(`${ApiRoutes.PROOF_TEMPLATES}`, data); + }, + deleteProofTemplate(id) { + return appAxios().delete(`${ApiRoutes.PROOF_TEMPLATES}/${id}`); + }, +}; diff --git a/frontend/src/store/actions.js b/frontend/src/store/actions.js index e8018ce1a..f0b82396c 100644 --- a/frontend/src/store/actions.js +++ b/frontend/src/store/actions.js @@ -3,6 +3,7 @@ import { CredentialTypes } from "../constants"; import { EventBus, axios, apiBaseUrl } from "../main"; import { getPartnerProfile } from "../utils/partnerUtils"; import adminService from "@/services/adminService"; +import proofTemplateService from "@/services/proofTemplateService"; export const loadSchemas = async ({ commit }) => { adminService @@ -143,3 +144,25 @@ export const loadSettings = async ({ commit }) => { EventBus.$emit("error", e); }); }; + +export const loadProofTemplates = async ({ commit }) => { + proofTemplateService + .getProofTemplates() + .then((result) => { + let proofTemplates = result.data; + + // convert date strings to locale specific date formats + proofTemplates.forEach((pt) => { + pt.createdAt = new Date(pt.createdAt).toLocaleString(); + }); + + commit({ + type: "setProofTemplates", + proofTemplates: proofTemplates, + }); + }) + .catch((e) => { + console.error(e); + EventBus.$emit("error", e); + }); +}; diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js index fd2b1d331..e77d2830d 100644 --- a/frontend/src/store/getters.js +++ b/frontend/src/store/getters.js @@ -97,3 +97,7 @@ export const getSettingByKey = (state) => (key) => { export const getSettings = (state) => { return state.settings; }; + +export const getProofTemplates = (state) => { + return state.proofTemplates; +}; diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index cdaf90b59..4195b20ac 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -24,6 +24,7 @@ const store = new Vuex.Store({ documents: [], credentials: [], schemas: [], + proofTemplates: [], tags: [], busyStack: 0, expertMode: false, @@ -56,6 +57,9 @@ const store = new Vuex.Store({ setSettings(state, payload) { state.settings = payload.settings; }, + setProofTemplates(state, payload) { + state.proofTemplates = payload.proofTemplates; + }, }, modules: { diff --git a/frontend/src/views/ProofTemplateCreate.vue b/frontend/src/views/ProofTemplateCreate.vue new file mode 100644 index 000000000..a50384da9 --- /dev/null +++ b/frontend/src/views/ProofTemplateCreate.vue @@ -0,0 +1,468 @@ + + + + + + + diff --git a/frontend/src/views/ProofTemplateView.vue b/frontend/src/views/ProofTemplateView.vue new file mode 100644 index 000000000..288b1939f --- /dev/null +++ b/frontend/src/views/ProofTemplateView.vue @@ -0,0 +1,297 @@ + + + + + + + diff --git a/frontend/src/views/ProofTemplates.vue b/frontend/src/views/ProofTemplates.vue new file mode 100644 index 000000000..87d0c2ea5 --- /dev/null +++ b/frontend/src/views/ProofTemplates.vue @@ -0,0 +1,81 @@ + + + +