Clarify that we support two-step, not two-factor, authentication
Two-step authentication is an older security method used for accounts without an Apple device, or who are unable to upgrade to iOS 9 or OS X El Capitan. https://support.apple.com/en-us/HT204152 If the account has two-factor authentication enabled, we can still fall back to the end-points for two-step authentication, as we do not support 2FA yet. Issue #102
This commit is contained in:
parent
be3d447c00
commit
69af919ad5
4 changed files with 49 additions and 19 deletions
17
README.rst
17
README.rst
|
@ -36,17 +36,19 @@ If you would like to delete a password stored in your system keyring, you can cl
|
|||
|
||||
>>> icloud --username=jappleseed@apple.com --delete-from-keyring
|
||||
|
||||
*******************************
|
||||
Two-factor authentication (2FA)
|
||||
*******************************
|
||||
**Note**: Authentication will expire after an interval set by Apple, at which point you will have to re-authenticate. This interval is currently two months.
|
||||
|
||||
If you have enabled two-factor authentication for the account you will have to do some extra work:
|
||||
************************************************
|
||||
Two-step and two-factor authentication (2SA/2FA)
|
||||
************************************************
|
||||
|
||||
If you have enabled `two-step authentication (2SA) <https://support.apple.com/en-us/HT204152>`_ for the account you will have to do some extra work:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if api.requires_2fa:
|
||||
if api.requires_2sa:
|
||||
import click
|
||||
print "Two-factor authentication required. Your trusted devices are:"
|
||||
print "Two-step authentication required. Your trusted devices are:"
|
||||
|
||||
devices = api.trusted_devices
|
||||
for i, device in enumerate(devices):
|
||||
|
@ -64,7 +66,8 @@ If you have enabled two-factor authentication for the account you will have to d
|
|||
print "Failed to verify verification code"
|
||||
sys.exit(1)
|
||||
|
||||
Note: Both regular login and two-factor authentication will expire after an interval set by Apple, at which point you will have to re-authenticate. This interval is currently two months.
|
||||
This approach also works if the account is set up for `two-factor authentication (2FA) <https://support.apple.com/en-us/HT204915>`_, but the authentication will time out after a few hours. Full support for two-factor authentication (2FA) is not implemented in PyiCloud yet. See issue `#102 <https://github.com/picklepete/pyicloud/issues/102>`_.
|
||||
|
||||
|
||||
=======
|
||||
Devices
|
||||
|
|
|
@ -13,7 +13,7 @@ from re import match
|
|||
from pyicloud.exceptions import (
|
||||
PyiCloudFailedLoginException,
|
||||
PyiCloudAPIResponseError,
|
||||
PyiCloud2FARequiredError
|
||||
PyiCloud2SARequiredError
|
||||
)
|
||||
from pyicloud.services import (
|
||||
FindMyiPhoneServiceManager,
|
||||
|
@ -100,7 +100,7 @@ class PyiCloudSession(requests.Session):
|
|||
def _raise_error(self, code, reason):
|
||||
if self.service.requires_2fa and \
|
||||
reason == 'Missing X-APPLE-WEBAUTH-TOKEN cookie':
|
||||
raise PyiCloud2FARequiredError(response.url)
|
||||
raise PyiCloud2SARequiredError(response.url)
|
||||
|
||||
api_error = PyiCloudAPIResponseError(reason, code)
|
||||
logger.error(api_error)
|
||||
|
@ -217,13 +217,15 @@ class PyiCloudService(object):
|
|||
)
|
||||
|
||||
@property
|
||||
def requires_2fa(self):
|
||||
""" Returns True if two-factor authentication is required."""
|
||||
return self.data.get('hsaChallengeRequired', False)
|
||||
def requires_2sa(self):
|
||||
""" Returns True if two-step authentication is required."""
|
||||
return self.data.get('hsaChallengeRequired', False) \
|
||||
and self.data['dsInfo'].get('hsaVersion', 0) >= 1
|
||||
# FIXME: Implement 2FA for hsaVersion == 2
|
||||
|
||||
@property
|
||||
def trusted_devices(self):
|
||||
""" Returns devices trusted for two-factor authentication."""
|
||||
""" Returns devices trusted for two-step authentication."""
|
||||
request = self.session.get(
|
||||
'%s/listDevices' % self._setup_endpoint,
|
||||
params=self.params
|
||||
|
@ -241,7 +243,7 @@ class PyiCloudService(object):
|
|||
return request.json().get('success', False)
|
||||
|
||||
def validate_verification_code(self, device, code):
|
||||
""" Verifies a verification code received on a two-factor device"""
|
||||
""" Verifies a verification code received on a trusted device"""
|
||||
device.update({
|
||||
'verificationCode': code,
|
||||
'trustBrowser': True
|
||||
|
@ -260,11 +262,11 @@ class PyiCloudService(object):
|
|||
return False
|
||||
raise
|
||||
|
||||
# Re-authenticate, which will both update the 2FA data, and
|
||||
# Re-authenticate, which will both update the HSA data, and
|
||||
# ensure that we save the X-APPLE-WEBAUTH-HSA-TRUST cookie.
|
||||
self.authenticate()
|
||||
|
||||
return not self.requires_2fa
|
||||
return not self.requires_2sa
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
|
|
|
@ -207,6 +207,31 @@ def main(args=None):
|
|||
confirm("Save password in keyring? ")
|
||||
):
|
||||
utils.store_password_in_keyring(username, password)
|
||||
|
||||
if api.requires_2sa:
|
||||
import click
|
||||
print("Two-step authentication required.",
|
||||
"Your trusted devices are:")
|
||||
|
||||
devices = api.trusted_devices
|
||||
for i, device in enumerate(devices):
|
||||
print(" %s: %s" % (
|
||||
i, device.get(
|
||||
'deviceName',
|
||||
"SMS to %s" % device.get('phoneNumber'))))
|
||||
|
||||
device = click.prompt('Which device would you like to use?',
|
||||
default=0)
|
||||
device = devices[device]
|
||||
if not api.send_verification_code(device):
|
||||
print("Failed to send verification code")
|
||||
sys.exit(1)
|
||||
|
||||
code = click.prompt('Please enter validation code')
|
||||
if not api.validate_verification_code(device, code):
|
||||
print("Failed to verify verification code")
|
||||
sys.exit(1)
|
||||
|
||||
break
|
||||
except pyicloud.exceptions.PyiCloudFailedLoginException:
|
||||
# If they have a stored password; we just used it and
|
||||
|
|
|
@ -22,10 +22,10 @@ class PyiCloudFailedLoginException(PyiCloudException):
|
|||
pass
|
||||
|
||||
|
||||
class PyiCloud2FARequiredError(PyiCloudException):
|
||||
class PyiCloud2SARequiredError(PyiCloudException):
|
||||
def __init__(self, url):
|
||||
message = "Two-factor authentication required for %s" % url
|
||||
super(PyiCloud2FARequiredError, self).__init__(message)
|
||||
message = "Two-step authentication required for %s" % url
|
||||
super(PyiCloud2SARequiredError, self).__init__(message)
|
||||
|
||||
|
||||
class PyiCloudNoDevicesException(Exception):
|
||||
|
|
Loading…
Reference in a new issue