From b700382610bd17dc68fe9971b7ce5457d02d9a1d Mon Sep 17 00:00:00 2001 From: iowk Date: Fri, 27 Dec 2024 12:51:42 +0800 Subject: [PATCH] fix: fallback to old raw password auth if srp auth fails (#1018) --- CHANGELOG.md | 2 + src/pyicloud_ipd/base.py | 201 ++++++++------- tests/test_authentication.py | 33 +++ tests/vcr_cassettes/failed_auth.yml | 49 ++++ tests/vcr_cassettes/fallback_raw_password.yml | 232 ++++++++++++++++++ 5 files changed, 431 insertions(+), 86 deletions(-) create mode 100644 tests/vcr_cassettes/fallback_raw_password.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index f646db492..86d121c7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fix: fallback to old raw password auth if srp auth fails [#975](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/975) + ## 1.25.0 (2024-12-03) - fix: failed to authenticate for accounts with srp s2k_fo auth protocol [#975](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/975) diff --git a/src/pyicloud_ipd/base.py b/src/pyicloud_ipd/base.py index a0d16ed51..e0240e8f7 100644 --- a/src/pyicloud_ipd/base.py +++ b/src/pyicloud_ipd/base.py @@ -192,93 +192,15 @@ def authenticate(self, force_refresh:bool=False, service:Optional[Any]=None) -> if not login_successful: LOGGER.debug("Authenticating as %s", self.user["accountName"]) - - headers = self._get_auth_headers() - - class SrpPassword(): - # srp uses the encoded password at process_challenge(), thus set_encrypt_info() should be called before that - def __init__(self, password: str): - self.pwd = password - - def set_encrypt_info(self, protocol: str, salt: bytes, iterations: int) -> None: - self.protocol = protocol - self.salt = salt - self.iterations = iterations - - def encode(self) -> bytes: - password_hash = hashlib.sha256(self.pwd.encode()) - password_digest = password_hash.hexdigest().encode() if self.protocol == 's2k_fo' else password_hash.digest() - key_length = 32 - return hashlib.pbkdf2_hmac('sha256', password_digest, salt, iterations, key_length) - - # Step 1: client generates private key a (stored in srp.User) and public key A, sends to server - srp_password = SrpPassword(self.user["password"]) - srp.rfc5054_enable() - srp.no_username_in_x() - usr = srp.User(self.user["accountName"], srp_password, hash_alg=srp.SHA256, ng_type=srp.NG_2048) - uname, A = usr.start_authentication() - data = { - 'a': base64.b64encode(A).decode(), - 'accountName': uname, - 'protocols': ['s2k', 's2k_fo'] - } - - try: - response = self.session.post("%s/signin/init" % self.AUTH_ENDPOINT, data=json.dumps(data), headers=headers) - if response.status_code == 401: - raise PyiCloudAPIResponseException(response.text, str(response.status_code)) - except PyiCloudAPIResponseException as error: - msg = "Failed to initiate srp authentication." - raise PyiCloudFailedLoginException(msg, error) from error - - # Step 2: server sends public key B, salt, and c to client - body = response.json() - salt = base64.b64decode(body['salt']) - b = base64.b64decode(body['b']) - c = body['c'] - iterations = body['iteration'] - protocol = body['protocol'] - - # Step 3: client generates session key M1 and M2 with salt and b, sends to server - srp_password.set_encrypt_info(protocol, salt, iterations) - m1 = usr.process_challenge( salt, b ) - m2 = usr.H_AMK - - data = { - "accountName": uname, - "c": c, - "m1": base64.b64encode(m1).decode(), - "m2": base64.b64encode(m2).decode(), - "rememberMe": True, - "trustTokens": [], - } - - if self.session_data.get("trust_token"): - data["trustTokens"] = [self.session_data.get("trust_token")] - try: - response = self.session.post( - "%s/signin/complete" % self.AUTH_ENDPOINT, - params={"isRememberMeEnabled": "true"}, - data=json.dumps(data), - headers=headers, - ) - if response.status_code == 409: - # requires 2FA - pass - elif response.status_code == 412: - # non 2FA account returns 412 "precondition no met" - headers = self._get_auth_headers() - response = self.session.post( - "%s/repair/complete" % self.AUTH_ENDPOINT, - data=json.dumps({}), - headers=headers, - ) - elif response.status_code >= 400 and response.status_code < 600: - raise PyiCloudAPIResponseException(response.text, str(response.status_code)) - except PyiCloudAPIResponseException as error: - msg = "Invalid email/password combination." - raise PyiCloudFailedLoginException(msg, error) from error + self._authenticate_srp() + except PyiCloudFailedLoginException as error: + LOGGER.error("Failed to login with srp, falling back to old raw password authentication. Error: %s", error) + try: + self._authenticate_raw_password() + except PyiCloudFailedLoginException as error: + LOGGER.error("Failed to login with raw password. Error: %s", error) + raise error self._authenticate_with_token() @@ -332,6 +254,113 @@ def _authenticate_with_credentials_service(self, service: str) -> None: msg = "Invalid email/password combination." raise PyiCloudFailedLoginException(msg, error) from error + def _authenticate_srp(self) -> None: + class SrpPassword(): + # srp uses the encoded password at process_challenge(), thus set_encrypt_info() should be called before that + def __init__(self, password: str): + self.pwd = password + + def set_encrypt_info(self, protocol: str, salt: bytes, iterations: int) -> None: + self.protocol = protocol + self.salt = salt + self.iterations = iterations + + def encode(self) -> bytes: + password_hash = hashlib.sha256(self.pwd.encode()) + password_digest = password_hash.hexdigest().encode() if self.protocol == 's2k_fo' else password_hash.digest() + key_length = 32 + return hashlib.pbkdf2_hmac('sha256', password_digest, self.salt, self.iterations, key_length) + + # Step 1: client generates private key a (stored in srp.User) and public key A, sends to server + srp_password = SrpPassword(self.user["password"]) + srp.rfc5054_enable() + srp.no_username_in_x() + usr = srp.User(self.user["accountName"], srp_password, hash_alg=srp.SHA256, ng_type=srp.NG_2048) + uname, A = usr.start_authentication() + data = { + 'a': base64.b64encode(A).decode(), + 'accountName': uname, + 'protocols': ['s2k', 's2k_fo'] + } + + headers = self._get_auth_headers() + try: + response = self.session.post("%s/signin/init" % self.AUTH_ENDPOINT, data=json.dumps(data), headers=headers) + if response.status_code == 401: + raise PyiCloudAPIResponseException(response.text, str(response.status_code)) + except PyiCloudAPIResponseException as error: + msg = "Failed to initiate srp authentication." + raise PyiCloudFailedLoginException(msg, error) from error + + # Step 2: server sends public key B, salt, and c to client + body = response.json() + salt = base64.b64decode(body['salt']) + b = base64.b64decode(body['b']) + c = body['c'] + iterations = body['iteration'] + protocol = body['protocol'] + + # Step 3: client generates session key M1 and M2 with salt and b, sends to server + srp_password.set_encrypt_info(protocol, salt, iterations) + m1 = usr.process_challenge( salt, b ) + m2 = usr.H_AMK + + data = { + "accountName": uname, + "c": c, + "m1": base64.b64encode(m1).decode(), + "m2": base64.b64encode(m2).decode(), + "rememberMe": True, + "trustTokens": [], + } + + if self.session_data.get("trust_token"): + data["trustTokens"] = [self.session_data.get("trust_token")] + + try: + response = self.session.post( + "%s/signin/complete" % self.AUTH_ENDPOINT, + params={"isRememberMeEnabled": "true"}, + data=json.dumps(data), + headers=headers, + ) + if response.status_code == 409: + # requires 2FA + pass + elif response.status_code == 412: + # non 2FA account returns 412 "precondition no met" + headers = self._get_auth_headers() + response = self.session.post( + "%s/repair/complete" % self.AUTH_ENDPOINT, + data=json.dumps({}), + headers=headers, + ) + elif response.status_code >= 400 and response.status_code < 600: + raise PyiCloudAPIResponseException(response.text, str(response.status_code)) + except PyiCloudAPIResponseException as error: + msg = "Invalid email/password combination." + raise PyiCloudFailedLoginException(msg, error) from error + + def _authenticate_raw_password(self) -> None: + data = dict(self.user) + + data["rememberMe"] = True + data["trustTokens"] = [] + if self.session_data.get("trust_token"): + data["trustTokens"] = [self.session_data.get("trust_token")] + + headers = self._get_auth_headers() + try: + self.session.post( + "%s/signin" % self.AUTH_ENDPOINT, + params={"isRememberMeEnabled": "true"}, + data=json.dumps(data), + headers=headers, + ) + except PyiCloudAPIResponseException as error: + msg = "Invalid email/password combination." + raise PyiCloudFailedLoginException(msg, error) from error + def _validate_token(self) -> Dict[str, Any]: """Checks if the current access token is still valid.""" LOGGER.debug("Checking session token validity") diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 575785ca3..6cdca156a 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -58,8 +58,41 @@ def test_failed_auth(self) -> None: "EC5646DE-9423-11E8-BF21-14109FE0B321", ) + self.assertIn( + "ERROR Failed to login with srp, falling back to old raw password authentication.", + self._caplog.text, + ) self.assertTrue("Invalid email/password combination." in str(context.exception)) + def test_fallback_raw_password(self) -> None: + base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3]) + cookie_dir = os.path.join(base_dir, "cookie") + + for dir in [base_dir, cookie_dir]: + recreate_path(dir) + + with vcr.use_cassette(os.path.join(self.vcr_path, "fallback_raw_password.yml")): # noqa: SIM117 + runner = CliRunner(env={"CLIENT_ID": "EC5646DE-9423-11E8-BF21-14109FE0B321"}) + result = runner.invoke( + main, + [ + "--username", + "jdoe@gmail.com", + "--password", + "password1", + "--no-progress-bar", + "--cookie-directory", + cookie_dir, + "--auth-only", + ], + ) + self.assertIn( + "ERROR Failed to login with srp, falling back to old raw password authentication.", + self._caplog.text, + ) + self.assertIn("INFO Authentication completed successfully", self._caplog.text) + assert result.exit_code == 0 + def test_2sa_required(self) -> None: base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3]) cookie_dir = os.path.join(base_dir, "cookie") diff --git a/tests/vcr_cassettes/failed_auth.yml b/tests/vcr_cassettes/failed_auth.yml index 597e163e7..fcb5065a6 100644 --- a/tests/vcr_cassettes/failed_auth.yml +++ b/tests/vcr_cassettes/failed_auth.yml @@ -92,4 +92,53 @@ interactions: status: code: 401 message: '' +- request: + body: !!python/unicode '{"accountName": "bad_username", "password": "bad_password", "rememberMe": + true, "trustTokens": []}' + headers: + Accept: ['*/*'] + Accept-Encoding: ['gzip, deflate'] + Connection: ['keep-alive'] + Content-Length: ['98'] + Content-Type: ['application/json'] + Origin: ['https://www.icloud.com'] + Referer: ['https://www.icloud.com/'] + User-Agent: ['Opera/9.52 (X11; Linux i686; U; en)'] + X-Apple-OAuth-Client-Id: ['d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d'] + X-Apple-OAuth-Client-Type: ['firstPartyAuth'] + X-Apple-OAuth-Redirect-URI: ['https://www.icloud.com'] + X-Apple-OAuth-Require-Grant-Code: ['true'] + X-Apple-OAuth-Response-Mode: ['web_message'] + X-Apple-OAuth-Response-Type: ['code'] + X-Apple-OAuth-State: ['EC5646DE-9423-11E8-BF21-14109FE0B321'] + X-Apple-Widget-Key: ['d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d'] + method: POST + uri: https://idmsa.apple.com/appleauth/auth/signin?isRememberMeEnabled=true + response: + body: + string: !!python/unicode '{}' + headers: + Cache-Control: + - 'no-cache' + - 'no-store' + Connection: ['keep-alive'] + Content-Type: ['text/html;charset=UTF-8'] + Date: ['Fri, 15 Dec 2023 17:28:03 GMT'] + Pragma: ['no-cache'] + Referrer-Policy: ['origin'] + Server: ['Apple'] + Strict-Transport-Security: ['max-age=31536000; includeSubDomains; preload'] + Transfer-Encoding: ['chunked'] + X-Apple-I-Request-ID: ['12345678-1234-1234-1234-123456789012'] + X-Apple-I-Rscd: ['401'] + X-BuildVersion: ['R4_1'] + X-Content-Type-Options: ['nosniff'] + X-FRAME-OPTIONS: ['DENY'] + X-XSS-Protection: ['1; mode=block'] + content-length: ['23705'] + scnt: ['scnt-1234567890'] + vary: ['accept-encoding'] + status: + code: 200 + message: '' version: 1 \ No newline at end of file diff --git a/tests/vcr_cassettes/fallback_raw_password.yml b/tests/vcr_cassettes/fallback_raw_password.yml new file mode 100644 index 000000000..24eadf2df --- /dev/null +++ b/tests/vcr_cassettes/fallback_raw_password.yml @@ -0,0 +1,232 @@ +interactions: +- request: + body: !!python/unicode '{"accountName": "jdoe@gmail.com", "protocols": ["s2k", "s2k_fo"]}' + headers: + Accept: ['*/*'] + Accept-Encoding: ['gzip, deflate'] + Connection: ['keep-alive'] + Content-Length: ['98'] + Content-Type: ['application/json'] + Origin: ['https://www.icloud.com'] + Referer: ['https://www.icloud.com/'] + User-Agent: ['Opera/9.52 (X11; Linux i686; U; en)'] + X-Apple-OAuth-Client-Id: ['d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d'] + X-Apple-OAuth-Client-Type: ['firstPartyAuth'] + X-Apple-OAuth-Redirect-URI: ['https://www.icloud.com'] + X-Apple-OAuth-Require-Grant-Code: ['true'] + X-Apple-OAuth-Response-Mode: ['web_message'] + X-Apple-OAuth-Response-Type: ['code'] + X-Apple-OAuth-State: ['EC5646DE-9423-11E8-BF21-14109FE0B321'] + X-Apple-Widget-Key: ['d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d'] + method: POST + uri: https://idmsa.apple.com/appleauth/auth/signin/init + response: + body: {string: '{"iteration":20064,"salt":"UUN/abcdefghijklmnopqr==","protocol":"s2k","version":1,"b":"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd==","c":"d-123-456789ab-cdef-0123-4567-89abcdef0123:MSA"}'} + headers: + Cache-Control: + - 'no-cache' + - 'no-store' + Connection: ['keep-alive'] + Content-Type: ['text/html;charset=UTF-8'] + Date: ['Fri, 15 Dec 2023 17:28:03 GMT'] + Pragma: ['no-cache'] + Referrer-Policy: ['origin'] + Server: ['Apple'] + Strict-Transport-Security: ['max-age=31536000; includeSubDomains; preload'] + Transfer-Encoding: ['chunked'] + X-Apple-I-Request-ID: ['12345678-1234-1234-1234-123456789012'] + X-BuildVersion: ['R4_1'] + X-Content-Type-Options: ['nosniff'] + X-FRAME-OPTIONS: ['DENY'] + X-XSS-Protection: ['1; mode=block'] + content-length: ['23705'] + scnt: ['scnt-1234567890'] + vary: ['accept-encoding'] + status: + code: 200 + message: '' +- request: + body: !!python/unicode '{"accountName": "jdoe@gmail.com", "rememberMe": true, "trustTokens": []}' + headers: + Accept: ['*/*'] + Accept-Encoding: ['gzip, deflate'] + Connection: ['keep-alive'] + Content-Length: ['98'] + Content-Type: ['application/json'] + Origin: ['https://www.icloud.com'] + Referer: ['https://www.icloud.com/'] + User-Agent: ['Opera/9.52 (X11; Linux i686; U; en)'] + X-Apple-OAuth-Client-Id: ['d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d'] + X-Apple-OAuth-Client-Type: ['firstPartyAuth'] + X-Apple-OAuth-Redirect-URI: ['https://www.icloud.com'] + X-Apple-OAuth-Require-Grant-Code: ['true'] + X-Apple-OAuth-Response-Mode: ['web_message'] + X-Apple-OAuth-Response-Type: ['code'] + X-Apple-OAuth-State: ['EC5646DE-9423-11E8-BF21-14109FE0B321'] + X-Apple-Widget-Key: ['d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d'] + method: POST + uri: https://idmsa.apple.com/appleauth/auth/signin/complete?isRememberMeEnabled=true + response: + body: + string: !!python/unicode '{"serviceErrors": [{"code":"-20101","message":"Enter the email or phone number and password for your Apple Account.","suppressDismissal":false}]}' + headers: + Cache-Control: + - 'no-cache' + - 'no-store' + Connection: ['keep-alive'] + Content-Type: ['application/json;charset=UTF-8'] + Date: ['Fri, 15 Dec 2023 17:28:03 GMT'] + Pragma: ['no-cache'] + Referrer-Policy: ['origin'] + Server: ['Apple'] + Strict-Transport-Security: ['max-age=31536000; includeSubDomains; preload'] + Transfer-Encoding: ['chunked'] + X-Apple-I-Request-ID: ['12345678-1234-1234-1234-123456789012'] + X-BuildVersion: ['R4_1'] + X-Content-Type-Options: ['nosniff'] + X-FRAME-OPTIONS: ['DENY'] + X-XSS-Protection: ['1; mode=block'] + content-length: ['23705'] + scnt: ['scnt-1234567890'] + vary: ['accept-encoding'] + status: + code: 401 + message: '' +- request: + body: !!python/unicode '{"accountName": "jdoe@gmail.com", "password": "password1", "rememberMe": + true, "trustTokens": []}' + headers: + Accept: ['*/*'] + Accept-Encoding: ['gzip, deflate'] + Connection: ['keep-alive'] + Content-Length: ['98'] + Content-Type: ['application/json'] + Origin: ['https://www.icloud.com'] + Referer: ['https://www.icloud.com/'] + User-Agent: ['Opera/9.52 (X11; Linux i686; U; en)'] + X-Apple-OAuth-Client-Id: ['d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d'] + X-Apple-OAuth-Client-Type: ['firstPartyAuth'] + X-Apple-OAuth-Redirect-URI: ['https://www.icloud.com'] + X-Apple-OAuth-Require-Grant-Code: ['true'] + X-Apple-OAuth-Response-Mode: ['web_message'] + X-Apple-OAuth-Response-Type: ['code'] + X-Apple-OAuth-State: ['EC5646DE-9423-11E8-BF21-14109FE0B321'] + X-Apple-Widget-Key: ['d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d'] + method: POST + uri: https://idmsa.apple.com/appleauth/auth/signin?isRememberMeEnabled=true + response: + body: + string: !!python/unicode '{}' + headers: + Cache-Control: + - 'no-cache' + - 'no-store' + Connection: ['keep-alive'] + Content-Type: ['text/html;charset=UTF-8'] + Date: ['Wed, 13 Dec 2023 05:06:31 GMT'] + Location: ['/auth'] + Pragma: ['no-cache'] + Referrer-Policy: ['origin'] + Server: ['Apple'] + Strict-Transport-Security: ['max-age=31536000; includeSubDomains; preload'] + Transfer-Encoding: ['chunked'] + X-Apple-Auth-Attributes: ['123456789abcdefg'] + X-Apple-I-Request-ID: ['12345678-1234-1234-1234-123456789012'] + X-Apple-I-Rscd: ['409'] + X-Apple-ID-Account-Country: ['USA'] + X-Apple-ID-Session-Id: ['sess-1234567890'] + X-Apple-Session-Token: ['token-1234567890'] + X-Apple-TwoSV-Trust-Eligible: ['true'] + X-BuildVersion: ['R4_1'] + content-length: ['23705'] + scnt: ['scnt-1234567890'] + vary: ['accept-encoding'] + status: + code: 200 + message: '' +- request: + body: !!python/unicode '{"accountCountryCode": "USA", "dsWebAuthToken": "token-1234567890", "extended_login": true, "trustToken": ""}' + headers: + Accept: + - '*/*' + Accept-Encoding: ['gzip, deflate'] + Connection: ['keep-alive'] + Content-Length: ['1157'] + Origin: ['https://www.icloud.com'] + Referer: ['https://www.icloud.com/'] + User-Agent: ['Opera/9.52 (X11; Linux i686; U; en)'] + method: POST + uri: https://setup.icloud.com/setup/ws/1/accountLogin + response: + body: + string: !!python/unicode '{"dsInfo": {"lastName":"Doe","iCDPEnabled":false,"tantorMigrated":false,"dsid":"12345678901","hsaEnabled":true, + "ironcadeMigrated":true,"locale":"en-us_US","brZoneConsolidated":false,"ICDRSCapableDeviceList":"","isManagedAppleID":false, + "isCustomDomainsFeatureAvailable":true,"isHideMyEmailFeatureAvailable":true,"ContinueOnDeviceEligibleDeviceInfo":[],"gilligan-invited":true, + "appleIdAliases":[],"hsaVersion":1,"ubiquityEOLEnabled":true,"isPaidDeveloper":false,"countryCode":"USA","notificationId":"12341234-1234-12341234-1234", + "primaryEmailVerified":true,"aDsID":"123456-12-12345678-1234-1234-1234-123456789012","locked":false,"ICDRSCapableDeviceCount":0, + "hasICloudQualifyingDevice":false,"primaryEmail":"jdoe@gmail.com","appleIdEntries": [{"isPrimary":true,"type":"EMAIL","value":"jdoe@gmail.com"}], + "gilligan-enabled":true,"isWebAccessAllowed":true,"fullName":"John Doe","mailFlags":{"isThreadingAvailable":false,"isSearchV2Provisioned":false, + "rawBits":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + "isCKMail":false,"isMppSupportedInCurrentCountry":true},"languageCode":"en-us","appleId":"jdoe@gmail.com","analyticsOptInStatus":false, + "firstName":"john","iCloudAppleIdAlias":"","notesMigrated":true,"beneficiaryInfo":{"isBeneficiary":false},"hasPaymentInfo":true,"pcsDeleted":false, + "appleIdAlias":"","brMigrated":true,"statusCode":2,"familyEligible":true},"hasMinimumDeviceForPhotosWeb":true,"iCDPEnabled":false, + "webservices":{"reminders":{"url":"https://p61-remindersws.icloud.com:443","status":"active"},"ckdatabasews":{"pcsRequired":true, + "url":"https://p61-ckdatabasews.icloud.com:443","status":"active"},"photosupload":{"pcsRequired":true, + "url":"https://p61-uploadphotosws.icloud.com:443","status":"active"},"photos":{"pcsRequired":true, + "uploadUrl":"https://p61-uploadphotosws.icloud.com:443","url":"https://p61-photosws.icloud.com:443","status":"active"},"drivews":{"pcsRequired":true, + "url":"https://p61-drivews.icloud.com:443","status":"active"},"uploadimagews":{"url":"https://p61-uploadimagews.icloud.com:443","status":"active"}, + "schoolwork":{},"cksharews":{"url":"https://p61-ckshare.icloud.com:443","status":"active"},"findme":{"url":"https://p61-fmipweb.icloud.com:443", + "status":"active"},"ckdeviceservice":{"url":"https://p61-ckdevice.icloud.com:443"}, + "iworkthumbnailws":{"url":"https://p61-iworkthumbnailws.icloud.com:443","status":"active"}, + "mccgateway":{"url":"https://p61-mccgateway.icloud.com:443","status":"active"},"calendar":{"isMakoAccount":false, + "url":"https://p61-calendarws.icloud.com:443","status":"active"},"docws":{"pcsRequired":true,"url":"https://p61-docws.icloud.com:443", + "status":"active"},"settings":{"url":"https://p61-settingsws.icloud.com:443","status":"active"}, + "premiummailsettings":{"url":"https://p61-maildomainws.icloud.com:443","status":"active"},"ubiquity":{"url":"https://p61-ubiquityws.icloud.com:443", + "status":"active"},"keyvalue":{"url":"https://p61-keyvalueservice.icloud.com:443","status":"active"},"mpp":{"url":"https://relay.icloud-mpp.com", + "status":"active"},"archivews":{"url":"https://p61-archivews.icloud.com:443","status":"active"},"push":{"url":"https://p61-pushws.icloud.com:443", + "status":"active"},"iwmb":{"url":"https://p61-iwmb.icloud.com:443","status":"active"}, + "iworkexportws":{"url":"https://p61-iworkexportws.icloud.com:443","status":"active"},"sharedlibrary":{"url":"https://sharedlibrary.icloud.com:443", + "status":"active"},"geows":{"url":"https://p61-geows.icloud.com:443","status":"active"},"account":{"iCloudEnv":{"shortId":"p","vipSuffix":"prod"}, + "url":"https://p61-setup.icloud.com:443","status":"active"},"contacts":{"url":"https://p61-contactsws.icloud.com:443","status":"active"}, + "developerapi":{"url":"https://developer-api.icloud.com:443","status":"active"}},"pcsEnabled":true, + "configBag":{"urls":{"accountCreateUI":"https://appleid.apple.com/widget/account/?widgetKey=#!create", + "accountLoginUI":"https://idmsa.apple.com/appleauth/auth/signin?widgetKey=","accountLogin":"https://setup.icloud.com/setup/ws/1/accountLogin", + "accountRepairUI":"https://appleid.apple.com/widget/account/?widgetKey=#!repair", + "downloadICloudTerms":"https://setup.icloud.com/setup/ws/1/downloadLiteTerms","repairDone":"https://setup.icloud.com/setup/ws/1/repairDone", + "accountAuthorizeUI":"https://idmsa.apple.com/appleauth/auth/authorize/signin?client_id=", + "vettingUrlForEmail":"https://id.apple.com/IDMSEmailVetting/vetShareEmail","accountCreate":"https://setup.icloud.com/setup/ws/1/createLiteAccount", + "getICloudTerms":"https://setup.icloud.com/setup/ws/1/getTerms","vettingUrlForPhone":"https://id.apple.com/IDMSEmailVetting/vetSharePhone"}, + "accountCreateEnabled":true},"hsaTrustedBrowser":true,"appsOrder":["mail","contacts","calendar","photos","iclouddrive","notes3","reminders", + "pages","numbers","keynote","newspublisher","find","settings"],"version":2,"isExtendedLogin":true,"pcsServiceIdentitiesIncluded":false, + "hsaChallengeRequired":false,"requestInfo":{"country":"US","timeZone":"EST","region":"NC"},"pcsDeleted":false, + "iCloudInfo":{"SafariBookmarksHasMigratedToCloudKit":false},"apps":{"calendar":{},"reminders":{},"keynote":{"isQualifiedForBeta":true}, + "settings":{"canLaunchWithOneFactor":true},"mail":{},"numbers":{"isQualifiedForBeta":true},"photos":{},"pages":{"isQualifiedForBeta":true}, + "notes3":{},"find":{"canLaunchWithOneFactor":true},"iclouddrive":{},"newspublisher":{"isHidden":true},"contacts":{}}}' + headers: + Access-Control-Allow-Credentials: ['true'] + Access-Control-Allow-Origin: ['https://www.icloud.com'] + Cache-Control: ['no-cache, no-store, private'] + Connection: ['keep-alive'] + Content-Type: ['application/json; charset=UTF-8'] + Date: ['Wed, 13 Dec 2023 05:06:31 GMT'] + Server: ['AppleHttpServer/78689afb4479'] + Set-Cookie: + - 'X-APPLE-WEBAUTH-PCS-Documents="pcsdocs-1234567890=";Expires=Tue,16-Jan-2024 02:07:19 GMT;Path=/;Domain=.icloud.com;Secure;HttpOnly' + - 'X-APPLE-WEBAUTH-PCS-News="pcsnews-1234567890=";Expires=Tue,16-Jan-2024 02:07:19 GMT;Path=/;Domain=.icloud.com;Secure;HttpOnly' + - 'X-APPLE-WEBAUTH-PCS-Notes="pcsnotes-1234567890=";Expires=Tue,16-Jan-2024 02:07:19 GMT;Path=/;Domain=.icloud.com;Secure;HttpOnly' + - 'X-APPLE-WEBAUTH-PCS-Sharing="pcssharing-1234567890=";Expires=Tue,16-Jan-2024 02:07:19 GMT;Path=/;Domain=.icloud.com;Secure;HttpOnly' + - 'X-APPLE-WEBAUTH-VALIDATE="v=1:t=EQ==BST_IAAAAAAABL-1234567890~~";Path=/;Domain=.icloud.com;Secure' + - 'X-APPLE-WEBAUTH-TOKEN="v=2:t=EQ==BST_IAAAAAAABL-1234567890~~";Expires=Tue,16-Jan-2024 02:07:19 GMT;Path=/;Domain=.icloud.com;Secure;HttpOnly' + - 'X-APPLE-DS-WEB-SESSION-TOKEN="websessiontoken-1234567890=";Expires=Tue,16-Jan-2024 02:07:19 GMT;Path=/;Domain=.icloud.com;Secure;HttpOnly' + Strict-Transport-Security: ['max-age=31536000; includeSubDomains'] + X-Apple-Edge-Response-Time: ['501'] + X-Apple-Request-UUID: ['12345678-1234-1234-1234-123456789012'] + X-Responding-Instance: ['setupservice:33200401:mr55p32ic-qukt01202301:7001:2404B363:5934c9004de5'] + access-control-expose-headers: ['X-Apple-Request-UUID,Via'] + content-length: ['5758'] + via: ['via-123456789012345678901234567890'] + x-apple-user-partition: ['32'] + status: + code: 200 + message: OK +version: 1 \ No newline at end of file