Skip to content

Commit

Permalink
Expand on Duo capability to enable simplified linking between the rem…
Browse files Browse the repository at this point in the history
…ediation and its authenticator capability.

This gives the remediations an opportunity to fix up the state or perform validation before a request will be created.
  • Loading branch information
mikenachbaur-okta committed Nov 24, 2023
1 parent e6063a9 commit 45b68db
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 42 deletions.
6 changes: 5 additions & 1 deletion Sources/OktaIdx/Capabilities/IDXCapability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
16 changes: 14 additions & 2 deletions Sources/OktaIdx/Capabilities/IDXDuoCapability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,23 @@ import Foundation

extension Capability {
/// Capability to access data related to Duo
public struct Duo: AuthenticatorCapability,RemediationCapability {
public struct 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
}
}
}
17 changes: 17 additions & 0 deletions Sources/OktaIdx/Extensions/IDXCapability+Extensions.swift
Original file line number Diff line number Diff line change
@@ -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) {}
}
4 changes: 2 additions & 2 deletions Sources/OktaIdx/IDXAuthenticatorType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ extension Authenticator {
}

/// The type of authenticator.
public enum Kind {
public enum Kind: Equatable {
case unknown
case app
case email
Expand All @@ -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
Expand Down
2 changes: 0 additions & 2 deletions Sources/OktaIdx/IDXForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
5 changes: 5 additions & 0 deletions Sources/OktaIdx/IDXRemediation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -354,37 +354,24 @@ extension Capability.OTP {

extension Capability.Duo {
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)
}
}

Expand Down Expand Up @@ -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 })
}
}

Expand All @@ -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,
Expand Down

0 comments on commit 45b68db

Please sign in to comment.