Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pollux): pollux will be a plugin architecture from now on #192

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
92 changes: 92 additions & 0 deletions EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,95 @@ public extension Pollux {
)
}
}

public enum OperationResult {
case credential(Credential)
case forward(type: String, format: String?, payload: Data)
case verification(verified: Bool)

public var credential: Credential? {
switch self {
case .credential(let credential):
return credential
default:
return nil
}
}

public var forwardType: String? {
switch self {
case .forward(type: let type, format: _, payload: _):
return type
default:
return nil
}
}

public var forwardPayload: Data? {
switch self {
case .forward(type: _, format: _, payload: let payload):
return payload
default:
return nil
}
}

public var isVerified: Bool? {
switch self {
case .verification(verified: let verified):
return verified
default:
return nil
}
}
}

public protocol PolluxPlugin {
var version: String { get }
var supportedOperations: [String] { get }

func requiredOptions(operation: String) -> [CredentialOperationsOptions]

func operation(type: String, format: String?, payload: Data?, options: [CredentialOperationsOptions]) async throws -> OperationResult
}

public protocol CredentialPlugin: PolluxPlugin {
var credentialType: String { get }

func createCredential(_ credentialData: Data) async throws -> Credential
func credential(_ imported: Data) async throws -> Credential
}

public protocol ProtocolPlugin: PolluxPlugin {
var supportedCredentialTypes: [String] { get }
}

public protocol ProtocolCreateIssuancePlugin: ProtocolPlugin {
var protocolType: String { get }
var version: String { get }

// This is just a mock still to define
func issueOffer(withClaims: [ClaimFilter], issuer: DID, subject: DID) async throws -> OperationResult
// This is just a mock still to define
func issueCredential(withClaims: [ClaimFilter], issuer: DID, subject: DID) async throws -> OperationResult
}

public protocol ProtocolCreatePresentationPlugin: ProtocolPlugin {
var protocolType: String { get }
var version: String { get }

// This needs to be mocked still
func requestPresentation(withClaims: [ClaimFilter]) async throws -> OperationResult
}

extension ProtocolPlugin {
var credentialIssuance: ProtocolCreateIssuancePlugin? {
return self as? ProtocolCreateIssuancePlugin
}
}

extension ProtocolPlugin {
var credentialPresentation: ProtocolCreatePresentationPlugin? {
return self as? ProtocolCreatePresentationPlugin
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,22 @@ public extension DIDCommAgent {
throw EdgeAgentError.invalidAttachmentFormat(nil)
}

let credential = try await pollux.parseCredential(
type: format,
credentialPayload: jsonData,
guard let plugin = edgeAgent.credentialPlugins.first( where: { $0.supportedOperations.contains(message.type)
}) else {
throw EdgeAgentError.invalidAttachmentFormat(nil)
}
guard let credential = try await plugin.operation(
type: message.type,
format: attachment.format,
payload: jsonData,
options: [
.linkSecret(id: "", secret: linkSecretString),
.credentialDefinitionDownloader(downloader: downloader),
.schemaDownloader(downloader: downloader)
]
)
).credential else {
throw EdgeAgentError.invalidAttachmentFormat(nil)
}

guard let storableCredential = credential.storable else {
return credential
Expand Down
3 changes: 3 additions & 0 deletions EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class EdgeAgent {
public let castor: Castor
public let pluto: Pluto
public let pollux: Pollux & CredentialImporter
public let credentialPlugins: [PolluxPlugin]

public static func setupLogging(logLevels: [LogComponent: LogLevel]) {
SDKLogger.logLevels = logLevels
Expand All @@ -35,12 +36,14 @@ public class EdgeAgent {
castor: Castor,
pluto: Pluto,
pollux: Pollux & CredentialImporter,
credentialPlugins: [PolluxPlugin] = [],
seed: Seed? = nil
) {
self.apollo = apollo
self.castor = castor
self.pluto = pluto
self.pollux = pollux
self.credentialPlugins = credentialPlugins
self.seed = seed ?? apollo.createRandomSeed().seed
}

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

struct DIDCommPlugin: ProtocolPlugin {
let version: String = "0.1"
let supportedOperations: [String] = [
"https://didcomm.org/issue-credential/3.0/offer-credential",
"https://didcomm.org/issue-credential/3.0/issue-credential"
]
var supportedCredentialTypes: [String] {
credentialPlugins.map(\.credentialType)
}
private let supportProtocols: [ProtocolPlugin]
private let credentialPlugins: [CredentialPlugin]

func requiredOptions(operation: String) -> [Domain.CredentialOperationsOptions] {
[]
}
func operation(
type: String,
format: String?,
payload: Data?,
options: [Domain.CredentialOperationsOptions]
) async throws -> Domain.OperationResult {
guard let format else { throw PolluxError.unsupportedIssuedMessage }
switch type {
case "https://didcomm.org/issue-credential/3.0/offer-credential":
guard
let supportProtocol = supportProtocols
.first(where: {
$0.supportedOperations.contains("offer-credential") && $0.supportedCredentialTypes.contains(format)
})
else {
throw PolluxError.unsupportedIssuedMessage
}
return try await supportProtocol.operation(
type: "offer-credential",
format: format,
payload: payload,
options: options
)
case "https://didcomm.org/issue-credential/3.0/issue-credential":
guard
let supportProtocol = supportProtocols
.first(where: {
$0.supportedOperations.contains("issue-credential")
&& $0.supportedCredentialTypes.contains(format)
})
else {
throw PolluxError.unsupportedIssuedMessage
}
return try await supportProtocol.operation(
type: "issue-credential",
format: format,
payload: payload,
options: options
)
default:
return .verification(verified: false)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Domain
import Foundation
import JSONWebKey
import JSONWebToken
import JSONWebSignature

struct JWTCredentialPlugin: CredentialPlugin {
let version = "0.1"
let credentialType = "jwt"
let supportedOperations = [
"offer",
"offer-credential",
"issue",
"issue-credential"
]

func requiredOptions(operation: String) -> [Domain.CredentialOperationsOptions] {
[]
}

func operation(
type: String,
format: String?,
payload: Data?,
options: [Domain.CredentialOperationsOptions]
) async throws -> Domain.OperationResult {
guard let payload else { throw PolluxError.invalidJWTCredential }
switch type {
case "offer", "offer-credential":
let processedJWTCredentialRequest = try await processJWTCredentialRequest(
offerData: payload,
options: options
)
return try .forward(
type: "request-credential",
format: format,
payload: processedJWTCredentialRequest.tryToData()
)
case "issue", "issue-credential":
return try await .credential(createCredential(payload))
default:
throw PolluxError.unsupportedIssuedMessage
}
}

func createCredential(_ credentialData: Data) async throws -> Credential {
try JWTCredential(data: credentialData)
}

func credential(_ imported: Data) async throws -> Credential {
try JWTCredential(data: imported)
}

private func processJWTCredentialRequest(offerData: Data, options: [CredentialOperationsOptions]) async throws -> String {
guard
let subjectDIDOption = options.first(where: {
if case .subjectDID = $0 { return true }
return false
}),
case let CredentialOperationsOptions.subjectDID(did) = subjectDIDOption
else {
throw PolluxError.invalidPrismDID
}

guard
let exportableKeyOption = options.first(where: {
if case .exportableKey = $0 { return true }
return false
}),
case let CredentialOperationsOptions.exportableKey(exportableKey) = exportableKeyOption
else {
throw PolluxError.requiresExportableKeyForOperation(operation: "Create Credential Request")
}

return try await CreateJWTCredentialRequest.create(didStr: did.string, key: exportableKey, offerData: offerData)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Domain
import Foundation
import JSONWebKey
import JSONWebToken
import JSONWebSignature

struct PrismJWTCredentialPlugin: CredentialPlugin {
let credentialType = "prismJWT"
var version: String { jwtPlugin.version }
var supportedOperations: [String] { jwtPlugin.supportedOperations }
private let jwtPlugin = JWTCredentialPlugin()

func createCredential(_ credentialData: Data) async throws -> Credential {
try await jwtPlugin.createCredential(credentialData)
}

func credential(_ imported: Data) async throws -> Credential {
try await jwtPlugin.credential(imported)
}

func requiredOptions(operation: String) -> [Domain.CredentialOperationsOptions] {
jwtPlugin.requiredOptions(operation: operation)
}

func operation(
type: String,
format: String?,
payload: Data?,
options: [Domain.CredentialOperationsOptions]
) async throws -> Domain.OperationResult {
try await jwtPlugin.operation(
type: type,
format: format,
payload: payload,
options: options
)
}
}
Loading