Skip to content

Commit

Permalink
Merge pull request #157 from okta/okta-bindingCodeSupport
Browse files Browse the repository at this point in the history
Okta binding code support
  • Loading branch information
mikenachbaur-okta authored Nov 19, 2023
2 parents e53e2ad + bc06458 commit e273562
Show file tree
Hide file tree
Showing 27 changed files with 550 additions and 272 deletions.
404 changes: 202 additions & 202 deletions .github/workflows/tests.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion OktaAuthFoundation.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "OktaAuthFoundation"
s.module_name = "AuthFoundation"
s.version = "1.4.3"
s.version = "1.5.0"
s.summary = "Okta Authentication Foundation"
s.description = <<-DESC
Provides the foundation and common features used to authenticate users, managing the lifecycle and storage of tokens and credentials, and provide a base for other Okta SDKs to build upon.
Expand Down
2 changes: 1 addition & 1 deletion OktaDirectAuth.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "OktaDirectAuth"
s.version = "1.4.3"
s.version = "1.5.0"
s.summary = "Okta Direct Authentication"
s.description = <<-DESC
Enables application developers to build native sign in experiences using the Okta Direct Authentication API.
Expand Down
2 changes: 1 addition & 1 deletion OktaOAuth2.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "OktaOAuth2"
s.version = "1.4.3"
s.version = "1.5.0"
s.summary = "Okta OAuth2 Authentication"
s.description = <<-DESC
Enables application developers to authenticate users utilizing a variety of OAuth2 authentication flows.
Expand Down
2 changes: 1 addition & 1 deletion OktaWebAuthenticationUI.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "OktaWebAuthenticationUI"
s.module_name = "WebAuthenticationUI"
s.version = "1.4.3"
s.version = "1.5.0"
s.summary = "Okta Web Authentication UI"
s.description = <<-DESC
Authenticate users using web-based OIDC.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This library uses semantic versioning and follows Okta's [Library Version Policy

| Version | Status |
| ------- | ---------------------------------- |
| 1.4.3 | ✔️ Stable |
| 1.5.0 | ✔️ Stable |

The latest release can always be found on the [releases page][github-releases].

Expand Down
2 changes: 1 addition & 1 deletion Sources/AuthFoundation/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
import Foundation

// swiftlint:disable identifier_name
public let Version = SDKVersion(sdk: "okta-authfoundation-swift", version: "1.4.3")
public let Version = SDKVersion(sdk: "okta-authfoundation-swift", version: "1.5.0")
// swiftlint:enable identifier_name
17 changes: 17 additions & 0 deletions Sources/OktaDirectAuth/DirectAuthFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public protocol DirectAuthenticationFlowDelegate: AuthenticationDelegate {
/// Errors that may be generated while authenticating using ``DirectAuthenticationFlow``.
public enum DirectAuthenticationFlowError: Error {
case pollingTimeoutExceeded
case bindingCodeMissing
case missingArguments(_ names: [String])
case network(error: APIClientError)
case oauth2(error: OAuth2Error)
Expand Down Expand Up @@ -111,6 +112,19 @@ public class DirectAuthenticationFlow: AuthenticationFlow {
self.mfaToken = mfaToken
}
}

/// Represents the different types of binding updates that can be received
public enum BindingUpdateType {
/// Binding requires transfer of a code from one channel to another
case transfer(_ code: String)
}

/// Holds information about the binding update received when verifying OOB factors
public struct BindingUpdateContext {
/// Holds the type of binding update received from the server
public let update: BindingUpdateType
let oobResponse: OOBResponse
}

/// The current status of the authentication flow.
///
Expand All @@ -119,6 +133,9 @@ public class DirectAuthenticationFlow: AuthenticationFlow {
/// Authentication was successful, returning the given token.
case success(_ token: Token)

/// Indicates that there is an update about binding authentication channels when verifying OOB factors
case bindingUpdate(_ context: BindingUpdateContext)

/// Indicates the user should be challenged with some other secondary factor.
///
/// When this status is returned, the developer should use the ``DirectAuthenticationFlow/resume(_:with:)`` function to supply a secondary factor to verify the user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extension DirectAuthenticationFlowError: Equatable {
public static func == (lhs: DirectAuthenticationFlowError, rhs: DirectAuthenticationFlowError) -> Bool {
switch (lhs, rhs) {
case (.pollingTimeoutExceeded, .pollingTimeoutExceeded): return true
case (.bindingCodeMissing, .bindingCodeMissing): return true
case (.missingArguments(let lhsNames), .missingArguments(let rhsNames)):
return lhsNames.sorted() == rhsNames.sorted()
case (.network(error: let lhsError), .network(error: let rhsError)):
Expand Down
7 changes: 6 additions & 1 deletion Sources/OktaDirectAuth/Extensions/ErrorExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ extension DirectAuthenticationFlowError: LocalizedError {
switch self {
case .pollingTimeoutExceeded:
return NSLocalizedString("polling_timeout_exceeded",
tableName: "OktaOAuth2",
tableName: "OktaDirectAuth",
bundle: .oktaDirectAuth,
comment: "Polling timeout exceeded")
case .bindingCodeMissing:
return NSLocalizedString("binding_code_missing",
tableName: "OktaDirectAuth",
bundle: .oktaDirectAuth,
comment: "Binding Code is missing")
case .missingArguments(let arguments):
return String.localizedStringWithFormat(
NSLocalizedString("missing_arguments",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ extension DirectAuthenticationFlow.PrimaryFactor: AuthenticationFactor {
currentStatus: DirectAuthenticationFlow.Status? = nil,
factor: DirectAuthenticationFlow.PrimaryFactor) throws -> StepHandler
{
var bindingContext: DirectAuthenticationFlow.BindingUpdateContext?
if case .bindingUpdate(let context) = currentStatus {
bindingContext = context
}
switch self {
case .otp: fallthrough
case .password:
Expand All @@ -46,7 +50,8 @@ extension DirectAuthenticationFlow.PrimaryFactor: AuthenticationFactor {
loginHint: loginHint,
mfaToken: currentStatus?.mfaToken,
channel: channel,
factor: factor)
factor: factor,
bindingContext: bindingContext)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ extension DirectAuthenticationFlow.SecondaryFactor: AuthenticationFactor {
currentStatus: DirectAuthenticationFlow.Status?,
factor: DirectAuthenticationFlow.SecondaryFactor) throws -> StepHandler
{
var bindingContext: DirectAuthenticationFlow.BindingUpdateContext?
if case .bindingUpdate(let context) = currentStatus {
bindingContext = context
}
switch self {
case .otp:
let request = TokenRequest(openIdConfiguration: openIdConfiguration,
Expand All @@ -35,7 +39,8 @@ extension DirectAuthenticationFlow.SecondaryFactor: AuthenticationFactor {
loginHint: loginHint,
mfaToken: currentStatus?.mfaToken,
channel: channel,
factor: factor)
factor: factor,
bindingContext: bindingContext)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct ChallengeRequest {
let interval: TimeInterval?
let channel: DirectAuthenticationFlow.OOBChannel?
let bindingMethod: BindingMethod?
let bindingCode: String?

var oobResponse: OOBResponse? {
guard let oobCode = oobCode,
Expand All @@ -62,7 +63,8 @@ struct ChallengeRequest {
expiresIn: expiresIn,
interval: interval,
channel: channel,
bindingMethod: bindingMethod)
bindingMethod: bindingMethod,
bindingCode: bindingCode)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ struct OOBResponse: Codable {
let interval: TimeInterval
let channel: DirectAuthenticationFlow.OOBChannel
let bindingMethod: BindingMethod
let bindingCode: String?

init(oobCode: String, expiresIn: TimeInterval, interval: TimeInterval, channel: DirectAuthenticationFlow.OOBChannel, bindingMethod: BindingMethod, bindingCode: String? = nil) {
self.oobCode = oobCode
self.expiresIn = expiresIn
self.interval = interval
self.channel = channel
self.bindingMethod = bindingMethod
self.bindingCode = bindingCode
}
}

struct OOBAuthenticateRequest {
Expand All @@ -51,6 +61,7 @@ struct OOBAuthenticateRequest {

enum BindingMethod: String, Codable {
case none
case transfer
}

extension OOBAuthenticateRequest: APIRequest, APIRequestBody {
Expand Down
123 changes: 73 additions & 50 deletions Sources/OktaDirectAuth/Internal/Step Handlers/OOBStepHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,76 +20,49 @@ class OOBStepHandler<Factor: AuthenticationFactor>: StepHandler {
let mfaToken: String?
let channel: DirectAuthenticationFlow.OOBChannel
let factor: Factor
private let bindingContext: DirectAuthenticationFlow.BindingUpdateContext?
private var poll: PollingHandler<TokenRequest>?

init(flow: DirectAuthenticationFlow,
openIdConfiguration: OpenIdConfiguration,
loginHint: String?,
mfaToken: String?,
channel: DirectAuthenticationFlow.OOBChannel,
factor: Factor) throws
factor: Factor,
bindingContext: DirectAuthenticationFlow.BindingUpdateContext? = nil) throws
{
self.flow = flow
self.openIdConfiguration = openIdConfiguration
self.loginHint = loginHint
self.mfaToken = mfaToken
self.channel = channel
self.factor = factor
self.bindingContext = bindingContext
}

func process(completion: @escaping (Result<DirectAuthenticationFlow.Status, DirectAuthenticationFlowError>) -> Void) {
requestOOBCode { result in
switch result {
case .failure(let error):
self.flow.process(error, completion: completion)

case .success(let response):
let request = TokenRequest(openIdConfiguration: self.openIdConfiguration,
clientConfiguration: self.flow.client.configuration,
factor: self.factor,
mfaToken: self.mfaToken,
oobCode: response.oobCode,
grantTypesSupported: self.flow.supportedGrantTypes)
self.poll = PollingHandler(client: self.flow.client,
request: request,
expiresIn: response.expiresIn,
interval: response.interval) { pollHandler, result in
switch result {
case .success(let response):
return .success(response.result)
case .failure(let error):
guard case let .serverError(serverError) = error,
let oauthError = serverError as? OAuth2ServerError
else {
return .failure(error)
}

switch oauthError.code {
case .slowDown:
pollHandler.interval += 5
fallthrough
case .authorizationPending: fallthrough
case .directAuthAuthorizationPending:
return .continuePolling
default:
return .failure(error)
}
}
if let bindingContext {
self.requestToken(using: bindingContext.oobResponse, completion: completion)
} else {
requestOOBCode { [weak self] result in
guard let self else {
return
}

self.poll?.start { result in
switch result {
case .success(let token):
completion(.success(.success(token)))
case .failure(let error):
switch error {
case .apiClientError(let error):
self.flow.process(error, completion: completion)
case .timeout:
completion(.failure(.pollingTimeoutExceeded))
switch result {
case .failure(let error):
self.flow.process(error, completion: completion)
case .success(let response):
switch response.bindingMethod {
case .none:
self.requestToken(using: response, completion: completion)
case .transfer:
guard let bindingCode = response.bindingCode, bindingCode.isEmpty == false else {
completion(.failure(.bindingCodeMissing))
return
}
let context = DirectAuthenticationFlow.BindingUpdateContext(update: .transfer(bindingCode), oobResponse: response)
completion(.success(.bindingUpdate(context)))
}
self.poll = nil
}
}
}
Expand Down Expand Up @@ -160,4 +133,54 @@ class OOBStepHandler<Factor: AuthenticationFactor>: StepHandler {
completion(.failure(.validation(error: error)))
}
}

private func requestToken(using response: OOBResponse, completion: @escaping (Result<DirectAuthenticationFlow.Status, DirectAuthenticationFlowError>) -> Void) {
let request = TokenRequest(openIdConfiguration: self.openIdConfiguration,
clientConfiguration: self.flow.client.configuration,
factor: self.factor,
mfaToken: self.mfaToken,
oobCode: response.oobCode,
grantTypesSupported: self.flow.supportedGrantTypes)
self.poll = PollingHandler(client: self.flow.client,
request: request,
expiresIn: response.expiresIn,
interval: response.interval) { pollHandler, result in
switch result {
case .success(let response):
return .success(response.result)
case .failure(let error):
guard case let .serverError(serverError) = error,
let oauthError = serverError as? OAuth2ServerError
else {
return .failure(error)
}

switch oauthError.code {
case .slowDown:
pollHandler.interval += 5
fallthrough
case .authorizationPending: fallthrough
case .directAuthAuthorizationPending:
return .continuePolling
default:
return .failure(error)
}
}
}

self.poll?.start { result in
switch result {
case .success(let token):
completion(.success(.success(token)))
case .failure(let error):
switch error {
case .apiClientError(let apiClientError):
self.flow.process(apiClientError, completion: completion)
case .timeout:
completion(.failure(.pollingTimeoutExceeded))
}
}
self.poll = nil
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* DirectAuthenticationFlowError */
"polling_timeout_exceeded" = "Authentication timed out while polling the server.";
"missing_arguments" = "Could not authenticate since some expected arguments were missing. [%@]";
"binding_code_missing" = "Did not receive a binding code from server";
2 changes: 1 addition & 1 deletion Sources/OktaDirectAuth/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
@_exported import AuthFoundation

// swiftlint:disable identifier_name
public let Version = SDKVersion(sdk: "okta-directauth-swift", version: "1.4.3")
public let Version = SDKVersion(sdk: "okta-directauth-swift", version: "1.5.0")
// swiftlint:enable identifier_name
2 changes: 1 addition & 1 deletion Sources/OktaOAuth2/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
@_exported import AuthFoundation

// swiftlint:disable identifier_name
public let Version = SDKVersion(sdk: "okta-oauth2-swift", version: "1.4.3")
public let Version = SDKVersion(sdk: "okta-oauth2-swift", version: "1.5.0")
// swiftlint:enable identifier_name
2 changes: 1 addition & 1 deletion Sources/WebAuthenticationUI/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ import Foundation
import AuthFoundation

// swiftlint:disable identifier_name
public let Version = SDKVersion(sdk: "okta-webauthenticationui-swift", version: "1.4.3")
public let Version = SDKVersion(sdk: "okta-webauthenticationui-swift", version: "1.5.0")
// swiftlint:enable identifier_name
Loading

0 comments on commit e273562

Please sign in to comment.