Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement DID management API #203

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,29 @@
* 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 static final int DID_PREFIX_INDEX = 4;
private final Map<String, DidDocumentPublisher> publishers = new HashMap<>();


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

@Override
public DidDocumentPublisher getPublisher(String did) {
return publishers.get(did);
}

@Override
public boolean canPublish(String did) {
return publishers.containsKey(did);
if (!did.startsWith(DID_PREFIX)) {
throw new IllegalArgumentException("A DID must include the 'did' prefix.");
}
var endIndex = did.indexOf(DID_METHOD_SEPARATOR, DID_PREFIX_INDEX);
if (endIndex >= 0) {
var method = did.substring(0, endIndex);
return publishers.get(method);
} else {
return publishers.get(did); // endIndex can be -1 when only the method was passed, e.g. "did:web"
}
}

}
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());
ndr-brt marked this conversation as resolved.
Show resolved Hide resolved
});
}

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