Skip to content

Commit

Permalink
feat: API Supports ValidFrom And ValidTo (PART 2) (#1302)
Browse files Browse the repository at this point in the history
Signed-off-by: Bassam Riman <[email protected]>
  • Loading branch information
CryptoKnightIOG authored Aug 28, 2024
1 parent 0984406 commit b0c58f3
Showing 1 changed file with 81 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ object DID {

case class Issuer(did: DID, signer: Signer, publicKey: PublicKey)

case class ApolloIssuer(did: DID, signer: Signer, publicKey: ApolloPublicKey)

sealed trait VerifiableCredentialPayload

case class W3cVerifiableCredentialPayload(payload: W3cCredentialPayload, proof: JwtProof)
Expand All @@ -43,8 +41,6 @@ case class W3cVerifiableCredentialPayload(payload: W3cCredentialPayload, proof:

case class JwtVerifiableCredentialPayload(jwt: JWT) extends VerifiableCredentialPayload

case class AnoncredVerifiableCredentialPayload(json: String) extends VerifiableCredentialPayload //FIXME json type

enum StatusPurpose(val str: String) {
case Revocation extends StatusPurpose("Revocation")
case Suspension extends StatusPurpose("Suspension")
Expand Down Expand Up @@ -143,42 +139,6 @@ sealed trait CredentialPayload {
)
}

object CredentialPayloadValidation {

val DEFAULT_VC_TYPE = "VerifiableCredential"
val DEFAULT_CONTEXT = "https://www.w3.org/2018/credentials/v1"

def validateIssuerDid(maybeIssuerDid: Option[String]): Validation[String, String] = {
Validation
.fromOptionWith("Issuer Did is empty")(maybeIssuerDid)
}

def validateIssuanceDate(maybeIssuanceDate: Option[Instant]): Validation[String, Instant] = {
Validation
.fromOptionWith("Issuance Date is empty")(maybeIssuanceDate)
}

def validateContext(`@context`: Set[String]): Validation[String, NonEmptySet[String]] = {
Validation
.fromOptionWith("@context is empty")(`@context`.toNonEmptySet)
.flatMap(`nonEmpty@context` =>
Validation.fromPredicateWith("Missing default context from @context")(`nonEmpty@context`)(
_.contains(DEFAULT_CONTEXT)
)
)
}

def validateVcType(`type`: Set[String]): Validation[String, NonEmptySet[String]] = {
Validation
.fromOptionWith("Type is empty")(`type`.toNonEmptySet)
.flatMap(nonEmptyType =>
Validation.fromPredicateWith("Missing default type from `type`")(nonEmptyType)(
_.contains(DEFAULT_VC_TYPE)
)
)
}
}

case class JwtVc(
`@context`: Set[String],
`type`: Set[String],
Expand Down Expand Up @@ -285,6 +245,8 @@ object CredentialPayload {
("issuer", w3cCredentialPayload.issuer.asJson),
("issuanceDate", w3cCredentialPayload.issuanceDate.asJson),
("expirationDate", w3cCredentialPayload.maybeExpirationDate.asJson),
("validFrom", w3cCredentialPayload.maybeValidFrom.asJson),
("validUntil", w3cCredentialPayload.maybeValidUntil.asJson),
("credentialSchema", w3cCredentialPayload.maybeCredentialSchema.asJson),
("credentialSubject", w3cCredentialPayload.credentialSubject),
("credentialStatus", w3cCredentialPayload.maybeCredentialStatus.asJson),
Expand Down Expand Up @@ -519,52 +481,67 @@ object CredentialVerification {

import CredentialPayload.Implicits.*

def validateCredential(
verifiableCredentialPayload: VerifiableCredentialPayload
)(didResolver: DidResolver): IO[String, Validation[String, Unit]] = {
verifiableCredentialPayload match {
case (w3cVerifiableCredentialPayload: W3cVerifiableCredentialPayload) =>
W3CCredential.validateW3C(w3cVerifiableCredentialPayload)(didResolver)
case (jwtVerifiableCredentialPayload: JwtVerifiableCredentialPayload) =>
JwtCredential.validateEncodedJWT(jwtVerifiableCredentialPayload.jwt)(didResolver)
}
def validateValidFromNotAfterValidUntil(
maybeValidFrom: Option[Instant],
maybeValidUntil: Option[Instant],
validFromName: String,
validUntilName: String
): Validation[String, Unit] = {
(maybeValidFrom, maybeValidUntil)
.mapN((validFrom, validUntil) =>
if (validFrom.isAfter(validUntil))
Validation.fail(
s"Credential cannot expire before being in effect. $validFromName=$validFrom $validUntilName=$validUntil"
)
else Validation.unit
)
.getOrElse(Validation.unit)
}

def verifyDates(issuanceDate: Instant, maybeExpirationDate: Option[Instant], leeway: TemporalAmount)(implicit
clock: Clock
private def validateValidFrom(
maybeValidFrom: Option[Instant],
now: Instant,
leeway: TemporalAmount,
validFromName: String,
): Validation[String, Unit] = {
val now = clock.instant()

def validateNbfNotAfterExp(nbf: Instant, maybeExp: Option[Instant]): Validation[String, Unit] = {
maybeExp
.map(exp =>
if (nbf.isAfter(exp))
Validation.fail(s"Credential cannot expire before being in effect. nbf=$nbf exp=$exp")
else Validation.unit
)
.getOrElse(Validation.unit)
}

def validateNbf(nbf: Instant): Validation[String, Unit] = {
if (now.isBefore(nbf.minus(leeway)))
Validation.fail(s"Credential is not yet in effect. now=$now nbf=$nbf leeway=$leeway")
else Validation.unit
}
maybeValidFrom
.map(validFrom =>
if (now.isBefore(validFrom.minus(leeway)))
Validation.fail(s"Credential is not yet in effect. now=$now $validFromName=$validFrom leeway=$leeway")
else Validation.unit
)
.getOrElse(Validation.unit)
}

def validateExp(maybeExp: Option[Instant]): Validation[String, Unit] = {
maybeExp
.map(exp =>
if (now.isAfter(exp.plus(leeway)))
Validation.fail(s"Credential has expired. now=$now exp=$exp leeway=$leeway")
else Validation.unit
)
.getOrElse(Validation.unit)
}
private def validateValidUntil(
maybeValidUntil: Option[Instant],
now: Instant,
leeway: TemporalAmount,
validUntilName: String,
): Validation[String, Unit] = {
maybeValidUntil
.map(validUntil =>
if (now.isAfter(validUntil.plus(leeway)))
Validation.fail(s"Credential has expired. now=$now $validUntilName=$validUntil leeway=$leeway")
else Validation.unit
)
.getOrElse(Validation.unit)
}

def verifyDates(
maybeValidFrom: Option[Instant],
maybeValidUntil: Option[Instant],
leeway: TemporalAmount,
validFromName: String,
validUntilName: String
)(implicit
clock: Clock
): Validation[String, Unit] = {
val now = clock.instant()
Validation.validateWith(
validateNbfNotAfterExp(issuanceDate, maybeExpirationDate),
validateNbf(issuanceDate),
validateExp(maybeExpirationDate)
validateValidFromNotAfterValidUntil(maybeValidFrom, maybeValidUntil, validFromName, validUntilName),
validateValidFrom(maybeValidFrom, now, leeway, validFromName),
validateValidUntil(maybeValidUntil, now, leeway, validUntilName)
)((l, _, _) => l)
}

Expand Down Expand Up @@ -791,7 +768,12 @@ object JwtCredential {
jwtCredentialPayload <- Validation.fromEither(decode[JwtCredentialPayload](decodedJWT)).mapError(_.getMessage)
nbf = jwtCredentialPayload.nbf
maybeExp = jwtCredentialPayload.maybeExp
result <- CredentialVerification.verifyDates(nbf, maybeExp, leeway)(clock)
maybeValidFrom = jwtCredentialPayload.vc.maybeValidFrom
maybeValidUntil = jwtCredentialPayload.vc.maybeValidUntil
result <- Validation.validateWith(
CredentialVerification.verifyDates(maybeValidFrom, maybeValidUntil, leeway, "validFrom", "validUntil")(clock),
CredentialVerification.verifyDates(Some(nbf), maybeExp, leeway, "nbf", "exp")(clock)
)((l, _) => l)
} yield result
}

Expand Down Expand Up @@ -878,9 +860,24 @@ object W3CCredential {
def verifyDates(w3cPayload: W3cVerifiableCredentialPayload, leeway: TemporalAmount)(implicit
clock: Clock
): Validation[String, Unit] = {
CredentialVerification.verifyDates(w3cPayload.payload.issuanceDate, w3cPayload.payload.maybeExpirationDate, leeway)(
clock
)
Validation.validateWith(
CredentialVerification.verifyDates(
w3cPayload.payload.maybeValidFrom,
w3cPayload.payload.maybeValidUntil,
leeway,
"validFrom",
"validUntil"
)(clock),
CredentialVerification.verifyDates(
Some(w3cPayload.payload.issuanceDate),
w3cPayload.payload.maybeExpirationDate,
leeway,
"issuanceDate",
"expirationDate"
)(
clock
)
)((l, _) => l)
}

private def verifyRevocationStatusW3c(
Expand Down

0 comments on commit b0c58f3

Please sign in to comment.