diff --git a/.mega-linter.yml b/.mega-linter.yml index 786cf36edd..6736cb65d1 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -28,6 +28,7 @@ DISABLE_LINTERS: - PYTHON_MYPY - PYTHON_PYRIGHT - PYTHON_RUFF + - TYPESCRIPT_STANDARD DISABLE_ERRORS_LINTERS: - KOTLIN_KTLINT @@ -65,5 +66,5 @@ YAML_PRETTIER_FILTER_REGEX_EXCLUDE: "infrastructure/charts/agent/*|cloud-agent/s YAML_V8R_FILTER_REGEX_EXCLUDE: "infrastructure/charts/agent/*" JAVASCRIPT_STANDARD_FILTER_REGEX_EXCLUDE: "tests/performance-tests/agent-performance-tests-k6/src/k6chaijs.js\ - |tests/didcomm-tests/docker/initdb.js" + |tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts|tests/didcomm-tests/docker/initdb.js" BASH_SHELLCHECK_FILTER_REGEX_EXCLUDE: "infrastructure/*" diff --git a/cloud-agent/service/server/src/main/resources/logback.xml b/cloud-agent/service/server/src/main/resources/logback.xml index 9121c1c22d..a91551a3d1 100644 --- a/cloud-agent/service/server/src/main/resources/logback.xml +++ b/cloud-agent/service/server/src/main/resources/logback.xml @@ -13,6 +13,9 @@ + + + diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala index 4bfb247176..a6f2bfae67 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala @@ -20,7 +20,7 @@ import org.hyperledger.identus.mercury.model.* import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.mercury.protocol.presentproof.* import org.hyperledger.identus.mercury.protocol.reportproblem.v2.{ProblemCode, ReportProblem} -import org.hyperledger.identus.pollux.core.model.* +import org.hyperledger.identus.pollux.core.model.{presentation, *} import org.hyperledger.identus.pollux.core.model.error.{CredentialServiceError, PresentationError} import org.hyperledger.identus.pollux.core.model.error.PresentationError.* import org.hyperledger.identus.pollux.core.model.presentation.Options @@ -28,6 +28,7 @@ import org.hyperledger.identus.pollux.core.service.{CredentialService, Presentat import org.hyperledger.identus.pollux.core.service.serdes.AnoncredCredentialProofsV1 import org.hyperledger.identus.pollux.sdjwt.{HolderPrivateKey, IssuerPublicKey, PresentationCompact, SDJWT} import org.hyperledger.identus.pollux.vc.jwt.{DidResolver as JwtDidResolver, Issuer as JwtIssuer, JWT, JwtPresentation} +import org.hyperledger.identus.pollux.vc.jwt.CredentialSchemaAndTrustedIssuersConstraint import org.hyperledger.identus.resolvers.DIDResolver import org.hyperledger.identus.shared.http.* import org.hyperledger.identus.shared.messaging @@ -37,7 +38,7 @@ import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds import zio.* import zio.metrics.* -import zio.prelude.Validation +import zio.prelude.{Validation, ZValidation} import zio.prelude.ZValidation.{Failure as ZFailure, *} import java.time.{Instant, ZoneId} @@ -947,7 +948,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { object Verifier { - def handleRequestPending(id: DidCommID, record: RequestPresentation): ZIO[ + def handleRequestPending(id: DidCommID, requestPresentation: RequestPresentation): ZIO[ JwtDidResolver & COMMON_RESOURCES & MESSAGING_RESOURCES, Failure, Unit @@ -978,17 +979,20 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { val verifierReqPendingToSentFlow = for { _ <- ZIO.log(s"PresentationRecord: RequestPending (Send Message)") - walletAccessContext <- buildWalletAccessContextLayer( - record.from.getOrElse(throw new RuntimeException("from is None is not possible")) - ) + walletAccessContext <- ZIO + .fromOption(requestPresentation.from) + .mapError(_ => RequestPresentationMissingField(id.value, "sender")) + .flatMap(buildWalletAccessContextLayer) + result <- for { didOps <- ZIO.service[DidOps] - didCommAgent <- buildDIDCommAgent( - record.from.getOrElse(throw new RuntimeException("from is None is not possible")) - ).provideSomeLayer(ZLayer.succeed(walletAccessContext)) + didCommAgent <- ZIO + .fromOption(requestPresentation.from) + .mapError(_ => RequestPresentationMissingField(id.value, "sender")) + .flatMap(buildDIDCommAgent(_).provideSomeLayer(ZLayer.succeed(walletAccessContext))) resp <- MessagingService - .send(record.makeMessage) + .send(requestPresentation.makeMessage) .provideSomeLayer(didCommAgent) @@ Metric .gauge("present_proof_flow_verifier_send_presentation_request_msg_ms_gauge") @@ -1069,6 +1073,16 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { } } + private def buildReportProblem(presentation: Presentation, error: String): ReportProblem = { + ReportProblem.build( + fromDID = presentation.to, + toDID = presentation.from, + pthid = presentation.thid.getOrElse(presentation.id), + code = ProblemCode("e.p.presentation-verification-failed"), + comment = Some(error) + ) + } + private def handleJWT( id: DidCommID, requestPresentation: RequestPresentation, @@ -1085,7 +1099,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _ <- checkInvitationExpiry(id, invitation).provideSomeLayer(ZLayer.succeed(walletAccessContext)) result <- for { didResolverService <- ZIO.service[JwtDidResolver] - credentialsClaimsValidationResult <- presentation.attachments.head.data match { + claimsValidationResult <- presentation.attachments.head.data match { case Base64(data) => val base64Decoded = new String(java.util.Base64.getUrlDecoder.decode(data)) val maybePresentationOptions: Either[PresentationError, Option[Options]] = @@ -1103,17 +1117,33 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ) ) .getOrElse(Right(None)) + val schemaIdAndTrustedIssuers = requestPresentation.body.proof_types.map { proofType => + CredentialSchemaAndTrustedIssuersConstraint( + proofType.schema, + proofType.trustIssuers.map(_.map(_.value)) + ) + } val presentationClaimsValidationResult = for { - _ <- ZIO.fromEither(maybePresentationOptions.map { + validationResult: Validation[String, Unit] <- ZIO.fromEither(maybePresentationOptions.map { case Some(options) => - JwtPresentation.validatePresentation( - JWT(base64Decoded), - options.domain, - options.challenge - ) - case _ => Validation.unit + JwtPresentation + .validatePresentation( + JWT(base64Decoded), + Some(options.domain), + Some(options.challenge), + schemaIdAndTrustedIssuers + ) + case _ => + JwtPresentation + .validatePresentation( + JWT(base64Decoded), + None, + None, + schemaIdAndTrustedIssuers + ) }) + verificationConfig <- ZIO.service[AppConfig].map(_.agent.verification) _ <- ZIO.log(s"VerificationConfig: ${verificationConfig}") @@ -1121,18 +1151,21 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { // A proof is typically attached to a verifiable presentation for authentication purposes // and to a verifiable credential as a method of assertion. uriResolver <- ZIO.service[UriResolver] - result <- JwtPresentation + result: Validation[String, Unit] <- JwtPresentation .verify( JWT(base64Decoded), verificationConfig.toPresentationVerificationOptions() )(didResolverService, uriResolver)(clock) .mapError(error => PresentationError.PresentationVerificationError(error.mkString)) - } yield result + } yield Seq(validationResult, result) presentationClaimsValidationResult case any => ZIO.fail(PresentationReceivedError("Only Base64 Supported")) } + credentialsClaimsValidationResult = ZValidation + .validateAll(claimsValidationResult) + .map(_ => ()) _ <- credentialsClaimsValidationResult match case l @ ZFailure(_, _) => ZIO.logError(s"CredentialsClaimsValidationResult: $l") case l @ Success(_, _) => ZIO.logInfo(s"CredentialsClaimsValidationResult: $l") @@ -1151,19 +1184,13 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _ <- service .markPresentationVerificationFailed(id) .provideSomeLayer(ZLayer.succeed(walletAccessContext)) @@ presReceivedToProcessedAspect - didCommAgent <- buildDIDCommAgent(presentation.from).provideSomeLayer( + didCommAgent <- buildDIDCommAgent(presentation.to).provideSomeLayer( ZLayer.succeed(walletAccessContext) ) - reportproblem = ReportProblem.build( - fromDID = presentation.to, - toDID = presentation.from, - pthid = presentation.thid.getOrElse(presentation.id), - code = ProblemCode("e.p.presentation-verification-failed"), - comment = Some(error.mkString) - ) + reportProblem = buildReportProblem(presentation, error.mkString) _ <- MessagingService - .send(reportproblem.toMessage) + .send(reportProblem.toMessage) .provideSomeLayer(didCommAgent) _ <- ZIO.log(s"CredentialsClaimsValidationResult: $error") } yield () @@ -1219,19 +1246,13 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _ <- service .markPresentationVerificationFailed(id) .provideSomeLayer(ZLayer.succeed(walletAccessContext)) @@ presReceivedToProcessedAspect - didCommAgent <- buildDIDCommAgent(presentation.from).provideSomeLayer( + didCommAgent <- buildDIDCommAgent(presentation.to).provideSomeLayer( ZLayer.succeed(walletAccessContext) ) - reportproblem = ReportProblem.build( - fromDID = presentation.to, - toDID = presentation.from, - pthid = presentation.thid.getOrElse(presentation.id), - code = ProblemCode("e.p.presentation-verification-failed"), - comment = Some(invalid.toString) - ) + reportProblem = buildReportProblem(presentation, invalid.toString) resp <- MessagingService - .send(reportproblem.toMessage) + .send(reportProblem.toMessage) .provideSomeLayer(didCommAgent) _ <- ZIO.log(s"CredentialsClaimsValidationResult: ${invalid.toString}") } yield () @@ -1263,19 +1284,13 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { .provideSomeLayer(ZLayer.succeed(walletAccessContext)) @@ presReceivedToProcessedAspect) .flatMapError(e => for { - didCommAgent <- buildDIDCommAgent(presentation.from).provideSomeLayer( + didCommAgent <- buildDIDCommAgent(presentation.to).provideSomeLayer( ZLayer.succeed(walletAccessContext) ) - reportproblem = ReportProblem.build( - fromDID = presentation.to, - toDID = presentation.from, - pthid = presentation.thid.getOrElse(presentation.id), - code = ProblemCode("e.p.presentation-verification-failed"), - comment = Some(e.toString) - ) + reportProblem = buildReportProblem(presentation, e.toString) _ <- MessagingService - .send(reportproblem.toMessage) + .send(reportProblem.toMessage) .provideSomeLayer(didCommAgent) _ <- ZIO.log(s"CredentialsClaimsValidationResult: ${e.toString}") } yield () @@ -1286,12 +1301,4 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { } } } - -// val syncDIDPublicationStateFromDlt: ZIO[WalletAccessContext & ManagedDIDService, GetManagedDIDError, Unit] = -// for { -// managedDidService <- ZIO.service[ManagedDIDService] -// _ <- managedDidService.syncManagedDIDState -// _ <- managedDidService.syncUnconfirmedUpdateOperations -// } yield () - } diff --git a/mercury/protocol-report-problem/src/main/scala/org/hyperledger/identus/mercury/protocol/reportproblem/v2/ReportProblem.scala b/mercury/protocol-report-problem/src/main/scala/org/hyperledger/identus/mercury/protocol/reportproblem/v2/ReportProblem.scala index 4c6867e52a..22e64637ef 100644 --- a/mercury/protocol-report-problem/src/main/scala/org/hyperledger/identus/mercury/protocol/reportproblem/v2/ReportProblem.scala +++ b/mercury/protocol-report-problem/src/main/scala/org/hyperledger/identus/mercury/protocol/reportproblem/v2/ReportProblem.scala @@ -50,7 +50,7 @@ object ReportProblem { given Encoder[ReportProblem] = deriveEncoder[ReportProblem] given Decoder[ReportProblem] = deriveDecoder[ReportProblem] - def readFromMessage(message: Message): ReportProblem = + def fromMessage(message: Message): ReportProblem = val body = message.body.asJson.as[ReportProblem.Body].toOption.get // TODO get ReportProblem( id = message.id, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala index 4f4a1d7eef..4ea28a3359 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala @@ -198,6 +198,12 @@ object PresentationError { msg ) + final case class PresentationValidationError(msg: String) + extends PresentationError( + StatusCode.BadRequest, + msg + ) + final case class PresentationReceivedError(msg: String) extends PresentationError( StatusCode.InternalServerError, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala index bdd741e155..8fdcc7cd57 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala @@ -26,6 +26,7 @@ import org.hyperledger.identus.pollux.core.repository.{CredentialRepository, Cre import org.hyperledger.identus.pollux.prex.{ClaimFormat, Jwt, PresentationDefinition} import org.hyperledger.identus.pollux.sdjwt.* import org.hyperledger.identus.pollux.vc.jwt.{Issuer as JwtIssuer, *} +import org.hyperledger.identus.pollux.vc.jwt.PresentationPayload.Implicits.* import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair} import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.messaging.{Producer, WalletIdAndRecordId} @@ -1505,7 +1506,7 @@ class CredentialServiceImpl( ZIO.fail(CredentialRequestValidationFailed(s"JWT presentation verification failed: $error")) jwtPresentation <- ZIO - .fromTry(JwtPresentation.decodeJwt(jwt)) + .fromTry(JwtPresentation.decodeJwt[JwtPresentationPayload](jwt)) .mapError(t => CredentialRequestValidationFailed(s"JWT presentation decoding failed: ${t.getMessage}")) } yield jwtPresentation } diff --git a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala index fe167a1122..cf816034a5 100644 --- a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala +++ b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala @@ -13,7 +13,7 @@ import org.hyperledger.identus.pollux.prex.PresentationSubmissionError.{ JsonPathNotFound, SubmissionNotSatisfyInputDescriptors } -import org.hyperledger.identus.pollux.vc.jwt.{JWT, JwtCredential, JwtPresentation} +import org.hyperledger.identus.pollux.vc.jwt.{JWT, JwtCredential, JwtPresentation, JwtPresentationPayload} import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* import org.hyperledger.identus.pollux.vc.jwt.PresentationPayload.Implicits.* import org.hyperledger.identus.shared.json.{JsonInterop, JsonPath, JsonPathError, JsonSchemaValidatorImpl} @@ -220,7 +220,7 @@ object PresentationSubmissionVerification { .map(JWT(_)) .mapError(_ => InvalidDataTypeForClaimFormat(format, path, "string")) payload <- ZIO - .fromTry(JwtPresentation.decodeJwt(jwt)) + .fromTry(JwtPresentation.decodeJwt[JwtPresentationPayload](jwt)) .mapError(e => ClaimDecodeFailure(format, path, e.getMessage())) _ <- formatVerification(jwt) .mapError(errors => ClaimFormatVerificationFailure(format, path, errors.mkString)) diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/CredentialSchemaAndTrustedIssuersConstraint.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/CredentialSchemaAndTrustedIssuersConstraint.scala new file mode 100644 index 0000000000..0f8f0e68e0 --- /dev/null +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/CredentialSchemaAndTrustedIssuersConstraint.scala @@ -0,0 +1,6 @@ +package org.hyperledger.identus.pollux.vc.jwt + +case class CredentialSchemaAndTrustedIssuersConstraint( + schemaId: String, + trustedIssuers: Option[Seq[String]] +) diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala index 9cada23670..ffe96ca470 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala @@ -337,6 +337,12 @@ object JwtPresentation { .flatMap(decode[JwtPresentationPayload](_).toTry) } + def decodeJwt[A](jwt: JWT)(using decoder: io.circe.Decoder[A]): Try[A] = { + JwtCirce + .decodeRaw(jwt.value, options = JwtOptions(signature = false, expiration = false, notBefore = false)) + .flatMap(decode[A](_).toTry) + } + def decodeJwt(jwt: JWT, publicKey: PublicKey): Try[JwtPresentationPayload] = { JwtCirce .decodeRaw(jwt.value, publicKey, JwtOptions(expiration = false, notBefore = false)) @@ -370,7 +376,7 @@ object JwtPresentation { )(didResolver: DidResolver, uriResolver: UriResolver)(implicit clock: Clock ): IO[List[String], Validation[String, Unit]] = { - val validateJwtPresentation = Validation.fromTry(decodeJwt(jwt)).mapError(_.toString) + val validateJwtPresentation = Validation.fromTry(decodeJwt[JwtPresentationPayload](jwt)).mapError(_.toString) val credentialValidationZIO = ValidationUtils.foreach( @@ -405,12 +411,94 @@ object JwtPresentation { domain: String, challenge: String ): Validation[String, Unit] = { - val validateJwtPresentation = Validation.fromTry(decodeJwt(jwt)).mapError(_.toString) + val validateJwtPresentation = Validation.fromTry(decodeJwt[JwtPresentationPayload](jwt)).mapError(_.toString) for { decodeJwtPresentation <- validateJwtPresentation - aud <- validateAudience(decodeJwtPresentation, domain) + aud <- validateAudience(decodeJwtPresentation, Some(domain)) result <- validateNonce(decodeJwtPresentation, Some(challenge)) } yield result + } + + def validatePresentation( + jwt: JWT, + domain: Option[String], + challenge: Option[String], + schemaIdAndTrustedIssuers: Seq[CredentialSchemaAndTrustedIssuersConstraint] + ): Validation[String, Unit] = { + val validateJwtPresentation = Validation.fromTry(decodeJwt[JwtPresentationPayload](jwt)).mapError(_.toString) + for { + decodeJwtPresentation <- validateJwtPresentation + aud <- validateAudience(decodeJwtPresentation, domain) + nonce <- validateNonce(decodeJwtPresentation, challenge) + result <- validateSchemaIdAndTrustedIssuers(decodeJwtPresentation, schemaIdAndTrustedIssuers) + } yield { + result + } + } + + def validateSchemaIdAndTrustedIssuers( + decodedJwtPresentation: JwtPresentationPayload, + schemaIdAndTrustedIssuers: Seq[CredentialSchemaAndTrustedIssuersConstraint] + ): Validation[String, Unit] = { + import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* + + val vcList = decodedJwtPresentation.vp.verifiableCredential + val expectedSchemaIds = schemaIdAndTrustedIssuers.map(_.schemaId) + val trustedIssuers = schemaIdAndTrustedIssuers.flatMap(_.trustedIssuers).flatten + ZValidation + .validateAll( + vcList.map { + case (w3cVerifiableCredentialPayload: W3cVerifiableCredentialPayload) => + val credentialSchemas = w3cVerifiableCredentialPayload.payload.maybeCredentialSchema + val issuer = w3cVerifiableCredentialPayload.payload.issuer + for { + s <- validateSchemaIds(credentialSchemas, expectedSchemaIds) + i <- validateIsTrustedIssuer(issuer, trustedIssuers) + } yield i + + case (jwtVerifiableCredentialPayload: JwtVerifiableCredentialPayload) => + for { + jwtCredentialPayload <- Validation + .fromTry(decodeJwt[JwtCredentialPayload](jwtVerifiableCredentialPayload.jwt)) + .mapError(_.toString) + issuer = jwtCredentialPayload.issuer + credentialSchemas = jwtCredentialPayload.maybeCredentialSchema + s <- validateSchemaIds(credentialSchemas, expectedSchemaIds) + i <- validateIsTrustedIssuer(issuer, trustedIssuers) + } yield i + } + ) + .map(_ => ()) + } + def validateSchemaIds( + credentialSchemas: Option[CredentialSchema | List[CredentialSchema]], + expectedSchemaIds: Seq[String] + ): Validation[String, Unit] = { + if (expectedSchemaIds.nonEmpty) { + val isValidSchema = credentialSchemas match { + case Some(schema: CredentialSchema) => expectedSchemaIds.contains(schema.id) + case Some(schemaList: List[CredentialSchema]) => expectedSchemaIds.intersect(schemaList.map(_.id)).nonEmpty + case _ => false + } + if (!isValidSchema) { + Validation.fail(s"SchemaId expected =$expectedSchemaIds actual found =$credentialSchemas") + } else Validation.unit + } else Validation.unit + + } + + def validateIsTrustedIssuer( + credentialIssuer: String | CredentialIssuer, + trustedIssuers: Seq[String] + ): Validation[String, Unit] = { + if (trustedIssuers.nonEmpty) { + val isValidIssuer = credentialIssuer match + case issuer: String => trustedIssuers.contains(issuer) + case issuer: CredentialIssuer => trustedIssuers.contains(issuer.id) + if (!isValidIssuer) { + Validation.fail(s"TrustedIssuers = ${trustedIssuers.mkString(",")} actual issuer = $credentialIssuer") + } else Validation.unit + } else Validation.unit } @@ -424,19 +512,15 @@ object JwtPresentation { } def validateAudience( decodedJwtPresentation: JwtPresentationPayload, - domain: String + domain: Option[String] ): Validation[String, Unit] = { - if (!decodedJwtPresentation.aud.contains(domain)) { + if (!domain.forall(domain => decodedJwtPresentation.aud.contains(domain))) { Validation.fail(s"domain/Audience dont match doamin=$domain, exp=${decodedJwtPresentation.aud}") } else Validation.unit } def verifyHolderBinding(jwt: JWT): Validation[String, Unit] = { import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* - val decodeJWT = (jwt: JWT) => - Validation - .fromTry(JwtCirce.decodeRaw(jwt.value, options = JwtOptions(false, false, false))) - .mapError(_.getMessage) def validateCredentialSubjectId( vcList: IndexedSeq[VerifiableCredentialPayload], @@ -459,10 +543,9 @@ object JwtPresentation { case (jwtVerifiableCredentialPayload: JwtVerifiableCredentialPayload) => for { - jwtCredentialDecoded <- decodeJWT(jwtVerifiableCredentialPayload.jwt) jwtCredentialPayload <- Validation - .fromEither(decode[JwtCredentialPayload](jwtCredentialDecoded)) - .mapError(_.getMessage) + .fromTry(decodeJwt[JwtCredentialPayload](jwtVerifiableCredentialPayload.jwt)) + .mapError(_.toString) mayBeSubjectDid = jwtCredentialPayload.maybeSub x <- if (mayBeSubjectDid.contains(iss)) { @@ -477,20 +560,15 @@ object JwtPresentation { .map(_ => ()) } for { - decodedJWT <- decodeJWT(jwt) - jwtPresentationPayload <- Validation.fromEither(decode[JwtPresentationPayload](decodedJWT)).mapError(_.getMessage) + jwtPresentationPayload <- Validation + .fromTry(decodeJwt[JwtPresentationPayload](jwt)) + .mapError(_.toString) result <- validateCredentialSubjectId(jwtPresentationPayload.vp.verifiableCredential, jwtPresentationPayload.iss) } yield result } def verifyDates(jwt: JWT, leeway: TemporalAmount)(implicit clock: Clock): Validation[String, Unit] = { val now = clock.instant() - - val decodeJWT = - Validation - .fromTry(JwtCirce.decodeRaw(jwt.value, options = JwtOptions(false, false, false))) - .mapError(_.getMessage) - def validateNbfNotAfterExp(maybeNbf: Option[Instant], maybeExp: Option[Instant]): Validation[String, Unit] = { val maybeResult = for { @@ -525,8 +603,9 @@ object JwtPresentation { } for { - decodedJWT <- decodeJWT - jwtCredentialPayload <- Validation.fromEither(decode[JwtPresentationPayload](decodedJWT)).mapError(_.getMessage) + jwtCredentialPayload <- Validation + .fromTry(decodeJwt[JwtPresentationPayload](jwt)) + .mapError(_.toString) maybeNbf = jwtCredentialPayload.maybeNbf maybeExp = jwtCredentialPayload.maybeExp result <- Validation.validateWith( diff --git a/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JwtPresentationTest.scala b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JwtPresentationTest.scala new file mode 100644 index 0000000000..7122818572 --- /dev/null +++ b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JwtPresentationTest.scala @@ -0,0 +1,99 @@ +package org.hyperledger.identus.pollux.vc.jwt + +import zio.test.* +import zio.test.ZIOSpecDefault + +object JwtPresentationTest extends ZIOSpecDefault { + val jwt = JWT( + "eyJraWQiOiJteS1hdXRoLWtleSIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2SyJ9.eyJpc3MiOiJkaWQ6cHJpc206YjgwZjAxMTZhYWY2OTI5MGRkMzJiZDE0OTNmN2IxYWJhMDM5OTYyM2JkNDk5Mzk3NTRjNThhNGNmZTU4M2QwYzpDcGNDQ3BRQ0VqOEtDMjE1TFdGMWRHZ3RhMlY1RUFSS0xnb0pjMlZqY0RJMU5tc3hFaUVDQnViQkpoNDhRNjhqOTJZS3NjMVFqQ0prOHFvRXBMamRoejNRRVFzRWpWRVNTZ29XYlhrdGEyVjVMV0Z6YzJWeWRHbHZiazFsZEdodlpCQUNTaTRLQ1hObFkzQXlOVFpyTVJJaEFoTENSMkhTa3NOWUh2Y0dCUWZzQVZrdDNFa1pMSVpMVEhYcFc4ckJRRHI5RWpzS0IyMWhjM1JsY2pBUUFVb3VDZ2x6WldOd01qVTJhekVTSVFMRC10d3c1SklVbzA2dXQ5MDQwaTZ5dTdhQUhMcWdxajdUcHBZNlUtQzhrQnBJQ2c1aFoyVnVkQzFpWVhObExYVnliQklRVEdsdWEyVmtVbVZ6YjNWeVkyVldNUm9rYUhSMGNEb3ZMekU1TWk0eE5qZ3VNUzQ0TmpvNU1EQXdMMk5zYjNWa0xXRm5aVzUwIiwiYXVkIjoiaHR0cHM6XC9cL3ByaXNtLXZlcmlmaWVyLmNvbSIsInZwIjp7InR5cGUiOlsiVmVyaWZpYWJsZVByZXNlbnRhdGlvbiJdLCJAY29udGV4dCI6WyJodHRwczpcL1wvd3d3LnczLm9yZ1wvMjAxOFwvcHJlc2VudGF0aW9uc1wvdjEiXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlpFUlRRU0o5LmV5SnBjM01pT2lKa2FXUTZjSEpwYzIwNk1HWmxaVFF4Wm1Sa05qZ3dOemMyTTJOall6VXpPV015T1RnME1XRTBNVGhqTnpaaE9EbGlNMlk0WTJRMlpHRTRPV1JsTW1FelpERmhNRFExTnpGbU5UcERjSE5EUTNCblEwVnJXVXRHVnpFMVRGZDBiR1ZUTVdoa1dGSnZXbGMxTUdGWFRtaGtSMngyWW1oQlJWTnBjMHRDTUZaclRXcFZNVTFVYTFOSlJIVjVSVlF4YUVOQmRVTjJhek5QYm05bmJETTRZMDFwU201NFgycHJURmxUVG5kWFRVbEJiblJzVEVWclkwdEdiVEUxVEZkMGJHVlRNV2hqTTA1c1kyNVNjR0l5TlU1YVdGSnZZakpSVVVGcmIzSkRaMlJHV2tSSk1VNVVSVFZGYVVOYVFWSkpibE5rVWtwRVN5MUZUSEp4VjI5a01YZE9UMGhvZFhSM1dsSkVTMG90YnpWQlpraHlZbkpTU1RkRFoyUjBXVmhPTUZwWVNYZEZRVVpMVEdkdlNtTXlWbXBqUkVreFRtMXplRVZwUlVSSE1XMXpkV056VlhOa1VVOHRibGx3Y2xGWU5HOUJXRnBJVnpKak9GQlNWM3BTWkZOMGF6WXdPRFZKWVZOQmIwOVpWMlJzWW01UmRGbHRSbnBhVXpFeFkyMTNVMFZGZUhCaWJYUnNXa1pLYkdNeU9URmpiVTVzVm1wRllVcEhhREJrU0VFMlRIazRlRTlVU1hWTlZGazBUR3BGZFU5RVdUWlBSRUYzVFVNNWFtSkhPVEZhUXpGb1dqSldkV1JCSWl3aWMzVmlJam9pWkdsa09uQnlhWE50T21JNE1HWXdNVEUyWVdGbU5qa3lPVEJrWkRNeVltUXhORGt6WmpkaU1XRmlZVEF6T1RrMk1qTmlaRFE1T1RNNU56VTBZelU0WVRSalptVTFPRE5rTUdNNlEzQmpRME53VVVORmFqaExRekl4TlV4WFJqRmtSMmQwWVRKV05VVkJVa3RNWjI5S1l6SldhbU5FU1RGT2JYTjRSV2xGUTBKMVlrSkthRFE0VVRZNGFqa3lXVXR6WXpGUmFrTkthemh4YjBWd1RHcGthSG96VVVWUmMwVnFWa1ZUVTJkdlYySllhM1JoTWxZMVRGZEdlbU15Vm5sa1IyeDJZbXN4YkdSSGFIWmFRa0ZEVTJrMFMwTllUbXhaTTBGNVRsUmFjazFTU1doQmFFeERVakpJVTJ0elRsbElkbU5IUWxGbWMwRldhM1F6Uld0YVRFbGFURlJJV0hCWE9ISkNVVVJ5T1VWcWMwdENNakZvWXpOU2JHTnFRVkZCVlc5MVEyZHNlbHBYVG5kTmFsVXlZWHBGVTBsUlRFUXRkSGQzTlVwSlZXOHdOblYwT1RBME1HazJlWFUzWVVGSVRIRm5jV28zVkhCd1dUWlZMVU00YTBKd1NVTm5OV2hhTWxaMVpFTXhhVmxZVG14TVdGWjVZa0pKVVZSSGJIVmhNbFpyVlcxV2VtSXpWbmxaTWxaWFRWSnZhMkZJVWpCalJHOTJUSHBGTlUxcE5IaE9hbWQxVFZNME5FNXFielZOUkVGM1RESk9jMkl6Vm10TVYwWnVXbGMxTUNJc0ltNWlaaUk2TVRjek1qQXhOVEl6TVN3aVpYaHdJam94TnpNeU1ERTRPRE14TENKMll5STZleUpqY21Wa1pXNTBhV0ZzVTJOb1pXMWhJanBiZXlKcFpDSTZJbWgwZEhBNlhDOWNMekU1TWk0eE5qZ3VNUzQ0TmpvNE1EQXdYQzlqYkc5MVpDMWhaMlZ1ZEZ3dmMyTm9aVzFoTFhKbFoybHpkSEo1WEM5elkyaGxiV0Z6WEM4Mk16TmhOamhtTnkwMFpUZGlMVE13TnpNdFlUbGhNQzA0WVdVNU5qUmtZVFU1TmpjaUxDSjBlWEJsSWpvaVEzSmxaR1Z1ZEdsaGJGTmphR1Z0WVRJd01qSWlmVjBzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltVnRZV2xzUVdSa2NtVnpjeUk2SW1Gc2FXTmxRSGR2Ym1SbGNteGhibVF1WTI5dElpd2laSEpwZG1sdVowTnNZWE56SWpvekxDSm1ZVzFwYkhsT1lXMWxJam9pVjI5dVpHVnliR0Z1WkNJc0ltZHBkbVZ1VG1GdFpTSTZJa0ZzYVdObElpd2laSEpwZG1sdVoweHBZMlZ1YzJWSlJDSTZJakV5TXpRMUlpd2lhV1FpT2lKa2FXUTZjSEpwYzIwNllqZ3daakF4TVRaaFlXWTJPVEk1TUdSa016SmlaREUwT1RObU4ySXhZV0poTURNNU9UWXlNMkprTkRrNU16azNOVFJqTlRoaE5HTm1aVFU0TTJRd1l6cERjR05EUTNCUlEwVnFPRXRETWpFMVRGZEdNV1JIWjNSaE1sWTFSVUZTUzB4bmIwcGpNbFpxWTBSSk1VNXRjM2hGYVVWRFFuVmlRa3BvTkRoUk5qaHFPVEpaUzNOak1WRnFRMHByT0hGdlJYQk1hbVJvZWpOUlJWRnpSV3BXUlZOVFoyOVhZbGhyZEdFeVZqVk1WMFo2WXpKV2VXUkhiSFppYXpGc1pFZG9kbHBDUVVOVGFUUkxRMWhPYkZrelFYbE9WRnB5VFZKSmFFRm9URU5TTWtoVGEzTk9XVWgyWTBkQ1VXWnpRVlpyZERORmExcE1TVnBNVkVoWWNGYzRja0pSUkhJNVJXcHpTMEl5TVdoak0xSnNZMnBCVVVGVmIzVkRaMng2V2xkT2QwMXFWVEpoZWtWVFNWRk1SQzEwZDNjMVNrbFZiekEyZFhRNU1EUXdhVFo1ZFRkaFFVaE1jV2R4YWpkVWNIQlpObFV0UXpoclFuQkpRMmMxYUZveVZuVmtRekZwV1ZoT2JFeFlWbmxpUWtsUlZFZHNkV0V5Vm10VmJWWjZZak5XZVZreVZsZE5VbTlyWVVoU01HTkViM1pNZWtVMVRXazBlRTVxWjNWTlV6UTBUbXB2TlUxRVFYZE1NazV6WWpOV2EweFhSbTVhVnpVd0lpd2laR0YwWlU5bVNYTnpkV0Z1WTJVaU9pSXlNREl3TFRFeExURXpWREl3T2pJd09qTTVLekF3T2pBd0luMHNJblI1Y0dVaU9sc2lWbVZ5YVdacFlXSnNaVU55WldSbGJuUnBZV3dpWFN3aVFHTnZiblJsZUhRaU9sc2lhSFIwY0hNNlhDOWNMM2QzZHk1M015NXZjbWRjTHpJd01UaGNMMk55WldSbGJuUnBZV3h6WEM5Mk1TSmRMQ0pwYzNOMVpYSWlPbnNpYVdRaU9pSmthV1E2Y0hKcGMyMDZNR1psWlRReFptUmtOamd3TnpjMk0yTmpZelV6T1dNeU9UZzBNV0UwTVRoak56WmhPRGxpTTJZNFkyUTJaR0U0T1dSbE1tRXpaREZoTURRMU56Rm1OVHBEY0hORFEzQm5RMFZyV1V0R1Z6RTFURmQwYkdWVE1XaGtXRkp2V2xjMU1HRlhUbWhrUjJ4MlltaEJSVk5wYzB0Q01GWnJUV3BWTVUxVWExTkpSSFY1UlZReGFFTkJkVU4yYXpOUGJtOW5iRE00WTAxcFNtNTRYMnByVEZsVFRuZFhUVWxCYm5Sc1RFVnJZMHRHYlRFMVRGZDBiR1ZUTVdoak0wNXNZMjVTY0dJeU5VNWFXRkp2WWpKUlVVRnJiM0pEWjJSR1drUkpNVTVVUlRWRmFVTmFRVkpKYmxOa1VrcEVTeTFGVEhKeFYyOWtNWGRPVDBob2RYUjNXbEpFUzBvdGJ6VkJaa2h5WW5KU1NUZERaMlIwV1ZoT01GcFlTWGRGUVVaTFRHZHZTbU15Vm1walJFa3hUbTF6ZUVWcFJVUkhNVzF6ZFdOelZYTmtVVTh0Ymxsd2NsRllORzlCV0ZwSVZ6SmpPRkJTVjNwU1pGTjBhell3T0RWSllWTkJiMDlaVjJSc1ltNVJkRmx0Um5wYVV6RXhZMjEzVTBWRmVIQmliWFJzV2taS2JHTXlPVEZqYlU1c1ZtcEZZVXBIYURCa1NFRTJUSGs0ZUU5VVNYVk5WRmswVEdwRmRVOUVXVFpQUkVGM1RVTTVhbUpIT1RGYVF6Rm9XakpXZFdSQklpd2lkSGx3WlNJNklsQnliMlpwYkdVaWZTd2lZM0psWkdWdWRHbGhiRk4wWVhSMWN5STZleUp6ZEdGMGRYTlFkWEp3YjNObElqb2lVbVYyYjJOaGRHbHZiaUlzSW5OMFlYUjFjMHhwYzNSSmJtUmxlQ0k2TVN3aWFXUWlPaUpvZEhSd09sd3ZYQzh4T1RJdU1UWTRMakV1T0RZNk9EQXdNRnd2WTJ4dmRXUXRZV2RsYm5SY0wyTnlaV1JsYm5ScFlXd3RjM1JoZEhWelhDODBaRGN4TmpZM01pMDFNekpqTFRRM056Y3RZVEJoTmkxbU56azJNR1F3TlRKbFpHVWpNU0lzSW5SNWNHVWlPaUpUZEdGMGRYTk1hWE4wTWpBeU1VVnVkSEo1SWl3aWMzUmhkSFZ6VEdsemRFTnlaV1JsYm5ScFlXd2lPaUpvZEhSd09sd3ZYQzh4T1RJdU1UWTRMakV1T0RZNk9EQXdNRnd2WTJ4dmRXUXRZV2RsYm5SY0wyTnlaV1JsYm5ScFlXd3RjM1JoZEhWelhDODBaRGN4TmpZM01pMDFNekpqTFRRM056Y3RZVEJoTmkxbU56azJNR1F3TlRKbFpHVWlmWDE5LmpiRE02NHQ1N3JoTXktNEt5ZnlsR3FCZGdzMUtJbXZpa0QzblFuVFRPbF9YcXV6UThTWGpZVEYyTWREbXJGRzAtSUk5NGo4ZmlUS0xYcWRpQ1NXNEN3Il19LCJub25jZSI6IjExYzkxNDkzLTAxYjMtNGM0ZC1hYzM2LWIzMzZiYWI1YmRkZiJ9.d5bzOLV-kQZvCceoFppqlPRG7aK3mo9sZVCj5_sPqIMvOqzAbxTOPyfI459GFKpeF8ApsNwJyx9jED_cRaqqiQ" + ) + val domain = Some("https://prism-verifier.com") + val challenge = Some("11c91493-01b3-4c4d-ac36-b336bab5bddf") + val schemaIdAndTrustedIssuers = Seq( + CredentialSchemaAndTrustedIssuersConstraint( + schemaId = "http://192.168.1.86:8000/cloud-agent/schema-registry/schemas/633a68f7-4e7b-3073-a9a0-8ae964da5967", + trustedIssuers = Some( + Seq( + "did:prism:0fee41fdd6807763ccc539c29841a418c76a89b3f8cd6da89de2a3d1a04571f5:CpsCCpgCEkYKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESisKB0VkMjU1MTkSIDuyET1hCAuCvk3Onogl38cMiJnx_jkLYSNwWMIAntlLEkcKFm15LWtleS1hc3NlcnRpb25NZXRob2QQAkorCgdFZDI1NTE5EiCZARInSdRJDK-ELrqWod1wNOHhutwZRDKJ-o5AfHrbrRI7CgdtYXN0ZXIwEAFKLgoJc2VjcDI1NmsxEiEDG1msucsUsdQO-nYprQX4oAXZHW2c8PRWzRdStk6085IaSAoOYWdlbnQtYmFzZS11cmwSEExpbmtlZFJlc291cmNlVjEaJGh0dHA6Ly8xOTIuMTY4LjEuODY6ODAwMC9jbG91ZC1hZ2VudA" + ) + ) + ) + ) + override def spec = suite("JWTVerificationSpec")( + test("validate true when issuer is trusted") { + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => false, _ => true)) + }, + test("fail when issuer is not trusted") { + val trustedIssuer = "did:prism:issuer" + val schemaIdAndTrustedIssuers = Seq( + CredentialSchemaAndTrustedIssuersConstraint( + schemaId = + "http://192.168.1.86:8000/cloud-agent/schema-registry/schemas/633a68f7-4e7b-3073-a9a0-8ae964da5967", + trustedIssuers = Some( + Seq( + trustedIssuer + ) + ) + ) + ) + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => true, _ => false)) + assertTrue(validation.fold(chunk => chunk.mkString.contains(trustedIssuer), _ => false)) + }, + test("fail when schema ID doesn't match") { + val expectedSchemaId = "http://192.168.1.86:8000/cloud-agent/schema-registry/schemas/schemaId" + val schemaIdAndTrustedIssuers = Seq( + CredentialSchemaAndTrustedIssuersConstraint( + schemaId = expectedSchemaId, + trustedIssuers = Some( + Seq( + "did:prism:0fee41fdd6807763ccc539c29841a418c76a89b3f8cd6da89de2a3d1a04571f5:CpsCCpgCEkYKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESisKB0VkMjU1MTkSIDuyET1hCAuCvk3Onogl38cMiJnx_jkLYSNwWMIAntlLEkcKFm15LWtleS1hc3NlcnRpb25NZXRob2QQAkorCgdFZDI1NTE5EiCZARInSdRJDK-ELrqWod1wNOHhutwZRDKJ-o5AfHrbrRI7CgdtYXN0ZXIwEAFKLgoJc2VjcDI1NmsxEiEDG1msucsUsdQO-nYprQX4oAXZHW2c8PRWzRdStk6085IaSAoOYWdlbnQtYmFzZS11cmwSEExpbmtlZFJlc291cmNlVjEaJGh0dHA6Ly8xOTIuMTY4LjEuODY6ODAwMC9jbG91ZC1hZ2VudA" + ) + ) + ) + ) + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => true, _ => false)) + assertTrue(validation.fold(chunk => chunk.mkString.contains(expectedSchemaId), _ => false)) + }, + test("fail when domain validation fails") { + val domain = Some("domain") + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => true, _ => false)) + assertTrue(validation.fold(chunk => chunk.mkString.contains("domain/Audience dont match"), _ => false)) + }, + test("fail when challenge validation fails") { + val challenge = Some("challenge") + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => true, _ => false)) + assertTrue(validation.fold(chunk => chunk.mkString.contains("Challenge/Nonce dont match"), _ => false)) + } + ) + +} diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala index 9180fc4d62..14e5ab0491 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala @@ -9,6 +9,7 @@ import zio.kafka.consumer.{ ConsumerSettings as ZKConsumerSettings, Subscription as ZKSubscription } +import zio.kafka.consumer.Consumer.{AutoOffsetStrategy, OffsetRetrieval} import zio.kafka.producer.{Producer as ZKProducer, ProducerSettings as ZKProducerSettings} import zio.kafka.serde.{Deserializer as ZKDeserializer, Serializer as ZKSerializer} @@ -80,7 +81,7 @@ class ZKafkaConsumerImpl[K, V]( .withMaxPollInterval(maxPollInterval) // Should be max.poll.records x 'max processing time per record' // 'pollTimeout' default is 50 millis. This is a ZIO Kafka property. .withPollTimeout(pollTimeout) - // .withOffsetRetrieval(OffsetRetrieval.Auto(AutoOffsetStrategy.Earliest)) + .withOffsetRetrieval(OffsetRetrieval.Auto(AutoOffsetStrategy.Earliest)) .withRebalanceSafeCommits(rebalanceSafeCommits) // .withMaxRebalanceDuration(30.seconds) ) diff --git a/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt b/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt index e2116d6793..3450e30dd5 100644 --- a/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt +++ b/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt @@ -3,26 +3,37 @@ package abilities import com.google.gson.GsonBuilder import common.TestConstants import io.iohk.atala.automation.restassured.CustomGsonObjectMapperFactory -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import models.* +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.engine.ApplicationEngine +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.request.receiveText +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import models.ConnectionEvent +import models.CredentialEvent +import models.DidEvent +import models.Event +import models.PresentationEvent +import models.PresentationStatusAdapter import net.serenitybdd.screenplay.Ability import net.serenitybdd.screenplay.Actor import net.serenitybdd.screenplay.HasTeardown import net.serenitybdd.screenplay.Question -import org.hyperledger.identus.client.models.* +import org.hyperledger.identus.client.models.Connection +import org.hyperledger.identus.client.models.IssueCredentialRecord import java.net.URL import java.time.OffsetDateTime open class ListenToEvents( private val url: URL, webhookPort: Int?, -) : Ability, HasTeardown { +) : Ability, + HasTeardown { private val server: ApplicationEngine private val gson = GsonBuilder() @@ -88,48 +99,36 @@ open class ListenToEvents( } companion object { - fun at(url: URL, webhookPort: Int?): ListenToEvents { - return ListenToEvents(url, webhookPort) - } + fun at(url: URL, webhookPort: Int?): ListenToEvents = ListenToEvents(url, webhookPort) - fun with(actor: Actor): ListenToEvents { - return actor.abilityTo(ListenToEvents::class.java) - } + fun with(actor: Actor): ListenToEvents = actor.abilityTo(ListenToEvents::class.java) - fun presentationProofStatus(actor: Actor): Question { - return Question.about("presentation status").answeredBy { - val proofEvent = with(actor).presentationEvents.lastOrNull { - it.data.thid == actor.recall("thid") - } - proofEvent?.data?.status + fun presentationProofStatus(actor: Actor): Question = Question.about("presentation status").answeredBy { + val proofEvent = with(actor).presentationEvents.lastOrNull { + it.data.thid == actor.recall("thid") } + proofEvent?.data?.status } - fun connectionState(actor: Actor): Question { - return Question.about("connection state").answeredBy { - val lastEvent = with(actor).connectionEvents.lastOrNull { - it.data.thid == actor.recall("connection").thid - } - lastEvent?.data?.state + fun connectionState(actor: Actor): Question = Question.about("connection state").answeredBy { + val lastEvent = with(actor).connectionEvents.lastOrNull { + it.data.thid == actor.recall("connection").thid } + lastEvent?.data?.state } - fun credentialState(actor: Actor): Question { - return Question.about("credential state").answeredBy { - val credentialEvent = ListenToEvents.with(actor).credentialEvents.lastOrNull { - it.data.thid == actor.recall("thid") - } - credentialEvent?.data?.protocolState + fun credentialState(actor: Actor): Question = Question.about("credential state").answeredBy { + val credentialEvent = ListenToEvents.with(actor).credentialEvents.lastOrNull { + it.data.thid == actor.recall("thid") } + credentialEvent?.data?.protocolState } - fun didStatus(actor: Actor): Question { - return Question.about("did status").answeredBy { - val didEvent = ListenToEvents.with(actor).didEvents.lastOrNull { - it.data.did == actor.recall("shortFormDid") - } - didEvent?.data?.status + fun didStatus(actor: Actor): Question = Question.about("did status").answeredBy { + val didEvent = ListenToEvents.with(actor).didEvents.lastOrNull { + it.data.did == actor.recall("shortFormDid") } + didEvent?.data?.status } } @@ -143,9 +142,7 @@ open class ListenToEvents( .start(wait = false) } - override fun toString(): String { - return "Listen HTTP port at $url" - } + override fun toString(): String = "Listen HTTP port at $url" override fun tearDown() { server.stop() diff --git a/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt b/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt index 05a6bbf37c..b128ddce39 100644 --- a/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt +++ b/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt @@ -34,7 +34,37 @@ enum class CredentialSchema { "name" to "Name", "age" to 18, ) - }, ; + }, + EMPLOYEE_SCHEMA { + override val credentialSchemaType: String = + "https://w3c-ccg.github.io/vc-json-schemas/schema/2.0/schema.json" + override val schemaType: String = "https://json-schema.org/draft/2020-12/schema" + override val schema: JsonSchema = JsonSchema( + id = "https://example.com/employee-schema-1.0", + schema = schemaType, + description = "Employee schema", + type = "object", + properties = mutableMapOf( + "name" to JsonSchemaProperty(type = "string"), + "age" to JsonSchemaProperty(type = "integer"), + ), + required = listOf("name", "age"), + ) + override val credentialSchema: CredentialSchemaInput = CredentialSchemaInput( + author = "did:prism:agent", + name = UUID.randomUUID().toString(), + description = "Simple employee credentials schema", + type = credentialSchemaType, + schema = schema, + tags = listOf("employee", "employees"), + version = "1.0.0", + ) + override val claims: Map = linkedMapOf( + "name" to "Name", + "age" to 18, + ) + }, + ; abstract val credentialSchema: CredentialSchemaInput abstract val schema: JsonSchema diff --git a/tests/integration-tests/src/test/kotlin/common/DidDocumentTemplate.kt b/tests/integration-tests/src/test/kotlin/common/DidDocumentTemplate.kt new file mode 100644 index 0000000000..a82f4cbced --- /dev/null +++ b/tests/integration-tests/src/test/kotlin/common/DidDocumentTemplate.kt @@ -0,0 +1,9 @@ +package common + +import org.hyperledger.identus.client.models.ManagedDIDKeyTemplate +import org.hyperledger.identus.client.models.Service + +data class DidDocumentTemplate( + val publicKeys: MutableList, + val services: MutableList, +) diff --git a/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt b/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt deleted file mode 100644 index c4c30ca83c..0000000000 --- a/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt +++ /dev/null @@ -1,41 +0,0 @@ -package common - -import org.hyperledger.identus.client.models.* - -enum class DidPurpose { - CUSTOM { - override val publicKeys = mutableListOf() - override val services = mutableListOf() - }, - SD_JWT { - override val publicKeys = mutableListOf( - ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.ED25519), - ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.ED25519), - ) - override val services = mutableListOf() - }, - JWT { - override val publicKeys = mutableListOf( - ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1), - ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519), - ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1), - ManagedDIDKeyTemplate("assertion-2", Purpose.ASSERTION_METHOD, Curve.ED25519), - ) - override val services = mutableListOf() - }, - OIDC_JWT { - override val publicKeys = mutableListOf( - ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1), - ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519), - ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1), - ) - override val services = mutableListOf() - }, - ANONCRED { - override val publicKeys = mutableListOf() - override val services = mutableListOf() - }, ; - - abstract val publicKeys: MutableList - abstract val services: MutableList -} diff --git a/tests/integration-tests/src/test/kotlin/common/DidType.kt b/tests/integration-tests/src/test/kotlin/common/DidType.kt new file mode 100644 index 0000000000..38f2c2b792 --- /dev/null +++ b/tests/integration-tests/src/test/kotlin/common/DidType.kt @@ -0,0 +1,50 @@ +package common + +import org.hyperledger.identus.client.models.* + +enum class DidType { + CUSTOM { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf(), + services = mutableListOf(), + ) + }, + SD_JWT { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf( + ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.ED25519), + ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.ED25519), + ), + services = mutableListOf(), + ) + }, + JWT { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf( + ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1), + ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519), + ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1), + ManagedDIDKeyTemplate("assertion-2", Purpose.ASSERTION_METHOD, Curve.ED25519), + ), + services = mutableListOf(), + ) + }, + OIDC_JWT { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf( + ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1), + ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519), + ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1), + ), + services = mutableListOf(), + ) + }, + ANONCRED { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf(), + services = mutableListOf(), + ) + }, ; + + abstract val documentTemplate: DidDocumentTemplate +} diff --git a/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt b/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt index d5c8428b13..06aaab22bf 100644 --- a/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt @@ -1,7 +1,7 @@ package steps.common import common.CredentialSchema -import common.DidPurpose +import common.DidType import interactions.Get import io.cucumber.java.en.Given import io.iohk.atala.automation.extensions.get @@ -13,7 +13,7 @@ import org.hyperledger.identus.client.models.Connection import org.hyperledger.identus.client.models.ConnectionsPage import steps.connection.ConnectionSteps import steps.credentials.* -import steps.did.PublishDidSteps +import steps.did.CreateDidSteps import steps.schemas.CredentialSchemasSteps class CommonSteps { @@ -21,21 +21,21 @@ class CommonSteps { fun holderHasIssuedJwtCredentialFromIssuer(holder: Actor, issuer: Actor) { actorsHaveExistingConnection(issuer, holder) - val publishDidSteps = PublishDidSteps() - publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.JWT) - publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.JWT) + val createDidSteps = CreateDidSteps() + createDidSteps.agentHasAnUnpublishedDID(holder, DidType.JWT) + createDidSteps.agentHasAPublishedDID(issuer, DidType.JWT) val jwtCredentialSteps = JwtCredentialSteps() val credentialSteps = CredentialSteps() jwtCredentialSteps.issuerOffersAJwtCredential(issuer, holder, "short") credentialSteps.holderReceivesCredentialOffer(holder) - jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder) + jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder, "auth-1") credentialSteps.issuerIssuesTheCredential(issuer) credentialSteps.holderReceivesTheIssuedCredential(holder) } - @Given("{actor} has a jwt issued credential with {} schema from {actor}") + @Given("{actor} has a jwt issued credential with '{}' schema from {actor}") fun holderHasIssuedJwtCredentialFromIssuerWithSchema( holder: Actor, schema: CredentialSchema, @@ -43,9 +43,9 @@ class CommonSteps { ) { actorsHaveExistingConnection(issuer, holder) - val publishDidSteps = PublishDidSteps() - publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.JWT) - publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.JWT) + val createDidSteps = CreateDidSteps() + createDidSteps.agentHasAnUnpublishedDID(holder, DidType.JWT) + createDidSteps.agentHasAPublishedDID(issuer, DidType.JWT) val schemaSteps = CredentialSchemasSteps() schemaSteps.agentHasAPublishedSchema(issuer, schema) @@ -54,7 +54,7 @@ class CommonSteps { val credentialSteps = CredentialSteps() jwtCredentialSteps.issuerOffersJwtCredentialToHolderUsingSchema(issuer, holder, "short", schema) credentialSteps.holderReceivesCredentialOffer(holder) - jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder) + jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder, "auth-1") credentialSteps.issuerIssuesTheCredential(issuer) credentialSteps.holderReceivesTheIssuedCredential(holder) } @@ -63,9 +63,9 @@ class CommonSteps { fun holderHasIssuedSdJwtCredentialFromIssuer(holder: Actor, issuer: Actor) { actorsHaveExistingConnection(issuer, holder) - val publishDidSteps = PublishDidSteps() - publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.SD_JWT) - publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.SD_JWT) + val createDidSteps = CreateDidSteps() + createDidSteps.agentHasAnUnpublishedDID(holder, DidType.SD_JWT) + createDidSteps.agentHasAPublishedDID(issuer, DidType.SD_JWT) val sdJwtCredentialSteps = SdJwtCredentialSteps() val credentialSteps = CredentialSteps() @@ -80,9 +80,9 @@ class CommonSteps { fun holderHasIssuedSdJwtCredentialFromIssuerWithKeyBind(holder: Actor, issuer: Actor) { actorsHaveExistingConnection(issuer, holder) - val publishDidSteps = PublishDidSteps() - publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.SD_JWT) - publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.SD_JWT) + val createDidSteps = CreateDidSteps() + createDidSteps.agentHasAnUnpublishedDID(holder, DidType.SD_JWT) + createDidSteps.agentHasAPublishedDID(issuer, DidType.SD_JWT) val sdJwtCredentialSteps = SdJwtCredentialSteps() val credentialSteps = CredentialSteps() @@ -97,8 +97,6 @@ class CommonSteps { fun actorsHaveExistingConnection(inviter: Actor, invitee: Actor) { inviter.attemptsTo( Get.resource("/connections"), - ) - inviter.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), ) val inviterConnection = SerenityRest.lastResponse().get().contents!!.firstOrNull { @@ -109,8 +107,6 @@ class CommonSteps { if (inviterConnection != null) { invitee.attemptsTo( Get.resource("/connections"), - ) - invitee.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), ) inviteeConnection = SerenityRest.lastResponse().get().contents!!.firstOrNull { diff --git a/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt index 17df7e4bf3..1c3e2e0e94 100644 --- a/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt @@ -15,9 +15,12 @@ import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK import org.assertj.core.api.Assertions.assertThat import org.hamcrest.CoreMatchers -import org.hyperledger.identus.client.models.* +import org.hyperledger.identus.client.models.AcceptConnectionInvitationRequest +import org.hyperledger.identus.client.models.Connection import org.hyperledger.identus.client.models.Connection.State.CONNECTION_RESPONSE_RECEIVED import org.hyperledger.identus.client.models.Connection.State.CONNECTION_RESPONSE_SENT +import org.hyperledger.identus.client.models.Connection.State.INVITATION_GENERATED +import org.hyperledger.identus.client.models.CreateConnectionRequest class ConnectionSteps { @@ -29,12 +32,11 @@ class ConnectionSteps { inviter.attemptsTo( Post.to("/connections").body(CreateConnectionRequest(label = connectionLabel)), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), ) val connection = SerenityRest.lastResponse().get() - inviter.attemptsTo( - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), Ensure.that(connection.label!!).isEqualTo(connectionLabel), Ensure.that(connection.state).isEqualTo(Connection.State.INVITATION_GENERATED), Ensure.that(connection.role).isEqualTo(Connection.Role.INVITER), @@ -48,20 +50,14 @@ class ConnectionSteps { fun inviteeSendsAConnectionRequestToInviter(invitee: Actor, inviter: Actor) { // Bob accepts connection using achieved out-of-band invitation val inviterConnection = inviter.recall("connection") + val body = AcceptConnectionInvitationRequest(inviterConnection.invitation.invitationUrl.split("=")[1]) invitee.attemptsTo( - Post.to("/connection-invitations") - .with { - it.body( - AcceptConnectionInvitationRequest( - inviterConnection.invitation.invitationUrl.split("=")[1], - ), - ) - }, + Post.to("/connection-invitations").body(body), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), ) - val inviteeConnection = SerenityRest.lastResponse().get() + val inviteeConnection = SerenityRest.lastResponse().get() invitee.attemptsTo( - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), Ensure.that(inviteeConnection.invitation.from).isEqualTo(inviterConnection.invitation.from), Ensure.that(inviteeConnection.invitation.id).isEqualTo(inviterConnection.invitation.id), Ensure.that(inviteeConnection.invitation.invitationUrl) diff --git a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt index ec7a49a0df..04d6772c38 100644 --- a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt @@ -14,7 +14,7 @@ import org.hyperledger.identus.client.models.* class ConnectionLessSteps { - @When("{actor} creates a {string} credential offer invitation with {string} form DID") + @When("{actor} creates a '{}' credential offer invitation with '{}' form DID") fun inviterGeneratesACredentialOfferInvitation(issuer: Actor, credentialFormat: String, didForm: String) { val claims = linkedMapOf( "firstName" to "Automation", @@ -90,12 +90,7 @@ class ConnectionLessSteps { challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf", domain = "https://example-verifier.com", ), - proofs = listOf( - ProofRequestAux( - schemaId = "https://schema.org/Person", - trustIssuers = listOf("did:web:atalaprism.io/users/testUser"), - ), - ), + proofs = emptyList(), ) verifier.attemptsTo( diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt index 7c2251ec83..d3aac42ea4 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt @@ -48,9 +48,6 @@ class CredentialSteps { issuer.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), - ) - - issuer.attemptsTo( PollingWait.until( ListenToEvents.credentialState(issuer), equalTo(CREDENTIAL_SENT), diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt index 5ca7c1d96b..551662d448 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt @@ -62,7 +62,7 @@ class JwtCredentialSteps { holder.remember("thid", credentialRecord.thid) } - @When("{actor} offers a jwt credential to {actor} with {string} form DID") + @When("{actor} offers a jwt credential to {actor} with '{}' form DID") fun issuerOffersAJwtCredential(issuer: Actor, holder: Actor, format: String) { val claims = linkedMapOf( "firstName" to "FirstName", @@ -72,7 +72,7 @@ class JwtCredentialSteps { saveCredentialOffer(issuer, holder) } - @When("{actor} offers a jwt credential to {actor} with {string} form DID using issuingKid {string}") + @When("{actor} offers a jwt credential to {actor} with '{}' form DID using issuingKid '{}'") fun issuerOffersAJwtCredentialWithIssuingKeyId(issuer: Actor, holder: Actor, format: String, issuingKid: String?) { val claims = linkedMapOf( "firstName" to "FirstName", @@ -82,7 +82,7 @@ class JwtCredentialSteps { saveCredentialOffer(issuer, holder) } - @When("{actor} offers a jwt credential to {actor} with {} form using {} schema") + @When("{actor} offers a jwt credential to {actor} with '{}' form using '{}' schema") fun issuerOffersJwtCredentialToHolderUsingSchema( issuer: Actor, holder: Actor, @@ -95,7 +95,7 @@ class JwtCredentialSteps { saveCredentialOffer(issuer, holder) } - @When("{actor} offers a jwt credential to {actor} with {} form DID with wrong claims structure using {} schema") + @When("{actor} offers a jwt credential to {actor} with '{}' form DID with wrong claims structure using '{}' schema") fun issuerOffersJwtCredentialToHolderWithWrongClaimStructure( issuer: Actor, holder: Actor, @@ -110,22 +110,13 @@ class JwtCredentialSteps { sendCredentialOffer(issuer, holder, format, schemaGuid, claims, "assertion-1") } - @When("{actor} accepts jwt credential offer") - fun holderAcceptsJwtCredentialOfferForJwt(holder: Actor) { + @When("{actor} accepts jwt credential offer using '{}' key id") + fun holderAcceptsJwtCredentialOfferForJwt(holder: Actor, keyId: String) { val recordId = holder.recall("recordId") + val longFormDid = holder.recall("longFormDid") + val acceptRequest = AcceptCredentialOfferRequest(longFormDid, keyId) holder.attemptsTo( - Post.to("/issue-credentials/records/$recordId/accept-offer") - .body(AcceptCredentialOfferRequest(holder.recall("longFormDid"), holder.recall("kidSecp256K1"))), - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), - ) - } - - @When("{actor} accepts jwt credential offer with keyId {string}") - fun holderAcceptsJwtCredentialOfferForJwtWithKeyId(holder: Actor, keyId: String?) { - val recordId = holder.recall("recordId") - holder.attemptsTo( - Post.to("/issue-credentials/records/$recordId/accept-offer") - .body(AcceptCredentialOfferRequest(holder.recall("longFormDid"), keyId)), + Post.to("/issue-credentials/records/$recordId/accept-offer").body(acceptRequest), Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), ) } diff --git a/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt b/tests/integration-tests/src/test/kotlin/steps/did/CreateDidSteps.kt similarity index 63% rename from tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt rename to tests/integration-tests/src/test/kotlin/steps/did/CreateDidSteps.kt index 8bff650f21..78ea918abf 100644 --- a/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/did/CreateDidSteps.kt @@ -1,11 +1,15 @@ package steps.did import abilities.ListenToEvents -import common.DidPurpose +import common.DidDocumentTemplate +import common.DidType +import common.DidType.CUSTOM import interactions.Get import interactions.Post import interactions.body -import io.cucumber.java.en.* +import io.cucumber.java.en.Given +import io.cucumber.java.en.Then +import io.cucumber.java.en.When import io.iohk.atala.automation.extensions.get import io.iohk.atala.automation.serenity.ensure.Ensure import io.iohk.atala.automation.serenity.interactions.PollingWait @@ -15,84 +19,64 @@ import org.apache.http.HttpStatus import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK import org.hamcrest.CoreMatchers.equalTo -import org.hyperledger.identus.client.models.* - -class PublishDidSteps { - - @Given("{actor} has a published DID for {}") - fun agentHasAPublishedDID(agent: Actor, didPurpose: DidPurpose) { - if (agent.recallAll().containsKey("hasPublishedDid") && actualDidHasSamePurpose(agent, didPurpose)) { +import org.hyperledger.identus.client.models.CreateManagedDidRequest +import org.hyperledger.identus.client.models.CreateManagedDidRequestDocumentTemplate +import org.hyperledger.identus.client.models.Curve +import org.hyperledger.identus.client.models.DIDOperationResponse +import org.hyperledger.identus.client.models.DIDResolutionResult +import org.hyperledger.identus.client.models.ManagedDID +import org.hyperledger.identus.client.models.ManagedDIDKeyTemplate +import org.hyperledger.identus.client.models.Purpose + +class CreateDidSteps { + + @Given("{actor} has a published DID for '{}'") + fun agentHasAPublishedDID(agent: Actor, didType: DidType) { + if (agent.recallAll().containsKey("hasPublishedDid") && actualDidHasSamePurpose(agent, didType)) { return } - agentHasAnUnpublishedDID(agent, didPurpose) + agentHasAnUnpublishedDID(agent, didType) hePublishesDidToLedger(agent) } - @Given("{actor} has an unpublished DID for {}") - fun agentHasAnUnpublishedDID(agent: Actor, didPurpose: DidPurpose) { + @Given("{actor} has an unpublished DID for '{}'") + fun agentHasAnUnpublishedDID(agent: Actor, didType: DidType) { if (agent.recallAll().containsKey("shortFormDid") || agent.recallAll().containsKey("longFormDid")) { // is not published and has the same purpose - if (!agent.recallAll().containsKey("hasPublishedDid") && actualDidHasSamePurpose(agent, didPurpose)) { + if (!agent.recallAll().containsKey("hasPublishedDid") && actualDidHasSamePurpose(agent, didType)) { return } } - agentCreatesUnpublishedDid(agent, didPurpose) - } - - private fun actualDidHasSamePurpose(agent: Actor, didPurpose: DidPurpose): Boolean { - val actualPurpose: DidPurpose = agent.recall("didPurpose") ?: return false - return actualPurpose == didPurpose + agentCreatesUnpublishedDid(agent, didType) } - @Given("{actor} creates unpublished DID") + @Given("{actor} creates empty unpublished DID") fun agentCreatesEmptyUnpublishedDid(actor: Actor) { - agentCreatesUnpublishedDid(actor, DidPurpose.CUSTOM) + agentCreatesUnpublishedDid(actor, CUSTOM) } - @Given("{actor} creates unpublished DID for {}") - fun agentCreatesUnpublishedDid(actor: Actor, didPurpose: DidPurpose) { - val createDidRequest = CreateManagedDidRequest( - CreateManagedDidRequestDocumentTemplate(didPurpose.publicKeys, services = didPurpose.services), - ) - actor.attemptsTo( - Post.to("/did-registrar/dids").body(createDidRequest), - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), - ) - - val managedDid = SerenityRest.lastResponse().get() - - actor.attemptsTo( - Ensure.that(managedDid.longFormDid!!).isNotEmpty(), - Get.resource("/did-registrar/dids/${managedDid.longFormDid}"), - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), - ) - - val did = SerenityRest.lastResponse().get() - - actor.remember("longFormDid", managedDid.longFormDid) - actor.remember("kidSecp256K1", "auth-1") - actor.remember("kidEd25519", "auth-2") - actor.remember("shortFormDid", did.did) - actor.remember("didPurpose", didPurpose) - actor.forget("hasPublishedDid") + @Given("{actor} creates unpublished DID for '{}'") + fun agentCreatesUnpublishedDid(actor: Actor, didType: DidType) { + createDid(actor, didType, didType.documentTemplate) } @When("{actor} prepares a custom PRISM DID") fun actorPreparesCustomDid(actor: Actor) { - val customDid = DidPurpose.CUSTOM + val customDid = CUSTOM.documentTemplate actor.remember("customDid", customDid) } @When("{actor} adds a '{curve}' key for '{purpose}' purpose with '{}' name to the custom PRISM DID") fun actorAddsKeyToCustomDid(actor: Actor, curve: Curve, purpose: Purpose, name: String) { - val customDid = actor.recall("customDid") - customDid.publicKeys.add(ManagedDIDKeyTemplate(name, purpose, curve)) + val documentTemplate = actor.recall("customDid") + documentTemplate.publicKeys.add(ManagedDIDKeyTemplate(name, purpose, curve)) + actor.remember("customDid", documentTemplate) } @When("{actor} creates the custom PRISM DID") fun actorCreatesTheCustomPrismDid(actor: Actor) { - val customDid = actor.recall("customDid") - agentCreatesUnpublishedDid(actor, customDid) + val documentTemplate = actor.recall("customDid") + createDid(actor, CUSTOM, documentTemplate) } @When("{actor} publishes DID to ledger") @@ -131,4 +115,38 @@ class PublishDidSteps { Ensure.that(didResolutionResult.didDocumentMetadata.deactivated!!).isFalse(), ) } + + private fun actualDidHasSamePurpose(agent: Actor, didType: DidType): Boolean { + val actualPurpose: DidType = agent.recall("didPurpose") ?: return false + return actualPurpose == didType + } + + private fun createDid(actor: Actor, didType: DidType, documentTemplate: DidDocumentTemplate) { + val createDidRequest = CreateManagedDidRequest( + CreateManagedDidRequestDocumentTemplate( + publicKeys = documentTemplate.publicKeys, + services = documentTemplate.services, + ), + ) + + actor.attemptsTo( + Post.to("/did-registrar/dids").body(createDidRequest), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + ) + + val managedDid = SerenityRest.lastResponse().get() + + actor.attemptsTo( + Ensure.that(managedDid.longFormDid!!).isNotEmpty(), + Get.resource("/did-registrar/dids/${managedDid.longFormDid}"), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), + ) + + val did = SerenityRest.lastResponse().get() + + actor.remember("longFormDid", managedDid.longFormDid) + actor.remember("shortFormDid", did.did) + actor.remember("didPurpose", didType) + actor.forget("hasPublishedDid") + } } diff --git a/tests/integration-tests/src/test/kotlin/steps/multitenancy/WalletsSteps.kt b/tests/integration-tests/src/test/kotlin/steps/multitenancy/WalletsSteps.kt index c2eb65cebb..9fbf303e51 100644 --- a/tests/integration-tests/src/test/kotlin/steps/multitenancy/WalletsSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/multitenancy/WalletsSteps.kt @@ -41,7 +41,7 @@ class WalletsSteps { return SerenityRest.lastResponse().get() } - @When("{actor} creates new wallet with name {string}") + @When("{actor} creates new wallet with name '{}'") fun iCreateNewWalletWithName(acme: Actor, name: String) { val wallet = createNewWallet(acme, name) acme.attemptsTo( @@ -88,7 +88,7 @@ class WalletsSteps { ) } - @Then("{actor} should have a wallet with name {string}") + @Then("{actor} should have a wallet with name '{}'") fun iShouldHaveAWalletWithName(acme: Actor, name: String) { acme.attemptsTo( Get.resource("/wallets/${acme.recall("walletId")}") diff --git a/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt index 6a2e906361..e639fda97a 100644 --- a/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt @@ -28,7 +28,7 @@ import java.net.URI import java.net.URL class IssueCredentialSteps { - @When("{actor} creates an offer using {string} configuration with {string} form DID") + @When("{actor} creates an offer using '{}' configuration with '{}' form DID") fun issuerCreateCredentialOffer(issuer: Actor, configurationId: String, didForm: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") val claims = linkedMapOf( diff --git a/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt b/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt index 8a53c311f9..7dbc0f41f6 100644 --- a/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt @@ -22,7 +22,7 @@ import org.hyperledger.identus.client.models.IssuerMetadata import java.util.UUID class ManageCredentialConfigSteps { - @Given("{actor} has {string} credential configuration created from {}") + @Given("{actor} has '{}' credential configuration created from '{}'") fun issuerHasExistingCredentialConfig(issuer: Actor, configurationId: String, schema: CredentialSchema) { ManageIssuerSteps().issuerHasExistingCredentialIssuer(issuer) val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") @@ -34,7 +34,7 @@ class ManageCredentialConfigSteps { } } - @When("{actor} uses {} to create a credential configuration {string}") + @When("{actor} uses '{}' to create a credential configuration '{}'") fun issuerCreateCredentialConfiguration(issuer: Actor, schema: CredentialSchema, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") val schemaGuid = issuer.recall(schema.name) @@ -52,7 +52,7 @@ class ManageCredentialConfigSteps { ) } - @When("{actor} deletes {string} credential configuration") + @When("{actor} deletes '{}' credential configuration") fun issuerDeletesCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( @@ -61,7 +61,7 @@ class ManageCredentialConfigSteps { ) } - @When("{actor} deletes a non existent {} credential configuration") + @When("{actor} deletes a non existent '{}' credential configuration") fun issuerDeletesANonExistentCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( @@ -75,7 +75,7 @@ class ManageCredentialConfigSteps { issuer.remember("credentialConfiguration", credentialConfiguration) } - @When("{actor} uses {} issuer id for credential configuration") + @When("{actor} uses '{}' issuer id for credential configuration") fun issuerUsesIssuerId(issuer: Actor, issuerId: String) { if (issuerId == "existing") { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") @@ -129,7 +129,7 @@ class ManageCredentialConfigSteps { ) } - @Then("{actor} sees the {string} configuration on IssuerMetadata endpoint") + @Then("{actor} sees the '{}' configuration on IssuerMetadata endpoint") fun issuerSeesCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( @@ -143,7 +143,7 @@ class ManageCredentialConfigSteps { ) } - @Then("{actor} cannot see the {string} configuration on IssuerMetadata endpoint") + @Then("{actor} cannot see the '{}' configuration on IssuerMetadata endpoint") fun issuerCannotSeeCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt index 78985e62b0..7e70708b41 100644 --- a/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt @@ -1,5 +1,6 @@ package steps.proofs +import common.CredentialSchema import interactions.* import io.cucumber.java.en.When import io.iohk.atala.automation.extensions.get @@ -15,6 +16,31 @@ class JwtProofSteps { @When("{actor} sends a request for jwt proof presentation to {actor}") fun verifierSendsARequestForJwtProofPresentationToHolder(verifier: Actor, holder: Actor) { val verifierConnectionToHolder = verifier.recall("connection-with-${holder.name}").connectionId + val presentationRequest = RequestPresentationInput( + connectionId = verifierConnectionToHolder, + options = Options( + challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf", + domain = "https://example-verifier.com", + ), + proofs = emptyList(), + ) + verifier.attemptsTo( + Post.to("/present-proof/presentations").body(presentationRequest), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + ) + val presentationStatus = SerenityRest.lastResponse().get() + verifier.remember("thid", presentationStatus.thid) + holder.remember("thid", presentationStatus.thid) + } + + @When("{actor} sends a request for jwt proof from trustedIssuer {actor} using {} schema presentation to {actor}") + fun verifierSendsARequestForJwtProofPresentationToHolderUsingSchemaFromTrustedIssuer(verifier: Actor, issuer: Actor, schema: CredentialSchema, holder: Actor) { + val verifierConnectionToHolder = verifier.recall("connection-with-${holder.name}").connectionId + val trustIssuer = issuer.recall("longFormDid") + val baseUrl = issuer.recall("baseUrl") + val schemaGuid = issuer.recall(schema.name)!! + val schemaId = "$baseUrl/schema-registry/schemas/$schemaGuid" + val presentationRequest = RequestPresentationInput( connectionId = verifierConnectionToHolder, options = Options( @@ -23,8 +49,8 @@ class JwtProofSteps { ), proofs = listOf( ProofRequestAux( - schemaId = "https://schema.org/Person", - trustIssuers = listOf("did:web:atalaprism.io/users/testUser"), + schemaId = schemaId, + trustIssuers = listOf(trustIssuer), ), ), ) diff --git a/tests/integration-tests/src/test/kotlin/steps/schemas/CredentialSchemasSteps.kt b/tests/integration-tests/src/test/kotlin/steps/schemas/CredentialSchemasSteps.kt index ae14368831..37511cd35f 100644 --- a/tests/integration-tests/src/test/kotlin/steps/schemas/CredentialSchemasSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/schemas/CredentialSchemasSteps.kt @@ -16,7 +16,7 @@ import java.util.UUID class CredentialSchemasSteps { - @Given("{actor} has published {} schema") + @Given("{actor} has published '{}' schema") fun agentHasAPublishedSchema(agent: Actor, schema: CredentialSchema) { if (agent.recallAll().containsKey(schema.name)) { return @@ -24,7 +24,7 @@ class CredentialSchemasSteps { agentCreatesANewCredentialSchema(agent, schema) } - @When("{actor} creates a new credential {} schema") + @When("{actor} creates a new credential '{}' schema") fun agentCreatesANewCredentialSchema(actor: Actor, schema: CredentialSchema) { actor.attemptsTo( Post.to("/schema-registry/schemas").body( diff --git a/tests/integration-tests/src/test/kotlin/steps/verification/VcVerificationSteps.kt b/tests/integration-tests/src/test/kotlin/steps/verification/VcVerificationSteps.kt index 32f285a197..174aed265d 100644 --- a/tests/integration-tests/src/test/kotlin/steps/verification/VcVerificationSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/verification/VcVerificationSteps.kt @@ -47,7 +47,7 @@ class VcVerificationSteps { holder.remember("issuerDid", "did:prism:issuer") } - @Given("{actor} has a {} problem in the Verifiable Credential") + @Given("{actor} has a '{}' problem in the Verifiable Credential") fun holderHasProblemInTheVerifiableCredential(holder: Actor, problem: JwtCredentialProblem) { val jwt = problem.jwt() holder.remember("jwt", jwt) @@ -64,7 +64,7 @@ class VcVerificationSteps { holder.remember("checks", checks) } - @Then("{actor} should see that verification has failed with {} problem") + @Then("{actor} should see that verification has failed with '{}' problem") fun holderShouldSeeThatVerificationHasFailedWithProblem(holder: Actor, problem: JwtCredentialProblem) { } diff --git a/tests/integration-tests/src/test/resources/features/connection/connection.feature b/tests/integration-tests/src/test/resources/features/connection/connection.feature index 329f9115bd..0b742d2fb7 100644 --- a/tests/integration-tests/src/test/resources/features/connection/connection.feature +++ b/tests/integration-tests/src/test/resources/features/connection/connection.feature @@ -1,9 +1,9 @@ @connection @create Feature: Agents connection -Scenario: Establish a connection between two agents - When Issuer generates a connection invitation to Holder - And Holder sends a connection request to Issuer - And Issuer receives the connection request and sends back the response - And Holder receives the connection response - Then Issuer and Holder have a connection + Scenario: Establish a connection between two agents + When Issuer generates a connection invitation to Holder + And Holder sends a connection request to Issuer + And Issuer receives the connection request and sends back the response + And Holder receives the connection response + Then Issuer and Holder have a connection diff --git a/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature index 712d6d7db6..3011d2cb2e 100644 --- a/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature +++ b/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature @@ -3,8 +3,8 @@ Feature: Issue Anoncred credential Background: Given Issuer and Holder have an existing connection - And Issuer has a published DID for ANONCRED - And Holder has an unpublished DID for ANONCRED + And Issuer has a published DID for 'ANONCRED' + And Holder has an unpublished DID for 'ANONCRED' Scenario: Issuing anoncred with published PRISM DID Given Issuer has an anoncred schema definition diff --git a/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature index 8bc05395c0..539bc1f0f7 100644 --- a/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature @@ -4,8 +4,8 @@ Feature: Present Proof Protocol Scenario: Holder presents anoncreds credential proof to verifier Given Issuer and Holder have an existing connection And Verifier and Holder have an existing connection - And Issuer has a published DID for ANONCRED - And Holder has an unpublished DID for ANONCRED + And Issuer has a published DID for 'ANONCRED' + And Holder has an unpublished DID for 'ANONCRED' And Issuer has an anoncred schema definition And Issuer offers anoncred to Holder And Holder receives the credential offer diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature index 6258cf5fdb..5e15b98f8f 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature @@ -1,61 +1,73 @@ @jwt @issuance Feature: Issue JWT credential - - Scenario: Issuing jwt credential with published PRISM DID + Scenario Outline: Issuing jwt credential using assertion Given Issuer and Holder have an existing connection - And Issuer has a published DID for JWT - And Holder has an unpublished DID for JWT - When Issuer offers a jwt credential to Holder with "short" form DID + And Holder creates unpublished DID for 'JWT' + When Issuer prepares a custom PRISM DID + And Issuer adds a '' key for 'assertionMethod' purpose with '' name to the custom PRISM DID + And Issuer creates the custom PRISM DID + And Issuer publishes DID to ledger + When Issuer offers a jwt credential to Holder with 'short' form DID using issuingKid '' And Holder receives the credential offer - And Holder accepts jwt credential offer + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential Then Holder receives the issued credential + When Issuer revokes the credential issued to Holder + Then Issuer should see the credential was revoked + When Issuer sends a request for jwt proof presentation to Holder + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Issuer sees the proof returned verification failed + Examples: + | assertionMethod | assertionName | + | secp256k1 | assert-1 | + | ed25519 | assert-1 | - Scenario: Issuing jwt credential with published PRISM DID using Ed25519 + Scenario: Issuing jwt credential with published PRISM DID Given Issuer and Holder have an existing connection - And Issuer has a published DID for JWT - And Holder has an unpublished DID for JWT - When Issuer offers a jwt credential to Holder with "short" form DID using issuingKid "assertion-2" + And Issuer has a published DID for 'JWT' + And Holder has an unpublished DID for 'JWT' + When Issuer offers a jwt credential to Holder with 'short' form DID And Holder receives the credential offer - And Holder accepts jwt credential offer with keyId "auth-2" + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential Then Holder receives the issued credential Scenario: Issuing jwt credential with a schema Given Issuer and Holder have an existing connection - And Issuer has a published DID for JWT - And Issuer has published STUDENT_SCHEMA schema - And Holder has an unpublished DID for JWT - When Issuer offers a jwt credential to Holder with "short" form using STUDENT_SCHEMA schema + And Issuer has a published DID for 'JWT' + And Issuer has published 'STUDENT_SCHEMA' schema + And Holder has an unpublished DID for 'JWT' + When Issuer offers a jwt credential to Holder with 'short' form using 'STUDENT_SCHEMA' schema And Holder receives the credential offer - And Holder accepts jwt credential offer + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential Then Holder receives the issued credential Scenario: Issuing jwt credential with wrong claim structure for schema Given Issuer and Holder have an existing connection - And Issuer has a published DID for JWT - And Issuer has published STUDENT_SCHEMA schema - And Holder has an unpublished DID for JWT - When Issuer offers a jwt credential to Holder with "short" form DID with wrong claims structure using STUDENT_SCHEMA schema + And Issuer has a published DID for 'JWT' + And Issuer has published 'STUDENT_SCHEMA' schema + And Holder has an unpublished DID for 'JWT' + When Issuer offers a jwt credential to Holder with 'short' form DID with wrong claims structure using 'STUDENT_SCHEMA' schema Then Issuer should see that credential issuance has failed Scenario: Issuing jwt credential with unpublished PRISM DID Given Issuer and Holder have an existing connection - And Issuer has an unpublished DID for JWT - And Holder has an unpublished DID for JWT - And Issuer offers a jwt credential to Holder with "long" form DID + And Issuer has an unpublished DID for 'JWT' + And Holder has an unpublished DID for 'JWT' + And Issuer offers a jwt credential to Holder with 'long' form DID And Holder receives the credential offer - And Holder accepts jwt credential offer + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential Then Holder receives the issued credential Scenario: Connectionless issuance of JWT credential using OOB invitation - Given Issuer has a published DID for JWT - And Holder has an unpublished DID for JWT - When Issuer creates a "JWT" credential offer invitation with "short" form DID + Given Issuer has a published DID for 'JWT' + And Holder has an unpublished DID for 'JWT' + When Issuer creates a 'JWT' credential offer invitation with 'short' form DID And Holder accepts the credential offer invitation from Issuer - And Holder accepts jwt credential offer + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential - Then Holder receives the issued credential \ No newline at end of file + Then Holder receives the issued credential diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature index 43612873d6..8c627d03d9 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature @@ -25,10 +25,48 @@ Feature: Present Proof Protocol And Holder rejects the proof Then Holder sees the proof is rejected + Scenario Outline: Verifying jwt credential using assertion + Given Issuer and Holder have an existing connection + And Verifier and Holder have an existing connection + And Holder creates unpublished DID for 'JWT' + When Issuer prepares a custom PRISM DID + And Issuer adds a '' key for 'assertionMethod' purpose with '' name to the custom PRISM DID + And Issuer creates the custom PRISM DID + When Issuer offers a jwt credential to Holder with 'long' form DID using issuingKid '' + And Holder receives the credential offer + And Holder accepts jwt credential offer using 'auth-1' key id + And Issuer issues the credential + Then Holder receives the issued credential + When Verifier sends a request for jwt proof presentation to Holder + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Verifier has the proof verified + Examples: + | assertionMethod | assertionName | + | secp256k1 | assert-1 | + | ed25519 | assert-1 | + Scenario: Connectionless Verification Holder presents jwt credential proof to verifier Given Holder has a jwt issued credential from Issuer When Verifier creates a OOB Invitation request for JWT proof presentation And Holder accepts the OOB invitation request for JWT proof presentation from Verifier And Holder receives the presentation proof request And Holder makes the jwt presentation of the proof - Then Verifier has the proof verified \ No newline at end of file + Then Verifier has the proof verified + + Scenario: Verifier request for jwt proof presentation to Holder from trusted issuer using specified schema + Given Verifier and Holder have an existing connection + And Holder has a jwt issued credential with 'STUDENT_SCHEMA' schema from Issuer + When Verifier sends a request for jwt proof from trustedIssuer Issuer using STUDENT_SCHEMA schema presentation to Holder + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Verifier has the proof verified + + Scenario: Verifier request for jwt proof presentation to Holder from trusted issuer using specified schema + Given Verifier and Holder have an existing connection + And Holder has a jwt issued credential with 'STUDENT_SCHEMA' schema from Issuer + And Holder has a jwt issued credential with 'EMPLOYEE_SCHEMA' schema from Issuer + When Verifier sends a request for jwt proof from trustedIssuer Issuer using STUDENT_SCHEMA schema presentation to Holder + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Verifier sees the proof returned verification failed diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature index 508380402d..284f008a60 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature @@ -1,10 +1,8 @@ @jwt @revocation Feature: JWT Credential revocation - Background: - Given Holder has a jwt issued credential from Issuer - Scenario: Revoke jwt issued credential + Given Holder has a jwt issued credential from Issuer When Issuer revokes the credential issued to Holder Then Issuer should see the credential was revoked When Issuer sends a request for jwt proof presentation to Holder @@ -13,6 +11,7 @@ Feature: JWT Credential revocation Then Issuer sees the proof returned verification failed Scenario: Holder tries to revoke jwt credential from issuer + Given Holder has a jwt issued credential from Issuer When Holder tries to revoke credential from Issuer And Issuer sends a request for jwt proof presentation to Holder And Holder receives the presentation proof request diff --git a/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature index a96c24f04f..2edc63f073 100644 --- a/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature +++ b/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature @@ -1,21 +1,28 @@ @sdjwt @issuance Feature: Issue SD-JWT credential - Scenario: Issuing sd-jwt credential + Scenario Outline: Issuing sd-jwt credential Given Issuer and Holder have an existing connection - And Issuer has a published DID for SD_JWT - And Holder has an unpublished DID for SD_JWT + And Holder has an unpublished DID for 'SD_JWT' + When Issuer prepares a custom PRISM DID + And Issuer adds a '' key for 'assertionMethod' purpose with '' name to the custom PRISM DID + And Issuer adds a '' key for 'authentication' purpose with '' name to the custom PRISM DID + And Issuer creates the custom PRISM DID + And Issuer publishes DID to ledger When Issuer offers a sd-jwt credential to Holder And Holder receives the credential offer And Holder accepts credential offer for sd-jwt And Issuer issues the credential Then Holder receives the issued credential And Holder checks the sd-jwt credential contents + Examples: + | assertionMethod | assertionName | authentication | authenticationName | + | ed25519 | assert-1 | ed25519 | auth-1 | Scenario: Issuing sd-jwt credential with holder binding Given Issuer and Holder have an existing connection - And Issuer has a published DID for SD_JWT - And Holder has an unpublished DID for SD_JWT + And Issuer has a published DID for 'SD_JWT' + And Holder has an unpublished DID for 'SD_JWT' When Issuer offers a sd-jwt credential to Holder And Holder receives the credential offer And Holder accepts credential offer for sd-jwt with 'auth-1' key binding @@ -24,25 +31,11 @@ Feature: Issue SD-JWT credential Then Holder checks the sd-jwt credential contents with holder binding Scenario: Connectionless issuance of sd-jwt credential with holder binding - And Issuer has a published DID for SD_JWT - And Holder has an unpublished DID for SD_JWT - When Issuer creates a "SDJWT" credential offer invitation with "short" form DID + And Issuer has a published DID for 'SD_JWT' + And Holder has an unpublished DID for 'SD_JWT' + When Issuer creates a 'SDJWT' credential offer invitation with 'short' form DID And Holder accepts the credential offer invitation from Issuer And Holder accepts credential offer for sd-jwt with 'auth-1' key binding And Issuer issues the credential Then Holder receives the issued credential Then Holder checks the sd-jwt credential contents with holder binding - - -# Scenario: Issuing sd-jwt with wrong algorithm -# Given Issuer and Holder have an existing connection -# When Issuer prepares a custom PRISM DID -# And Issuer adds a 'secp256k1' key for 'assertionMethod' purpose with 'assert-1' name to the custom PRISM DID -# And Issuer adds a 'secp256k1' key for 'authentication' purpose with 'auth-1' name to the custom PRISM DID -# And Issuer creates the custom PRISM DID -# And Holder has an unpublished DID for SD_JWT -# And Issuer offers a sd-jwt credential to Holder -# And Holder receives the credential offer -# And Holder accepts credential offer for sd-jwt -# And Issuer tries to issue the credential -# Then Issuer should see that credential issuance has failed diff --git a/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature index 96e8f4e961..d0d3ca9dd5 100644 --- a/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature @@ -25,7 +25,7 @@ Feature: Present SD-JWT Proof Protocol | Verifier | | Issuer | - Scenario Outline: Holder presents sd-jwt proof to + Scenario Outline: Holder presents sd-jwt out-of-band proof to Given Holder has a sd-jwt issued credential from Issuer When creates a OOB Invitation request for sd-jwt proof presentation requesting [firstName] claims And Holder accepts the OOB invitation request for JWT proof presentation from diff --git a/tests/integration-tests/src/test/resources/features/did/create_did.feature b/tests/integration-tests/src/test/resources/features/did/create_did.feature index 55ebe81a21..172fc301b0 100644 --- a/tests/integration-tests/src/test/resources/features/did/create_did.feature +++ b/tests/integration-tests/src/test/resources/features/did/create_did.feature @@ -1,7 +1,7 @@ @DLT @did @create Feature: Create and publish DID - Scenario Outline: Create PRISM DID + Scenario Outline: Create PRISM DID with for When Issuer creates PRISM DID with key having purpose Then He sees PRISM DID was created successfully And He sees PRISM DID data was stored correctly with and @@ -13,7 +13,7 @@ Feature: Create and publish DID | Ed25519 | assertionMethod | | X25519 | keyAgreement | - Scenario Outline: Create PRISM DID with disallowed key purpose + Scenario Outline: Create PRISM DID with for should not work When Issuer creates PRISM DID with key having purpose Then He sees PRISM DID was not successfully created Examples: @@ -23,6 +23,6 @@ Feature: Create and publish DID | X25519 | assertionMethod | Scenario: Successfully publish DID to ledger - Given Issuer creates unpublished DID + Given Issuer creates empty unpublished DID When He publishes DID to ledger Then He resolves DID document corresponds to W3C standard diff --git a/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature b/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature index 13b3ea03e3..37a1c3bebb 100644 --- a/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature +++ b/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature @@ -2,7 +2,7 @@ Feature: Deactivate DID Scenario: Deactivate DID - Given Issuer creates unpublished DID + Given Issuer creates empty unpublished DID And Issuer publishes DID to ledger When Issuer deactivates PRISM DID Then He sees that PRISM DID is successfully deactivated diff --git a/tests/integration-tests/src/test/resources/features/did/update_did.feature b/tests/integration-tests/src/test/resources/features/did/update_did.feature index 9aeb2e122f..6e3166fd78 100644 --- a/tests/integration-tests/src/test/resources/features/did/update_did.feature +++ b/tests/integration-tests/src/test/resources/features/did/update_did.feature @@ -2,7 +2,7 @@ Feature: Update DID Background: Published DID is created - Given Issuer has a published DID for JWT + Given Issuer has a published DID for 'JWT' Scenario: Update PRISM DID services When Issuer updates PRISM DID with new services @@ -17,7 +17,7 @@ Feature: Update DID Then He sees the PRISM DID should have been updated successfully And He sees the PRISM DID should have the service removed - Scenario Outline: Update PRISM DID keys + Scenario Outline: Update PRISM DID keys using for When Issuer updates PRISM DID by adding new key with curve and purpose Then He sees PRISM DID was successfully updated with new keys of purpose diff --git a/tests/integration-tests/src/test/resources/features/multitenancy/wallets.feature b/tests/integration-tests/src/test/resources/features/multitenancy/wallets.feature index f6f0cdf0c7..83ba9f0459 100644 --- a/tests/integration-tests/src/test/resources/features/multitenancy/wallets.feature +++ b/tests/integration-tests/src/test/resources/features/multitenancy/wallets.feature @@ -2,8 +2,8 @@ Feature: Wallets management Scenario Outline: Successful creation of a new wallet - When Admin creates new wallet with name - Then Admin should have a wallet with name + When Admin creates new wallet with name '' + Then Admin should have a wallet with name '' Examples: | name | | "wallet-1" | diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature b/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature index 00c7bf2f45..a48859a8e5 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature @@ -2,20 +2,20 @@ Feature: Issue JWT Credentials using OID4VCI authorization code flow Background: - Given Issuer has a published DID for OIDC_JWT - And Issuer has published STUDENT_SCHEMA schema + Given Issuer has a published DID for 'OIDC_JWT' + And Issuer has published 'STUDENT_SCHEMA' schema And Issuer has an existing oid4vci issuer - And Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA + And Issuer has 'StudentProfile' credential configuration created from 'STUDENT_SCHEMA' Scenario: Issuing credential with published PRISM DID - When Issuer creates an offer using "StudentProfile" configuration with "short" form DID + When Issuer creates an offer using 'StudentProfile' configuration with 'short' form DID And Holder receives oid4vci offer from Issuer And Holder resolves oid4vci issuer metadata and login via front-end channel And Holder presents the access token with JWT proof on CredentialEndpoint Then Holder sees credential issued successfully from CredentialEndpoint Scenario: Issuing credential with unpublished PRISM DID - When Issuer creates an offer using "StudentProfile" configuration with "long" form DID + When Issuer creates an offer using 'StudentProfile' configuration with 'long' form DID And Holder receives oid4vci offer from Issuer And Holder resolves oid4vci issuer metadata and login via front-end channel And Holder presents the access token with JWT proof on CredentialEndpoint diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature b/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature index 669fe6977e..991f93ea24 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature @@ -2,22 +2,22 @@ Feature: Manage OID4VCI credential configuration Background: - Given Issuer has a published DID for OIDC_JWT - And Issuer has published STUDENT_SCHEMA schema + Given Issuer has a published DID for 'OIDC_JWT' + And Issuer has published 'STUDENT_SCHEMA' schema And Issuer has an existing oid4vci issuer Scenario: Successfully create credential configuration - Given Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA - Then Issuer sees the "StudentProfile" configuration on IssuerMetadata endpoint + Given Issuer has 'StudentProfile' credential configuration created from 'STUDENT_SCHEMA' + Then Issuer sees the 'StudentProfile' configuration on IssuerMetadata endpoint Scenario: Successfully delete credential configuration - Given Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA - When Issuer deletes "StudentProfile" credential configuration - Then Issuer cannot see the "StudentProfile" configuration on IssuerMetadata endpoint + Given Issuer has 'StudentProfile' credential configuration created from 'STUDENT_SCHEMA' + When Issuer deletes 'StudentProfile' credential configuration + Then Issuer cannot see the 'StudentProfile' configuration on IssuerMetadata endpoint Scenario Outline: Create configuration with expect code When Issuer creates a new credential configuration request - And Issuer uses issuer id for credential configuration + And Issuer uses '' issuer id for credential configuration And Issuer adds '' configuration id for credential configuration request And Issuer adds '' format for credential configuration request And Issuer adds '' schemaId for credential configuration request @@ -35,5 +35,5 @@ Feature: Manage OID4VCI credential configuration | existing | StudentProfile | jwt_vc_json | STUDENT_SCHEMA | 409 | Duplicated credential | duplicated configuration id | Scenario: Delete non existent credential configuration - When Issuer deletes a non existent "NonExistentProfile" credential configuration + When Issuer deletes a non existent 'NonExistentProfile' credential configuration Then Issuer should see that create credential configuration has failed with '404' status code and 'There is no credential configuration' detail diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature b/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature index 6259934824..4ba9f9bbf7 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature @@ -18,18 +18,18 @@ Feature: Manage OID4VCI credential issuer Then Issuer cannot see the oid4vci issuer on the agent And Issuer cannot see the oid4vci IssuerMetadata endpoint - Scenario Outline: Create issuer with expect response + Scenario Outline: Create issuer with [id=; url=; clientId=; clientSecret=] expect response When Issuer tries to create oid4vci issuer with '', '', '' and '' Then Issuer should see the oid4vci '' http status response with '' detail Examples: - | id | url | clientId | clientSecret | httpStatus | errorDetail | description | - | null | null | null | null | 400 | authorizationServer.url | null values | - | null | malformed | id | secret | 400 | Relative URL 'malformed' is not | malformed url | - | null | http://example.com | id | null | 400 | authorizationServer.clientSecret | null client secret | - | null | http://example.com | null | secret | 400 | authorizationServer.clientId | null client id | - | null | null | id | secret | 400 | authorizationServer.url | null url | - | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 201 | | right values | - | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 500 | | duplicated id | + | id | url | clientId | clientSecret | httpStatus | errorDetail | + | null | null | null | null | 400 | authorizationServer.url | + | null | malformed | id | secret | 400 | Relative URL 'malformed' is not | + | null | http://example.com | id | null | 400 | authorizationServer.clientSecret | + | null | http://example.com | null | secret | 400 | authorizationServer.clientId | + | null | null | id | secret | 400 | authorizationServer.url | + | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 201 | | + | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 500 | | Scenario Outline: Update issuer with expect response Given Issuer has an existing oid4vci issuer diff --git a/tests/integration-tests/src/test/resources/features/schemas/credential_schemas.feature b/tests/integration-tests/src/test/resources/features/schemas/credential_schemas.feature index fdbbbc911f..d06b5a0090 100644 --- a/tests/integration-tests/src/test/resources/features/schemas/credential_schemas.feature +++ b/tests/integration-tests/src/test/resources/features/schemas/credential_schemas.feature @@ -2,10 +2,10 @@ Feature: Credential schemas Background: - Given Issuer creates unpublished DID + Given Issuer creates empty unpublished DID Scenario: Successful schema creation - When Issuer creates a new credential STUDENT_SCHEMA schema + When Issuer creates a new credential 'STUDENT_SCHEMA' schema Then He sees new credential schema is available Scenario Outline: Multiple schema creation @@ -15,7 +15,7 @@ Feature: Credential schemas | schemas | | 4 | - Scenario Outline: Schema creation should fail for cases + Scenario Outline: Schema creation should fail for When Issuer creates a schema containing '' issue Then Issuer should see the schema creation failed Examples: diff --git a/tests/integration-tests/src/test/resources/features/verificationapi/vc_verification.feature b/tests/integration-tests/src/test/resources/features/verificationapi/vc_verification.feature index 3db147f66d..6b74dc000c 100644 --- a/tests/integration-tests/src/test/resources/features/verificationapi/vc_verification.feature +++ b/tests/integration-tests/src/test/resources/features/verificationapi/vc_verification.feature @@ -1,8 +1,8 @@ @verification @api -Feature: Vc Verification schemas +Feature: Verification API - Scenario: Receive a jwt vc from cloud-agent and verify it - Given Holder has a jwt issued credential with STUDENT_SCHEMA schema from Issuer + Scenario: Verify a jwt credential using verification api + Given Holder has a jwt issued credential with 'STUDENT_SCHEMA' schema from Issuer And Holder uses that JWT VC issued from Issuer for Verification API And Holder sends the JWT Credential to Issuer Verification API | ALGORITHM_VERIFICATION | true | @@ -14,7 +14,7 @@ Feature: Vc Verification schemas | SEMANTIC_CHECK_OF_CLAIMS | true | Then Holder should see that all checks have passed - Scenario: Expected checks for generated JWT VC + Scenario: Verify a pre-generated jwt credential using verification api Given Holder has a JWT VC for Verification API When Holder sends the JWT Credential to Issuer Verification API | ALGORITHM_VERIFICATION | true | @@ -26,11 +26,11 @@ Feature: Vc Verification schemas | SEMANTIC_CHECK_OF_CLAIMS | true | Then Holder should see that all checks have passed - Scenario Outline: Expected failures - Given Holder has a problem in the Verifiable Credential + Scenario Outline: Verify credential with problem + Given Holder has a '' problem in the Verifiable Credential When Holder sends the JWT Credential to Issuer Verification API | | false | - Then Holder should see that verification has failed with problem + Then Holder should see that verification has failed with '' problem Examples: | problem | | ALGORITHM_VERIFICATION | @@ -41,7 +41,7 @@ Feature: Vc Verification schemas | SIGNATURE_VERIFICATION | | SEMANTIC_CHECK_OF_CLAIMS | - Scenario Outline: Unsupported verification check should fail + Scenario Outline: Unsupported verification check should fail Given Holder has a JWT VC for Verification API When Holder sends the JWT Credential to Issuer Verification API | | false | diff --git a/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts b/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts index 2624de0713..2be18cce6f 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts @@ -24,14 +24,7 @@ export class ProofsService extends HttpService { "challenge": "11c91493-01b3-4c4d-ac36-b336bab5bddf", "domain": "https://example-verifier.com" }, - "proofs":[ - { - "schemaId": "https://schema.org/${vu.vu.idInInstance}-${vu.vu.idInTest}-${vu.vu.iterationInScenario}", - "trustIssuers": [ - "did:web:atalaprism.io/users/testUser" - ] - } - ] + "proofs":[] }` const res = this.post("present-proof/presentations", payload); try {