-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(agent): enable parsing of enterprise invitation and add prism agent
Fixes ATL-2497
- Loading branch information
1 parent
2b69e0e
commit ec05665
Showing
6 changed files
with
282 additions
and
6 deletions.
There are no files selected for viewing
3 changes: 0 additions & 3 deletions
3
Castor/Sources/Resolvers/DIDResolver.swift → Domain/Sources/Models/DIDResolver.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,3 @@ | ||
import Domain | ||
import Foundation | ||
|
||
public protocol DIDResolver { | ||
func resolve(did: DID) throws -> DIDDocument | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,230 @@ | ||
// This file is just here as a foolproof/template since for Swift Packages a module source always have to have at least one Swift file | ||
import Builders | ||
import Combine | ||
import Domain | ||
import Foundation | ||
|
||
public class PrismAgent { | ||
public enum DIDType { | ||
case prism | ||
case peer | ||
} | ||
|
||
public enum InvitationType { | ||
public struct PrismOnboarding { | ||
public let from: String | ||
public let endpoint: URL | ||
public let ownDID: DID | ||
} | ||
|
||
case onboardingPrism(PrismOnboarding) | ||
case onboardingDIDComm(Message) | ||
} | ||
|
||
// swiftlint:disable force_unwrapping | ||
private static let prismMediatorEndpoint = URL(string: "localhost:8080")! | ||
// swiftlint:enable force_unwrapping | ||
|
||
private let apollo: Apollo | ||
private let castor: Castor | ||
private let pluto: Pluto | ||
private let mercury: Mercury | ||
private let mediatorServiceEnpoint: URL | ||
|
||
private var running = false | ||
private var connectionManager: ConnectionsManager | ||
private var cancellables = [AnyCancellable]() | ||
|
||
public let seed: Seed | ||
|
||
public init( | ||
apollo: Apollo, | ||
castor: Castor, | ||
pluto: Pluto, | ||
mercury: Mercury, | ||
seed: Seed? = nil, | ||
mediatorServiceEnpoint: URL? = nil | ||
) { | ||
self.apollo = apollo | ||
self.castor = castor | ||
self.pluto = pluto | ||
self.mercury = mercury | ||
self.seed = seed ?? apollo.createRandomSeed().seed | ||
self.mediatorServiceEnpoint = mediatorServiceEnpoint ?? Self.prismMediatorEndpoint | ||
self.connectionManager = ConnectionsManager( | ||
mercury: mercury, | ||
pluto: pluto, | ||
connections: [] | ||
) | ||
} | ||
|
||
public convenience init(seedData: Data? = nil, mediatorServiceEnpoint: URL? = nil) { | ||
let apollo = ApolloBuilder().build() | ||
let castor = CastorBuilder(apollo: apollo).build() | ||
let seed = seedData.map { Seed(value: $0) } ?? apollo.createRandomSeed().seed | ||
self.init( | ||
apollo: apollo, | ||
castor: castor, | ||
pluto: PlutoBuilder().build(), | ||
mercury: MercuryBuilder(castor: castor).build(), | ||
seed: seed, | ||
mediatorServiceEnpoint: mediatorServiceEnpoint ?? Self.prismMediatorEndpoint | ||
) | ||
} | ||
|
||
public func createNewDID( | ||
type: DIDType, | ||
keyPathIndex: Int? = nil, | ||
alias: String? = nil, | ||
services: [DIDDocument.Service] = [] | ||
) async throws -> DID { | ||
let seed = self.seed | ||
let apollo = self.apollo | ||
let castor = self.castor | ||
let pluto = self.pluto | ||
// Pluto is based on combine (Probably going to add async/await versions so this is in pluto) | ||
return try await withCheckedThrowingContinuation { continuation in | ||
pluto | ||
// Retrieve the last keyPath index used | ||
.getLastKeyPairIndex() | ||
.tryMap { | ||
// If the user provided a key path index use it, if not use the last + 1 | ||
let index = keyPathIndex ?? ($0 + 1) | ||
// Create the key pair | ||
let keyPair = apollo.createKeyPair(seed: seed, index: index) | ||
let newDID: DID | ||
switch type { | ||
case .prism: | ||
newDID = try castor.createPrismDID(masterPublicKey: keyPair.publicKey, services: services) | ||
case .peer: | ||
// For now we just have PRISM DID this will change for peerDID | ||
newDID = try castor.createPrismDID(masterPublicKey: keyPair.publicKey, services: services) | ||
} | ||
return (newDID, index, alias) | ||
} | ||
.flatMap { did, index, alias in | ||
// Store the did and its index path | ||
return pluto | ||
.storeDID(did: did, keyPairIndex: index, alias: alias) | ||
.map { did } | ||
} | ||
.first() | ||
.sink(receiveCompletion: { | ||
switch $0 { | ||
case .finished: | ||
break | ||
case let .failure(error): | ||
continuation.resume(throwing: error) | ||
} | ||
}, receiveValue: { | ||
continuation.resume(returning: $0) | ||
}) | ||
.store(in: &self.cancellables) | ||
} | ||
} | ||
|
||
public func signWith(did: DID, message: Data) async throws -> Signature { | ||
let seed = self.seed | ||
let apollo = self.apollo | ||
let pluto = self.pluto | ||
return try await withCheckedThrowingContinuation { continuation in | ||
pluto | ||
// First get DID info (KeyPathIndex in this case) | ||
.getDIDInfo(did: did) | ||
.tryMap { | ||
// if no register is found throw an error | ||
guard let index = $0?.keyPairIndex else { throw PrismAgentError.cannotFindDIDKeyPairIndex } | ||
// Re-Create the key pair to sign the message | ||
let keyPair = apollo.createKeyPair(seed: seed, index: index) | ||
return apollo.signMessage(privateKey: keyPair.privateKey, message: message) | ||
} | ||
.first() | ||
.sink(receiveCompletion: { | ||
switch $0 { | ||
case .finished: | ||
break | ||
case let .failure(error): | ||
continuation.resume(throwing: error) | ||
} | ||
}, receiveValue: { | ||
continuation.resume(returning: $0) | ||
}) | ||
.store(in: &self.cancellables) | ||
} | ||
} | ||
|
||
public func parseInvitation(str: String) async throws -> InvitationType { | ||
if let prismOnboarding = try? await parsePrismInvitation(str: str) { | ||
return .onboardingPrism(prismOnboarding) | ||
} else if let message = try? await parseOOBInvitation(url: str) { | ||
return .onboardingDIDComm(message) | ||
} | ||
throw PrismAgentError.unknownInvitationTypeError | ||
} | ||
|
||
public func parsePrismInvitation(str: String) async throws -> InvitationType.PrismOnboarding { | ||
let prismOnboarding = try PrismOnboardingInvitation(jsonString: str) | ||
guard | ||
let url = URL(string: prismOnboarding.body.onboardEndpoint) | ||
else { throw PrismAgentError.invalidURLError } | ||
|
||
let did = try await self.createNewDID( | ||
type: .peer, | ||
alias: prismOnboarding.body.onboardEndpoint, | ||
services: [.init( | ||
id: "#didcomm-1", | ||
type: ["DIDCommMessaging"], | ||
service: mediatorServiceEnpoint.absoluteString) | ||
] | ||
) | ||
|
||
return .init( | ||
from: prismOnboarding.body.from, | ||
endpoint: url, | ||
ownDID: did | ||
) | ||
} | ||
|
||
public func parseOOBInvitation(url: String) async throws -> Message { | ||
guard let url = URL(string: url) else { throw PrismAgentError.invalidURLError } | ||
return try await parseOOBInvitation(url: url) | ||
} | ||
|
||
public func parseOOBInvitation(url: URL) async throws -> Message { | ||
return try DIDCommInvitationRunner( | ||
mercury: mercury, | ||
url: url | ||
).run() | ||
} | ||
|
||
public func acceptDIDCommInvitation(invitation: Message) async throws { | ||
guard let fromDID = invitation.from else { throw PrismAgentError.invitationHasNoFromDIDError } | ||
let ownDID = try await createNewDID( | ||
type: .peer, | ||
alias: "com.connection.to.\(fromDID.string)" | ||
) | ||
let connection = try await DIDCommConnectionRunner( | ||
mercury: mercury, | ||
invitationMessage: invitation, | ||
ownDID: ownDID, | ||
connectionMaker: { ownDID, otherDID, mercury in | ||
Connection(holderDID: ownDID, otherDID: otherDID, mercury: mercury) | ||
} | ||
).run() | ||
connectionManager.addConnection(connection) | ||
} | ||
|
||
public func acceptPrismInvitation(invitation: InvitationType.PrismOnboarding) async throws { | ||
struct SendDID: Encodable { | ||
let did: String | ||
} | ||
var request = URLRequest(url: invitation.endpoint) | ||
request.httpMethod = "POST" | ||
request.httpBody = try JSONEncoder().encode(SendDID(did: invitation.ownDID.string)) | ||
request.setValue("application/json", forHTTPHeaderField: "content-type") | ||
let response = try await URLSession.shared.data(for: request) | ||
guard | ||
let urlResponse = response.1 as? HTTPURLResponse, | ||
urlResponse.statusCode == 200 | ||
else { throw PrismAgentError.failedToOnboardError } | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
PrismAgent/Sources/Protocols/PrismOnboarding/PrismOnboardingInvitation.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import Foundation | ||
|
||
struct PrismOnboardingInvitation { | ||
struct Body: Codable { | ||
let type: String | ||
let onboardEndpoint: String | ||
let from: String | ||
} | ||
|
||
let body: Body | ||
|
||
init(jsonString: String) throws { | ||
guard let jsonData = jsonString.data(using: .utf8) else { throw PrismAgentError.invitationIsInvalidError } | ||
let object = try JSONDecoder().decode(Body.self, from: jsonData) | ||
guard object.type == ProtocolTypes.prismOnboarding.rawValue else { | ||
throw PrismAgentError.unknownPrismOnboardingTypeError | ||
} | ||
self.body = object | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
@testable import PrismAgent | ||
import XCTest | ||
|
||
final class PrismOnboardingInvitationTests: XCTestCase { | ||
func testWhenValidJsonInvitationThenReturn() throws { | ||
let example = PrismOnboardingInvitation.Body( | ||
type: ProtocolTypes.prismOnboarding.rawValue, | ||
onboardEndpoint: "localhost:8080", | ||
from: "someone" | ||
) | ||
|
||
let jsonString = try String(data: JSONEncoder().encode(example), encoding: .utf8)! | ||
|
||
let invitation = try PrismOnboardingInvitation(jsonString: jsonString) | ||
|
||
XCTAssertEqual(invitation.body.from, example.from) | ||
XCTAssertEqual(invitation.body.onboardEndpoint, example.onboardEndpoint) | ||
XCTAssertEqual(invitation.body.type, example.type) | ||
} | ||
|
||
func testWhenInvalidTypeInvitationThenReturn() throws { | ||
let example = PrismOnboardingInvitation.Body( | ||
type: "wrong type", | ||
onboardEndpoint: "localhost:8080", | ||
from: "someone" | ||
) | ||
|
||
let jsonString = try String(data: JSONEncoder().encode(example), encoding: .utf8)! | ||
|
||
XCTAssertThrowsError(try PrismOnboardingInvitation(jsonString: jsonString)) | ||
} | ||
} |