From 5f48513aa5657e630bcacf61075d0ca0dca535eb Mon Sep 17 00:00:00 2001 From: Goncalo-FradeIOHK Date: Fri, 25 Nov 2022 13:28:06 +0000 Subject: [PATCH] feat(agent): implement issue credential protocol Fixes ATL-2499 --- .swiftlint.yml | 1 + .../Sources/Helpers/Base64+DIDMethodID.swift | 22 +-- Core/Sources/Base64Utils.swift | 20 --- Core/Sources/Helpers/Base64Utils.swift | 44 ++++++ Domain/Sources/Models/MessageAttachment.swift | 4 +- .../PrismPluto.xcdatamodeld/.xccurrentversion | 8 + PrismAgent/Sources/Error.swift | 3 - PrismAgent/Sources/Errors.swift | 4 + .../AttachmentDescriptor+Builder.swift | 18 +++ .../IssueCredential/CredentialPreview.swift | 30 ++++ .../IssueCredential/IssueCredential.swift | 134 +++++++++++++++++ .../IssueCredential/OfferCredential.swift | 139 ++++++++++++++++++ .../IssueCredential/ProposeCredential.swift | 116 +++++++++++++++ .../IssueCredential/RequestCredential.swift | 128 ++++++++++++++++ .../Sources/Protocols/ProtocolTypes.swift | 5 + .../Helper/MessageAttachment+Testing.swift | 8 +- PrismAgent/Tests/IssueCredentialTests.swift | 54 +++++++ PrismAgent/Tests/OfferCredentialTests.swift | 70 +++++++++ .../PrismOnboardingInvitationTests.swift | 29 ++++ PrismAgent/Tests/ProposeCredentialTests.swift | 45 ++++++ PrismAgent/Tests/RequestCredentialTests.swift | 63 ++++++++ 21 files changed, 899 insertions(+), 46 deletions(-) delete mode 100644 Core/Sources/Base64Utils.swift create mode 100644 Core/Sources/Helpers/Base64Utils.swift create mode 100644 Pluto/Sources/Resources/PrismPluto.xcdatamodeld/.xccurrentversion delete mode 100644 PrismAgent/Sources/Error.swift create mode 100644 PrismAgent/Sources/Helpers/AttachmentDescriptor+Builder.swift create mode 100644 PrismAgent/Sources/Protocols/IssueCredential/CredentialPreview.swift create mode 100644 PrismAgent/Sources/Protocols/IssueCredential/IssueCredential.swift create mode 100644 PrismAgent/Sources/Protocols/IssueCredential/OfferCredential.swift create mode 100644 PrismAgent/Sources/Protocols/IssueCredential/ProposeCredential.swift create mode 100644 PrismAgent/Sources/Protocols/IssueCredential/RequestCredential.swift create mode 100644 PrismAgent/Tests/IssueCredentialTests.swift create mode 100644 PrismAgent/Tests/OfferCredentialTests.swift create mode 100644 PrismAgent/Tests/ProposeCredentialTests.swift create mode 100644 PrismAgent/Tests/RequestCredentialTests.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index e39d840a..11b98b3a 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -71,6 +71,7 @@ opt_in_rules: - xct_specific_matcher - yoda_condition excluded: + - .build - build - Pods - protobuf diff --git a/Castor/Sources/Helpers/Base64+DIDMethodID.swift b/Castor/Sources/Helpers/Base64+DIDMethodID.swift index 3bffcce4..ff75619c 100644 --- a/Castor/Sources/Helpers/Base64+DIDMethodID.swift +++ b/Castor/Sources/Helpers/Base64+DIDMethodID.swift @@ -3,28 +3,10 @@ import Foundation extension Base64Utils { func encodeMethodID(data: Data) -> String { - encode(data) - .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: "+", with: "-") - .trimingTrailing(while: CharacterSet(charactersIn: "=")) + data.base64UrlEncodedString() } func decodeMethodID(str: String) -> Data? { - let expectedLength = (str.count + 3) / 4 * 4 - return decode(str - .replacingOccurrences(of: "_", with: "/") - .replacingOccurrences(of: "-", with: "+") - .appending(String(repeating: .init("="), count: expectedLength)) - ) - } -} - -private extension String { - func trimingTrailing(while characterSet: CharacterSet) -> String { - guard - let index = lastIndex(where: { !CharacterSet(charactersIn: String($0)).isSubset(of: characterSet) }) - else { return self } - - return String(self[...index]) + return Data(fromBase64URL: str) } } diff --git a/Core/Sources/Base64Utils.swift b/Core/Sources/Base64Utils.swift deleted file mode 100644 index 4ed9d72e..00000000 --- a/Core/Sources/Base64Utils.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -public struct Base64Utils { - public init() {} - - public func encode(_ data: Data) -> String { - String(data.base64EncodedString() - .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: "+", with: "-") - .dropLast(1)) - } - - public func decode(_ src: String) -> Data? { - let base64Encoded = src - .replacingOccurrences(of: "_", with: "/") - .replacingOccurrences(of: "-", with: "+") - + "=" - return Data(base64Encoded: base64Encoded) - } -} diff --git a/Core/Sources/Helpers/Base64Utils.swift b/Core/Sources/Helpers/Base64Utils.swift new file mode 100644 index 00000000..fc33930c --- /dev/null +++ b/Core/Sources/Helpers/Base64Utils.swift @@ -0,0 +1,44 @@ +import Foundation + +public struct Base64Utils { + public init() {} + + public func encode(_ data: Data) -> String { + String(data.base64EncodedString() + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "+", with: "-") + .trimingTrailing(while: CharacterSet(charactersIn: "="))) + } + + public func decode(_ src: String) -> Data? { + let expectedLength = (src.count + 3) / 4 * 4 + let base64Encoded = src + .replacingOccurrences(of: "_", with: "/") + .replacingOccurrences(of: "-", with: "+") + .appending(String(repeating: .init("="), count: expectedLength)) + return Data(base64Encoded: base64Encoded) + } +} + +public extension Data { + func base64UrlEncodedString() -> String { + Base64Utils().encode(self) + } + + init?(fromBase64URL: String) { + guard let data = Base64Utils().decode(fromBase64URL) else { + return nil + } + self = data + } +} + +private extension String { + func trimingTrailing(while characterSet: CharacterSet) -> String { + guard + let index = lastIndex(where: { !CharacterSet(charactersIn: String($0)).isSubset(of: characterSet) }) + else { return self } + + return String(self[...index]) + } +} diff --git a/Domain/Sources/Models/MessageAttachment.swift b/Domain/Sources/Models/MessageAttachment.swift index f703f39e..7fa55681 100644 --- a/Domain/Sources/Models/MessageAttachment.swift +++ b/Domain/Sources/Models/MessageAttachment.swift @@ -60,7 +60,7 @@ public struct AttachmentJsonData: AttachmentData { public struct AttachmentDescriptor { public let id: String - public let mediaType: [String]? + public let mediaType: String? public let data: AttachmentData public let filename: [String]? public let lastmodTime: Date? @@ -69,7 +69,7 @@ public struct AttachmentDescriptor { public init( id: String, - mediaType: [String]? = nil, + mediaType: String? = nil, data: AttachmentData, filename: [String]? = nil, lastmodTime: Date? = nil, diff --git a/Pluto/Sources/Resources/PrismPluto.xcdatamodeld/.xccurrentversion b/Pluto/Sources/Resources/PrismPluto.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..1939a3e2 --- /dev/null +++ b/Pluto/Sources/Resources/PrismPluto.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + PrismPluto.xcdatamodel + + diff --git a/PrismAgent/Sources/Error.swift b/PrismAgent/Sources/Error.swift deleted file mode 100644 index 72bfca55..00000000 --- a/PrismAgent/Sources/Error.swift +++ /dev/null @@ -1,3 +0,0 @@ -public enum PrismAgentError: Error { - case invalidURLError -} diff --git a/PrismAgent/Sources/Errors.swift b/PrismAgent/Sources/Errors.swift index 2acaafb1..9e797de9 100644 --- a/PrismAgent/Sources/Errors.swift +++ b/PrismAgent/Sources/Errors.swift @@ -10,4 +10,8 @@ public enum PrismAgentError: Error { case unknownPrismOnboardingTypeError case failedToOnboardError case invalidPickupDeliveryMessageError + case invalidOfferCredentialMessageError + case invalidProposedCredentialMessageError + case invalidIssueCredentialMessageError + case invalidRequestCredentialMessageError } diff --git a/PrismAgent/Sources/Helpers/AttachmentDescriptor+Builder.swift b/PrismAgent/Sources/Helpers/AttachmentDescriptor+Builder.swift new file mode 100644 index 00000000..ab213909 --- /dev/null +++ b/PrismAgent/Sources/Helpers/AttachmentDescriptor+Builder.swift @@ -0,0 +1,18 @@ +import Core +import Domain +import Foundation + +extension AttachmentDescriptor { + static func build( + id: String = UUID().uuidString, + payload: T, + mediaType: String? = "application/json" + ) throws -> AttachmentDescriptor { + let encoded = try JSONEncoder().encode(payload).base64UrlEncodedString() + return AttachmentDescriptor( + id: id, + mediaType: mediaType, + data: AttachmentBase64(base64: encoded) + ) + } +} diff --git a/PrismAgent/Sources/Protocols/IssueCredential/CredentialPreview.swift b/PrismAgent/Sources/Protocols/IssueCredential/CredentialPreview.swift new file mode 100644 index 00000000..4c5e279b --- /dev/null +++ b/PrismAgent/Sources/Protocols/IssueCredential/CredentialPreview.swift @@ -0,0 +1,30 @@ +import Domain +import Foundation + +// https://github.com/hyperledger/aries-rfcs/tree/main/features/0453-issue-credential-v2#preview-credential +public struct CredentialPreview: Codable, Equatable { + public struct Attribute: Codable, Equatable { + public let name: String + public let value: String + public let mimeType: String? + } + + public let type: String + public let attributes: [Attribute] + + public init(attributes: [Attribute]) { + self.type = ProtocolTypes.didcommCredentialPreview.rawValue + self.attributes = attributes + } +} + +struct CredentialFormat: Codable, Equatable { +// know Format: +// https://github.com/hyperledger/aries-rfcs/tree/main/features/0453-issue-credential-v2#propose-attachment-registry +// - dif/credential-manifest@v1.0 +// - aries/ld-proof-vc-detail@v1.0 +// - hlindy/cred-filter@v2.0 +// + let attachId: String + let format: String +} diff --git a/PrismAgent/Sources/Protocols/IssueCredential/IssueCredential.swift b/PrismAgent/Sources/Protocols/IssueCredential/IssueCredential.swift new file mode 100644 index 00000000..6a2113f7 --- /dev/null +++ b/PrismAgent/Sources/Protocols/IssueCredential/IssueCredential.swift @@ -0,0 +1,134 @@ +import Domain +import Foundation + +// ALL parameters are DIDCOMMV2 format and naming conventions and follows the protocol +// https://github.com/hyperledger/aries-rfcs/tree/main/features/0453-issue-credential-v2 +public struct IssueCredential { + struct Body: Codable, Equatable { + let goalCode: String? + let comment: String? + let replacementId: String? + let moreAvailable: String? + let formats: [CredentialFormat] + + init( + goalCode: String? = nil, + comment: String? = nil, + replacementId: String? = nil, + moreAvailable: String? = nil, + formats: [CredentialFormat] + ) { + self.goalCode = goalCode + self.comment = comment + self.replacementId = replacementId + self.moreAvailable = moreAvailable + self.formats = formats + } + } + + public let id: String + public let type = ProtocolTypes.didcommIssueCredential.rawValue + let body: Body + let attachments: [AttachmentDescriptor] + public let thid: String? + public let from: DID + // swiftlint:disable identifier_name + public let to: DID + // swiftlint:enable identifier_name + + init( + id: String = UUID().uuidString, + body: Body, + attachments: [AttachmentDescriptor], + thid: String?, + from: DID, + // swiftlint:disable identifier_name + to: DID + // swiftlint:enable identifier_name + ) { + self.id = id + self.body = body + self.attachments = attachments + self.thid = thid + self.from = from + self.to = to + } + + public init(fromMessage: Message) throws { + guard + fromMessage.piuri == ProtocolTypes.didcommIssueCredential.rawValue, + let fromDID = fromMessage.from, + let toDID = fromMessage.to + else { throw PrismAgentError.invalidIssueCredentialMessageError } + + let body = try JSONDecoder().decode(Body.self, from: fromMessage.body) + self.init( + id: fromMessage.id, + body: body, + attachments: fromMessage.attachments, + thid: fromMessage.thid, + from: fromDID, + to: toDID + ) + } + + public func makeMessage() throws -> Message { + .init( + id: id, + piuri: type, + from: from, + to: to, + body: try JSONEncoder().encode(body), + attachments: attachments, + thid: thid + ) + } + + public static func makeIssueFromRequestCredential(msg: Message) throws -> IssueCredential { + let request = try RequestCredential(fromMessage: msg) + + return IssueCredential( + body: Body( + goalCode: request.body.goalCode, + comment: request.body.comment, + formats: request.body.formats + ), + attachments: request.attachments, + thid: msg.id, + from: request.to, + to: request.from) + } + + static func build( + fromDID: DID, + toDID: DID, + thid: String?, + credentialPreview: CredentialPreview, + credentials: [String: T] = [:] + ) throws -> IssueCredential { + let aux = try credentials.map { key, value in + let attachment = try AttachmentDescriptor.build(payload: value) + let format = CredentialFormat(attachId: attachment.id, format: key) + return (format, attachment) + } + return IssueCredential( + body: Body( + formats: aux.map { $0.0 } + ), + attachments: aux.map { $0.1 }, + thid: thid, + from: fromDID, + to: toDID + ) + } +} + +extension IssueCredential: Equatable { + public static func == (lhs: IssueCredential, rhs: IssueCredential) -> Bool { + lhs.id == rhs.id && + lhs.type == rhs.type && + lhs.from == rhs.from && + lhs.to == rhs.to && + lhs.body == rhs.body + } +} diff --git a/PrismAgent/Sources/Protocols/IssueCredential/OfferCredential.swift b/PrismAgent/Sources/Protocols/IssueCredential/OfferCredential.swift new file mode 100644 index 00000000..ebd3c56e --- /dev/null +++ b/PrismAgent/Sources/Protocols/IssueCredential/OfferCredential.swift @@ -0,0 +1,139 @@ +import Domain +import Foundation + +// ALL parameters are DIDCOMMV2 format and naming conventions and follows the protocol +// https://github.com/hyperledger/aries-rfcs/tree/main/features/0453-issue-credential-v2 +public struct OfferCredential { + struct Body: Codable, Equatable { + let goalCode: String? + let comment: String? + let replacementId: String? + let multipleAvailable: String? + let credentialPreview: CredentialPreview + let formats: [CredentialFormat] + + init( + goalCode: String? = nil, + comment: String? = nil, + replacementId: String? = nil, + multipleAvailable: String? = nil, + credentialPreview: CredentialPreview, + formats: [CredentialFormat] + ) { + self.goalCode = goalCode + self.comment = comment + self.replacementId = replacementId + self.multipleAvailable = multipleAvailable + self.credentialPreview = credentialPreview + self.formats = formats + } + } + + public let id: String + public let type = ProtocolTypes.didcommOfferCredential.rawValue + let body: Body + let attachments: [AttachmentDescriptor] + public let thid: String? + public let from: DID + // swiftlint:disable identifier_name + public let to: DID + // swiftlint:enable identifier_name + + init( + id: String = UUID().uuidString, + body: Body, + attachments: [AttachmentDescriptor], + thid: String?, + from: DID, + // swiftlint:disable identifier_name + to: DID + // swiftlint:enable identifier_name + ) { + self.id = id + self.body = body + self.attachments = attachments + self.thid = thid + self.from = from + self.to = to + } + + public init(fromMessage: Message) throws { + guard + fromMessage.piuri == ProtocolTypes.didcommOfferCredential.rawValue, + let fromDID = fromMessage.from, + let toDID = fromMessage.to + else { throw PrismAgentError.invalidOfferCredentialMessageError } + + let body = try JSONDecoder().decode(Body.self, from: fromMessage.body) + self.init( + id: fromMessage.id, + body: body, + attachments: fromMessage.attachments, + thid: fromMessage.thid, + from: fromDID, + to: toDID + ) + } + + public func makeMessage() throws -> Message { + .init( + id: id, + piuri: type, + from: from, + to: to, + body: try JSONEncoder().encode(body), + attachments: attachments, + thid: thid + ) + } + + public static func makeOfferToProposedCredential(msg: Message) throws -> OfferCredential { + let proposed = try ProposeCredential(fromMessage: msg) + + return OfferCredential( + body: Body( + goalCode: proposed.body.goalCode, + comment: proposed.body.comment, + credentialPreview: proposed.body.credentialPreview, + formats: proposed.body.formats + ), + attachments: proposed.attachments, + thid: msg.id, + from: proposed.to, + to: proposed.from) + } + + static func build( + fromDID: DID, + toDID: DID, + thid: String?, + credentialPreview: CredentialPreview, + credentials: [String: T] = [:] + ) throws -> OfferCredential { + let aux = try credentials.map { key, value in + let attachment = try AttachmentDescriptor.build(payload: value) + let format = CredentialFormat(attachId: attachment.id, format: key) + return (format, attachment) + } + return OfferCredential( + body: Body( + credentialPreview: credentialPreview, + formats: aux.map { $0.0 } + ), + attachments: aux.map { $0.1 }, + thid: thid, + from: fromDID, + to: toDID + ) + } +} + +extension OfferCredential: Equatable { + public static func == (lhs: OfferCredential, rhs: OfferCredential) -> Bool { + lhs.id == rhs.id && + lhs.type == rhs.type && + lhs.from == rhs.from && + lhs.to == rhs.to && + lhs.body == rhs.body + } +} diff --git a/PrismAgent/Sources/Protocols/IssueCredential/ProposeCredential.swift b/PrismAgent/Sources/Protocols/IssueCredential/ProposeCredential.swift new file mode 100644 index 00000000..8c299654 --- /dev/null +++ b/PrismAgent/Sources/Protocols/IssueCredential/ProposeCredential.swift @@ -0,0 +1,116 @@ +import Domain +import Foundation + +// ALL parameterS are DIDCOMMV2 format and naming conventions and follows the protocol +// https://github.com/hyperledger/aries-rfcs/tree/main/features/0453-issue-credential-v2 +public struct ProposeCredential { + struct Body: Codable, Equatable { + let goalCode: String? + let comment: String? + let credentialPreview: CredentialPreview + let formats: [CredentialFormat] + + init( + goalCode: String? = nil, + comment: String? = nil, + credentialPreview: CredentialPreview, + formats: [CredentialFormat] + ) { + self.goalCode = goalCode + self.comment = comment + self.credentialPreview = credentialPreview + self.formats = formats + } + } + + public let id: String + public let type = ProtocolTypes.didcommProposeCredential.rawValue + let body: Body + let attachments: [AttachmentDescriptor] + public let thid: String? + public let from: DID + // swiftlint:disable identifier_name + public let to: DID + // swiftlint:enable identifier_name + + init( + id: String = UUID().uuidString, + body: Body, + attachments: [AttachmentDescriptor], + thid: String?, + from: DID, + // swiftlint:disable identifier_name + to: DID + // swiftlint:enable identifier_name + ) { + self.id = id + self.body = body + self.attachments = attachments + self.thid = thid + self.from = from + self.to = to + } + + public init(fromMessage: Message) throws { + guard + fromMessage.piuri == ProtocolTypes.didcommProposeCredential.rawValue, + let fromDID = fromMessage.from, + let toDID = fromMessage.to + else { throw PrismAgentError.invalidProposedCredentialMessageError } + let body = try JSONDecoder().decode(Body.self, from: fromMessage.body) + self.init( + id: fromMessage.id, + body: body, + attachments: fromMessage.attachments, + thid: fromMessage.thid, + from: fromDID, + to: toDID + ) + } + + public func makeMessage() throws -> Message { + .init( + id: id, + piuri: type, + from: from, + to: to, + body: try JSONEncoder().encode(body), + attachments: attachments, + thid: thid + ) + } + + static func build( + fromDID: DID, + toDID: DID, + thid: String?, + credentialPreview: CredentialPreview, + credentials: [String: T] = [:] + ) throws -> ProposeCredential { + let aux = try credentials.map { key, value in + let attachment = try AttachmentDescriptor.build(payload: value) + let format = CredentialFormat(attachId: attachment.id, format: key) + return (format, attachment) + } + return ProposeCredential( + body: Body( + credentialPreview: credentialPreview, + formats: aux.map { $0.0 } + ), + attachments: aux.map { $0.1 }, + thid: thid, + from: fromDID, + to: toDID + ) + } +} + +extension ProposeCredential: Equatable { + public static func == (lhs: ProposeCredential, rhs: ProposeCredential) -> Bool { + lhs.id == rhs.id && + lhs.type == rhs.type && + lhs.from == rhs.from && + lhs.to == rhs.to && + lhs.body == rhs.body + } +} diff --git a/PrismAgent/Sources/Protocols/IssueCredential/RequestCredential.swift b/PrismAgent/Sources/Protocols/IssueCredential/RequestCredential.swift new file mode 100644 index 00000000..1f6a411f --- /dev/null +++ b/PrismAgent/Sources/Protocols/IssueCredential/RequestCredential.swift @@ -0,0 +1,128 @@ +import Domain +import Foundation + +// ALL parameters are DIDCOMMV2 format and naming conventions and follows the protocol +// https://github.com/hyperledger/aries-rfcs/tree/main/features/0453-issue-credential-v2 +public struct RequestCredential { + struct Body: Codable, Equatable { + let goalCode: String? + let comment: String? + let formats: [CredentialFormat] + + init( + goalCode: String? = nil, + comment: String? = nil, + formats: [CredentialFormat] + ) { + self.goalCode = goalCode + self.comment = comment + self.formats = formats + } + } + + public let id: String + public let type = ProtocolTypes.didcommRequestCredential.rawValue + let body: Body + let attachments: [AttachmentDescriptor] + public let thid: String? + public let from: DID + // swiftlint:disable identifier_name + public let to: DID + // swiftlint:enable identifier_name + + init( + id: String = UUID().uuidString, + body: Body, + attachments: [AttachmentDescriptor], + thid: String?, + from: DID, + // swiftlint:disable identifier_name + to: DID + // swiftlint:enable identifier_name + ) { + self.id = id + self.body = body + self.attachments = attachments + self.thid = thid + self.from = from + self.to = to + } + + public init(fromMessage: Message) throws { + guard + fromMessage.piuri == ProtocolTypes.didcommRequestCredential.rawValue, + let fromDID = fromMessage.from, + let toDID = fromMessage.to + else { throw PrismAgentError.invalidRequestCredentialMessageError } + + let body = try JSONDecoder().decode(Body.self, from: fromMessage.body) + self.init( + id: fromMessage.id, + body: body, + attachments: fromMessage.attachments, + thid: fromMessage.thid, + from: fromDID, + to: toDID + ) + } + + public func makeMessage() throws -> Message { + .init( + id: id, + piuri: type, + from: from, + to: to, + body: try JSONEncoder().encode(body), + attachments: attachments, + thid: thid + ) + } + + public static func makeFromOffer(message: Message) throws -> RequestCredential { + let offer = try OfferCredential(fromMessage: message) + return RequestCredential( + body: .init( + goalCode: offer.body.goalCode, + comment: offer.body.comment, + formats: offer.body.formats + ), + attachments: offer.attachments, + thid: message.id, + from: offer.to, + to: offer.from + ) + } + + static func build( + fromDID: DID, + toDID: DID, + thid: String?, + credentialPreview: CredentialPreview, + credentials: [String: T] = [:] + ) throws -> RequestCredential { + let aux = try credentials.map { key, value in + let attachment = try AttachmentDescriptor.build(payload: value) + let format = CredentialFormat(attachId: attachment.id, format: key) + return (format, attachment) + } + return RequestCredential( + body: Body( + formats: aux.map { $0.0 } + ), + attachments: aux.map { $0.1 }, + thid: thid, + from: fromDID, + to: toDID + ) + } +} + +extension RequestCredential: Equatable { + public static func == (lhs: RequestCredential, rhs: RequestCredential) -> Bool { + lhs.id == rhs.id && + lhs.type == rhs.type && + lhs.from == rhs.from && + lhs.to == rhs.to && + lhs.body == rhs.body + } +} diff --git a/PrismAgent/Sources/Protocols/ProtocolTypes.swift b/PrismAgent/Sources/Protocols/ProtocolTypes.swift index e69447fd..9ed69398 100644 --- a/PrismAgent/Sources/Protocols/ProtocolTypes.swift +++ b/PrismAgent/Sources/Protocols/ProtocolTypes.swift @@ -1,6 +1,11 @@ import Foundation enum ProtocolTypes: String { + case didcommCredentialPreview = "https://didcomm.org/issue-credential/2.0/credential-preview" + case didcommIssueCredential = "https://didcomm.org/issue-credential/2.0/issue-credential" + case didcommOfferCredential = "https://didcomm.org/issue-credential/2.0/offer-credential" + case didcommProposeCredential = "https://didcomm.org/issue-credential/2.0/propose-credential" + case didcommRequestCredential = "https://didcomm.org/issue-credential/2.0/request-credential" case didcommconnectionRequest = "https://atalaprism.io/mercury/connections/1.0/request" case didcommconnectionResponse = "https://atalaprism.io/mercury/connections/1.0/response" case didcomminvitation = "https://didcomm.org/out-of-band/2.0/invitation" diff --git a/PrismAgent/Tests/Helper/MessageAttachment+Testing.swift b/PrismAgent/Tests/Helper/MessageAttachment+Testing.swift index 1f41dbc7..cb21686d 100644 --- a/PrismAgent/Tests/Helper/MessageAttachment+Testing.swift +++ b/PrismAgent/Tests/Helper/MessageAttachment+Testing.swift @@ -39,7 +39,7 @@ extension AttachmentDescriptor: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let id = try container.decode(String.self, forKey: .id) - let mediaType = try? container.decode([String].self, forKey: .mediaType) + let mediaType = try? container.decode(String.self, forKey: .mediaType) let filename = try? container.decode([String].self, forKey: .filename) let lastmodTime = try? container.decode(Date.self, forKey: .lastmodTime) let byteCount = try? container.decode(Int.self, forKey: .byteCount) @@ -71,3 +71,9 @@ extension AttachmentDescriptor: Codable { ) } } + +extension AttachmentDescriptor: Equatable { + public static func == (lhs: Domain.AttachmentDescriptor, rhs: Domain.AttachmentDescriptor) -> Bool { + lhs.id == rhs.id && lhs.mediaType == rhs.mediaType + } +} diff --git a/PrismAgent/Tests/IssueCredentialTests.swift b/PrismAgent/Tests/IssueCredentialTests.swift new file mode 100644 index 00000000..7b469553 --- /dev/null +++ b/PrismAgent/Tests/IssueCredentialTests.swift @@ -0,0 +1,54 @@ +import Domain +@testable import PrismAgent +import XCTest + +final class IssueCredentialTests: XCTestCase { + func testWhenValidIssueMessageThenInitIssueCredential() throws { + let fromDID = DID(index: 0) + let toDID = DID(index: 1) + let validIssueCredential = IssueCredential( + body: .init(formats: [.init(attachId: "test1", format: "test")]), + attachments: [], + thid: "1", + from: fromDID, + to: toDID + ) + let issueMessage = try validIssueCredential.makeMessage() + + let testIssueCredential = try IssueCredential(fromMessage: issueMessage) + XCTAssertEqual(validIssueCredential, testIssueCredential) + } + + func testWhenInvalidIssueMessageThenInitIssueCredential() throws { + let invalidIssueCredential = Message( + piuri: "InvalidType", + from: nil, + to: nil, + body: Data() + ) + + XCTAssertThrowsError(try IssueCredential(fromMessage: invalidIssueCredential)) + } + + func testWhenValidRequestMessageThenInitIssueCredential() throws { + let fromDID = DID(index: 0) + let toDID = DID(index: 1) + let validRequestCredential = RequestCredential( + body: .init(formats: [.init(attachId: "test1", format: "test")]), + attachments: [], + thid: "1", + from: fromDID, + to: toDID + ) + let requestMessage = try validRequestCredential.makeMessage() + + let testIssueCredential = try IssueCredential.makeIssueFromRequestCredential(msg: requestMessage) + XCTAssertEqual(validRequestCredential.from, testIssueCredential.to) + XCTAssertEqual(validRequestCredential.to, testIssueCredential.from) + XCTAssertEqual(validRequestCredential.attachments, testIssueCredential.attachments) + XCTAssertEqual(validRequestCredential.id, testIssueCredential.thid) + XCTAssertEqual(validRequestCredential.body.goalCode, validRequestCredential.body.goalCode) + XCTAssertEqual(validRequestCredential.body.comment, validRequestCredential.body.comment) + XCTAssertEqual(validRequestCredential.body.formats, validRequestCredential.body.formats) + } +} diff --git a/PrismAgent/Tests/OfferCredentialTests.swift b/PrismAgent/Tests/OfferCredentialTests.swift new file mode 100644 index 00000000..4a08175d --- /dev/null +++ b/PrismAgent/Tests/OfferCredentialTests.swift @@ -0,0 +1,70 @@ +import Domain +@testable import PrismAgent +import XCTest + +final class OfferCredentialTests: XCTestCase { + func testWhenValidOfferMessageThenInitOfferCredential() throws { + let fromDID = DID(index: 0) + let toDID = DID(index: 1) + let validOfferCredential = OfferCredential( + body: .init( + credentialPreview: .init( + attributes: [ + .init( + name: "test1", + value: "test", + mimeType: "test.x") + ]), + formats: [ + .init( + attachId: "test1", + format: "test") + ] + ), + attachments: [], + thid: "1", + from: fromDID, + to: toDID + ) + let offerMessage = try validOfferCredential.makeMessage() + + let testOfferCredential = try OfferCredential(fromMessage: offerMessage) + XCTAssertEqual(validOfferCredential, testOfferCredential) + } + + func testWhenInvalidOfferMessageThenInitOfferCredential() throws { + let invalidOfferCredential = Message( + piuri: "InvalidType", + from: nil, + to: nil, + body: Data() + ) + + XCTAssertThrowsError(try OfferCredential(fromMessage: invalidOfferCredential)) + } + + func testWhenValidProposeMessageThenInitOfferCredential() throws { + let fromDID = DID(index: 0) + let toDID = DID(index: 1) + let validProposeCredential = ProposeCredential( + body: .init( + credentialPreview: .init(attributes: []), + formats: [.init(attachId: "test1", format: "test")] + ), + attachments: [], + thid: "1", + from: fromDID, + to: toDID + ) + let proposeMessage = try validProposeCredential.makeMessage() + + let testOfferCredential = try OfferCredential.makeOfferToProposedCredential(msg: proposeMessage) + XCTAssertEqual(validProposeCredential.from, testOfferCredential.to) + XCTAssertEqual(validProposeCredential.to, testOfferCredential.from) + XCTAssertEqual(validProposeCredential.attachments, testOfferCredential.attachments) + XCTAssertEqual(validProposeCredential.id, testOfferCredential.thid) + XCTAssertEqual(validProposeCredential.body.goalCode, testOfferCredential.body.goalCode) + XCTAssertEqual(validProposeCredential.body.comment, testOfferCredential.body.comment) + XCTAssertEqual(validProposeCredential.body.formats, testOfferCredential.body.formats) + } +} diff --git a/PrismAgent/Tests/PrismOnboardingInvitationTests.swift b/PrismAgent/Tests/PrismOnboardingInvitationTests.swift index f2a0ac62..0fa465a1 100644 --- a/PrismAgent/Tests/PrismOnboardingInvitationTests.swift +++ b/PrismAgent/Tests/PrismOnboardingInvitationTests.swift @@ -1,4 +1,5 @@ @testable import PrismAgent +import Builders import XCTest final class PrismOnboardingInvitationTests: XCTestCase { @@ -29,4 +30,32 @@ final class PrismOnboardingInvitationTests: XCTestCase { XCTAssertThrowsError(try PrismOnboardingInvitation(jsonString: jsonString)) } + + func testLocal() async throws { + let example = PrismOnboardingInvitation.Body( + type: "https://atalaprism.io/did-request", + onboardEndpoint: "http://localhost:8070/onboard/fc16fa95-43e2-4313-a201-7bbca8d5641d", + from: "Goncalo Frade" + ) + + let jsonString = try String(data: JSONEncoder().encode(example), encoding: .utf8)! + + let apollo = ApolloBuilder().build() + let castor = CastorBuilder(apollo: apollo).build() + let agent = PrismAgent( + apollo: apollo, + castor: castor, + pluto: PlutoBuilder(setup: .init(coreDataSetup: .init( + modelPath: .storeName("PrismPluto"), + storeType: .memory + ))).build(), + mercury: MercuryBuilder(castor: castor).build() + ) + let invitation = try await agent.parsePrismInvitation(str: jsonString) + do { + try await agent.acceptPrismInvitation(invitation: invitation) + } catch { + print(error) + } + } } diff --git a/PrismAgent/Tests/ProposeCredentialTests.swift b/PrismAgent/Tests/ProposeCredentialTests.swift new file mode 100644 index 00000000..76e8acf9 --- /dev/null +++ b/PrismAgent/Tests/ProposeCredentialTests.swift @@ -0,0 +1,45 @@ +import Domain +@testable import PrismAgent +import XCTest + +final class ProposeCredentialTests: XCTestCase { + func testWhenValidProposeMessageThenInitProposeCredential() throws { + let fromDID = DID(index: 0) + let toDID = DID(index: 1) + let validProposeCredential = ProposeCredential( + body: .init( + credentialPreview: .init( + attributes: [ + .init( + name: "test1", + value: "test", + mimeType: "test.x") + ]), + formats: [ + .init( + attachId: "test1", + format: "test") + ] + ), + attachments: [], + thid: "1", + from: fromDID, + to: toDID + ) + let proposeMessage = try validProposeCredential.makeMessage() + + let testProposeCredential = try ProposeCredential(fromMessage: proposeMessage) + XCTAssertEqual(validProposeCredential, testProposeCredential) + } + + func testWhenInvalidProposeMessageThenInitProposeCredential() throws { + let invalidProposeCredential = Message( + piuri: "InvalidType", + from: nil, + to: nil, + body: Data() + ) + + XCTAssertThrowsError(try ProposeCredential(fromMessage: invalidProposeCredential)) + } +} diff --git a/PrismAgent/Tests/RequestCredentialTests.swift b/PrismAgent/Tests/RequestCredentialTests.swift new file mode 100644 index 00000000..00d205c5 --- /dev/null +++ b/PrismAgent/Tests/RequestCredentialTests.swift @@ -0,0 +1,63 @@ +import Domain +@testable import PrismAgent +import XCTest + +final class RequestCredentialTests: XCTestCase { + func testWhenValidRequestMessageThenInitRequestCredential() throws { + let fromDID = DID(index: 0) + let toDID = DID(index: 1) + let validRequestCredential = RequestCredential( + body: .init( + formats: [ + .init( + attachId: "test1", + format: "test") + ] + ), + attachments: [], + thid: "1", + from: fromDID, + to: toDID + ) + let requestMessage = try validRequestCredential.makeMessage() + + let testRequestCredential = try RequestCredential(fromMessage: requestMessage) + XCTAssertEqual(validRequestCredential, testRequestCredential) + } + + func testWhenInvalidRequestMessageThenInitRequestCredential() throws { + let invalidRequestCredential = Message( + piuri: "InvalidType", + from: nil, + to: nil, + body: Data() + ) + + XCTAssertThrowsError(try RequestCredential(fromMessage: invalidRequestCredential)) + } + + func testWhenValidOfferMessageThenInitRequestCredential() throws { + let fromDID = DID(index: 0) + let toDID = DID(index: 1) + let validOfferCredential = OfferCredential( + body: .init( + credentialPreview: .init(attributes: []), + formats: [.init(attachId: "test1", format: "test")] + ), + attachments: [], + thid: "1", + from: fromDID, + to: toDID + ) + let offerMessage = try validOfferCredential.makeMessage() + + let testRequestCredential = try RequestCredential.makeFromOffer(message: offerMessage) + XCTAssertEqual(validOfferCredential.from, testRequestCredential.to) + XCTAssertEqual(validOfferCredential.to, testRequestCredential.from) + XCTAssertEqual(validOfferCredential.attachments, testRequestCredential.attachments) + XCTAssertEqual(validOfferCredential.id, testRequestCredential.thid) + XCTAssertEqual(validOfferCredential.body.goalCode, testRequestCredential.body.goalCode) + XCTAssertEqual(validOfferCredential.body.comment, testRequestCredential.body.comment) + XCTAssertEqual(validOfferCredential.body.formats, testRequestCredential.body.formats) + } +}