diff --git a/Sources/OktaIdx/Capabilities/IDXCapability.swift b/Sources/OktaIdx/Capabilities/IDXCapability.swift index 14d4e2b4..dfa9571e 100644 --- a/Sources/OktaIdx/Capabilities/IDXCapability.swift +++ b/Sources/OktaIdx/Capabilities/IDXCapability.swift @@ -13,7 +13,11 @@ import Foundation /// A generic protocol used to identify objects that describe a capability. -public protocol IDXCapability {} +public protocol IDXCapability { + /// Message sent to capabilities when the response it's contained within will proceed through a remediation. + /// - Parameter remediation: Remediation being invoked. + func willProceed(to remediation: Remediation) +} /// Defines type conformance for capabilities that can be used with Authenticators. public protocol AuthenticatorCapability: IDXCapability {} diff --git a/Sources/OktaIdx/Capabilities/IDXDuoCapability.swift b/Sources/OktaIdx/Capabilities/IDXDuoCapability.swift index 87bd30cd..66a7d36a 100644 --- a/Sources/OktaIdx/Capabilities/IDXDuoCapability.swift +++ b/Sources/OktaIdx/Capabilities/IDXDuoCapability.swift @@ -9,11 +9,30 @@ import Foundation extension Capability { /// Capability to access data related to Duo - public struct Duo: AuthenticatorCapability,RemediationCapability { + public class Duo: AuthenticatorCapability { public let host: String public let signedToken: String public let script: String + public var signatureData: String? - let answerField: Remediation.Form.Field? + public func willProceed(to remediation: Remediation) { + guard remediation.authenticators.contains(where: { + $0.type == .app && $0.methods?.contains(.duo) ?? false + }), + let credentialsField = remediation.form["credentials"], + let signatureField = credentialsField.form?.allFields.first(where: { $0.name == "signatureData" }) + else { + return + } + + signatureField.value = signatureData + } + + init(host: String, signedToken: String, script: String, signatureData: String? = nil) { + self.host = host + self.signedToken = signedToken + self.script = script + self.signatureData = signatureData + } } } diff --git a/Sources/OktaIdx/Extensions/IDXCapability+Extensions.swift b/Sources/OktaIdx/Extensions/IDXCapability+Extensions.swift new file mode 100644 index 00000000..794f8586 --- /dev/null +++ b/Sources/OktaIdx/Extensions/IDXCapability+Extensions.swift @@ -0,0 +1,17 @@ +// +// Copyright (c) 2022-Present, Okta, Inc. and/or its affiliates. All rights reserved. +// The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") +// +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and limitations under the License. +// + +import Foundation + +extension IDXCapability { + public func willProceed(to remediation: Remediation) {} +} diff --git a/Sources/OktaIdx/IDXAuthenticatorType.swift b/Sources/OktaIdx/IDXAuthenticatorType.swift index 2fe315c5..d307d837 100644 --- a/Sources/OktaIdx/IDXAuthenticatorType.swift +++ b/Sources/OktaIdx/IDXAuthenticatorType.swift @@ -19,7 +19,7 @@ extension Authenticator { } /// The type of authenticator. - public enum Kind { + public enum Kind: Equatable { case unknown case app case email @@ -32,7 +32,7 @@ extension Authenticator { } /// The method, or sub-type, of an authenticator. - public enum Method { + public enum Method: Equatable { case unknown case sms case voice diff --git a/Sources/OktaIdx/IDXForm.swift b/Sources/OktaIdx/IDXForm.swift index ba4f21b8..2f547a02 100644 --- a/Sources/OktaIdx/IDXForm.swift +++ b/Sources/OktaIdx/IDXForm.swift @@ -33,14 +33,12 @@ extension Remediation { /// The array of ordered user-visible fields within this form. Each field may also contain nested forms for collections of related fields. public let fields: [Field] - let hiddenFields: [Field] let allFields: [Field] init?(fields: [Field]?) { guard let fields = fields else { return nil } self.allFields = fields self.fields = self.allFields.filter { $0.hasVisibleFields } - self.hiddenFields = self.allFields.filter{ !$0.hasVisibleFields } super.init() } } diff --git a/Sources/OktaIdx/IDXRemediation.swift b/Sources/OktaIdx/IDXRemediation.swift index c531aeb7..1761ceec 100644 --- a/Sources/OktaIdx/IDXRemediation.swift +++ b/Sources/OktaIdx/IDXRemediation.swift @@ -86,6 +86,11 @@ public class Remediation: Equatable, Hashable { return } + // Inform any capabilities associated with this remediation that it will proceed. + authenticators.compactMap({ $0.capabilities }) + .flatMap({ $0 }) + .forEach({ $0.willProceed(to: self) }) + let request: InteractionCodeFlow.RemediationRequest do { request = try InteractionCodeFlow.RemediationRequest(remediation: self) diff --git a/Sources/OktaIdx/Internal/Implementations/Version1/Responses/IDXClient+V1ResponseConstructors.swift b/Sources/OktaIdx/Internal/Implementations/Version1/Responses/IDXClient+V1ResponseConstructors.swift index 90b44b82..a6e39e34 100644 --- a/Sources/OktaIdx/Internal/Implementations/Version1/Responses/IDXClient+V1ResponseConstructors.swift +++ b/Sources/OktaIdx/Internal/Implementations/Version1/Responses/IDXClient+V1ResponseConstructors.swift @@ -193,9 +193,9 @@ extension Authenticator.Collection { .values .compactMap({ (mappingArray) in return try Authenticator.makeAuthenticator(flow: flow, - ion: mappingArray.map(\.authenticator), - jsonPaths: mappingArray.map(\.jsonPath), - in: object) + ion: mappingArray.map(\.authenticator), + jsonPaths: mappingArray.map(\.jsonPath), + in: object) }) self.init(authenticators: authenticators) @@ -275,8 +275,8 @@ extension Capability.Pollable { return nil } guard let remediation = Remediation.makeRemediation(flow: flow, - ion: form, - createCapabilities: false) + ion: form, + createCapabilities: false) else { return nil } @@ -353,38 +353,25 @@ extension Capability.OTP { } extension Capability.Duo { - init?(flow: InteractionCodeFlowAPI, ion authenticators: [IonAuthenticator]) { + convenience init?(flow: InteractionCodeFlowAPI, ion authenticators: [IonAuthenticator]) { + // Exit early if none of the authenticators have a "duo" method let methods = methodTypes(from: authenticators) guard methods.contains(.duo) else { return nil } - guard let typeName = authenticators.first?.type else { return nil } - let type = Authenticator.Kind(string: typeName) - - guard type == .app, - let contextualData = authenticators.compactMap(\.contextualData).first, + // Extract the duo authenticator data + let duoAuthenticators = authenticators.filter({ $0.type == "app" && $0.key == "duo" }) + guard let authenticator = duoAuthenticators.first(where: { $0.contextualData != nil }), + let contextualData = authenticator.contextualData, let host = contextualData["host"]?.stringValue(), let signedToken = contextualData["signedToken"]?.stringValue(), let script = contextualData["script"]?.stringValue() else { return nil } - self.init(host: host, signedToken: signedToken, script: script, answerField: nil) - } - - init?(form: Remediation.Form, object: IonForm) { - let type = Remediation.RemediationType(string: object.name) - guard type == .challengeAuthenticator else { // TODO: Find more ways to check this is Duo - return nil - } - - let field = form.hiddenFields.first - self.init(host: "", signedToken: "", script: "", answerField: field) - } - - func send(signature data:String) { - answerField?.value = data + + self.init(host: host, signedToken: signedToken, script: script) } } @@ -459,14 +446,14 @@ extension Authenticator { ] return Authenticator(flow: flow, - v1JsonPaths: jsonPaths, - state: state, - id: first.id, - displayName: first.displayName, - type: first.type, - key: key, - methods: methods, - capabilities: capabilities.compactMap { $0 }) + v1JsonPaths: jsonPaths, + state: state, + id: first.id, + displayName: first.displayName, + type: first.type, + key: key, + methods: methods, + capabilities: capabilities.compactMap { $0 }) } } @@ -486,8 +473,7 @@ extension Remediation { let capabilities: [RemediationCapability?] = createCapabilities ? [ Capability.SocialIDP(flow: flow, ion: object), - Capability.Pollable(flow: flow, ion: object), - Capability.Duo(form: form, object: object) + Capability.Pollable(flow: flow, ion: object) ] : [] return Remediation(flow: flow,