Skip to content

Commit

Permalink
feat: add DID Management API
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Dec 19, 2023
1 parent b67982c commit c27b329
Show file tree
Hide file tree
Showing 28 changed files with 1,346 additions and 99 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* Copyright (c) 2023 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
Expand All @@ -8,19 +8,19 @@
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
* Metaform Systems, Inc. - initial API and implementation
*
*/

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

import org.eclipse.edc.core.transform.transformer.to.JsonValueToGenericTypeTransformer;
import org.eclipse.edc.iam.identitytrust.transform.to.JsonObjectToPresentationQueryTransformer;
import org.eclipse.edc.identityhub.api.v1.PresentationApiController;
import org.eclipse.edc.identityhub.api.validation.PresentationQueryValidator;
import org.eclipse.edc.identityhub.spi.generator.VerifiablePresentationService;
import org.eclipse.edc.identityhub.spi.resolution.CredentialQueryResolver;
import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier;
import org.eclipse.edc.identityservice.api.v1.PresentationApiController;
import org.eclipse.edc.identityservice.api.validation.PresentationQueryValidator;
import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
Expand All @@ -34,7 +34,7 @@
import org.eclipse.edc.web.jersey.jsonld.ObjectMapperProvider;
import org.eclipse.edc.web.spi.WebService;

import static org.eclipse.edc.identityservice.api.PresentationApiExtension.NAME;
import static org.eclipse.edc.identityhub.api.PresentationApiExtension.NAME;
import static org.eclipse.edc.spi.CoreConstants.JSON_LD;

@Extension(value = NAME)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* Copyright (c) 2023 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
Expand All @@ -8,11 +8,11 @@
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityservice.api.v1;
package org.eclipse.edc.identityhub.api.v1;

import io.swagger.v3.oas.annotations.media.Schema;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* Copyright (c) 2023 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
Expand All @@ -8,11 +8,11 @@
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityservice.api.v1;
package org.eclipse.edc.identityhub.api.v1;


import io.swagger.v3.oas.annotations.OpenAPIDefinition;
Expand All @@ -29,7 +29,6 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.json.JsonObject;
import jakarta.ws.rs.core.Response;
import org.eclipse.edc.identityservice.api.v1.ApiSchema.ApiErrorDetailSchema;
import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponse;

@OpenAPIDefinition(
Expand All @@ -50,14 +49,14 @@ public interface PresentationApi {
@ApiResponse(responseCode = "200", description = "The query was successfully processed, the response contains the VerifiablePresentation",
content = @Content(schema = @Schema(implementation = PresentationResponse.class), mediaType = "application/ld+json")),
@ApiResponse(responseCode = "400", description = "Request body was malformed, for example when both scope and presentationDefinition are given",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetailSchema.class)), mediaType = "application/json")),
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)), mediaType = "application/json")),
@ApiResponse(responseCode = "401", description = "No Authorization header was given.",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetailSchema.class)), mediaType = "application/json")),
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)), mediaType = "application/json")),
@ApiResponse(responseCode = "403", description = "The given authentication token could not be validated. This can happen, when the request body " +
"calls for a broader query scope than the granted scope in the auth token",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetailSchema.class)), mediaType = "application/json")),
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)), mediaType = "application/json")),
@ApiResponse(responseCode = "501", description = "When the request contained a presentationDefinition object, but the implementation does not support it.",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetailSchema.class)), mediaType = "application/json"))
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)), mediaType = "application/json"))
}
)
Response queryPresentation(JsonObject query, String authHeader);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* Copyright (c) 2023 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
Expand All @@ -8,11 +8,11 @@
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityservice.api.v1;
package org.eclipse.edc.identityhub.api.v1;

import com.nimbusds.jwt.SignedJWT;
import jakarta.json.JsonObject;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* Copyright (c) 2023 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
Expand All @@ -8,11 +8,11 @@
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
* Metaform Systems, Inc. - initial API and implementation
*
*/

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

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
#
#

org.eclipse.edc.identityservice.api.PresentationApiExtension
org.eclipse.edc.identityhub.api.PresentationApiExtension
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import com.nimbusds.jwt.JWTClaimsSet;
import jakarta.json.JsonObject;
import org.eclipse.edc.identityhub.api.v1.PresentationApiController;
import org.eclipse.edc.identityhub.spi.generator.VerifiablePresentationService;
import org.eclipse.edc.identityhub.spi.resolution.CredentialQueryResolver;
import org.eclipse.edc.identityhub.spi.resolution.QueryResult;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import org.eclipse.edc.identityhub.api.validation.PresentationQueryValidator;
import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery;
import org.eclipse.edc.identitytrust.model.presentationdefinition.Constraints;
import org.eclipse.edc.identitytrust.model.presentationdefinition.Field;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,23 @@
* In-mem variant of the publisher registry.
*/
public class DidDocumentPublisherRegistryImpl implements DidDocumentPublisherRegistry {
public static final String DID_PREFIX = "did:";
public static final String DID_METHOD_SEPARATOR = ":";
private final Map<String, DidDocumentPublisher> publishers = new HashMap<>();


@Override
public void addPublisher(String didMethodName, DidDocumentPublisher publisher) {
publishers.put(didMethodName, publisher);
}

@Override
public DidDocumentPublisher getPublisher(String did) {
return publishers.get(did);
// only need the "did" and method prefix
did = did.replace(DID_PREFIX, "");
var endIndex = did.indexOf(DID_METHOD_SEPARATOR);
return endIndex >= 0 ?
publishers.get(DID_PREFIX + did.substring(0, endIndex)) :
null;
}

@Override
public boolean canPublish(String did) {
return publishers.containsKey(did);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright (c) 2023 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.did;

import org.eclipse.edc.iam.did.spi.document.DidDocument;
import org.eclipse.edc.identithub.did.spi.DidDocumentPublisherRegistry;
import org.eclipse.edc.identithub.did.spi.DidDocumentService;
import org.eclipse.edc.identithub.did.spi.model.DidResource;
import org.eclipse.edc.identithub.did.spi.model.DidState;
import org.eclipse.edc.identithub.did.spi.store.DidResourceStore;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.util.Collection;

/**
* This is an aggregate service to manage CRUD operations of {@link DidDocument}s as well as handle their
* publishing and un-publishing. All methods are executed transactionally.
*/
public class DidDocumentServiceImpl implements DidDocumentService {

private final TransactionContext transactionContext;
private final DidResourceStore didResourceStore;
private final DidDocumentPublisherRegistry registry;

public DidDocumentServiceImpl(TransactionContext transactionContext, DidResourceStore didResourceStore, DidDocumentPublisherRegistry registry) {
this.transactionContext = transactionContext;
this.didResourceStore = didResourceStore;
this.registry = registry;
}

@Override
public ServiceResult<Void> store(DidDocument document) {
return transactionContext.execute(() -> {
var res = DidResource.Builder.newInstance()
.document(document)
.did(document.getId())
.build();
var result = didResourceStore.save(res);
return result.succeeded() ?
ServiceResult.success() :
ServiceResult.fromFailure(result);
});
}

@Override
public ServiceResult<Void> publish(String did) {
return transactionContext.execute(() -> {
var existingDoc = didResourceStore.findById(did);
if (existingDoc == null) {
return ServiceResult.notFound(notFoundMessage(did));
}
var publisher = registry.getPublisher(did);
if (publisher == null) {
return ServiceResult.badRequest(noPublisherFoundMessage(did));
}
var publishResult = publisher.publish(did);
return publishResult.succeeded() ?
ServiceResult.success() :
ServiceResult.badRequest(publishResult.getFailureDetail());

});
}

@Override
public ServiceResult<Void> unpublish(String did) {
return transactionContext.execute(() -> {
var existingDoc = didResourceStore.findById(did);
if (existingDoc == null) {
return ServiceResult.notFound(notFoundMessage(did));
}
var publisher = registry.getPublisher(did);
if (publisher == null) {
return ServiceResult.badRequest(noPublisherFoundMessage(did));
}
var publishResult = publisher.unpublish(did);
return publishResult.succeeded() ?
ServiceResult.success() :
ServiceResult.badRequest(publishResult.getFailureDetail());

});
}

@Override
public ServiceResult<Void> update(DidDocument document) {
return transactionContext.execute(() -> {
// obtain existing resource from storage
var did = document.getId();
var existing = didResourceStore.findById(did);
if (existing == null) {
return ServiceResult.notFound(notFoundMessage(did));
}

//update only the did document
var updatedResource = DidResource.Builder.newInstance()
.document(document)
.did(did)
.state(existing.getState())
.createTimestamp(existing.getCreateTimestamp())
.stateTimeStamp(existing.getStateTimestamp())
.build();

var res = didResourceStore.update(updatedResource);
return res.succeeded() ?
ServiceResult.success() :
ServiceResult.fromFailure(res);
});
}

@Override
public ServiceResult<Void> deleteById(String did) {
return transactionContext.execute(() -> {
var existing = didResourceStore.findById(did);
if (existing == null) {
return ServiceResult.notFound(notFoundMessage(did));
}
if (existing.getState() == DidState.PUBLISHED.code()) {
return ServiceResult.conflict("Cannot delete DID '%s' because it is already published. Un-publish first!".formatted(did));
}
var res = didResourceStore.deleteById(did);
return res.succeeded() ?
ServiceResult.success() :
ServiceResult.fromFailure(res);
});
}

@Override
public ServiceResult<Collection<DidDocument>> queryDocuments(QuerySpec query) {
return transactionContext.execute(() -> {
var res = didResourceStore.query(query);
return ServiceResult.success(res.stream().map(DidResource::getDocument).toList());
});
}

@Override
public DidResource findById(String did) {
return transactionContext.execute(() -> didResourceStore.findById(did));
}
}
Loading

0 comments on commit c27b329

Please sign in to comment.