diff --git a/build.sbt b/build.sbt index 6ec00f16..350d351e 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ inThisBuild( /** Versions */ lazy val V = new { - val scalaDID = "0.1.0-M9" + val scalaDID = "0.1.0-M10" // val scalajsJavaSecureRandom = "1.0.0" // FIXME another bug in the test framework https://github.com/scalameta/munit/issues/554 diff --git a/mediator/src/main/resources/application.conf b/mediator/src/main/resources/application.conf index 6b82ea33..a115ea5e 100644 --- a/mediator/src/main/resources/application.conf +++ b/mediator/src/main/resources/application.conf @@ -20,7 +20,7 @@ mediator = { endpoint = ${?SERVICE_ENDPOINT} } server.http.port = 8080 - # server.http.port = ${?PORT} + server.http.port = ${?PORT} database = { protocol = mongodb protocol = ${?MONGODB_PROTOCOL} diff --git a/mediator/src/main/scala/io/iohk/atala/mediator/actions/ActionUtils.scala b/mediator/src/main/scala/io/iohk/atala/mediator/actions/ActionUtils.scala index 6a0bd2b8..31e82b6f 100644 --- a/mediator/src/main/scala/io/iohk/atala/mediator/actions/ActionUtils.scala +++ b/mediator/src/main/scala/io/iohk/atala/mediator/actions/ActionUtils.scala @@ -23,27 +23,37 @@ object ActionUtils { ): ZIO[ Operations & Agent & Resolver & MessageDispatcher & OutboxMessageRepo, MediatorError, - Option[EncryptedMessage] + Option[SignedMessage | EncryptedMessage] ] = action match { case _: NoReply.type => ZIO.succeed(None) case action: AnyReply => val reply = action.msg for { - msg <- { - reply.from match - case Some(value) => authEncrypt(reply) - case None => anonEncrypt(reply) - }.mapError(fail => MediatorDidError(fail)) + outboxRepo <- ZIO.service[OutboxMessageRepo] // TODO forward message - maybeSyncReplyMsg <- reply.to.map(_.toSeq) match // TODO improve - case None => ZIO.logWarning("Have a reply but the field 'to' is missing") *> ZIO.none - case Some(Seq()) => ZIO.logWarning("Have a reply but the field 'to' is empty") *> ZIO.none + maybeSyncReplyMsg: Option[SignedMessage | EncryptedMessage] <- reply.to.map(_.toSeq) match // TODO improve + case None => + ZIO.logWarning("Have a reply but the field 'to' is missing") *> + sign(reply) + .mapError(fail => MediatorDidError(fail)) + .map(Some(_)) + case Some(Seq()) => + ZIO.logWarning("Have a reply but the field 'to' is empty") *> + sign(reply) + .mapError(fail => MediatorDidError(fail)) + .map(Some(_)) case Some(send2DIDs) => - ZIO - .foreach(send2DIDs)(to => - val job: ZIO[MessageDispatcher & (Resolver & Any), MediatorError, Matchable] = for { + for { + msg <- { + reply.from match + case Some(value) => authEncrypt(reply) + case None => anonEncrypt(reply) + }.mapError(fail => MediatorDidError(fail)) + + replyViaDIDCommMessagingProgramme = ZIO.foreach(send2DIDs) { to => + for { messageDispatcher <- ZIO.service[MessageDispatcher] resolver <- ZIO.service[Resolver] @@ -94,19 +104,23 @@ object ActionUtils { .catchAll { case error => ZIO.logError(s"Store Outbox Error: $error") } } yield () } - - } yield (jobToRun) - action match - case Reply(_) => - job - .when( // this is +- the opposite condition as below - originalMessage - .map { oMsg => oMsg.return_route.isEmpty || oMsg.return_route.contains(ReturnRoute.none) } - .getOrElse(true) // If originalMessage is None - ) - case SyncReplyOnly(_) => ZIO.unit - case AsyncReplyOnly(_) => job - ) *> ZIO + } yield () + } + returnTmp <- action match + case Reply(_) => + if ( + originalMessage // this condition is +- the opposite condition as below + .map { oMsg => oMsg.return_route.isEmpty || oMsg.return_route.contains(ReturnRoute.none) } + .getOrElse(true) // If originalMessage is None + ) (replyViaDIDCommMessagingProgramme *> ZIO.none) + else ZIO.some(msg) + case SyncReplyOnly(_) => ZIO.some(msg) + case AsyncReplyOnly(_) => replyViaDIDCommMessagingProgramme *> ZIO.none + } yield (returnTmp) + _ <- maybeSyncReplyMsg match { + case None => ZIO.unit + case Some(msg) => + ZIO // Store send message INLINE_REPLY .succeed(msg) .tap(msg => outboxRepo @@ -127,11 +141,18 @@ object ActionUtils { .map { oMsg => { // Should replies use the same transport channel? oMsg.return_route.contains(ReturnRoute.all) || oMsg.return_route.contains(ReturnRoute.thread) - } && - oMsg.from.map(_.asTO).exists(send2DIDs.contains) // Is the reply back to the original sender? + } && { + msg match + case sMsg: SignedMessage => true // TODO If the Message is only sign shoud we reply back? + case eMsg: EncryptedMessage => // Is the reply back to the original sender/caller? + val recipients = eMsg.recipientsSubject.toSeq.map(subject => TO(subject.did)) + oMsg.from.map(_.asTO).exists(recipients.contains) + } } .getOrElse(false) // If originalMessage is None ) + } + } yield maybeSyncReplyMsg } } diff --git a/mediator/src/main/scala/io/iohk/atala/mediator/actions/ProtocolExecute.scala b/mediator/src/main/scala/io/iohk/atala/mediator/actions/ProtocolExecute.scala index f6120954..cb132c24 100644 --- a/mediator/src/main/scala/io/iohk/atala/mediator/actions/ProtocolExecute.scala +++ b/mediator/src/main/scala/io/iohk/atala/mediator/actions/ProtocolExecute.scala @@ -23,7 +23,7 @@ trait ProtocolExecuter[-R, +E] { // <: MediatorError | StorageError] { /** @return can return a Sync Reply Msg */ def execute[R1 <: R]( plaintextMessage: PlaintextMessage - ): ZIO[R1, E, Option[EncryptedMessage]] = + ): ZIO[R1, E, Option[SignedMessage | EncryptedMessage]] = program(plaintextMessage) *> ZIO.none def program[R1 <: R](plaintextMessage: PlaintextMessage): ZIO[R1, E, Action] @@ -43,7 +43,7 @@ case class ProtocolExecuterCollection[-R <: Agent, +E]( override def execute[R1 <: R]( plaintextMessage: PlaintextMessage, - ): ZIO[R1, E, Option[EncryptedMessage]] = + ): ZIO[R1, E, Option[SignedMessage | EncryptedMessage]] = selectExecutersFor(plaintextMessage.`type`) match // case None => NullProtocolExecuter.execute(plaintextMessage) case None => MissingProtocolExecuter.execute(plaintextMessage) @@ -66,7 +66,7 @@ trait ProtocolExecuterWithServices[ override def execute[R1 <: R]( plaintextMessage: PlaintextMessage, // context: Context - ): ZIO[R1, E, Option[EncryptedMessage]] = + ): ZIO[R1, E, Option[SignedMessage | EncryptedMessage]] = program(plaintextMessage) .tap(v => ZIO.logDebug(v.toString)) // DEBUG .flatMap(action => ActionUtils.packResponse(Some(plaintextMessage), action)) diff --git a/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorAgent.scala b/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorAgent.scala index ea29d72d..e8dc9891 100644 --- a/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorAgent.scala +++ b/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorAgent.scala @@ -93,7 +93,7 @@ case class MediatorAgent( ): ZIO[ Operations & Resolver & MessageDispatcher & MediatorAgent & MessageItemRepo & UserAccountRepo & OutboxMessageRepo, MediatorError | StorageError, - Option[EncryptedMessage] + Option[SignedMessage | EncryptedMessage] ] = for { msg <- data.fromJson[EncryptedMessage] match @@ -114,7 +114,7 @@ case class MediatorAgent( ): ZIO[ Operations & Resolver & MessageDispatcher & MediatorAgent & MessageItemRepo & UserAccountRepo & OutboxMessageRepo, MediatorError | StorageError, - Option[EncryptedMessage] + Option[SignedMessage | EncryptedMessage] ] = ZIO .logAnnotate("msgHash", msg.sha1) { @@ -338,8 +338,9 @@ object MediatorAgent { ret <- agent .receiveMessage(data, None) .map { - case None => Response.ok - case Some(value) => Response.json(value.toJson) + case None => Response.ok + case Some(value: SignedMessage) => Response.json(value.toJson) + case Some(value: EncryptedMessage) => Response.json(value.toJson) } .catchAll { case MediatorDidError(error) => diff --git a/mediator/src/main/scala/io/iohk/atala/mediator/db/BsonImplicits.scala b/mediator/src/main/scala/io/iohk/atala/mediator/db/BsonImplicits.scala index afa43fa3..b9549e9e 100644 --- a/mediator/src/main/scala/io/iohk/atala/mediator/db/BsonImplicits.scala +++ b/mediator/src/main/scala/io/iohk/atala/mediator/db/BsonImplicits.scala @@ -251,7 +251,82 @@ given BSONDocumentReader[EncryptedMessage] with { aux.readDocument(doc) } -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1 +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +given BSONWriter[Payload] with { + import Payload.* + def writeTry(obj: Payload): Try[BSONValue] = Try(BSONString(obj.base64url)) +} +given BSONReader[Payload] with { + def readTry(bson: BSONValue): Try[Payload] = bson.asTry[String].map(v => Payload.fromBase64url(v)) +} + +given BSONWriter[SigningAlgorithm] with { + def writeTry(obj: SigningAlgorithm): Try[BSONValue] = Try(BSONString(obj.toString())) +} +given BSONReader[SigningAlgorithm] with { + def readTry(bson: BSONValue): Try[SigningAlgorithm] = bson.asTry[String].map(v => SigningAlgorithm.valueOf(v)) +} + +given BSONDocumentWriter[SignProtectedHeader] = Macros.writer[SignProtectedHeader] +given BSONDocumentReader[SignProtectedHeader] = Macros.reader[SignProtectedHeader] + +given given_BSONWriter_Base64Obj_SignProtectedHeader: BSONWriter[Base64Obj[SignProtectedHeader]] with { + import Base64Obj.* + def writeTry(obj: Base64Obj[SignProtectedHeader]): Try[BSONValue] = { + val protectedHeader: String = (obj.obj, obj.original) match { + case (_, Some(op)) => op.urlBase64 + case (p, None) => obj.base64url + } + Try(BSONString(protectedHeader)) + } +} +given given_BSONReader_Base64Obj_SignProtectedHeader: BSONReader[Base64Obj[SignProtectedHeader]] with { + def readTry(bson: BSONValue): Try[Base64Obj[SignProtectedHeader]] = + bson + .asTry[String] + .flatMap { v => + s""""$v"""".fromJson[Base64Obj[SignProtectedHeader]] match // TODO with a new methods from ScalaDid + case Left(value) => Failure(RuntimeException(value)) + case Right(value) => Try(value) + } +} + +given BSONWriter[SignatureJWM] with { + import SignatureJWM.* + def writeTry(obj: SignatureJWM): Try[BSONValue] = Try(BSONString(obj.value)) +} +given BSONReader[SignatureJWM] with { + def readTry(bson: BSONValue): Try[SignatureJWM] = bson.asTry[String].map(v => SignatureJWM(v)) +} + +given BSONDocumentWriter[JWMHeader] = Macros.writer[JWMHeader] +given BSONDocumentReader[JWMHeader] = Macros.reader[JWMHeader] + +given BSONDocumentWriter[JWMSignatureObj] = Macros.writer[JWMSignatureObj] +given BSONDocumentReader[JWMSignatureObj] = Macros.reader[JWMSignatureObj] + +given BSONDocumentWriter[SignedMessage] = Macros.writer[SignedMessage] +given BSONDocumentReader[SignedMessage] = Macros.reader[SignedMessage] + +given BSONDocumentWriter[SignedMessage | EncryptedMessage] with { + override def writeTry(obj: SignedMessage | EncryptedMessage): Try[BSONDocument] = + obj match { + case msg: EncryptedMessage => given_BSONDocumentWriter_EncryptedMessage.writeTry(msg) + case msg: SignedMessage => given_BSONDocumentWriter_SignedMessage.writeTry(msg) + } +} +given BSONDocumentReader[SignedMessage | EncryptedMessage] with { + override def readDocument(doc: BSONDocument): Try[SignedMessage | EncryptedMessage] = + given_BSONDocumentReader_EncryptedMessage + .readDocument(doc) + .orElse( + given_BSONDocumentReader_SignedMessage + .readDocument(doc) + ) +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! given BSONWriter[MsgID] with { import MsgID.* diff --git a/mediator/src/main/scala/io/iohk/atala/mediator/db/DataModels.scala b/mediator/src/main/scala/io/iohk/atala/mediator/db/DataModels.scala index 7656c274..c4a04116 100644 --- a/mediator/src/main/scala/io/iohk/atala/mediator/db/DataModels.scala +++ b/mediator/src/main/scala/io/iohk/atala/mediator/db/DataModels.scala @@ -5,6 +5,7 @@ import fmgp.did.comm.* import reactivemongo.api.bson.* import java.time.Instant import scala.util.Try +import zio.json._ type HASH = String // messages @@ -43,9 +44,9 @@ object DidAccount { // messages outbox case class SentMessageItem( _id: BSONObjectID = BSONObjectID.generate(), - encrypt: EncryptedMessage, + encrypt: SignedMessage | EncryptedMessage, hash: HASH, - headers: ProtectedHeader, + headers: ast.Json, // ProtectedHeader | SignProtectedHeader, plaintext: PlaintextMessage, transport: Seq[SentMessageItem.TransportInfo], ) @@ -53,22 +54,35 @@ case class SentMessageItem( object SentMessageItem { def apply( - msg: EncryptedMessage, + msg: SignedMessage | EncryptedMessage, plaintext: PlaintextMessage, recipient: Set[TO], distination: Option[String], sendMethod: MessageSendMethod, result: Option[String] ): SentMessageItem = { - new SentMessageItem( - encrypt = msg, - hash = msg.sha1, - headers = msg.`protected`.obj, - plaintext = plaintext, - transport = Seq( - TransportInfo(recipient = recipient, distination = distination, sendMethod = sendMethod, result = result) - ) - ) + msg match + case sMsg: SignedMessage => + new SentMessageItem( + encrypt = msg, + hash = sMsg.sha1, // FIXME + headers = sMsg.signatures.headOption.flatMap(_.`protected`.obj.toJsonAST.toOption).getOrElse(ast.Json.Null), + plaintext = plaintext, + transport = Seq( + TransportInfo(recipient = recipient, distination = distination, sendMethod = sendMethod, result = result) + ) + ) + case eMsg: EncryptedMessage => + new SentMessageItem( + encrypt = msg, + hash = eMsg.sha1, + headers = eMsg.`protected`.obj.toJsonAST.getOrElse(ast.Json.Null), + plaintext = plaintext, + transport = Seq( + TransportInfo(recipient = recipient, distination = distination, sendMethod = sendMethod, result = result) + ) + ) + } given BSONDocumentWriter[SentMessageItem] = {