Skip to content

Instantly share code, notes, and snippets.

@dedoussis
Created August 22, 2022 15:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dedoussis/6b211016b1c516cdc860391ba2c7b5b5 to your computer and use it in GitHub Desktop.
Save dedoussis/6b211016b1c516cdc860391ba2c7b5b5 to your computer and use it in GitHub Desktop.
iCloud API authentication (Python)
import requests
import typing as t
from uuid import uuid4
HEADERS = (
ACCOUNT_COUNTRY_HEADER := "X-Apple-ID-Account-Country",
SESSION_ID_HEADER := "X-Apple-ID-Session-Id",
SESSION_TOKEN_HEADER := "X-Apple-Session-Token",
TWOSV_TRUST_TOKEN_HEADER := "X-Apple-TwoSV-Trust-Token",
SCNT_HEADER := "scnt",
)
class BaseUrlConfig(t.NamedTuple):
auth: str = "https://idmsa.apple.com/appleauth/auth"
home: str = "https://www.icloud.com"
setup: str = "https://setup.icloud.com/setup/ws/1"
class ICloudClient:
def __init__(
self, apple_id: str, password: str, base_urls: BaseUrlConfig = BaseUrlConfig()
) -> None:
self.apple_id = apple_id
self.password = password
self.base_urs = base_urls
self.requester = requests.Session()
self.requester.headers["Origin"] = self.base_urs.home
self.requester.headers["Referer"] = f"{self.base_urs.home}/"
self.logger = print
self.client_id = f"auth-{uuid4()}"
self._webservices = {}
def _handle_response(self, response: requests.Response) -> None:
if not 199 < response.status_code < 300:
cookie_names = ", ".join([c.name for c in self.requester.cookies])
self.logger(
f"Call failed with {response.status_code} code and "
f"body: {response.text} and cookies: {cookie_names}"
)
raise RuntimeError
for header in HEADERS:
if header in response.headers:
self.requester.headers[header] = response.headers[header]
def _get_auth_headers(self, overrides: t.Optional[t.Mapping[str, str]] = None):
headers = {
"Accept": "*/*",
"Content-Type": "application/json",
"X-Apple-OAuth-Client-Id": "d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d",
"X-Apple-OAuth-Client-Type": "firstPartyAuth",
"X-Apple-OAuth-Redirect-URI": self.base_urs.home,
"X-Apple-OAuth-Require-Grant-Code": "true",
"X-Apple-OAuth-Response-Mode": "web_message",
"X-Apple-OAuth-Response-Type": "code",
"X-Apple-OAuth-State": self.client_id,
"X-Apple-Widget-Key": "d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d",
}
if overrides:
headers.update(overrides)
return headers
def webservice_url(self, service_name: str) -> str:
try:
return self._webservices[service_name]["url"]
except KeyError:
raise RuntimeError(f"{service_name} does not exist")
def sign_in(self, remember_me: bool = True) -> None:
resp = self.requester.post(
f"{self.base_urs.auth}/signin",
json={
"accountName": self.apple_id,
"password": self.password,
"rememberMe": remember_me,
"trustTokens": [],
},
headers=self._get_auth_headers(),
)
self._handle_response(resp)
def account_login(self) -> None:
resp = self.requester.post(
f"{self.base_urs.setup}/accountLogin",
json={
"accountCountryCode": self.requester.headers.get(
ACCOUNT_COUNTRY_HEADER
),
"dsWebAuthToken": self.requester.headers.get(SESSION_TOKEN_HEADER),
"extended_login": True,
"trustToken": self.requester.headers.get(TWOSV_TRUST_TOKEN_HEADER),
},
)
self._handle_response(resp)
self._webservices = resp.json()["webservices"]
def verify_2fa_code(self, code: str) -> None:
resp = self.requester.post(
f"{self.base_urs.auth}/verify/trusteddevice/securitycode",
json={"securityCode": {"code": code}},
headers=self._get_auth_headers({"Accept": "application/json"}),
)
self._handle_response(resp)
def trust_device(self) -> None:
resp = self.requester.get(
f"{self.base_urs.auth}/2sv/trust", headers=self._get_auth_headers()
)
self._handle_response(resp)
class PremiumMailSettings:
def __init__(self, client: ICloudClient):
self.client = client
self.base_url = f"{client.webservice_url('premiummailsettings')}/v1"
def list_hme(self) -> t.Sequence[t.Mapping[str, str]]:
resp = self.client.requester.get(f"{self.base_url}/hme/list")
self.client._handle_response(resp)
return resp.json()["result"]["hmeEmails"]
if __name__ == "__main__":
client = ICloudClient(apple_id="foo@bar.com", password="FooBarBuz!")
client.sign_in()
client.account_login()
code = input("Please enter verification code: ")
client.verify_2fa_code(code)
client.trust_device()
client.account_login()
pms = PremiumMailSettings(client)
print(pms.list_hme())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment