From fce79669742b9e646784d1ec48cdf936ea73f2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Thu, 7 Jan 2016 22:43:13 +0100 Subject: [PATCH] Simplify login sequence There's no need to validate before authenticating, as we don't log in with extended_login=True, which means there are no persisted login cookies besides the one ensuring we only get a single e-mail. The id that refresh_validate used to generate is also not needed, as authentication with just the username and password works fine, and is what the icloud.com webapp also does. Finally, we can skip the second validate after authentication as the dsInfo/dsid is available through the response we get from the authentication. --- pyicloud/base.py | 55 ++++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/pyicloud/base.py b/pyicloud/base.py index f58d6cb..c1bb149 100644 --- a/pyicloud/base.py +++ b/pyicloud/base.py @@ -32,7 +32,7 @@ logger = logging.getLogger(__name__) class PyiCloudService(object): """ A base authentication class for the iCloud service. Handles the - validation and authentication required to access iCloud services. + authentication required to access iCloud services. Usage: from pyicloud import PyiCloudService @@ -48,7 +48,6 @@ class PyiCloudService(object): self._setup_endpoint = 'https://setup.icloud.com/setup/ws/1' self._base_login_url = '%s/login' % self._setup_endpoint - self._base_validate_url = '%s/validate' % self._setup_endpoint if cookie_directory: self._cookie_directory = os.path.expanduser( @@ -77,64 +76,42 @@ class PyiCloudService(object): # Most likely a pickled cookiejar from earlier versions pass - self.params = {} + self.params = { + 'clientBuildNumber': '14E45', + 'clientId': self.client_id, + } self.authenticate() - def refresh_validate(self): - """ - Queries the /validate endpoint and fetches two key values we need: - 1. "dsInfo" is a nested object which contains the "dsid" integer. - This object doesn't exist until *after* the login has taken place, - the first request will compain about a X-APPLE-WEBAUTH-TOKEN cookie - 2. "instance" is an int which is used to build the "id" query string. - This is, pseudo: sha1(email + "instance") to uppercase. - """ - req = self.session.get(self._base_validate_url, params=self.params) - resp = req.json() - if 'dsInfo' in resp: - dsid = resp['dsInfo']['dsid'] - self.params.update({'dsid': dsid}) - instance = resp.get( - 'instance', - uuid.uuid4().hex.encode('utf-8') - ) - sha = hashlib.sha1( - self.user.get('apple_id').encode('utf-8') + instance - ) - self.params.update({'id': sha.hexdigest().upper()}) - - self.params.update({ - 'clientBuildNumber': '14E45', - 'clientId': self.client_id, - }) - def authenticate(self): """ - Handles the full authentication steps, validating, - authenticating and then validating again. + Handles authentication, and persists the X-APPLE-WEB-KB cookie so that + subsequent logins will not cause additional e-mails from Apple. """ - self.refresh_validate() data = dict(self.user) - data.update({'id': self.params['id'], 'extended_login': False}) + + # We authenticate every time, so "remember me" is not needed + data.update({'extended_login': False}) + req = self.session.post( self._base_login_url, params=self.params, data=json.dumps(data) ) - if not req.ok: + resp = req.json() if req.ok else {} + if 'dsInfo' not in resp: msg = 'Invalid email/password combination.' raise PyiCloudFailedLoginException(msg) + self.params.update({'dsid': resp['dsInfo']['dsid']}) + if not os.path.exists(self._cookie_directory): os.mkdir(self._cookie_directory) self.session.cookies.save() - self.refresh_validate() - - self.discovery = req.json() + self.discovery = resp self.webservices = self.discovery['webservices'] def _get_cookiejar_path(self):