Skip to content

Commit

Permalink
Merge branch 'main' into FabioPinheiro-patch-3
Browse files Browse the repository at this point in the history
  • Loading branch information
yshyn-iohk authored Sep 18, 2024
2 parents ed910e7 + 91cb4e7 commit 56cb5c0
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 23 deletions.
11 changes: 0 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,6 @@ The next diagrams offer a concise architectural overview, depicting a Cloud Agen
- SBT (latest version)
- Git (for cloning the repository)
- Docker (for running the PostgreSQL database, Hashicorp Vault, APISIX, and PRISM Node)
- [GITHUB_TOKEN](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) environment variable (required for SBT plugins and access to the GitHub packages)

#### Login to GitHub packages

To login to GitHub packages, you need to create a personal access token and set it as an environment variable together with your GitHub username. Here is an example of how you can do this:

```bash
export GITHUB_TOKEN=your-personal-access-token
export GITHUB_USER=your-github-username
docker login ghcr.io -u $GITHUB_USER -p $GITHUB_TOKEN
```

#### Compile, Test, create the docker image of the Cloud Agent

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ case class CredentialIssuerControllerImpl(
nonce <- getNonceFromJwt(JWT(jwt))
.mapError(throwable => badRequestInvalidProof(jwt, throwable.getMessage))
session <- credentialIssuerService
.getIssuanceSessionByNonce(nonce)
.getPendingIssuanceSessionByNonce(nonce)
.mapError(_ => badRequestInvalidProof(jwt, "nonce is not associated to the issuance session"))
subjectDid <- parseDIDUrlFromKeyId(JWT(jwt))
.map(_.did)
Expand Down Expand Up @@ -240,11 +240,14 @@ case class CredentialIssuerControllerImpl(
request: NonceRequest
): IO[ErrorResponse, NonceResponse] = {
credentialIssuerService
.getIssuanceSessionByIssuerState(request.issuerState)
.getPendingIssuanceSessionByIssuerState(request.issuerState)
.map(session => NonceResponse(session.nonce))
.mapError(ue =>
internalServerError(detail = Some(s"Unexpected error while creating credential offer: ${ue.userFacingMessage}"))
)
// Ideally we don't want this here, but this is used by keycloak plugin and error is not bubbled to the user.
// We log it manually to help with debugging until we find a better way.
.tapError(error => ZIO.logWarning(error.toString()))
}

override def createCredentialIssuer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ trait OIDCCredentialIssuerService {

def getIssuanceSessionByIssuerState(issuerState: String): IO[Error, IssuanceSession]

def getIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession]
def getPendingIssuanceSessionByIssuerState(issuerState: String): IO[Error, IssuanceSession]

def getPendingIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession]

def updateIssuanceSession(issuanceSession: IssuanceSession): IO[Error, IssuanceSession]
}
Expand All @@ -85,6 +87,11 @@ object OIDCCredentialIssuerService {
s"Credential configuration with id $credentialConfigurationId not found for issuer $issuerId"
}

case class IssuanceSessionAlreadyIssued(issuerState: String) extends Error {
override def userFacingMessage: String =
s"Issuance session with issuerState $issuerState is already issued"
}

case class CredentialSchemaError(cause: org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError)
extends Error {
override def userFacingMessage: String = cause.userFacingMessage
Expand Down Expand Up @@ -230,6 +237,10 @@ case class OIDCCredentialIssuerServiceImpl(
.mapError(e => ServiceError(s"Failed to get issuance session: ${e.message}"))
.someOrFail(ServiceError(s"The IssuanceSession with the issuerState $issuerState does not exist"))

override def getPendingIssuanceSessionByIssuerState(
issuerState: String
): IO[Error, IssuanceSession] = getIssuanceSessionByIssuerState(issuerState).ensurePendingSession

override def createCredentialOffer(
credentialIssuerBaseUrl: URL,
issuerId: UUID,
Expand Down Expand Up @@ -261,11 +272,12 @@ case class OIDCCredentialIssuerServiceImpl(
)
)

def getIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession] = {
def getPendingIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession] = {
issuanceSessionStorage
.getByNonce(nonce)
.mapError(e => ServiceError(s"Failed to get issuance session: ${e.message}"))
.someOrFail(ServiceError(s"The IssuanceSession with the nonce $nonce does not exist"))
.ensurePendingSession
}

override def updateIssuanceSession(issuanceSession: IssuanceSession): IO[Error, IssuanceSession] = {
Expand Down Expand Up @@ -295,6 +307,15 @@ case class OIDCCredentialIssuerServiceImpl(
issuingDid = issuerDid,
)
}

extension [R, A](result: ZIO[R, Error, IssuanceSession]) {
def ensurePendingSession: ZIO[R, Error, IssuanceSession] =
result.flatMap { session =>
if session.subjectDid.isEmpty
then ZIO.succeed(session)
else ZIO.fail(IssuanceSessionAlreadyIssued(session.issuerState))
}
}
}

object OIDCCredentialIssuerServiceImpl {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,6 @@ object OIDCCredentialIssuerServiceSpec
MockDIDNonSecretStorage.empty,
getCredentialConfigurationExpectations.toLayer,
layers
)
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package org.hyperledger.identus.pollux.core.service.verification

import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema
import org.hyperledger.identus.pollux.core.service.URIDereferencer
import org.hyperledger.identus.pollux.vc.jwt.{DidResolver, JWT, JWTVerification, JwtCredential}
import org.hyperledger.identus.pollux.vc.jwt.{CredentialPayload, DidResolver, JWT, JWTVerification, JwtCredential}
import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits
import zio.*

import java.time.OffsetDateTime
Expand Down Expand Up @@ -104,7 +105,7 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
CredentialSchema
.validateJWTCredentialSubject(
credentialSchema.id,
decodedJwt.credentialSubject.noSpaces,
CredentialPayload.Implicits.jwtVcEncoder(decodedJwt.vc).noSpaces,
uriDereferencer
)
.mapError(error =>
Expand Down
6 changes: 3 additions & 3 deletions pollux/core/src/test/resources/vc-schema-driver-license.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
"format": "date-time"
},
"drivingLicenseID": {
"type": "string"
"type": "integer"
},
"drivingClass": {
"type": "integer"
}
},
"required": ["dateOfIssuance", "drivingLicenseID", "drivingClass"],
"additionalProperties": false
"additionalProperties": true
}
},
"required": ["credentialSubject"],
"additionalProperties": false
"additionalProperties": true
}
4 changes: 2 additions & 2 deletions pollux/core/src/test/resources/vc-schema-personal.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
}
},
"required": ["email", "userName", "age"],
"additionalProperties": false
"additionalProperties": true
}
},
"required": ["credentialSubject"],
"additionalProperties": false
"additionalProperties": true
}
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,86 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS
someVcVerificationServiceLayer ++
ZLayer.succeed(WalletAccessContext(WalletId.random))
),
test("verify subject given multiple schema") {
for {
svc <- ZIO.service[VcVerificationService]
verifier = "did:prism:verifier"
jwtCredentialPayload = W3cCredentialPayload(
`@context` =
Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"),
maybeId = Some("http://example.edu/credentials/3732"),
`type` = Set("VerifiableCredential", "UniversityDegreeCredential"),
issuer = Left(issuer.did.toString),
issuanceDate = Instant.parse("2010-01-01T00:00:00Z"),
maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")),
maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")),
maybeValidUntil = Some(Instant.parse("2010-01-12T00:00:00Z")),
maybeCredentialSchema = Some(
Right(
List(
CredentialSchema(
id = "resource:///vc-schema-personal.json",
`type` = "JsonSchemaValidator2018"
),
CredentialSchema(
id = "resource:///vc-schema-driver-license.json",
`type` = "JsonSchemaValidator2018"
)
)
)
),
credentialSubject = Json.obj(
"userName" -> Json.fromString("Alice"),
"age" -> Json.fromInt(42),
"email" -> Json.fromString("alice@wonderland.com"),
"dateOfIssuance" -> Json.fromString("2000-01-01T10:00:00Z"),
"drivingLicenseID" -> Json.fromInt(12345),
"drivingClass" -> Json.fromInt(5)
),
maybeCredentialStatus = Some(
CredentialStatus(
id = "did:work:MDP8AsFhHzhwUvGNuYkX7T;id=06e126d1-fa44-4882-a243-1e326fbe21db;version=1.0",
`type` = "StatusList2021Entry",
statusPurpose = StatusPurpose.Revocation,
statusListIndex = 0,
statusListCredential = "https://example.com/credentials/status/3"
)
),
maybeRefreshService = Some(
RefreshService(
id = "https://example.edu/refresh/3732",
`type` = "ManualRefreshService2018"
)
),
maybeEvidence = Option.empty,
maybeTermsOfUse = Option.empty,
aud = Set(verifier)
).toJwtCredentialPayload
signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson)
result <-
svc.verify(
List(
VcVerificationRequest(signedJwtCredential.value, VcVerification.SubjectVerification)
)
)
} yield {
assertTrue(
result.contains(
VcVerificationResult(
signedJwtCredential.value,
VcVerification.SubjectVerification,
true
)
)
)
}
}.provideSomeLayer(
MockDIDService.empty ++
MockManagedDIDService.empty ++
ResourceURIDereferencerImpl.layer >+>
someVcVerificationServiceLayer ++
ZLayer.succeed(WalletAccessContext(WalletId.random))
),
test("verify nbf given valid") {
for {
svc <- ZIO.service[VcVerificationService]
Expand Down

0 comments on commit 56cb5c0

Please sign in to comment.