From 73dbbceb7566c128bb661401bd835a8cb36f38b2 Mon Sep 17 00:00:00 2001 From: Maciej Buchert Date: Tue, 15 May 2018 21:37:12 +0200 Subject: [PATCH 1/8] added new function I added function that return calendars - names, guid, url and more --- pyicloud/services/calendar.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyicloud/services/calendar.py b/pyicloud/services/calendar.py index 49cf1188..4e42a08d 100644 --- a/pyicloud/services/calendar.py +++ b/pyicloud/services/calendar.py @@ -19,6 +19,7 @@ def __init__(self, service_root, session, params): self._calendar_event_detail_url = '%s/eventdetail' % ( self._calendar_endpoint, ) + self._calendars = '%s/startup' % self._calendar_endpoint def get_event_detail(self, pguid, guid): """ @@ -60,3 +61,22 @@ def events(self, from_dt=None, to_dt=None): """ self.refresh_client(from_dt, to_dt) return self.response['Event'] + + def calendars(self): + """ + Retrieves calendars for this month + """ + today = datetime.today() + first_day, last_day = monthrange(today.year, today.month) + from_dt = datetime(today.year, today.month, first_day) + to_dt = datetime(today.year, today.month, last_day) + params = dict(self.params) + params.update({ + 'lang': 'en-us', + 'usertz': get_localzone().zone, + 'startDate': from_dt.strftime('%Y-%m-%d'), + 'endDate': to_dt.strftime('%Y-%m-%d') + }) + req = self.session.get(self._calendars, params=params) + self.response = req.json() + return self.response['Collection'] From abbe2368125f6ae25b226c555e199c6bcd21889d Mon Sep 17 00:00:00 2001 From: Maciej Buchert Date: Tue, 15 May 2018 22:15:41 +0200 Subject: [PATCH 2/8] Update calendar.py --- pyicloud/services/calendar.py | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pyicloud/services/calendar.py b/pyicloud/services/calendar.py index 4e42a08d..f10990a9 100644 --- a/pyicloud/services/calendar.py +++ b/pyicloud/services/calendar.py @@ -62,21 +62,21 @@ def events(self, from_dt=None, to_dt=None): self.refresh_client(from_dt, to_dt) return self.response['Event'] - def calendars(self): - """ - Retrieves calendars for this month - """ - today = datetime.today() - first_day, last_day = monthrange(today.year, today.month) - from_dt = datetime(today.year, today.month, first_day) - to_dt = datetime(today.year, today.month, last_day) - params = dict(self.params) - params.update({ - 'lang': 'en-us', - 'usertz': get_localzone().zone, - 'startDate': from_dt.strftime('%Y-%m-%d'), - 'endDate': to_dt.strftime('%Y-%m-%d') - }) - req = self.session.get(self._calendars, params=params) - self.response = req.json() - return self.response['Collection'] + def calendars(self): + """ + Retrieves calendars for this month + """ + today = datetime.today() + first_day, last_day = monthrange(today.year, today.month) + from_dt = datetime(today.year, today.month, first_day) + to_dt = datetime(today.year, today.month, last_day) + params = dict(self.params) + params.update({ + 'lang': 'en-us', + 'usertz': get_localzone().zone, + 'startDate': from_dt.strftime('%Y-%m-%d'), + 'endDate': to_dt.strftime('%Y-%m-%d') + }) + req = self.session.get(self._calendars, params=params) + self.response = req.json() + return self.response['Collection'] From bbee61f566e22a20370dccdfd6433f6f56ce2b82 Mon Sep 17 00:00:00 2001 From: Maciej Buchert Date: Wed, 16 May 2018 06:10:06 +0200 Subject: [PATCH 3/8] Update calendar.py From 3e912d8ccb0f0c4b25ae49bea5b4f5dc49be6886 Mon Sep 17 00:00:00 2001 From: Maciej Buchert Date: Sun, 20 May 2018 17:22:50 +0200 Subject: [PATCH 4/8] Update calendar.py --- pyicloud/services/calendar.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pyicloud/services/calendar.py b/pyicloud/services/calendar.py index f10990a9..774eea16 100644 --- a/pyicloud/services/calendar.py +++ b/pyicloud/services/calendar.py @@ -63,20 +63,20 @@ def events(self, from_dt=None, to_dt=None): return self.response['Event'] def calendars(self): - """ - Retrieves calendars for this month - """ - today = datetime.today() - first_day, last_day = monthrange(today.year, today.month) - from_dt = datetime(today.year, today.month, first_day) - to_dt = datetime(today.year, today.month, last_day) - params = dict(self.params) - params.update({ - 'lang': 'en-us', - 'usertz': get_localzone().zone, - 'startDate': from_dt.strftime('%Y-%m-%d'), - 'endDate': to_dt.strftime('%Y-%m-%d') - }) - req = self.session.get(self._calendars, params=params) - self.response = req.json() - return self.response['Collection'] + """ + Retrieves calendars for this month + """ + today = datetime.today() + first_day, last_day = monthrange(today.year, today.month) + from_dt = datetime(today.year, today.month, first_day) + to_dt = datetime(today.year, today.month, last_day) + params = dict(self.params) + params.update({ + 'lang': 'en-us', + 'usertz': get_localzone().zone, + 'startDate': from_dt.strftime('%Y-%m-%d'), + 'endDate': to_dt.strftime('%Y-%m-%d') + }) + req = self.session.get(self._calendars, params=params) + self.response = req.json() + return self.response['Collection'] From b7401940c7bfe9ccc869bd92d6639d468d7429d3 Mon Sep 17 00:00:00 2001 From: Maciej Buchert Date: Sun, 20 May 2018 17:27:38 +0200 Subject: [PATCH 5/8] Update calendar.py --- pyicloud/services/calendar.py | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pyicloud/services/calendar.py b/pyicloud/services/calendar.py index 774eea16..08cdd761 100644 --- a/pyicloud/services/calendar.py +++ b/pyicloud/services/calendar.py @@ -19,7 +19,7 @@ def __init__(self, service_root, session, params): self._calendar_event_detail_url = '%s/eventdetail' % ( self._calendar_endpoint, ) - self._calendars = '%s/startup' % self._calendar_endpoint + self._calendars = '%s/startup' % self._calendar_endpoint def get_event_detail(self, pguid, guid): """ @@ -63,20 +63,20 @@ def events(self, from_dt=None, to_dt=None): return self.response['Event'] def calendars(self): - """ - Retrieves calendars for this month - """ - today = datetime.today() - first_day, last_day = monthrange(today.year, today.month) - from_dt = datetime(today.year, today.month, first_day) - to_dt = datetime(today.year, today.month, last_day) - params = dict(self.params) - params.update({ - 'lang': 'en-us', - 'usertz': get_localzone().zone, - 'startDate': from_dt.strftime('%Y-%m-%d'), - 'endDate': to_dt.strftime('%Y-%m-%d') - }) - req = self.session.get(self._calendars, params=params) - self.response = req.json() - return self.response['Collection'] + """ + Retrieves calendars for this month + """ + today = datetime.today() + first_day, last_day = monthrange(today.year, today.month) + from_dt = datetime(today.year, today.month, first_day) + to_dt = datetime(today.year, today.month, last_day) + params = dict(self.params) + params.update({ + 'lang': 'en-us', + 'usertz': get_localzone().zone, + 'startDate': from_dt.strftime('%Y-%m-%d'), + 'endDate': to_dt.strftime('%Y-%m-%d') + }) + req = self.session.get(self._calendars, params=params) + self.response = req.json() + return self.response['Collection'] From fa644415a4f8ad21703810672da8c816f40d347f Mon Sep 17 00:00:00 2001 From: Nathan Broadbent Date: Sun, 22 Jul 2018 03:32:52 +0700 Subject: [PATCH 6/8] Fix 'ValueError: year is out of range' when year is set to 0000. Just set the time to the earliest valid time (in 1970) --- pyicloud/services/photos.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyicloud/services/photos.py b/pyicloud/services/photos.py index 2b83071d..47d2e37e 100644 --- a/pyicloud/services/photos.py +++ b/pyicloud/services/photos.py @@ -452,9 +452,12 @@ def created(self): @property def asset_date(self): - dt = datetime.fromtimestamp( - self._asset_record['fields']['assetDate']['value'] / 1000.0, - tz=pytz.utc) + try: + dt = datetime.fromtimestamp( + self._asset_record['fields']['assetDate']['value'] / 1000.0, + tz=pytz.utc) + except: + dt = datetime.fromtimestamp(0) return dt @property From 602ef0fbf5e14d1ae583742de4e36cef55082866 Mon Sep 17 00:00:00 2001 From: Nathan Broadbent Date: Tue, 31 Jul 2018 01:16:56 +0700 Subject: [PATCH 7/8] Allow client_id to be overridden in __init__ (for replayable HTTP requests in pyvcr tests) --- pyicloud/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyicloud/base.py b/pyicloud/base.py index 0ad84e5a..81d77077 100644 --- a/pyicloud/base.py +++ b/pyicloud/base.py @@ -134,13 +134,14 @@ class PyiCloudService(object): """ def __init__( - self, apple_id, password=None, cookie_directory=None, verify=True + self, apple_id, password=None, cookie_directory=None, verify=True, + client_id=None ): if password is None: password = get_password_from_keyring(apple_id) self.data = {} - self.client_id = str(uuid.uuid1()).upper() + self.client_id = client_id or str(uuid.uuid1()).upper() self.user = {'apple_id': apple_id, 'password': password} self._password_filter = PyiCloudPasswordFilter(password) From 6bf11c87c784fa9b0f94318ea845e441b0937cf5 Mon Sep 17 00:00:00 2001 From: PeterHedleyJHA Date: Wed, 12 Jun 2019 14:41:36 +0100 Subject: [PATCH 8/8] updated credentials --- pyicloud/base.py | 22 +++- pyicloud/hack.py | 306 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 pyicloud/hack.py diff --git a/pyicloud/base.py b/pyicloud/base.py index 81d77077..095960dd 100644 --- a/pyicloud/base.py +++ b/pyicloud/base.py @@ -26,6 +26,7 @@ AccountService ) from pyicloud.utils import get_password_from_keyring +import pyicloud.hack as hack if six.PY3: import http.cookiejar as cookielib @@ -203,12 +204,27 @@ def authenticate(self): data = dict(self.user) # We authenticate every time, so "remember me" is not needed - data.update({'extended_login': False}) + + myICloud = hack.PyiCloudService() + sess_token = myICloud.get_session_token (self.user['apple_id'], + self.user['password'] + ) + + self.session.headers = { + 'Origin': 'https://www.icloud.com', + 'Referer': 'https://www.icloud.com/', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36', + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/javascript, */*; q=0.01' + } + data = {'accountCountryCode': "GBR", + 'extended_login': False, + 'dsWebAuthToken': sess_token + } try: req = self.session.post( - self._base_login_url, - params=self.params, + 'https://setup.icloud.com/setup/ws/1/accountLogin', data=json.dumps(data) ) except PyiCloudAPIResponseError as error: diff --git a/pyicloud/hack.py b/pyicloud/hack.py new file mode 100644 index 00000000..253d6b85 --- /dev/null +++ b/pyicloud/hack.py @@ -0,0 +1,306 @@ +import requests +from json import dumps as json +from uuid import uuid1 as generateClientID +import getpass +import pickle + +__author__ = "JiaJiunn Chiou" +__license__= "" +__version__= "0.0.2" +__status__ = "Prototype" + +class Pointer: + def __init__ (self, value): + try: + self.value = value.value + except: + self.value = value + + def __call__ (self, value=None): + if value == None: + return self.value + try: + self.value = value.value + except: + self.value = value + + +class HTTPService: + def __init__ (self, session, response=None, origin=None, referer=None): + self.userAgent = "Python (X11; Linux x86_64)" + try: + self.session = session.session + self.response = session.response + self.origin = session.origin + self.referer = session.referer + except: + session = session + self.response = response + self.origin = origin + self.referer = referer + + +class IdmsaAppleService(HTTPService): + def __init__ (self, session): + super(IdmsaAppleService, self).__init__(session) + self.url = "https://idmsa.apple.com" + self.urlAuth = self.url + "/appleauth/auth/signin?widgetKey=" + + self.appleSessionToken = None + + def requestAppleSessionToken (self, user, password, appleWidgetKey): + self.session.headers.update(self.getRequestHeader(appleWidgetKey)) + self.response.value = self.session.post(self.urlAuth + appleWidgetKey, + self.getRequestPayload(user, password)) + try: + self.appleSessionToken = self.response().headers["X-Apple-Session-Token"] + except Exception as e: + raise Exception("requestAppleSessionToken: Apple Session Token query failed", + self.urlAuth, repr(e)) + return self.appleSessionToken + + def getRequestHeader (self, appleWidgetKey): + if not appleWidgetKey: + raise NameError("getRequestHeader: clientID not found") + return { + "Accept": "application/json, text/javascript", + "Content-Type": "application/json", + "User-Agent": self.userAgent, + "X-Apple-Widget-Key": appleWidgetKey, + "X-Requested-With": "XMLHttpRequest", + "Origin": self.origin, + "Referer": self.referer, + } + + def getRequestPayload (self, user, password): + if not user: + raise NameError("getAuthenticationRequestPayload: user not found") + if not password: + raise NameError("getAuthenticationRequestPayload: password not found") + return json({ + "accountName": user, + "password": password, + "rememberMe": False, + }) + + +class SetupiCloudService(HTTPService): + def __init__ (self, session): + super(SetupiCloudService, self).__init__(session) + self.url = "https://setup.icloud.com/setup/ws/1" + self.urlKey = self.url + "/validate" + self.urlLogin = self.url + "/accountLogin" + + self.appleWidgetKey = None + self.cookies = None + self.dsid = None + + def requestAppleWidgetKey (self, clientID): + #self.urlBase + "/system/cloudos/16CHotfix21/en-us/javascript-packed.js" + self.session.headers.update(self.getRequestHeader()) + self.response.value = self.session.get(self.urlKey, params=self.getQueryParameters(clientID)) + try: + self.appleWidgetKey = self.findQyery(self.response().text, "widgetKey=") + except Exception as e: + raise Exception("requestAppletWidgetKey: Apple Widget Key query failed", + self.urlKey, repr(e)) + return self.appleWidgetKey + + def requestCookies (self, appleSessionToken, clientID): + self.session.headers.update(self.getRequestHeader()) + self.response.value = self.session.post(self.urlLogin, + self.getLoginRequestPayload(appleSessionToken), + params=self.getQueryParameters(clientID)) + try: + self.cookies = self.response().headers["Set-Cookie"] + except Exception as e: + raise Exception("requestCookies: Cookies query failed", + self.urlLogin, repr(e)) + try: + self.dsid = self.response().json()["dsInfo"]["dsid"] + except Exception as e: + raise Exception("requestCookies: dsid query failed", + self.urlLogin, repr(e)) + return self.cookies, self.dsid + + def findQyery (self, data, query): + response = '' + foundAt = data.find(query) + if foundAt == -1: + raise Exception("findQyery: " + query + " could not be found in data") + foundAt += len(query) + char = data[foundAt] + while char.isalnum(): + response += char + foundAt += 1 + char = data[foundAt] + return response + + + def getRequestHeader (self): + return { + "Accept": "*/*", + "Connection": "keep-alive", + "Content-Type": "text/plain", + "User-Agent": self.userAgent, + "Origin": self.origin, + "Referer": self.referer, + } + + def getQueryParameters (self, clientID): + if not clientID: + raise NameError("getQueryParameters: clientID not found") + return { + "clientBuildNumber": "16CHotfix21", + "clientID": clientID, + "clientMasteringNumber": "16CHotfix21", + } + + def getLoginRequestPayload (self, appleSessionToken): + if not appleSessionToken: + raise NameError("getLoginRequestPayload: X-Apple-ID-Session-Id not found") + return json({ + "dsWebAuthToken": appleSessionToken, + "extended_login": False, + }) + + +class ICloudWebService(HTTPService): + def __init__ (self, session): + super(ICloudWebService, self).__init__(session) + self.url = "https://www.icloud.com" + self.urlApp = self.url + "/applications" + + def requestReminderWidget(self, cookies, clientID, dsid): + self.response.value = self.session.get("https://p47-remindersws.icloud.com/rd/startup", #TODO: hardcoded! + params=self.getQueryParameters(clientID, dsid)) + self.reminderResponse = self.response() + + def getReminderLists (self): + reminderLists = [] + for collection in self.reminderResponse.json()["Collections"]: + reminderLists.append(collection["title"]) + return reminderLists + + def getCollectionGUID (self, title): + for collection in self.reminderResponse.json()["Collections"]: + if title == collection["title"]: + return collection["guid"] + + def getReminderList (self, collection): + reminderList = [] + print(self.getReminderLists()) + if collection not in self.getReminderLists(): + raise Exception("getReminderList: " + collection + " could not be found") #TODO: error handling full class + guid = self.getCollectionGUID(collection) + for reminder in self.reminderResponse.json()["Reminders"]: + if guid == reminder["pGuid"]: + reminderList.append(reminder["title"]) + return reminderList + + def getRequestHeader (self, cookies): + return { + "Accept": "*/*", + "Accpet-Encoding": "gzip, deflate, sdch", + "Connection": "keep-alive", + "Content-Type": "text/plain", + "Cookie": cookies, + "Origin": self.origin, + "Referer": self.referer, + "User-Agent": self.userAgent, + } + + def getQueryParameters (self, clientID, dsid): + return { + "clientBuildNumber": "16CProject50", + "clientId": clientID, + "clientMasteringNumber": "16C149", + "dsid": dsid, + "clientVersion": "4.0", + "lang": "en-gb", #TODO: hardcoded! + "usertz": "Europe/Madrid", #TODO: hardcoded! + } + + +class PyiCloudService (HTTPService): + def __init__ (self): + self.session = requests.Session() + self.session.verify = True + self.response = Pointer(None) + self.origin = "https://www.icloud.com" + self.referer = "https://www.icloud.com" + super(PyiCloudService, self).__init__(self) + + self._ESCAPE_CHAR = ",;&%!?|(){}[]~>*\'\"\\" + + self.clientID = self.generateClientID() + + self.idmsaApple = IdmsaAppleService(self) + self.setupiCloud = SetupiCloudService(self) + self.iCloudWeb = ICloudWebService(self) + self.iCloudResponse = None + + def login (self): + user = self.parseAccountName(input("User: ")) + password = getpass.getpass() + try: #Try to restore previous login + self.session.cookies.update(self.restoreCookies()) + except: + pass #TODO: raise something? + self.initSession(user, password) + + def initSession (self,user, password): #TODO: to much selfs! + self.clientID = self.generateClientID() + widgetKey = self.setupiCloud.requestAppleWidgetKey(self.clientID) + sessionToken = self.idmsaApple.requestAppleSessionToken(user, password, widgetKey) + self.cookies, self.dsid = self.setupiCloud.requestCookies(sessionToken, self.clientID) + self.iCloudResponse = self.response() + appsOrder = self.iCloudResponse.json()["appsOrder"] + webservices = self.iCloudResponse.json()["webservices"] + self.storeCookies() + + def get_session_token (self,user, password): #TODO: to much selfs! + self.clientID = self.generateClientID() + widgetKey = self.setupiCloud.requestAppleWidgetKey(self.clientID) + return self.idmsaApple.requestAppleSessionToken(user, password, widgetKey) + + def storeCookies (self): #TODO: path hardcoded + with open (".config/cookies", "wb") as cookieFile: + pickle.dump(self.session.cookies, cookieFile, pickle.HIGHEST_PROTOCOL) + + def restoreCookies (self): #TODO: path hardcoded + cookies = None + with open (".config/cookies", "rb") as cookieFile: + cookies = pickle.load(cookieFile) + return cookies + + def generateClientID (self): + return str(generateClientID()).upper() + + def parseAccountName (self, accountName): + cleanAccountName = self.stripSpaces(self.cleanSpecialChar(accountName)) + if '@' not in cleanAccountName: + cleanAccountName += "@icloud.com" + return cleanAccountName + + + def cleanSpecialChar (self, text): + cleanText = text + for char in self._ESCAPE_CHAR: + cleanText = cleanText.replace(char, '') + return cleanText + + def stripSpaces (self, text): + return text.replace(' ', '').replace('\t', '') + +if __name__ == "__main__": + myICloud = PyiCloudService() + myICloud.login() + myICloud.iCloudWeb.requestReminderWidget(myICloud.cookies, myICloud.clientID, myICloud.dsid) + TODO_LIST = myICloud.iCloudWeb.getReminderList("TODO") + print(TODO_LIST) + +### TEST ### + #tPyiCloudService = PyiCloudService() + #tPyiCloudService.requestAppleWidgetKey() + #assert tPyiCloudService.appleWidgetKey == "83545bf919730e51dbfba24e7e8a78d2"