Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Service Temporarily Unavailable response error #389

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions Sources/AppleAPI/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class Client {
case incorrectSecurityCode
case unexpectedSignInResponse(statusCode: Int, message: String?)
case appleIDAndPrivacyAcknowledgementRequired
case serviceTemporarilyUnavailable
case noTrustedPhoneNumbers
case notAuthenticated
case invalidHashcash
Expand All @@ -27,6 +28,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 @@ -92,20 +95,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>
Loading