Skip to content

Commit

Permalink
Merge pull request #3 from okta/duo
Browse files Browse the repository at this point in the history
Expand on Duo capability to enable simplified linking between the remediation and its authenticator capabilities.
  • Loading branch information
sameh0 authored Nov 24, 2023
2 parents e6063a9 + 57ca1b7 commit b16ff5c
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 43 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
23 changes: 21 additions & 2 deletions Sources/OktaIdx/Capabilities/IDXDuoCapability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
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 @@ -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)
}
}

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 b16ff5c

Please sign in to comment.