Skip to content

Commit

Permalink
Be more flexible on MFAP API response parsing (See issue #7)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebsto committed Sep 12, 2022
1 parent 3b88832 commit aba78e0
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 37 deletions.
65 changes: 33 additions & 32 deletions Sources/xcodeinstall/API/AuthenticationMFA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,48 +57,48 @@ import Foundation
struct MFAType: Codable {

struct PhoneNumber: Codable {
let numberWithDialCode: String
let pushMode: String
let obfuscatedNumber: String
let lastTwoDigits: String
let id: Int
let numberWithDialCode: String?
let pushMode: String?
let obfuscatedNumber: String?
let lastTwoDigits: String?
let id: Int?
}

struct SecurityCode: Codable {
let length: Int
let tooManyCodesSent: Bool
let tooManyCodesValidated: Bool
let securityCodeLocked: Bool
let securityCodeCooldown: Bool
let length: Int?
let tooManyCodesSent: Bool?
let tooManyCodesValidated: Bool?
let securityCodeLocked: Bool?
let securityCodeCooldown: Bool?
}

enum AuthenticationType: String, Codable {
case hsa
case hsa2
}

let trustedPhoneNumbers: [PhoneNumber]
let trustedPhoneNumbers: [PhoneNumber]?
let securityCode: SecurityCode?
let authenticationType: AuthenticationType
let recoveryUrl: String
let cantUsePhoneNumberUrl: String
let recoveryWebUrl: String
let repairPhoneNumberUrl: String
let repairPhoneNumberWebUrl: String
let aboutTwoFactorAuthenticationUrl: String
let twoFactorVerificationSupportUrl: String
let hasRecoveryKey: Bool
let supportsRecoveryKey: Bool
let autoVerified: Bool
let showAutoVerificationUI: Bool
let supportsCustodianRecovery: Bool
let hideSendSMSCodeOption: Bool
let supervisedChangePasswordFlow: Bool
let supportsRecovery: Bool
let trustedPhoneNumber: PhoneNumber
let hsa2Account: Bool
let restrictedAccount: Bool
let managedAccount: Bool
let authenticationType: AuthenticationType?
let recoveryUrl: String?
let cantUsePhoneNumberUrl: String?
let recoveryWebUrl: String?
let repairPhoneNumberUrl: String?
let repairPhoneNumberWebUrl: String?
let aboutTwoFactorAuthenticationUrl: String?
let twoFactorVerificationSupportUrl: String?
let hasRecoveryKey: Bool?
let supportsRecoveryKey: Bool?
let autoVerified: Bool?
let showAutoVerificationUI: Bool?
let supportsCustodianRecovery: Bool?
let hideSendSMSCodeOption: Bool?
let supervisedChangePasswordFlow: Bool?
let supportsRecovery: Bool?
let trustedPhoneNumber: PhoneNumber?
let hsa2Account: Bool?
let restrictedAccount: Bool?
let managedAccount: Bool?
}

extension AppleAuthenticator {
Expand All @@ -118,7 +118,8 @@ extension AppleAuthenticator {
// when there is no 'trustedDevices', we are supposed to fallback to SMS to a phone number
// but for my account, the API return no 'trustedDevices' but I still receive the code on my mac and iphone

guard mfaType.trustedPhoneNumbers.count > 0,
guard let count = mfaType.trustedPhoneNumbers?.count,
count > 0,
let securityCodeLength = mfaType.securityCode?.length else {
logger.warning("⚠️ No Security code length provided in answer")
throw AuthenticationError.requires2FATrustedPhoneNumber
Expand Down
64 changes: 61 additions & 3 deletions Tests/xcodeinstallTests/API/MFAuthenticationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class MFAuthenticationTest: NetworkAgentTestCase {

let url = "https://dummy"

session.nextData = getMFAType().data(using: .utf8)
session.nextData = getMFATypeOK().data(using: .utf8)
session.nextResponse = HTTPURLResponse(url: URL(string: url)!, statusCode: 200, httpVersion: nil, headerFields: nil)

do {
Expand Down Expand Up @@ -127,7 +127,7 @@ class MFAuthenticationTest: NetworkAgentTestCase {
// test MFA encoding
func testMFAEncoding() async {

let data = getMFAType().data(using: .utf8)
let data = getMFATypeOK().data(using: .utf8)

do {
_ = try JSONDecoder().decode(MFAType.self, from: data!)
Expand All @@ -137,7 +137,21 @@ class MFAuthenticationTest: NetworkAgentTestCase {

}

private func getMFAType() -> String {
// test MFA encoding
func testMFAEncodingUKExample1() async {

let data = getMFATypeUKExample1().data(using: .utf8)

do {
_ = try JSONDecoder().decode(MFAType.self, from: data!)
} catch {
XCTAssert(false, "Error while decoding \(error)")
}

}


private func getMFATypeOK() -> String {
return """
{
"trustedPhoneNumbers" : [ {
Expand Down Expand Up @@ -221,6 +235,50 @@ class MFAuthenticationTest: NetworkAgentTestCase {
"restrictedAccount" : false,
"managedAccount" : false
}
"""
}

private func getMFATypeUKExample1() -> String {
return """
{
"trustedPhoneNumbers" : [ {
"numberWithDialCode" : "+44 ••••• ••••24",
"pushMode" : "sms",
"lastTwoDigits" : "24",
"obfuscatedNumber" : "••••• ••••24",
"id" : 1
} ],
"securityCode" : {
"length" : 6,
"tooManyCodesSent" : false,
"tooManyCodesValidated" : false,
"securityCodeLocked" : false,
"securityCodeCooldown" : false
},
"authenticationType" : "hsa2",
"recoveryUrl" : "https://iforgot.apple.com/phone/add?prs_account_nm=ricsue%40amazon.co.uk&autoSubmitAccount=true&appId=142",
"cantUsePhoneNumberUrl" : "https://iforgot.apple.com/iforgot/phone/add?context=cantuse&prs_account_nm=ricsue%40amazon.co.uk&autoSubmitAccount=true&appId=142",
"recoveryWebUrl" : "https://iforgot.apple.com/password/verify/appleid?prs_account_nm=ricsue%40amazon.co.uk&autoSubmitAccount=true&appId=142",
"repairPhoneNumberUrl" : "https://gsa.apple.com/appleid/account/manage/repair/verify/phone",
"repairPhoneNumberWebUrl" : "https://appleid.apple.com/widget/account/repair?#!repair",
"aboutTwoFactorAuthenticationUrl" : "https://support.apple.com/kb/HT204921",
"autoVerified" : false,
"showAutoVerificationUI" : false,
"supportsCustodianRecovery" : false,
"hideSendSMSCodeOption" : false,
"supervisedChangePasswordFlow" : false,
"trustedPhoneNumber" : {
"numberWithDialCode" : "+44 ••••• ••••24",
"pushMode" : "sms",
"lastTwoDigits" : "24",
"obfuscatedNumber" : "••••• ••••24",
"id" : 1
},
"hsa2Account" : true,
"restrictedAccount" : false,
"supportsRecovery" : true,
"managedAccount" : false
}
"""
}

Expand Down
2 changes: 0 additions & 2 deletions xcodeinstall.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@
007585D0289C07130094D2DC /* Download List.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "Download List.json"; path = "Tests/Data/Download List.json"; sourceTree = SOURCE_ROOT; };
007585D1289C07130094D2DC /* curl.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = curl.txt; path = Tests/Data/curl.txt; sourceTree = SOURCE_ROOT; };
007585D2289C07130094D2DC /* Download Error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "Download Error.json"; path = "Tests/Data/Download Error.json"; sourceTree = SOURCE_ROOT; };
007585D3289C07130094D2DC /* auth_response.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = auth_response.json; path = Tests/Data/auth_response.json; sourceTree = SOURCE_ROOT; };
007585D5289C16850094D2DC /* Download Unknown Error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "Download Unknown Error.json"; path = "Tests/Data/Download Unknown Error.json"; sourceTree = SOURCE_ROOT; };
007585D6289C17670094D2DC /* DownloadListParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadListParserTest.swift; sourceTree = "<group>"; };
007585DA289C1E1E0094D2DC /* TestHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelper.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -283,7 +282,6 @@
isa = PBXGroup;
children = (
007585D5289C16850094D2DC /* Download Unknown Error.json */,
007585D3289C07130094D2DC /* auth_response.json */,
007585D1289C07130094D2DC /* curl.txt */,
007585D2289C07130094D2DC /* Download Error.json */,
007585D0289C07130094D2DC /* Download List.json */,
Expand Down

0 comments on commit aba78e0

Please sign in to comment.