Skip to content

Commit

Permalink
Merge pull request #389 from noppefoxwolf/hotfix/supports-503-html-re…
Browse files Browse the repository at this point in the history
…sponse

Support Service Temporarily Unavailable response error
  • Loading branch information
MattKiazyk authored Oct 30, 2024
2 parents d1439a4 + d6d00b2 commit b5b31d2
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 14 deletions.
36 changes: 22 additions & 14 deletions Sources/AppleAPI/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class Client {
case incorrectSecurityCode
case unexpectedSignInResponse(statusCode: Int, message: String?)
case appleIDAndPrivacyAcknowledgementRequired
case serviceTemporarilyUnavailable
case noTrustedPhoneNumbers
case notAuthenticated
case invalidHashcash
Expand All @@ -32,6 +33,8 @@ public class Client {
return "Invalid username and password combination. Attempted to sign in with username \(username)."
case .appleIDAndPrivacyAcknowledgementRequired:
return "You must sign in to https://appstoreconnect.apple.com and acknowledge the Apple ID & Privacy agreement."
case .serviceTemporarilyUnavailable:
return "The service is temporarily unavailable. Please try again later."
case .invalidPhoneNumberIndex(let min, let max, let given):
return "Not a valid phone number index. Expecting a whole number between \(min)-\(max), but was given \(given ?? "nothing")."
case .noTrustedPhoneNumbers:
Expand Down Expand Up @@ -188,20 +191,25 @@ public class Client {
}

let httpResponse = response as! HTTPURLResponse
let responseBody = try JSONDecoder().decode(SignInResponse.self, from: data)

switch httpResponse.statusCode {
case 200:
return Current.network.dataTask(with: URLRequest.olympusSession).asVoid()
case 401:
throw Error.invalidUsernameOrPassword(username: accountName)
case 409:
return self.handleTwoStepOrFactor(data: data, response: response, serviceKey: serviceKey)
case 412 where Client.authTypes.contains(responseBody.authType ?? ""):
throw Error.appleIDAndPrivacyAcknowledgementRequired
default:
throw Error.unexpectedSignInResponse(statusCode: httpResponse.statusCode,
message: responseBody.serviceErrors?.map { $0.description }.joined(separator: ", "))
do {
let responseBody = try JSONDecoder().decode(SignInResponse.self, from: data)
switch httpResponse.statusCode {
case 200:
return Current.network.dataTask(with: URLRequest.olympusSession).asVoid()
case 401:
throw Error.invalidUsernameOrPassword(username: accountName)
case 409:
return self.handleTwoStepOrFactor(data: data, response: response, serviceKey: serviceKey)
case 412 where Client.authTypes.contains(responseBody.authType ?? ""):
throw Error.appleIDAndPrivacyAcknowledgementRequired
default:
throw Error.unexpectedSignInResponse(statusCode: httpResponse.statusCode,
message: responseBody.serviceErrors?.map { $0.description }.joined(separator: ", "))
}
} catch DecodingError.dataCorrupted where httpResponse.statusCode == 503 {
throw Error.serviceTemporarilyUnavailable
} catch {
throw error
}
}
}
Expand Down
90 changes: 90 additions & 0 deletions Tests/AppleAPITests/AppleAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,96 @@ final class AppleAPITests: XCTestCase {
""")
}

func test_Login_Service_Temporarily_Unavailable() {
var log = ""
Current.logging.log = { log.append($0 + "\n") }

var readLineCount = 0
Current.shell.readLine = { prompt in
defer { readLineCount += 1 }

Current.logging.log(prompt)

// security code
return "000000"
}

Current.network.dataTask = { convertible in

switch convertible.pmkRequest.url! {
case .itcServiceKey:
return fixture(for: .itcServiceKey,
fileURL: Bundle.module.url(forResource: "ITCServiceKey", withExtension: "json", subdirectory: "Fixtures/Login_Service_Temporarily_Unavailable")!,
statusCode: 200,
headers: ["Content-Type": "application/json"])
case .signIn:
if convertible.pmkRequest.httpMethod == "GET" {
return fixture(for: .signIn,
fileURL: Bundle.module.url(forResource: "Federate", withExtension: "json", subdirectory: "Fixtures/Login_Service_Temporarily_Unavailable")!,
statusCode: 200,
headers: ["Content-Type": "application/json",
"X-Apple-HC-Bits": "10",
"X-Apple-HC-Challenge": "somestring",
"scnt": ""])
} else {
return fixture(for: .signIn,
fileURL: Bundle.module.url(forResource: "SignIn", withExtension: "json", subdirectory: "Fixtures/Login_Service_Temporarily_Unavailable")!,
statusCode: 503,
headers: ["Content-Type": "text/html",
"X-Apple-ID-Session-Id": "",
"scnt": ""])
}
case .authOptions:
return fixture(for: .authOptions,
fileURL: Bundle.module.url(forResource: "AuthOptions", withExtension: "json", subdirectory: "Fixtures/Login_Service_Temporarily_Unavailable")!,
statusCode: 200,
headers: ["Content-Type": "application/json",
"X-Apple-ID-Session-Id": "",
"scnt": ""])
case .submitSecurityCode(.device(code: "000000")):
return fixture(for: .submitSecurityCode(.device(code: "000000")),
statusCode: 204,
headers: ["Content-Type": "application/json",
"X-Apple-ID-Session-Id": "",
"scnt": ""])
case .trust:
return fixture(for: .trust,
statusCode: 204,
headers: [:])
case .olympusSession:
return fixture(for: .olympusSession,
fileURL: Bundle.module.url(forResource: "OlympusSession", withExtension: "json", subdirectory: "Fixtures/Login_Service_Temporarily_Unavailable")!,
statusCode: 200,
headers: ["Content-Type": "application/json",
"X-Apple-ID-Session-Id": "",
"scnt": ""])
default:
print(convertible.pmkRequest.url!)
XCTFail()
return .init(error: PMKError.invalidCallingConvention)
}
}

let expectation = self.expectation(description: "promise fulfills")

let client = Client()
client.login(accountName: "[email protected]", password: "ABC123")
.tap { result in
guard case .rejected(let error as AppleAPI.Client.Error) = result else {
XCTFail("login fulfilled, but should have rejected with .noTrustedPhoneNumbers error")
return
}
XCTAssertEqual(error, AppleAPI.Client.Error.serviceTemporarilyUnavailable)
expectation.fulfill()
}
.cauterize()

wait(for: [expectation], timeout: 1.0)

XCTAssertEqual(log, "")
}


func testValidHashCashMint() {
let bits: UInt = 11
let resource = "4d74fb15eb23f465f1f6fcbf534e5877"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"trustedPhoneNumbers" : [ {
"obfuscatedNumber" : "(•••) •••-••00",
"pushMode" : "sms",
"numberWithDialCode" : "+1 (•••) •••-••00",
"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=test%40example.com&autoSubmitAccount=true&appId=142",
"cantUsePhoneNumberUrl" : "https://iforgot.apple.com/iforgot/phone/add?context=cantuse&prs_account_nm=test%40example.com&autoSubmitAccount=true&appId=142",
"recoveryWebUrl" : "https://iforgot.apple.com/password/verify/appleid?prs_account_nm=test%40example.com&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,
"managedAccount" : false,
"trustedPhoneNumber" : {
"obfuscatedNumber" : "(•••) •••-••00",
"pushMode" : "sms",
"numberWithDialCode" : "+1 (•••) •••-••00",
"id" : 1
},
"hsa2Account" : true,
"restrictedAccount" : false,
"supportsRecovery" : true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"authType" : "hsa2"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"authServiceUrl" : "https://idmsa.apple.com/appleauth",
"authServiceKey" : "NNNNN"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
{
"user" : {
"fullName" : "Test User",
"firstName" : "Test",
"lastName" : "User",
"emailAddress" : "[email protected]",
"prsId" : "000000000"
},
"provider" : {
"providerId" : 00000,
"name" : "Test User",
"contentTypes" : [ "SOFTWARE" ],
"subType" : "INDIVIDUAL",
"pla" : [ {
"id" : "1BC01216-52D4-43DC-8555-195F4454C348",
"version" : "5014",
"types" : [ "contractContentTypeDisplay.iOSFreeApps", "contractContentTypeDisplay.MacOSXFreeApplications" ],
"contractCountryOfOrigins" : [ "CAN" ]
} ]
},
"theme" : "APPSTORE_CONNECT",
"availableProviders" : [ {
"providerId" : 000000,
"name" : "Test User",
"contentTypes" : [ "SOFTWARE" ],
"subType" : "INDIVIDUAL"
} ],
"backingType" : "ITC",
"backingTypes" : [ "ITC" ],
"roles" : [ "ADMIN", "LEGAL" ],
"unverifiedRoles" : [ ],
"featureFlags" : [ "showWwdrUserRoles", "adpRad", "apiKeys" ],
"agreeToTerms" : true,
"termsSignatures" : [ "ASC", "RAD" ],
"modules" : [ {
"key" : "Apps",
"name" : "ITC.HomePage.Apps.IconText",
"localizedName" : "My Apps",
"url" : "https://appstoreconnect.apple.com/apps",
"iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/[email protected]",
"down" : false,
"visible" : true,
"hasNotifications" : false
}, {
"key" : "AppAnalytics",
"name" : "ITC.HomePage.AppAnalytics.IconText",
"localizedName" : "App Analytics",
"url" : "https://analytics.itunes.apple.com/",
"iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/[email protected]",
"down" : false,
"visible" : true,
"hasNotifications" : false
}, {
"key" : "SalesTrends",
"name" : "ITC.HomePage.SalesTrends.IconText",
"localizedName" : "Sales and Trends",
"url" : "https://appstoreconnect.apple.com/trends",
"iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/[email protected]",
"down" : false,
"visible" : true,
"hasNotifications" : false
}, {
"key" : "FinancialReports",
"name" : "ITC.HomePage.FinancialReports.IconText",
"localizedName" : "Payments and Financial Reports",
"url" : "https://appstoreconnect.apple.com/itc/payments_and_financial_reports",
"iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/[email protected]",
"down" : false,
"visible" : true,
"hasNotifications" : false
}, {
"key" : "Account",
"name" : "ITC.HomePage.Account.IconText",
"localizedName" : "Users and Access",
"url" : "https://appstoreconnect.apple.com/access/users",
"iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/[email protected]",
"down" : false,
"visible" : true,
"hasNotifications" : false
}, {
"key" : "ContractsTaxBanking",
"name" : "ITC.HomePage.ContractsTaxBanking.IconText",
"localizedName" : "Agreements, Tax, and Banking",
"url" : "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/da/jumpTo?page=contracts",
"iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/[email protected]",
"down" : false,
"visible" : true,
"hasNotifications" : false
}, {
"key" : "Resources",
"name" : "ITC.HomePage.Resources.IconText",
"localizedName" : "Resources and Help",
"url" : "https://developer.apple.com/app-store-connect/",
"iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/[email protected]",
"down" : false,
"visible" : true,
"hasNotifications" : false
} ],
"helpLinks" : [ {
"key" : "AllAsc",
"url" : "https://help.apple.com/app-store-connect/",
"localizedText" : "App Store Connect Resources"
}, {
"key" : "Xcode",
"url" : "https://help.apple.com/xcode/mac/current/",
"localizedText" : "Xcode Help"
}, {
"key" : "SupportContact",
"url" : "https://developer.apple.com/support/",
"localizedText" : "Support and Contact"
} ],
"userProfile" : [ {
"key" : "signIn",
"url" : "https://appstoreconnect.apple.com/login",
"localizedText" : "Sign In"
}, {
"key" : "personalDetails",
"url" : "https://appstoreconnect.apple.com/access/users/07E3E586-44B1-48D3-BF8D-430F754F1BAA/settings",
"localizedText" : "Edit Profile"
}, {
"key" : "signOut",
"url" : "https://appstoreconnect.apple.com/logout",
"localizedText" : "Sign Out"
} ],
"pccDto" : null,
"publicUserId" : "07E3E586-44B1-48D3-BF8D-430F754F1BAA",
"ofacState" : null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<html>

<head>
<title>503 Service Temporarily Unavailable</title>
</head>

<body>
<center>
<h1>503 Service Temporarily Unavailable</h1>
</center>
<hr>
<center>Apple</center>
</body>

</html>

0 comments on commit b5b31d2

Please sign in to comment.