Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Core/Sources/Helpers/OneOrMany.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
/// - Parameters:
/// - encoder: The encoder to write data to.
/// - Throws: An error if encoding fails.
public enum OneOrMany<T: RawCodable>: RawCodable {
public enum OneOrMany<T: Codable>: RawCodable {
case one(T)
case many([T])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public struct AuthenticateChallenged {
masterPublicKey: publicKey,
services: [.init(
id: "deeplink",
type: ["deeplink"],
serviceEndpoint: [.init(uri: scheme.scheme + "://" + scheme.host)]
type: .one("deeplink"),
serviceEndpoint: .one(.init(uri: scheme.scheme + "://" + scheme.host))
)]
)
}
Expand Down Expand Up @@ -65,8 +65,8 @@ public struct AuthenticateChallenged {
let didDocument = try await castor.resolveDID(did: did)

guard let service = didDocument.services
.first(where: { $0.type.contains(where: { $0 == "deeplink" }) })?
.serviceEndpoint.first?.uri
.first(where: { $0.type.array.contains(where: { $0 == "deeplink" }) })?
.serviceEndpoint.array.first?.uri
else { throw AuthenticateError.cannotFindDeepLinkServiceError }

guard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public struct AuthenticateChallenger {
masterPublicKey: publicKey,
services: [.init(
id: "deeplink",
type: ["deeplink"],
serviceEndpoint: [.init(uri: scheme.scheme + "://" + scheme.host)]
type: .one("deeplink"),
serviceEndpoint: .one(.init(uri: scheme.scheme + "://" + scheme.host))
)]
)
}
Expand Down Expand Up @@ -54,8 +54,8 @@ public struct AuthenticateChallenger {
let didDocument = try await castor.resolveDID(did: did)

guard let service = didDocument.services
.first(where: { $0.type.contains(where: { $0 == "deeplink" }) })?
.serviceEndpoint.first?.uri
.first(where: { $0.type.array.contains(where: { $0 == "deeplink" }) })?
.serviceEndpoint.array.first?.uri
else { throw AuthenticateError.cannotFindDeepLinkServiceError }

guard
Expand Down
2 changes: 1 addition & 1 deletion EdgeAgentSDK/Builders/Sources/CastorBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public struct CastorBuilder {
self.apollo = apollo
}

public func build() -> Castor {
public func build(resolvers: [DIDResolverDomain] = []) -> Castor {
CastorImpl(apollo: apollo)
}
}
5 changes: 4 additions & 1 deletion EdgeAgentSDK/Castor/Sources/CastorImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ public struct CastorImpl {
self.logger = SDKLogger(category: LogComponent.castor)
self.apollo = apollo
self.resolvers = resolvers + [
LongFormPrismDIDResolver(apollo: apollo, logger: logger),
CompactPrismDIDResolver(
longFormResolver: LongFormPrismDIDResolver(apollo: apollo, logger: logger),
shortFormResolver: EndpointShortFormPrismDIDRemoteResolver.githubResolver()
),
PeerDIDResolver()
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ struct CreatePeerDIDOperation {
authenticationKeys: authenticationKeys,
agreementKeys: agreementKeys,
services: services.flatMap { service in
service.serviceEndpoint.map {
service.serviceEndpoint.array.map {
AnyCodable(dictionaryLiteral:
("id", service.id),
("type", service.type.first ?? ""),
("id", service.id ?? ""),
("type", service.type.array.first ?? ""),
("serviceEndpoint", [
"uri" : $0.uri,
"accept" : $0.accept,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ struct CreatePrismDIDOperation {
didData.publicKeys = try publicKeys.map { try $0.toProto() }
didData.services = services.map {
var service = Io_Iohk_Atala_Prism_Protos_Service()
service.id = $0.id
service.type = $0.type.first ?? ""
service.serviceEndpoint = $0.serviceEndpoint.map { $0.uri }
service.id = $0.id ?? ""
service.type = $0.type.array.first ?? ""
service.serviceEndpoint = $0.serviceEndpoint.array.map { $0.uri }
return service
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Domain

public struct CompactPrismDIDResolver: DIDResolverDomain {
public let method = "prism"
let longFormResolver: DIDResolverDomain
let shortFormResolver: DIDResolverDomain

init(longFormResolver: DIDResolverDomain, shortFormResolver: DIDResolverDomain) {
self.longFormResolver = longFormResolver
self.shortFormResolver = shortFormResolver
}

public func resolve(did: Domain.DID) async throws -> Domain.DIDDocument {
do {
return try await shortFormResolver.resolve(did: did)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this try to resolve the short form. Independently if the given DID is a long or a short form, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is dependent on the dependency injection, but our short form resolver is here EdgeAgentSDK/Castor/Sources/Resolvers/EndpointShortFormPrismDIDRemoteResolver.swift, and is as you say it will work for short and long form independently.

} catch {
guard did.isLongFormDID() else { throw error }
return try await longFormResolver.resolve(did: did)
}
}
}

private extension DID {
func isLongFormDID() -> Bool {
return string.split(separator: ":").count > 3
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Domain
import Foundation

public protocol DIDDocumentUrlBuilder {
func didDocumentEndpoint(did: DID) throws -> String
}

public protocol DIDDocumentSerializer {
func serialize(data: Data) throws -> DIDDocument
}

public struct EndpointShortFormPrismDIDRemoteResolver: DIDResolverDomain {
public let method = "prism"
let urlBuilder: DIDDocumentUrlBuilder
let downloader: Downloader
let serializer: DIDDocumentSerializer

init(urlBuilder: DIDDocumentUrlBuilder, downloader: Downloader, serializer: DIDDocumentSerializer) {
self.urlBuilder = urlBuilder
self.downloader = downloader
self.serializer = serializer
}

public func resolve(did: DID) async throws -> DIDDocument {
let did = try did.removingPrismLongForm()
let data = try await downloader.downloadFromEndpoint(urlOrDID: urlBuilder.didDocumentEndpoint(did: did))
return try serializer.serialize(data: data)
}
}

private extension DID {
func removingPrismLongForm() throws -> DID {
let separated = string
.split(separator: ":")
let shortForm = separated.prefix(3).joined(separator: ":")
return try DID(string: shortForm)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Domain
import Foundation

public extension EndpointShortFormPrismDIDRemoteResolver {
static func githubResolver() -> Self {
.init(
urlBuilder: GitHubResolverURLBuilder(),
downloader: DownloadDataWithResolver(),
serializer: GitHubResolverDIDDocumentSerializer()
)
}
}

public struct GitHubResolverURLBuilder: DIDDocumentUrlBuilder {
let baseURL = "https://raw.githubusercontent.com/FabioPinheiro/prism-vdr/refs/heads/main/mainnet/diddoc/"

public func didDocumentEndpoint(did: DID) throws -> String {
return baseURL.appending(did.string)
}
}

public struct GitHubResolverDIDDocumentSerializer: DIDDocumentSerializer {
let coder: JSONDecoder

init(coder: JSONDecoder = .normalized) {
self.coder = coder
}

public func serialize(data: Data) throws -> DIDDocument {
return try coder.decode(DIDDocument.self, from: data)
}
}

fileprivate struct DownloadDataWithResolver: Downloader {

func downloadFromEndpoint(urlOrDID: String) async throws -> Data {
let url: URL

if let validUrl = URL(string: urlOrDID.replacingOccurrences(of: "host.docker.internal", with: "localhost")) {
url = validUrl
} else {
throw CommonError.invalidURLError(url: urlOrDID)
}

let (data, urlResponse) = try await URLSession.shared.data(from: url)

guard
let code = (urlResponse as? HTTPURLResponse)?.statusCode,
200...299 ~= code
else {
throw CommonError.httpError(
code: (urlResponse as? HTTPURLResponse)?.statusCode ?? 500,
message: String(data: data, encoding: .utf8) ?? ""
)
}

return data
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ struct LongFormPrismDIDResolver: DIDResolverDomain {
let services = operation.createDid.didData.services.map {
DIDDocument.Service(
id: $0.id,
type: [$0.type],
serviceEndpoint: $0.serviceEndpoint.map { .init(uri: $0) }
type: .one($0.type),
serviceEndpoint: .many($0.serviceEndpoint.map { .init(uri: $0) })
)
}
let groupByPurpose = Dictionary(
Expand Down
18 changes: 9 additions & 9 deletions EdgeAgentSDK/Castor/Sources/Resolvers/PeerDIDResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ extension DIDCore.DIDDocument {
let keyAgreementIds = from.keyAgreement.map(\.id.string)

let services = from.services.flatMap { service in
service.serviceEndpoint.map {
service.serviceEndpoint.array.map {
DIDCore.DIDDocument.Service(
id: service.id,
type: service.type.first ?? "",
id: service.id ?? "",
type: service.type.array.first ?? "",
serviceEndpoint: AnyCodable(
dictionaryLiteral:
("uri", $0.uri),
Expand Down Expand Up @@ -108,26 +108,26 @@ extension DIDCore.DIDDocument {
}
return Domain.DIDDocument.Service(
id: service.id,
type: [service.type],
serviceEndpoint: [
type: .one(service.type),
serviceEndpoint: .one(
.init(
uri: uri,
accept: endpoint["accept"] as? [String] ?? [],
routingKeys: endpoint["routing_keys"] as? [String] ?? []
)
]
)
)
case let endpoint as String:
return Domain.DIDDocument.Service(
id: service.id,
type: [service.type],
serviceEndpoint: [
type: .one(service.type),
serviceEndpoint: .one(
.init(
uri: endpoint,
accept: ($0.value as? [String: Any])?["accept"] as? [String] ?? [],
routingKeys: ($0.value as? [String: Any])?["routing_keys"] as? [String] ?? []
)
]
)
)
default:
throw CastorError.notPossibleToResolveDID(did: service.id, reason: "Invalid service")
Expand Down
46 changes: 46 additions & 0 deletions EdgeAgentSDK/Castor/Tests/CompactPrismDIDResolverTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import XCTest
import Domain
@testable import Castor

final class CompactPrismDIDResolverTests: XCTestCase {
func testUsesShortFormResolverWhenItSucceeds() async throws {
let did = DID(method: "prism", methodId: "abc")
let expected = DIDDocument(id: did, coreProperties: [])

let short = DIDResolverMock(result: .success(expected))
let long = DIDResolverMock(result: .failure(MockError.forced))
let sut = CompactPrismDIDResolver(longFormResolver: long, shortFormResolver: short)
let doc = try await sut.resolve(did: did)

XCTAssertEqual(doc.id, did)
XCTAssertEqual(short.calls, [did])
XCTAssertTrue(long.calls.isEmpty)
}

func testFallsBackToLongFormWhenShortFailsAndDIDIsLongForm() async throws {
let longForm = DID(method: "prism", methodId: "abc:def")
let expected = DIDDocument(id: longForm, coreProperties: [])

let short = DIDResolverMock(result: .failure(MockError.forced))
let long = DIDResolverMock(result: .success(expected))
let sut = CompactPrismDIDResolver(longFormResolver: long, shortFormResolver: short)
let doc = try await sut.resolve(did: longForm)

XCTAssertEqual(doc.id, longForm)
XCTAssertEqual(short.calls, [longForm])
XCTAssertEqual(long.calls, [longForm])
}

func testRethrowsWhenShortFailsAndDIDIsShortForm() async {
let shortForm = DID(method: "prism", methodId: "abc")
let short = DIDResolverMock(result: .failure(MockError.forced))
let long = DIDResolverMock(result: .success(DIDDocument(id: shortForm, coreProperties: [])))
let sut = CompactPrismDIDResolver(longFormResolver: long, shortFormResolver: short)
do {
_ = try await sut.resolve(did: shortForm)
XCTFail("Expected error")
} catch {
XCTAssertTrue(long.calls.isEmpty)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Domain
@testable import Castor
import XCTest

final class EndpointShortFormPrismDIDRemoteResolverSmokeTests: XCTestCase {
func testMethodIsPrism() {
let sut = EndpointShortFormPrismDIDRemoteResolver(
urlBuilder: DIDDocumentUrlBuilderMock(),
downloader: DownloaderMock(result: .failure(MockError.forced)),
serializer: DIDDocumentSerializerMock(result: .failure(MockError.forced))
)
XCTAssertEqual(sut.method, "prism")
}

func testTrimsLongFormBeforeBuildingURL() async {
let longForm = DID(method: "prism", methodId: "abc:def")
let expectedShort = DID(method: "prism", methodId: "abc")
let urlBuilder = DIDDocumentUrlBuilderMock()
let downloader = DownloaderMock(result: .success(Data("{}".utf8)))
let serializer = DIDDocumentSerializerMock(result: .failure(MockError.forced))

let sut = EndpointShortFormPrismDIDRemoteResolver(
urlBuilder: urlBuilder,
downloader: downloader,
serializer: serializer
)

do {
_ = try await sut.resolve(did: longForm)
XCTFail("Expected error")
} catch {
XCTAssertEqual(urlBuilder.dids.first, expectedShort)
}
}

func testIntegrationWithGithubResolver() async throws {
let githubResolver = EndpointShortFormPrismDIDRemoteResolver.githubResolver()
let document = try await githubResolver.resolve(did: DID(string: "did:prism:076b993f6070d39ee0f0964970ef3d07af3e821cb51106952100fa803b03cc51"))
}
}
6 changes: 3 additions & 3 deletions EdgeAgentSDK/Castor/Tests/PeerDIDCreationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ final class PeerDIDCreationTests: XCTestCase {

let service = DIDDocument.Service(
id: "didcomm",
type: ["DIDCommMessaging"],
serviceEndpoint: [.init(
type: .one("DIDCommMessaging"),
serviceEndpoint: .one(.init(
uri: "https://example.com/endpoint",
routingKeys: ["did:example:somemediator#somekey"]
)]
))
)
let did = try castor.createPeerDID(
keyAgreementPublicKey: keyAgreementPrivateKey.publicKey(),
Expand Down
Loading