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(pollux): update storage for static resources to be wrapped in an envelope #1283

Merged
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.hyperledger.identus.issue.controller

import io.lemonlabs.uri.Url
import org.hyperledger.identus.agent.server.config.AppConfig
import org.hyperledger.identus.agent.server.ControllerHelper
import org.hyperledger.identus.agent.walletapi.model.PublicationState
Expand All @@ -9,7 +8,7 @@ import org.hyperledger.identus.agent.walletapi.service.ManagedDIDService
import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext}
import org.hyperledger.identus.api.http.model.{CollectionStats, PaginationInput}
import org.hyperledger.identus.api.util.PaginationUtils
import org.hyperledger.identus.castor.core.model.did.{PrismDID, VerificationRelationship}
import org.hyperledger.identus.castor.core.model.did.{DIDUrl, PrismDID, VerificationRelationship}
import org.hyperledger.identus.castor.core.service.DIDService
import org.hyperledger.identus.connect.core.service.ConnectionService
import org.hyperledger.identus.issue.controller.http.{
Expand All @@ -21,14 +20,19 @@ import org.hyperledger.identus.issue.controller.http.{
import org.hyperledger.identus.pollux.core.model.{CredentialFormat, DidCommID}
import org.hyperledger.identus.pollux.core.model.CredentialFormat.{AnonCreds, JWT, SDJWT}
import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.Role
import org.hyperledger.identus.pollux.core.service.CredentialService
import org.hyperledger.identus.pollux.core.service.{CredentialDefinitionService, CredentialService}
import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext}
import zio.{URLayer, ZIO, ZLayer}
import org.hyperledger.identus.shared.utils.{Base64Utils, Json as JsonUtils}
import zio.*
import zio.json.given
import org.hyperledger.identus.shared.crypto.Sha256Hash
import scala.collection.immutable.ListMap

import scala.language.implicitConversions

class IssueControllerImpl(
credentialService: CredentialService,
credentialDefinitionService: CredentialDefinitionService,
connectionService: ConnectionService,
didService: DIDService,
managedDIDService: ManagedDIDService,
Expand Down Expand Up @@ -92,16 +96,31 @@ class IssueControllerImpl(
.mapError(_ =>
ErrorResponse.badRequest(detail = Some("Missing request parameter: credentialDefinitionId"))
)
credentialDefinitionId = {
credentialDefinition <- credentialDefinitionService.getByGUID(credentialDefinitionGUID)
credentialDefinitionId <- {

// TODO: this needs to be either DID or HTTP url based on credentialDefinition.resolutionMethod
val publicEndpointServiceName = appConfig.agent.httpEndpoint.serviceName
val resourcePath =
s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition"
val didUrl = Url
.parse(s"$issuingDID?resourceService=$publicEndpointServiceName&resourcePath=$resourcePath")
.toString

didUrl
val didUrl = for {
canonicalized <- JsonUtils.canonicalizeToJcs(credentialDefinition.definition.toJson)
encoded = Base64Utils.encodeURL(canonicalized.getBytes)
hash = Sha256Hash.compute(encoded.getBytes).hexEncoded
didUrl = DIDUrl(
issuingDID.did,
Seq(),
ListMap(
"resourceService" -> Seq(publicEndpointServiceName),
"resourcePath" -> Seq(s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition?resourceHash=$hash"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the prefix cloud-agent be added to the path?
It should be a matter of configuration, so the agent should know the full external path

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to add "cloud-agent" in the path. when we later construct an HTTP URL to resolve an actual resource from the DID URL, we will use resource service, which is going to be located inside the DID, we will resolve the did and this service will include the server base URL path, in the next ticket PR, when I add this service by default to every DID that issuer creates, the value of this service will be from the config, the value of public endpoint, and it does already include "cloud-agent" as you can see here

publicEndpointUrl = "https://host.docker.internal:8080/cloud-agent"

),
None
).toString
} yield didUrl

ZIO.fromEither(didUrl).mapError(_ => ErrorResponse.badRequest(detail = Some("Could not parse credential definition")))

}
record <- credentialService
.createAnonCredsIssueCredentialRecord(
Expand Down Expand Up @@ -244,7 +263,9 @@ class IssueControllerImpl(
}

object IssueControllerImpl {
val layer
: URLayer[CredentialService & ConnectionService & DIDService & ManagedDIDService & AppConfig, IssueController] =
ZLayer.fromFunction(IssueControllerImpl(_, _, _, _, _))
val layer: URLayer[
CredentialService & CredentialDefinitionService & ConnectionService & DIDService & ManagedDIDService & AppConfig,
IssueController
] =
ZLayer.fromFunction(IssueControllerImpl(_, _, _, _, _, _))
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import org.hyperledger.identus.iam.authentication.apikey.ApiKeyEndpointSecurityL
import org.hyperledger.identus.iam.authentication.oidc.JwtCredentials
import org.hyperledger.identus.iam.authentication.oidc.JwtSecurityLogic.jwtAuthHeader
import org.hyperledger.identus.pollux.credentialdefinition.http.{
CredentialDefinitionDidUrlResponse,
CredentialDefinitionDidUrlResponsePage,
CredentialDefinitionInnerDefinitionDidUrlResponse,
CredentialDefinitionInput,
CredentialDefinitionResponse,
CredentialDefinitionResponsePage,
Expand Down Expand Up @@ -52,7 +55,7 @@ object CredentialDefinitionRegistryEndpoints {

val tag = Tag(name = tagName, description = Option(tagDescription), externalDocs = Option(tagExternalDocumentation))

val createCredentialDefinitionEndpoint: Endpoint[
val createCredentialDefinitionHttpUrlEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(RequestContext, CredentialDefinitionInput),
ErrorResponse,
Expand All @@ -79,15 +82,52 @@ object CredentialDefinitionRegistryEndpoints {
.out(jsonBody[http.CredentialDefinitionResponse])
.description("Credential definition record")
.errorOut(basicFailureAndNotFoundAndForbidden)
.name("createCredentialDefinition")
.summary("Publish new definition to the definition registry")
.name("createCredentialDefinitionHttpUrl")
.summary("Publish new definition to the definition registry, resolvable by HTTP url")
.description(
"Create the new credential definition record with metadata and internal JSON Schema on behalf of Cloud Agent. " +
"The credential definition will be signed by the keys of Cloud Agent and issued by the DID that corresponds to it."
)
.tag(tagName)

val getCredentialDefinitionByIdEndpoint: PublicEndpoint[
val createCredentialDefinitionDidUrlEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(RequestContext, CredentialDefinitionInput),
ErrorResponse,
CredentialDefinitionResponse,
Any
] =
endpoint.post
.securityIn(apiKeyHeader)
.securityIn(jwtAuthHeader)
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in("credential-definition-registry" / "definitions" / "did-url")
.in(
jsonBody[CredentialDefinitionInput]
.description(
"JSON object required for the credential definition creation"
)
)
.out(
statusCode(StatusCode.Created)
.description(
"The new credential definition record is successfully created"
)
)
.out(
jsonBody[http.CredentialDefinitionResponse]
) // We use same response as for HTTP url on DID url for definitions
.description("Credential definition record")
.errorOut(basicFailureAndNotFoundAndForbidden)
.name("createCredentialDefinitionDidUrl")
.summary("Publish new definition to the definition registry, resolvable by DID url")
.description(
"Create the new credential definition record with metadata and internal JSON Schema on behalf of Cloud Agent. " +
shotexa marked this conversation as resolved.
Show resolved Hide resolved
"The credential definition will be signed by the keys of Cloud Agent and issued by the DID that corresponds to it."
shotexa marked this conversation as resolved.
Show resolved Hide resolved
)
.tag(tagName)

val getCredentialDefinitionByIdHttpUrlEndpoint: PublicEndpoint[
(RequestContext, UUID),
ErrorResponse,
CredentialDefinitionResponse,
Expand All @@ -102,14 +142,40 @@ object CredentialDefinitionRegistryEndpoints {
)
.out(jsonBody[CredentialDefinitionResponse].description("CredentialDefinition found by `guid`"))
.errorOut(basicFailuresAndNotFound)
.name("getCredentialDefinitionById")
.name("getCredentialDefinitionByIdHttpUrl")
.summary("Fetch the credential definition from the registry by `guid`")
.description(
"Fetch the credential definition by the unique identifier"
)
.tag(tagName)

val getCredentialDefinitionInnerDefinitionByIdEndpoint: PublicEndpoint[
val getCredentialDefinitionByIdDidUrlEndpoint: PublicEndpoint[
(RequestContext, UUID),
ErrorResponse,
CredentialDefinitionDidUrlResponse,
Any
] =
endpoint.get
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in(
"credential-definition-registry" / "definitions" / "did-url" / path[UUID]("guid").description(
"Globally unique identifier of the credential definition record"
)
)
.out(
jsonBody[CredentialDefinitionDidUrlResponse].description(
"CredentialDefinition found by `guid`, wrapped in an envelope"
)
)
.errorOut(basicFailuresAndNotFound)
.name("getCredentialDefinitionByIdDidUrl")
.summary("Fetch the credential definition from the registry by `guid`, wrapped in an envelope")
.description(
"Fetch the credential definition by the unique identifier, it should have been crated via DID url, otherwise not found error is returned."
)
.tag(tagName)

val getCredentialDefinitionInnerDefinitionByIdHttpUrlEndpoint: PublicEndpoint[
(RequestContext, UUID),
ErrorResponse,
zio.json.ast.Json,
Expand All @@ -124,16 +190,42 @@ object CredentialDefinitionRegistryEndpoints {
)
.out(jsonBody[zio.json.ast.Json].description("CredentialDefinition found by `guid`"))
.errorOut(basicFailuresAndNotFound)
.name("getCredentialDefinitionInnerDefinitionById")
.name("getCredentialDefinitionInnerDefinitionByIdHttpUrl")
.summary("Fetch the inner definition field of the credential definition from the registry by `guid`")
.description(
"Fetch the inner definition fields of the credential definition by the unique identifier"
)
.tag(tagName)

val getCredentialDefinitionInnerDefinitionByIdDidUrlEndpoint: PublicEndpoint[
(RequestContext, UUID),
ErrorResponse,
CredentialDefinitionInnerDefinitionDidUrlResponse,
Any
] =
endpoint.get
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in(
"credential-definition-registry" / "definitions" / path[UUID]("guid") / "definition".description(
"Globally unique identifier of the credential definition record"
)
)
.out(
jsonBody[CredentialDefinitionInnerDefinitionDidUrlResponse].description("CredentialDefinition found by `guid`")
)
.errorOut(basicFailuresAndNotFound)
.name("getCredentialDefinitionInnerDefinitionByIdDidUrl")
.summary(
"Fetch the inner definition field of the credential definition from the registry by `guid`, wrapped in an envelope"
)
.description(
"Fetch the inner definition fields of the credential definition by the unique identifier, it should have been crated via DID url, otherwise not found error is returned."
)
.tag(tagName)

private val credentialDefinitionFilterInput: EndpointInput[http.FilterInput] = EndpointInput.derived[http.FilterInput]
private val paginationInput: EndpointInput[PaginationInput] = EndpointInput.derived[PaginationInput]
val lookupCredentialDefinitionsByQueryEndpoint: Endpoint[
val lookupCredentialDefinitionsByQueryHttpUrlEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(
RequestContext,
Expand All @@ -159,10 +251,43 @@ object CredentialDefinitionRegistryEndpoints {
.in(query[Option[Order]]("order"))
.out(jsonBody[CredentialDefinitionResponsePage].description("Collection of CredentialDefinitions records."))
.errorOut(basicFailures)
.name("lookupCredentialDefinitionsByQuery")
.name("lookupCredentialDefinitionsByQueryHttpUrl")
.summary("Lookup credential definitions by indexed fields")
.description(
"Lookup credential definitions by `author`, `name`, `tag` parameters and control the pagination by `offset` and `limit` parameters "
)
.tag(tagName)

val lookupCredentialDefinitionsByQueryDidUrlEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(
RequestContext,
FilterInput,
PaginationInput,
Option[Order]
),
ErrorResponse,
CredentialDefinitionDidUrlResponsePage,
Any
] =
endpoint.get
.securityIn(apiKeyHeader)
.securityIn(jwtAuthHeader)
.in(extractFromRequest[RequestContext](RequestContext.apply))
.in(
"credential-definition-registry" / "definitions" / "did-url".description(
"Lookup credential definitions by query"
)
)
.in(credentialDefinitionFilterInput)
.in(paginationInput)
.in(query[Option[Order]]("order"))
.out(jsonBody[CredentialDefinitionDidUrlResponsePage].description("Collection of CredentialDefinitions records."))
.errorOut(basicFailures)
.name("lookupCredentialDefinitionsByQueryDidUrl")
.summary("Lookup credential definitions by indexed fields")
.description(
"Lookup DID url resolvable credential definitions by `author`, `name`, `tag` parameters and control the pagination by `offset` and `limit` parameters "
)
.tag(tagName)
}
Loading
Loading