Skip to content

Commit

Permalink
Symorton/issuance cancel method (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
symorton authored Mar 27, 2023
1 parent a9a9b00 commit 3445b0b
Show file tree
Hide file tree
Showing 14 changed files with 645 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
/**
* Errors thrown in Presentation Request Mappable extension.
*/
enum PresentationRequestMappingError: Error {
enum PresentationRequestMappingError: Error, Equatable {
case presentationDefinitionMissingInRequest
case callbackURLMalformed(String?)
}

/**
Expand All @@ -26,6 +27,13 @@ extension PresentationRequest: Mappable {
throw PresentationRequestMappingError.presentationDefinitionMissingInRequest
}

let requestState = try self.getRequiredProperty(property: content.state, propertyName: "state")
let redirectUri = try self.getRequiredProperty(property: content.redirectURI, propertyName: "redirectUri")

guard let callbackUrl = URL(string: redirectUri) else {
throw PresentationRequestMappingError.callbackURLMalformed(content.redirectURI)
}

let requirement = try mapper.map(presentationDefinition)
let rootOfTrust = try mapper.map(linkedDomainResult)
let injectedIdToken = try createInjectedIdTokenIfExists(using: mapper)
Expand All @@ -36,6 +44,8 @@ extension PresentationRequest: Mappable {
let content = PresentationRequestContent(style: requesterStyle,
requirement: requirement,
rootOfTrust: rootOfTrust,
requestState: requestState,
callbackUrl: callbackUrl,
injectedIdToken: injectedIdToken)

return content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

enum IssuanceServiceVCRequesterError: Error {
case unableToCastIssuanceResponseContainerFromType(String)
case unableToCastIssuanceCompletionResponseFromType(String)
}
/**
* An extension of the VCServices.IssuanceService class
Expand All @@ -36,4 +37,16 @@ extension IssuanceService: VerifiedIdRequester {
from: issuanceResponseContainer.contract)
return verifiableCredential
}

func send<IssuanceResult>(result: IssuanceResult, to url: URL) async throws -> Void {

guard let issuanceCompletionResponse = result as? IssuanceCompletionResponse else {
let resultType = String(describing: result.self)
throw IssuanceServiceVCRequesterError.unableToCastIssuanceCompletionResponseFromType(resultType)
}

_ = try await AsyncWrapper().wrap { () in
self.sendCompletionResponse(for: issuanceCompletionResponse, to: url.absoluteString)
}()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ public protocol VerifiedIdRequest {
func complete() async -> Result<T, Error>

/// Cancel the request with an optional message.
func cancel(message: String?) -> Result<Void, Error>
func cancel(message: String?) async -> Result<Void, Error>
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ protocol VerifiedIdRequester {

/// Given generic request, requests a raw Verified Id from an issuer.
func send<Request>(request: Request) async throws -> VerifiedId

func send<IssuanceResult>(result: IssuanceResult, to url: URL) async throws -> Void
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,59 @@ struct OpenIdRequestHandler: RequestHandling {
return handlePresentationRequest(requestContent: requestContent, rawRequest: request)
}

private func handleIssuanceRequest(from requestContent: PresentationRequestContent) async throws -> any VerifiedIdIssuanceRequest {
private func handleIssuanceRequest(from presentationRequestContent: PresentationRequestContent) async throws -> any VerifiedIdIssuanceRequest {

guard let verifiedIdRequirement = requestContent.requirement as? VerifiedIdRequirement else {
guard let verifiedIdRequirement = presentationRequestContent.requirement as? VerifiedIdRequirement else {
throw OpenIdRequestHandlerError.unableToCastRequirementToVerifiedIdRequirement
}

guard let issuanceOption = verifiedIdRequirement.issuanceOptions.first as? VerifiedIdRequestURL else {
throw OpenIdRequestHandlerError.noIssuanceOptionsPresentToCreateIssuanceRequest
}

let rawContract = try await manifestResolver.resolve(with: issuanceOption.url)
var rawContract: any RawManifest
do {
rawContract = try await manifestResolver.resolve(with: issuanceOption.url)
} catch {
await sendManifestResolutionErrorResult(callbackUrl: presentationRequestContent.callbackUrl,
state: presentationRequestContent.requestState)
throw error
}

let issuanceRequestContent = try createIssuanceRequestContent(rawContract: rawContract,
requestContent: presentationRequestContent)
let issuanceResponseContainer = try IssuanceResponseContainer(from: rawContract, input: issuanceOption)

return ContractIssuanceRequest(content: issuanceRequestContent,
issuanceResponseContainer: issuanceResponseContainer,
verifiedIdRequester: verifiedIdRequester,
configuration: configuration)
}

private func createIssuanceRequestContent(rawContract: any RawManifest,
requestContent: PresentationRequestContent) throws -> IssuanceRequestContent {

var issuanceRequestContent = try configuration.mapper.map(rawContract)

issuanceRequestContent.add(requestState: requestContent.requestState)
issuanceRequestContent.add(issuanceResultCallbackUrl: requestContent.callbackUrl)

if let injectedIdToken = requestContent.injectedIdToken {
issuanceRequestContent.addRequirement(from: injectedIdToken)
}

return ContractIssuanceRequest(content: issuanceRequestContent,
issuanceResponseContainer: issuanceResponseContainer,
verifiedIdRequester: verifiedIdRequester,
configuration: configuration)
return issuanceRequestContent
}

private func sendManifestResolutionErrorResult(callbackUrl: URL, state: String) async {
do {
let issuanceErrorResult = IssuanceCompletionResponse(wasSuccessful: false,
withState: state,
andDetails: .fetchContractError)
try await self.verifiedIdRequester.send(result: issuanceErrorResult, to: callbackUrl)
} catch {
configuration.logger.logError(message: "Unable to send Issuance Result to callback. Error: \(String(describing: error))")
}
}

private func handlePresentationRequest(requestContent: PresentationRequestContent,
Expand Down
12 changes: 12 additions & 0 deletions WalletLibrary/WalletLibrary/Requests/IssuanceRequestContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,20 @@ struct IssuanceRequestContent {

private(set) var requirement: Requirement

private(set) var requestState: String?

private(set) var issuanceResultCallbackUrl: URL?

let rootOfTrust: RootOfTrust

mutating func add(requestState: String) {
self.requestState = requestState
}

mutating func add(issuanceResultCallbackUrl: URL) {
self.issuanceResultCallbackUrl = issuanceResultCallbackUrl
}

mutating func addRequirement(from injectedIdToken: InjectedIdToken) {
switch (requirement) {
case let groupRequirement as GroupRequirement:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import VCEntities
#endif

enum VerifiedIdIssuanceRequestError: Error {
case missingRequestStateForIssuanceResultCallback
case missingCallbackURLForIssuanceResultCallback
}

/**
* Issuance Request that is Contract specific.
* TODO: add VerifiedIdStyle property.
Expand All @@ -21,6 +26,10 @@ class ContractIssuanceRequest: VerifiedIdIssuanceRequest {

public let rootOfTrust: RootOfTrust

let requestState: String?

let issuanceResultCallbackUrl: URL?

private let verifiedIdRequester: VerifiedIdRequester

private let configuration: LibraryConfiguration
Expand All @@ -35,6 +44,8 @@ class ContractIssuanceRequest: VerifiedIdIssuanceRequest {
self.verifiedIdStyle = content.verifiedIdStyle
self.requirement = content.requirement
self.rootOfTrust = content.rootOfTrust
self.requestState = content.requestState
self.issuanceResultCallbackUrl = content.issuanceResultCallbackUrl
self.responseContainer = issuanceResponseContainer
self.verifiedIdRequester = verifiedIdRequester
self.configuration = configuration
Expand All @@ -53,14 +64,58 @@ class ContractIssuanceRequest: VerifiedIdIssuanceRequest {
do {
try self.responseContainer.add(requirement: requirement)
let verifiedId = try await verifiedIdRequester.send(request: responseContainer)

await sendResultIfCallbackAndStateExist(wasIssuanceSuccessful: true)

return Result.success(verifiedId)
} catch {
await sendResultIfCallbackAndStateExist(wasIssuanceSuccessful: false,
withErrorDetails: .issuanceServiceError)
return Result.failure(error)
}
}

public func cancel(message: String?) -> Result<Void, Error> {
return Result.failure(VerifiedIdClientError.TODO(message: "implement"))
/// Send the result back to the original requester. If call fails, fail silently, and log result.
private func sendResultIfCallbackAndStateExist(wasIssuanceSuccessful: Bool,
withErrorDetails details: IssuanceCompletionErrorDetails? = nil) async {
do {
guard let requestState = requestState,
let issuanceResultCallbackUrl = issuanceResultCallbackUrl else {
return
}

let result = IssuanceCompletionResponse(wasSuccessful: wasIssuanceSuccessful,
withState: requestState,
andDetails: details)

try await verifiedIdRequester.send(result: result, to: issuanceResultCallbackUrl)
} catch {
configuration.logger.logError(message: "Unable to send issuance result back to requester with error: \(String(describing: error))")
}
}

/// Send the issuance result back to the original requester.
/// TODO: Add support for injecting cancel message into callback. Right now "user canceled"
/// will be the message sent back.
public func cancel(message: String? = nil) async -> Result<Void, Error> {
do {

guard let requestState = requestState else {
throw VerifiedIdIssuanceRequestError.missingRequestStateForIssuanceResultCallback
}

guard let issuanceResultCallbackUrl = issuanceResultCallbackUrl else {
throw VerifiedIdIssuanceRequestError.missingCallbackURLForIssuanceResultCallback
}

let result = IssuanceCompletionResponse(wasSuccessful: false,
withState: requestState,
andDetails: IssuanceCompletionErrorDetails.userCanceled)
try await verifiedIdRequester.send(result: result, to: issuanceResultCallbackUrl)
return Result.success(())
} catch {
return Result.failure(error)
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import VCEntities
#endif

enum VerifiedIdPresentationRequestError: Error {
case cancelPresentationRequestIsUnsupported
}

/**
* Presentation Requst that is Open Id specific.
*/
Expand Down Expand Up @@ -64,7 +68,7 @@ class OpenIdPresentationRequest: VerifiedIdPresentationRequest {
}

/// Cancel the request with an optional message.
func cancel(message: String?) -> Result<Void, Error> {
return Result.failure(VerifiedIdClientError.TODO(message: "implement"))
func cancel(message: String?) async -> Result<Void, Error> {
return Result.failure(VerifiedIdPresentationRequestError.cancelPresentationRequestIsUnsupported)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,23 @@ struct PresentationRequestContent {

let rootOfTrust: RootOfTrust

let requestState: String

let callbackUrl: URL

let injectedIdToken: InjectedIdToken?

init(style: RequesterStyle,
requirement: Requirement,
rootOfTrust: RootOfTrust,
requestState: String,
callbackUrl: URL,
injectedIdToken: InjectedIdToken? = nil) {
self.style = style
self.requirement = requirement
self.rootOfTrust = rootOfTrust
self.requestState = requestState
self.callbackUrl = callbackUrl
self.injectedIdToken = injectedIdToken
}
}
Loading

0 comments on commit 3445b0b

Please sign in to comment.