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.
This commit is contained in:
Tor Arne Vestbø 2016-01-07 22:43:13 +01:00
parent 3b3857d295
commit fce7966974

View file

@ -32,7 +32,7 @@ logger = logging.getLogger(__name__)
class PyiCloudService(object): class PyiCloudService(object):
""" """
A base authentication class for the iCloud service. Handles the 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: Usage:
from pyicloud import PyiCloudService from pyicloud import PyiCloudService
@ -48,7 +48,6 @@ class PyiCloudService(object):
self._setup_endpoint = 'https://setup.icloud.com/setup/ws/1' self._setup_endpoint = 'https://setup.icloud.com/setup/ws/1'
self._base_login_url = '%s/login' % self._setup_endpoint self._base_login_url = '%s/login' % self._setup_endpoint
self._base_validate_url = '%s/validate' % self._setup_endpoint
if cookie_directory: if cookie_directory:
self._cookie_directory = os.path.expanduser( self._cookie_directory = os.path.expanduser(
@ -77,64 +76,42 @@ class PyiCloudService(object):
# Most likely a pickled cookiejar from earlier versions # Most likely a pickled cookiejar from earlier versions
pass pass
self.params = {} self.params = {
'clientBuildNumber': '14E45',
'clientId': self.client_id,
}
self.authenticate() 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): def authenticate(self):
""" """
Handles the full authentication steps, validating, Handles authentication, and persists the X-APPLE-WEB-KB cookie so that
authenticating and then validating again. subsequent logins will not cause additional e-mails from Apple.
""" """
self.refresh_validate()
data = dict(self.user) 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( req = self.session.post(
self._base_login_url, self._base_login_url,
params=self.params, params=self.params,
data=json.dumps(data) 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.' msg = 'Invalid email/password combination.'
raise PyiCloudFailedLoginException(msg) raise PyiCloudFailedLoginException(msg)
self.params.update({'dsid': resp['dsInfo']['dsid']})
if not os.path.exists(self._cookie_directory): if not os.path.exists(self._cookie_directory):
os.mkdir(self._cookie_directory) os.mkdir(self._cookie_directory)
self.session.cookies.save() self.session.cookies.save()
self.refresh_validate() self.discovery = resp
self.discovery = req.json()
self.webservices = self.discovery['webservices'] self.webservices = self.discovery['webservices']
def _get_cookiejar_path(self): def _get_cookiejar_path(self):