Skip to content

Commit

Permalink
Add new MFARequestChallengeError and MFAGetAuthMethodsError (#2340)
Browse files Browse the repository at this point in the history
  • Loading branch information
nilo-ms authored Sep 20, 2024
1 parent 2809183 commit cb9eb7f
Show file tree
Hide file tree
Showing 20 changed files with 215 additions and 72 deletions.
48 changes: 24 additions & 24 deletions MSAL/MSAL.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion MSAL/src/native_auth/MSALNativeAuthLogMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
import Foundation

enum MSALNativeAuthLogMessage {
static let privatePreviewLog = "Warning ⚠️: this API is experimental. It may be changed in the future without notice. Do not use in production applications."
static let privatePreviewLog =
"Warning ⚠️: this API is experimental. It may be changed in the future without notice. Do not use in production applications."
}
4 changes: 2 additions & 2 deletions MSAL/src/native_auth/controllers/responses/MFAResults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import Foundation
enum MFARequestChallengeResult {
case verificationRequired(sentTo: String, channelTargetType: MSALNativeAuthChannelType, codeLength: Int, newState: MFARequiredState)
case selectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState)
case error(error: MFAError, newState: MFARequiredState?)
case error(error: MFARequestChallengeError, newState: MFARequiredState?)
}

enum MFAGetAuthMethodsResult {
case selectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState)
case error(error: MFAError, newState: MFARequiredState?)
case error(error: MFAGetAuthMethodsError, newState: MFARequiredState?)
}

enum MFASubmitChallengeResult {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN
)
switch result {
case .passwordRequired:
let error = MFAError(type: .generalError, correlationId: context.correlationId())
let error = MFARequestChallengeError(type: .generalError, correlationId: context.correlationId())
MSALLogger.log(level: .error, context: context, format: "MFA request challenge: received unexpected password required API result")
stopTelemetryEvent(event, context: context, error: error)
return .init(.error(error: error, newState: nil), correlationId: context.correlationId())
Expand Down Expand Up @@ -400,7 +400,8 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN
self?.stopTelemetryEvent(telemetryInfo.event, context: telemetryInfo.context, delegateDispatcherResult: result)
})
case .error(let error, let newState):
return .init(.error(error: error, newState: newState), correlationId: introspectResponse.correlationId)
let mfaRequestChallengeError = error.toMFARequestChallengeError()
return .init(.error(error: mfaRequestChallengeError, newState: newState), correlationId: introspectResponse.correlationId)
}
}
}
Expand Down Expand Up @@ -655,7 +656,7 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN
)
stopTelemetryEvent(telemetryInfo, error: error)
return .init(.error(
error: error.convertToMFARequestChallengeError(correlationId: telemetryInfo.context.correlationId()),
error: error.convertToMFAGetAuthMethodsError(correlationId: telemetryInfo.context.correlationId()),
newState: MFARequiredState(
controller: self,
scopes: scopes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ enum MSALNativeAuthSignInChallengeValidatedErrorType: Error {
}
}

func convertToMFARequestChallengeError(correlationId: UUID) -> MFAError {
func convertToMFARequestChallengeError(correlationId: UUID) -> MFARequestChallengeError {
switch self {
case .redirect:
return .init(type: .browserRequired, correlationId: correlationId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ enum MSALNativeAuthSignInIntrospectValidatedErrorType: Error {
case invalidRequest(MSALNativeAuthSignInIntrospectResponseError)
case unexpectedError(MSALNativeAuthSignInIntrospectResponseError?)

func convertToMFARequestChallengeError(correlationId: UUID) -> MFAError {
func convertToMFAGetAuthMethodsError(correlationId: UUID) -> MFAGetAuthMethodsError {
switch self {
case .redirect:
return .init(type: .browserRequired, correlationId: correlationId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public protocol MFARequestChallengeDelegate {
/// - Parameters:
/// - error: An error object indicating why the operation failed.
/// - newState: An object representing the new state of the flow with follow on methods.
@MainActor func onMFARequestChallengeError(error: MFAError, newState: MFARequiredState?)
@MainActor func onMFARequestChallengeError(error: MFARequestChallengeError, newState: MFARequiredState?)

/// Notifies the delegate that a verification is required from the user to continue.
/// - Note: If a flow requires this optional method and it is not implemented, then ``onMFARequestChallengeError(error:)`` will be called.
Expand Down Expand Up @@ -64,7 +64,7 @@ public protocol MFAGetAuthMethodsDelegate {
/// - Parameters:
/// - error: An error object indicating why the operation failed.
/// - newState: An object representing the new state of the flow with follow on methods.
@MainActor func onMFAGetAuthMethodsError(error: MFAError, newState: MFARequiredState?)
@MainActor func onMFAGetAuthMethodsError(error: MFAGetAuthMethodsError, newState: MFARequiredState?)

/// Notifies the delegate that the list of authentication methods is now available.
/// - Note: If a flow requires this optional method and it is not implemented, then ``onMFAGetAuthMethodsError(error:)`` will be called.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class MFARequestChallengeDelegateDispatcher: DelegateDispatcher<MFARequest
codeLength
)
} else {
let error = MFAError(
let error = MFARequestChallengeError(
type: .generalError,
message: requiredErrorMessage(for: "onMFARequestChallengeVerificationRequired"),
correlationId: correlationId
Expand All @@ -56,7 +56,7 @@ final class MFARequestChallengeDelegateDispatcher: DelegateDispatcher<MFARequest
telemetryUpdate?(.success(()))
await onSelectionRequired(authMethods, newState)
} else {
let error = MFAError(
let error = MFARequestChallengeError(
type: .generalError,
message: requiredErrorMessage(for: "onMFARequestChallengeSelectionRequired"),
correlationId: correlationId
Expand All @@ -74,7 +74,7 @@ final class MFAGetAuthMethodsDelegateDispatcher: DelegateDispatcher<MFAGetAuthMe
telemetryUpdate?(.success(()))
await onSelectionRequired(authMethods, newState)
} else {
let error = MFAError(
let error = MFAGetAuthMethodsError(
type: .generalError,
message: requiredErrorMessage(for: "onMFAGetAuthMethodsSelectionRequired"),
correlationId: correlationId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

/// Class that defines the structure and type of a MFAGetAuthMethodsError
@objcMembers
public class MFAGetAuthMethodsError: MSALNativeAuthError {
enum ErrorType: CaseIterable {
case browserRequired
case generalError
}

let type: ErrorType

init(type: ErrorType, message: String? = nil, correlationId: UUID, errorCodes: [Int] = [], errorUri: String? = nil) {
self.type = type
super.init(message: message, correlationId: correlationId, errorCodes: errorCodes, errorUri: errorUri)
}

/// Describes why an error occurred and provides more information about the error.
public override var errorDescription: String? {
if let description = super.errorDescription {
return description
}

switch type {
case .browserRequired:
return MSALNativeAuthErrorMessage.browserRequired
case .generalError:
return MSALNativeAuthErrorMessage.generalError
}
}

/// Returns `true` if a browser is required to continue the operation.
public var isBrowserRequired: Bool {
return type == .browserRequired
}

func toMFARequestChallengeError() -> MFARequestChallengeError {
var requestChallengeType = MFARequestChallengeError.ErrorType.browserRequired
switch type {
case .browserRequired:
requestChallengeType = .browserRequired
case .generalError:
requestChallengeType = .generalError
}
return MFARequestChallengeError(
type: requestChallengeType,
message: errorDescription,
correlationId: correlationId,
errorCodes: errorCodes,
errorUri: errorUri
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@

import Foundation

/// Class that defines the structure and type of a MFAError
/// Class that defines the structure and type of a MFARequestChallengeError
@objcMembers
public class MFAError: MSALNativeAuthError {
public class MFARequestChallengeError: MSALNativeAuthError {
enum ErrorType: CaseIterable {
case browserRequired
case generalError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class MFARequestChallengeDelegateSpy: MFARequestChallengeDelegate {

private let expectation: XCTestExpectation
private(set) var onMFARequestChallengeError = false
private(set) var error: MSAL.MFAError?
private(set) var error: MSAL.MFARequestChallengeError?

private(set) var onVerificationRequiredCalled = false
private(set) var newStateMFARequired: MSAL.MFARequiredState?
Expand All @@ -45,7 +45,7 @@ class MFARequestChallengeDelegateSpy: MFARequestChallengeDelegate {
self.expectation = expectation
}

func onMFARequestChallengeError(error: MSAL.MFAError, newState: MSAL.MFARequiredState?) {
func onMFARequestChallengeError(error: MSAL.MFARequestChallengeError, newState: MSAL.MFARequiredState?) {
onMFARequestChallengeError = true
self.newStateMFARequired = newState
self.error = error
Expand Down Expand Up @@ -107,14 +107,14 @@ final class MFAGetAuthMethodsDelegateSpy: MFAGetAuthMethodsDelegate {
private(set) var onSelectionRequiredCalled = false
private(set) var onMFAGetAuthMethodsErrorCalled = false
private(set) var authMethods: [MSALAuthMethod]?
private(set) var error: MSAL.MFAError?
private(set) var error: MSAL.MFAGetAuthMethodsError?
private(set) var newStateMFARequired: MSAL.MFARequiredState?

init(expectation: XCTestExpectation) {
self.expectation = expectation
}

func onMFAGetAuthMethodsError(error: MSAL.MFAError, newState: MSAL.MFARequiredState?) {
func onMFAGetAuthMethodsError(error: MSAL.MFAGetAuthMethodsError, newState: MSAL.MFARequiredState?) {
onMFAGetAuthMethodsErrorCalled = true
self.error = error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ class MSALNativeAuthMFAControllerTests: MSALNativeAuthSignInControllerTests {

// MARK: Private methods

private func checkGetAuthMethodsWithIntrospectValidatorError(validatedError: MSALNativeAuthSignInIntrospectValidatedErrorType, expectedType: MFAError.ErrorType) async {
private func checkGetAuthMethodsWithIntrospectValidatorError(validatedError: MSALNativeAuthSignInIntrospectValidatedErrorType, expectedType: MFAGetAuthMethodsError.ErrorType) async {
let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID)

signInRequestProviderMock.expectedContext = expectedContext
Expand Down
24 changes: 12 additions & 12 deletions MSAL/test/unit/native_auth/mock/delegate/MFADelegatesSpies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ import XCTest
open class MFARequestChallengeDelegateSpy: MFARequestChallengeDelegate {

let expectation: XCTestExpectation
var expectedError: MFAError?
var expectedError: MFARequestChallengeError?
private(set) var newSentTo: String?
private(set) var newChannelTargetType: MSALNativeAuthChannelType?
private(set) var newCodeLength: Int?
private(set) var newMFARequiredState: MFARequiredState?
private(set) var newAuthMethods: [MSALAuthMethod]?

init(expectation: XCTestExpectation, expectedError: MFAError? = nil) {
init(expectation: XCTestExpectation, expectedError: MFARequestChallengeError? = nil) {
self.expectation = expectation
self.expectedError = expectedError
}

public func onMFARequestChallengeError(error: MSAL.MFAError, newState: MSAL.MFARequiredState?) {
public func onMFARequestChallengeError(error: MSAL.MFARequestChallengeError, newState: MSAL.MFARequiredState?) {
if let expectedError = expectedError {
XCTAssertTrue(Thread.isMainThread)
checkErrors(error: error, expectedError: expectedError)
Expand Down Expand Up @@ -73,14 +73,14 @@ open class MFARequestChallengeDelegateSpy: MFARequestChallengeDelegate {
open class MFARequestChallengeNotImplementedDelegateSpy: MFARequestChallengeDelegate {

let expectation: XCTestExpectation
let expectedError: MFAError
let expectedError: MFARequestChallengeError

init(expectation: XCTestExpectation, expectedError: MFAError) {
init(expectation: XCTestExpectation, expectedError: MFARequestChallengeError) {
self.expectation = expectation
self.expectedError = expectedError
}

public func onMFARequestChallengeError(error: MSAL.MFAError, newState: MSAL.MFARequiredState?) {
public func onMFARequestChallengeError(error: MSAL.MFARequestChallengeError, newState: MSAL.MFARequiredState?) {
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(newState)
checkErrors(error: error, expectedError: expectedError)
Expand All @@ -91,16 +91,16 @@ open class MFARequestChallengeNotImplementedDelegateSpy: MFARequestChallengeDele
open class MFAGetAuthMethodsDelegateSpy: MFAGetAuthMethodsDelegate {

let expectation: XCTestExpectation
var expectedError: MFAError?
var expectedError: MFAGetAuthMethodsError?
private(set) var newMFARequiredState: MFARequiredState?
private(set) var newAuthMethods: [MSALAuthMethod]?

init(expectation: XCTestExpectation, expectedError: MFAError? = nil) {
init(expectation: XCTestExpectation, expectedError: MFAGetAuthMethodsError? = nil) {
self.expectation = expectation
self.expectedError = expectedError
}

public func onMFAGetAuthMethodsError(error: MSAL.MFAError, newState: MSAL.MFARequiredState?) {
public func onMFAGetAuthMethodsError(error: MSAL.MFAGetAuthMethodsError, newState: MSAL.MFARequiredState?) {
if let expectedError = expectedError {
XCTAssertTrue(Thread.isMainThread)
checkErrors(error: error, expectedError: expectedError)
Expand All @@ -124,14 +124,14 @@ open class MFAGetAuthMethodsDelegateSpy: MFAGetAuthMethodsDelegate {
open class MFAGetAuthMethodsNotImplementedDelegateSpy: MFAGetAuthMethodsDelegate {

let expectation: XCTestExpectation
let expectedError: MFAError
let expectedError: MFAGetAuthMethodsError

init(expectation: XCTestExpectation, expectedError: MFAError) {
init(expectation: XCTestExpectation, expectedError: MFAGetAuthMethodsError) {
self.expectation = expectation
self.expectedError = expectedError
}

public func onMFAGetAuthMethodsError(error: MSAL.MFAError, newState: MSAL.MFARequiredState?) {
public func onMFAGetAuthMethodsError(error: MSAL.MFAGetAuthMethodsError, newState: MSAL.MFARequiredState?) {
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(newState)
checkErrors(error: error, expectedError: expectedError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ final class MFAGetAuthMethodsDelegateDispatcherTests: XCTestCase {
}

func test_dispatchSelection_whenDelegateOptionalMethodNotImplemented() async {
let expectedError = MFAError(
let expectedError = MFAGetAuthMethodsError(
type: .generalError,
message: String(format: MSALNativeAuthErrorMessage.delegateNotImplemented, "onMFAGetAuthMethodsSelectionRequired"),
correlationId: correlationId
)
let delegate = MFAGetAuthMethodsNotImplementedDelegateSpy(expectation: delegateExp, expectedError: expectedError)

sut = .init(delegate: delegate, telemetryUpdate: { result in
guard case let .failure(error) = result, let customError = error as? MFAError else {
guard case let .failure(error) = result, let customError = error as? MFAGetAuthMethodsError else {
return XCTFail("wrong result")
}

Expand All @@ -83,7 +83,7 @@ final class MFAGetAuthMethodsDelegateDispatcherTests: XCTestCase {
await fulfillment(of: [telemetryExp, delegateExp], timeout: 1)
checkError(delegate.expectedError)

func checkError(_ error: MFAError?) {
func checkError(_ error: MFAGetAuthMethodsError?) {
XCTAssertEqual(error?.type, expectedError.type)
XCTAssertEqual(error?.errorDescription, expectedError.errorDescription)
XCTAssertEqual(error?.correlationId, expectedError.correlationId)
Expand Down
Loading

0 comments on commit cb9eb7f

Please sign in to comment.