summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/leap/bitmask/crypto/srpauth.py956
-rw-r--r--src/leap/bitmask/crypto/srpregister.py104
2 files changed, 546 insertions, 514 deletions
diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py
index c2a5f158..fe177e5a 100644
--- a/src/leap/bitmask/crypto/srpauth.py
+++ b/src/leap/bitmask/crypto/srpauth.py
@@ -118,475 +118,516 @@ class SRPAuthNoSessionId(SRPAuthenticationError):
pass
-class SRPAuth(object):
+class SRPAuthImpl(object):
"""
- SRPAuth singleton
+ Implementation of the SRPAuth interface
"""
- class __impl(object):
+ LOGIN_KEY = "login"
+ A_KEY = "A"
+ CLIENT_AUTH_KEY = "client_auth"
+ SESSION_ID_KEY = "_session_id"
+ USER_VERIFIER_KEY = 'user[password_verifier]'
+ USER_SALT_KEY = 'user[password_salt]'
+ AUTHORIZATION_KEY = "Authorization"
+
+ def __init__(self, provider_config):
+ """
+ Constructor for SRPAuth implementation
+
+ :param provider_config: ProviderConfig needed to authenticate.
+ :type provider_config: ProviderConfig
"""
- Implementation of the SRPAuth interface
+ leap_assert(provider_config,
+ "We need a provider config to authenticate")
+
+ self._provider_config = provider_config
+ self._settings = Settings()
+
+ # **************************************************** #
+ # Dependency injection helpers, override this for more
+ # granular testing
+ self._fetcher = requests
+ self._srp = srp
+ self._hashfun = self._srp.SHA256
+ self._ng = self._srp.NG_1024
+ # **************************************************** #
+
+ self._reset_session()
+
+ self._session_id = None
+ self._session_id_lock = threading.Lock()
+ self._uuid = None
+ self._uuid_lock = threading.Lock()
+ self._token = None
+ self._token_lock = threading.Lock()
+
+ self._srp_user = None
+ self._srp_a = None
+
+ # User credentials stored for password changing checks
+ self._username = None
+ self._password = None
+
+ def _reset_session(self):
"""
+ Resets the current session and sets max retries to 30.
+ """
+ self._session = self._fetcher.session()
+ # We need to bump the default retries, otherwise logout
+ # fails most of the times
+ # NOTE: This is a workaround for the moment, the server
+ # side seems to return correctly every time, but it fails
+ # on the client end.
+ if requests_has_max_retries:
+ adapter = HTTPAdapter(max_retries=30)
+ else:
+ adapter = HTTPAdapter()
+ self._session.mount('https://', adapter)
+
+ def _safe_unhexlify(self, val):
+ """
+ Rounds the val to a multiple of 2 and returns the
+ unhexlified value
- LOGIN_KEY = "login"
- A_KEY = "A"
- CLIENT_AUTH_KEY = "client_auth"
- SESSION_ID_KEY = "_session_id"
- USER_VERIFIER_KEY = 'user[password_verifier]'
- USER_SALT_KEY = 'user[password_salt]'
- AUTHORIZATION_KEY = "Authorization"
+ :param val: hexlified value
+ :type val: str
- def __init__(self, provider_config, signaler=None):
- """
- Constructor for SRPAuth implementation
+ :rtype: binary hex data
+ :return: unhexlified val
+ """
+ return binascii.unhexlify(val) \
+ if (len(val) % 2 == 0) else binascii.unhexlify('0' + val)
- :param provider_config: ProviderConfig needed to authenticate.
- :type provider_config: ProviderConfig
- :param signaler: Signaler object used to receive notifications
- from the backend
- :type signaler: Signaler
- """
- leap_assert(provider_config,
- "We need a provider config to authenticate")
+ def _authentication_preprocessing(self, username, password):
+ """
+ Generates the SRP.User to get the A SRP parameter
- self._provider_config = provider_config
- self._signaler = signaler
- self._settings = Settings()
-
- # **************************************************** #
- # Dependency injection helpers, override this for more
- # granular testing
- self._fetcher = requests
- self._srp = srp
- self._hashfun = self._srp.SHA256
- self._ng = self._srp.NG_1024
- # **************************************************** #
-
- self._reset_session()
-
- self._session_id = None
- self._session_id_lock = threading.Lock()
- self._uuid = None
- self._uuid_lock = threading.Lock()
- self._token = None
- self._token_lock = threading.Lock()
-
- self._srp_user = None
- self._srp_a = None
+ :param username: username to login
+ :type username: str
+ :param password: password for the username
+ :type password: str
+ """
+ logger.debug("Authentication preprocessing...")
- # User credentials stored for password changing checks
- self._username = None
- self._password = None
+ self._srp_user = self._srp.User(username.encode('utf-8'),
+ password.encode('utf-8'),
+ self._hashfun, self._ng)
+ _, A = self._srp_user.start_authentication()
- def _reset_session(self):
- """
- Resets the current session and sets max retries to 30.
- """
- self._session = self._fetcher.session()
- # We need to bump the default retries, otherwise logout
- # fails most of the times
- # NOTE: This is a workaround for the moment, the server
- # side seems to return correctly every time, but it fails
- # on the client end.
- if requests_has_max_retries:
- adapter = HTTPAdapter(max_retries=30)
- else:
- adapter = HTTPAdapter()
- self._session.mount('https://', adapter)
+ self._srp_a = A
- def _safe_unhexlify(self, val):
- """
- Rounds the val to a multiple of 2 and returns the
- unhexlified value
+ def _start_authentication(self, _, username):
+ """
+ Sends the first request for authentication to retrieve the
+ salt and B parameter
+
+ Might raise all SRPAuthenticationError based:
+ SRPAuthenticationError
+ SRPAuthConnectionError
+ SRPAuthBadStatusCode
+ SRPAuthNoSalt
+ SRPAuthNoB
+
+ :param _: IGNORED, output from the previous callback (None)
+ :type _: IGNORED
+ :param username: username to login
+ :type username: str
- :param val: hexlified value
- :type val: str
+ :return: salt and B parameters
+ :rtype: tuple
+ """
+ logger.debug("Starting authentication process...")
+ try:
+ auth_data = {
+ self.LOGIN_KEY: username,
+ self.A_KEY: binascii.hexlify(self._srp_a)
+ }
+ sessions_url = "%s/%s/%s/" % \
+ (self._provider_config.get_api_uri(),
+ self._provider_config.get_api_version(),
+ "sessions")
+
+ ca_cert_path = self._provider_config.get_ca_cert_path()
+ ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding())
+
+ init_session = self._session.post(sessions_url,
+ data=auth_data,
+ verify=ca_cert_path,
+ timeout=REQUEST_TIMEOUT)
+ # Clean up A value, we don't need it anymore
+ self._srp_a = None
+ except requests.exceptions.ConnectionError as e:
+ logger.error("No connection made (salt): {0!r}".format(e))
+ raise SRPAuthConnectionError()
+ except Exception as e:
+ logger.error("Unknown error: %r" % (e,))
+ raise SRPAuthenticationError()
- :rtype: binary hex data
- :return: unhexlified val
- """
- return binascii.unhexlify(val) \
- if (len(val) % 2 == 0) else binascii.unhexlify('0' + val)
+ content, mtime = reqhelper.get_content(init_session)
- def _authentication_preprocessing(self, username, password):
- """
- Generates the SRP.User to get the A SRP parameter
+ if init_session.status_code not in (200,):
+ logger.error("No valid response (salt): "
+ "Status code = %r. Content: %r" %
+ (init_session.status_code, content))
+ if init_session.status_code == 422:
+ logger.error("Invalid username or password.")
+ raise SRPAuthBadUserOrPassword()
- :param username: username to login
- :type username: str
- :param password: password for the username
- :type password: str
- """
- logger.debug("Authentication preprocessing...")
+ logger.error("There was a problem with authentication.")
+ raise SRPAuthBadStatusCode()
- self._srp_user = self._srp.User(username.encode('utf-8'),
- password.encode('utf-8'),
- self._hashfun, self._ng)
- _, A = self._srp_user.start_authentication()
+ json_content = json.loads(content)
+ salt = json_content.get("salt", None)
+ B = json_content.get("B", None)
- self._srp_a = A
+ if salt is None:
+ logger.error("The server didn't send the salt parameter.")
+ raise SRPAuthNoSalt()
+ if B is None:
+ logger.error("The server didn't send the B parameter.")
+ raise SRPAuthNoB()
- def _start_authentication(self, _, username):
- """
- Sends the first request for authentication to retrieve the
- salt and B parameter
+ return salt, B
- Might raise all SRPAuthenticationError based:
- SRPAuthenticationError
- SRPAuthConnectionError
- SRPAuthBadStatusCode
- SRPAuthNoSalt
- SRPAuthNoB
+ def _process_challenge(self, salt_B, username):
+ """
+ Given the salt and B processes the auth challenge and
+ generates the M2 parameter
+
+ Might raise SRPAuthenticationError based:
+ SRPAuthenticationError
+ SRPAuthBadDataFromServer
+ SRPAuthConnectionError
+ SRPAuthJSONDecodeError
+ SRPAuthBadUserOrPassword
+
+ :param salt_B: salt and B parameters for the username
+ :type salt_B: tuple
+ :param username: username for this session
+ :type username: str
- :param _: IGNORED, output from the previous callback (None)
- :type _: IGNORED
- :param username: username to login
- :type username: str
+ :return: the M2 SRP parameter
+ :rtype: str
+ """
+ logger.debug("Processing challenge...")
+ try:
+ salt, B = salt_B
+ unhex_salt = self._safe_unhexlify(salt)
+ unhex_B = self._safe_unhexlify(B)
+ except (TypeError, ValueError) as e:
+ logger.error("Bad data from server: %r" % (e,))
+ raise SRPAuthBadDataFromServer()
+ M = self._srp_user.process_challenge(unhex_salt, unhex_B)
+
+ auth_url = "%s/%s/%s/%s" % (self._provider_config.get_api_uri(),
+ self._provider_config.
+ get_api_version(),
+ "sessions",
+ username)
+
+ auth_data = {
+ self.CLIENT_AUTH_KEY: binascii.hexlify(M)
+ }
- :return: salt and B parameters
- :rtype: tuple
- """
- logger.debug("Starting authentication process...")
- try:
- auth_data = {
- self.LOGIN_KEY: username,
- self.A_KEY: binascii.hexlify(self._srp_a)
- }
- sessions_url = "%s/%s/%s/" % \
- (self._provider_config.get_api_uri(),
- self._provider_config.get_api_version(),
- "sessions")
-
- ca_cert_path = self._provider_config.get_ca_cert_path()
- ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding())
-
- init_session = self._session.post(sessions_url,
- data=auth_data,
- verify=ca_cert_path,
- timeout=REQUEST_TIMEOUT)
- # Clean up A value, we don't need it anymore
- self._srp_a = None
- except requests.exceptions.ConnectionError as e:
- logger.error("No connection made (salt): {0!r}".format(e))
- raise SRPAuthConnectionError()
- except Exception as e:
- logger.error("Unknown error: %r" % (e,))
- raise SRPAuthenticationError()
-
- content, mtime = reqhelper.get_content(init_session)
-
- if init_session.status_code not in (200,):
- logger.error("No valid response (salt): "
- "Status code = %r. Content: %r" %
- (init_session.status_code, content))
- if init_session.status_code == 422:
- logger.error("Invalid username or password.")
- raise SRPAuthBadUserOrPassword()
-
- logger.error("There was a problem with authentication.")
- raise SRPAuthBadStatusCode()
-
- json_content = json.loads(content)
- salt = json_content.get("salt", None)
- B = json_content.get("B", None)
-
- if salt is None:
- logger.error("The server didn't send the salt parameter.")
- raise SRPAuthNoSalt()
- if B is None:
- logger.error("The server didn't send the B parameter.")
- raise SRPAuthNoB()
-
- return salt, B
-
- def _process_challenge(self, salt_B, username):
- """
- Given the salt and B processes the auth challenge and
- generates the M2 parameter
-
- Might raise SRPAuthenticationError based:
- SRPAuthenticationError
- SRPAuthBadDataFromServer
- SRPAuthConnectionError
- SRPAuthJSONDecodeError
- SRPAuthBadUserOrPassword
-
- :param salt_B: salt and B parameters for the username
- :type salt_B: tuple
- :param username: username for this session
- :type username: str
-
- :return: the M2 SRP parameter
- :rtype: str
- """
- logger.debug("Processing challenge...")
- try:
- salt, B = salt_B
- unhex_salt = self._safe_unhexlify(salt)
- unhex_B = self._safe_unhexlify(B)
- except (TypeError, ValueError) as e:
- logger.error("Bad data from server: %r" % (e,))
- raise SRPAuthBadDataFromServer()
- M = self._srp_user.process_challenge(unhex_salt, unhex_B)
-
- auth_url = "%s/%s/%s/%s" % (self._provider_config.get_api_uri(),
- self._provider_config.
- get_api_version(),
- "sessions",
- username)
+ try:
+ auth_result = self._session.put(auth_url,
+ data=auth_data,
+ verify=self._provider_config.
+ get_ca_cert_path(),
+ timeout=REQUEST_TIMEOUT)
+ except requests.exceptions.ConnectionError as e:
+ logger.error("No connection made (HAMK): %r" % (e,))
+ raise SRPAuthConnectionError()
- auth_data = {
- self.CLIENT_AUTH_KEY: binascii.hexlify(M)
- }
+ try:
+ content, mtime = reqhelper.get_content(auth_result)
+ except JSONDecodeError:
+ logger.error("Bad JSON content in auth result.")
+ raise SRPAuthJSONDecodeError()
+ if auth_result.status_code == 422:
+ error = ""
try:
- auth_result = self._session.put(auth_url,
- data=auth_data,
- verify=self._provider_config.
- get_ca_cert_path(),
- timeout=REQUEST_TIMEOUT)
- except requests.exceptions.ConnectionError as e:
- logger.error("No connection made (HAMK): %r" % (e,))
- raise SRPAuthConnectionError()
+ error = json.loads(content).get("errors", "")
+ except ValueError:
+ logger.error("Problem parsing the received response: %s"
+ % (content,))
+ except AttributeError:
+ logger.error("Expecting a dict but something else was "
+ "received: %s", (content,))
+ logger.error("[%s] Wrong password (HAMK): [%s]" %
+ (auth_result.status_code, error))
+ raise SRPAuthBadUserOrPassword()
+
+ if auth_result.status_code not in (200,):
+ logger.error("No valid response (HAMK): "
+ "Status code = %s. Content = %r" %
+ (auth_result.status_code, content))
+ raise SRPAuthBadStatusCode()
+
+ return json.loads(content)
+
+ def _extract_data(self, json_content):
+ """
+ Extracts the necessary parameters from json_content (M2,
+ id, token)
- try:
- content, mtime = reqhelper.get_content(auth_result)
- except JSONDecodeError:
- logger.error("Bad JSON content in auth result.")
- raise SRPAuthJSONDecodeError()
-
- if auth_result.status_code == 422:
- error = ""
- try:
- error = json.loads(content).get("errors", "")
- except ValueError:
- logger.error("Problem parsing the received response: %s"
- % (content,))
- except AttributeError:
- logger.error("Expecting a dict but something else was "
- "received: %s", (content,))
- logger.error("[%s] Wrong password (HAMK): [%s]" %
- (auth_result.status_code, error))
- raise SRPAuthBadUserOrPassword()
+ Might raise SRPAuthenticationError based:
+ SRPBadDataFromServer
- if auth_result.status_code not in (200,):
- logger.error("No valid response (HAMK): "
- "Status code = %s. Content = %r" %
- (auth_result.status_code, content))
- raise SRPAuthBadStatusCode()
+ :param json_content: Data received from the server
+ :type json_content: dict
+ """
+ try:
+ M2 = json_content.get("M2", None)
+ uuid = json_content.get("id", None)
+ token = json_content.get("token", None)
+ except Exception as e:
+ logger.error(e)
+ raise SRPAuthBadDataFromServer()
- return json.loads(content)
+ self.set_uuid(uuid)
+ self.set_token(token)
- def _extract_data(self, json_content):
- """
- Extracts the necessary parameters from json_content (M2,
- id, token)
+ if M2 is None or self.get_uuid() is None:
+ logger.error("Something went wrong. Content = %r" %
+ (json_content,))
+ raise SRPAuthBadDataFromServer()
- Might raise SRPAuthenticationError based:
- SRPBadDataFromServer
+ events_signal(
+ proto.CLIENT_UID, content=uuid,
+ reqcbk=lambda req, res: None) # make the rpc call async
- :param json_content: Data received from the server
- :type json_content: dict
- """
- try:
- M2 = json_content.get("M2", None)
- uuid = json_content.get("id", None)
- token = json_content.get("token", None)
- except Exception as e:
- logger.error(e)
- raise SRPAuthBadDataFromServer()
+ return M2
- self.set_uuid(uuid)
- self.set_token(token)
+ def _verify_session(self, M2):
+ """
+ Verifies the session based on the M2 parameter. If the
+ verification succeeds, it sets the session_id for this
+ session
- if M2 is None or self.get_uuid() is None:
- logger.error("Something went wrong. Content = %r" %
- (json_content,))
- raise SRPAuthBadDataFromServer()
+ Might raise SRPAuthenticationError based:
+ SRPAuthBadDataFromServer
+ SRPAuthVerificationFailed
- events_signal(
- proto.CLIENT_UID, content=uuid,
- reqcbk=lambda req, res: None) # make the rpc call async
+ :param M2: M2 SRP parameter
+ :type M2: str
+ """
+ logger.debug("Verifying session...")
+ try:
+ unhex_M2 = self._safe_unhexlify(M2)
+ except TypeError:
+ logger.error("Bad data from server (HAWK)")
+ raise SRPAuthBadDataFromServer()
- return M2
+ self._srp_user.verify_session(unhex_M2)
- def _verify_session(self, M2):
- """
- Verifies the session based on the M2 parameter. If the
- verification succeeds, it sets the session_id for this
- session
+ if not self._srp_user.authenticated():
+ logger.error("Auth verification failed.")
+ raise SRPAuthVerificationFailed()
+ logger.debug("Session verified.")
- Might raise SRPAuthenticationError based:
- SRPAuthBadDataFromServer
- SRPAuthVerificationFailed
+ session_id = self._session.cookies.get(self.SESSION_ID_KEY, None)
+ if not session_id:
+ logger.error("Bad cookie from server (missing _session_id)")
+ raise SRPAuthNoSessionId()
- :param M2: M2 SRP parameter
- :type M2: str
- """
- logger.debug("Verifying session...")
- try:
- unhex_M2 = self._safe_unhexlify(M2)
- except TypeError:
- logger.error("Bad data from server (HAWK)")
- raise SRPAuthBadDataFromServer()
+ events_signal(
+ proto.CLIENT_SESSION_ID, content=session_id,
+ reqcbk=lambda req, res: None) # make the rpc call asynch
- self._srp_user.verify_session(unhex_M2)
+ self.set_session_id(session_id)
+ logger.debug("SUCCESS LOGIN")
+ return True
- if not self._srp_user.authenticated():
- logger.error("Auth verification failed.")
- raise SRPAuthVerificationFailed()
- logger.debug("Session verified.")
+ def _threader(self, cb, res, *args, **kwargs):
+ return threads.deferToThread(cb, res, *args, **kwargs)
- session_id = self._session.cookies.get(self.SESSION_ID_KEY, None)
- if not session_id:
- logger.error("Bad cookie from server (missing _session_id)")
- raise SRPAuthNoSessionId()
+ def _change_password(self, current_password, new_password):
+ """
+ Changes the password for the currently logged user if the current
+ password match.
+ It requires to be authenticated.
- events_signal(
- proto.CLIENT_SESSION_ID, content=session_id,
- reqcbk=lambda req, res: None) # make the rpc call async
+ Might raise:
+ SRPAuthBadUserOrPassword
+ requests.exceptions.HTTPError
- self.set_session_id(session_id)
+ :param current_password: the current password for the logged user.
+ :type current_password: str
+ :param new_password: the new password for the user
+ :type new_password: str
+ """
+ leap_assert(self.get_uuid() is not None)
+
+ if current_password != self._password:
+ raise SRPAuthBadUserOrPassword
+
+ url = "%s/%s/users/%s.json" % (
+ self._provider_config.get_api_uri(),
+ self._provider_config.get_api_version(),
+ self.get_uuid())
+
+ salt, verifier = self._srp.create_salted_verification_key(
+ self._username.encode('utf-8'), new_password.encode('utf-8'),
+ self._hashfun, self._ng)
+
+ cookies = {self.SESSION_ID_KEY: self.get_session_id()}
+ headers = {
+ self.AUTHORIZATION_KEY:
+ "Token token={0}".format(self.get_token())
+ }
+ user_data = {
+ self.USER_VERIFIER_KEY: binascii.hexlify(verifier),
+ self.USER_SALT_KEY: binascii.hexlify(salt)
+ }
+
+ change_password = self._session.put(
+ url, data=user_data,
+ verify=self._provider_config.get_ca_cert_path(),
+ cookies=cookies,
+ timeout=REQUEST_TIMEOUT,
+ headers=headers)
+
+ # In case of non 2xx it raises HTTPError
+ change_password.raise_for_status()
+
+ self._password = new_password
- def _threader(self, cb, res, *args, **kwargs):
- return threads.deferToThread(cb, res, *args, **kwargs)
+ def change_password(self, current_password, new_password):
+ """
+ Changes the password for the currently logged user if the current
+ password match.
+ It requires to be authenticated.
- def _change_password(self, current_password, new_password):
- """
- Changes the password for the currently logged user if the current
- password match.
- It requires to be authenticated.
+ :param current_password: the current password for the logged user.
+ :type current_password: str
+ :param new_password: the new password for the user
+ :type new_password: str
+ """
+ d = threads.deferToThread(
+ self._change_password, current_password, new_password)
+ return d
- Might raise:
- SRPAuthBadUserOrPassword
- requests.exceptions.HTTPError
+ def authenticate(self, username, password):
+ """
+ Executes the whole authentication process for a user
- :param current_password: the current password for the logged user.
- :type current_password: str
- :param new_password: the new password for the user
- :type new_password: str
- """
- leap_assert(self.get_uuid() is not None)
+ Might raise SRPAuthenticationError
- if current_password != self._password:
- raise SRPAuthBadUserOrPassword
+ :param username: username for this session
+ :type username: unicode
+ :param password: password for this user
+ :type password: unicode
- url = "%s/%s/users/%s.json" % (
- self._provider_config.get_api_uri(),
- self._provider_config.get_api_version(),
- self.get_uuid())
+ :returns: A defer on a different thread
+ :rtype: twisted.internet.defer.Deferred
+ """
+ leap_assert(self.get_session_id() is None, "Already logged in")
- salt, verifier = self._srp.create_salted_verification_key(
- self._username.encode('utf-8'), new_password.encode('utf-8'),
- self._hashfun, self._ng)
+ # User credentials stored for password changing checks
+ self._username = username
+ self._password = password
- cookies = {self.SESSION_ID_KEY: self.get_session_id()}
- headers = {
- self.AUTHORIZATION_KEY:
- "Token token={0}".format(self.get_token())
- }
- user_data = {
- self.USER_VERIFIER_KEY: binascii.hexlify(verifier),
- self.USER_SALT_KEY: binascii.hexlify(salt)
- }
+ self._reset_session()
- change_password = self._session.put(
- url, data=user_data,
- verify=self._provider_config.get_ca_cert_path(),
- cookies=cookies,
- timeout=REQUEST_TIMEOUT,
- headers=headers)
+ d = threads.deferToThread(self._authentication_preprocessing,
+ username=username,
+ password=password)
+ d.addCallback(partial(self._start_authentication, username=username))
- # In case of non 2xx it raises HTTPError
- change_password.raise_for_status()
+ d.addCallback(partial(self._process_challenge, username=username))
+ d.addCallback(self._extract_data)
+ d.addCallback(self._verify_session)
+ return d
- self._password = new_password
+ def logout(self):
+ """
+ Logs out the current session.
+ Expects a session_id to exists, might raise AssertionError
+ """
+ logger.debug("Starting logout...")
- def change_password(self, current_password, new_password):
- """
- Changes the password for the currently logged user if the current
- password match.
- It requires to be authenticated.
+ if self.get_session_id() is None:
+ logger.debug("Already logged out")
+ return
- :param current_password: the current password for the logged user.
- :type current_password: str
- :param new_password: the new password for the user
- :type new_password: str
- """
- d = threads.deferToThread(
- self._change_password, current_password, new_password)
- d.addCallback(self._change_password_ok)
- d.addErrback(self._change_password_error)
+ logout_url = "%s/%s/%s/" % (self._provider_config.get_api_uri(),
+ self._provider_config.
+ get_api_version(),
+ "logout")
+ try:
+ self._session.delete(logout_url,
+ data=self.get_session_id(),
+ verify=self._provider_config.
+ get_ca_cert_path(),
+ timeout=REQUEST_TIMEOUT)
+ except Exception as e:
+ logger.warning("Something went wrong with the logout: %r" %
+ (e,))
+ raise
+ else:
+ self.set_session_id(None)
+ self.set_uuid(None)
+ self.set_token(None)
+ # Also reset the session
+ self._session = self._fetcher.session()
+ logger.debug("Successfully logged out.")
- def _change_password_ok(self, _):
- """
- Password change callback.
- """
- if self._signaler is not None:
- self._signaler.signal(self._signaler.srp_password_change_ok)
+ def set_session_id(self, session_id):
+ with self._session_id_lock:
+ self._session_id = session_id
- def _change_password_error(self, failure):
- """
- Password change errback.
- """
- logger.debug(
- "Error changing password. Failure: {0}".format(failure))
- if self._signaler is None:
- return
+ def get_session_id(self):
+ with self._session_id_lock:
+ return self._session_id
- if failure.check(SRPAuthBadUserOrPassword):
- self._signaler.signal(self._signaler.srp_password_change_badpw)
- else:
- self._signaler.signal(self._signaler.srp_password_change_error)
+ def set_uuid(self, uuid):
+ with self._uuid_lock:
+ full_uid = "%s@%s" % (
+ self._username, self._provider_config.get_domain())
+ if uuid is not None: # avoid removing the uuid from settings
+ self._settings.set_uuid(full_uid, uuid)
+ self._uuid = uuid
- def authenticate(self, username, password):
- """
- Executes the whole authentication process for a user
+ def get_uuid(self):
+ with self._uuid_lock:
+ return self._uuid
- Might raise SRPAuthenticationError
+ def set_token(self, token):
+ with self._token_lock:
+ self._token = token
- :param username: username for this session
- :type username: unicode
- :param password: password for this user
- :type password: unicode
+ def get_token(self):
+ with self._token_lock:
+ return self._token
- :returns: A defer on a different thread
- :rtype: twisted.internet.defer.Deferred
- """
- leap_assert(self.get_session_id() is None, "Already logged in")
-
- # User credentials stored for password changing checks
- self._username = username
- self._password = password
-
- self._reset_session()
-
- # FIXME ---------------------------------------------------------
- # 1. it makes no sense to defer each callback to a thread
- # 2. the decision to use threads should be at another level.
- # (although it's not really needed, that was a hack around
- # the gui blocks)
- # it makes very hard to test this. The __impl could be
- # separated and decoupled from the provider_config abstraction.
-
- d = threads.deferToThread(self._authentication_preprocessing,
- username=username,
- password=password)
-
- d.addCallback(
- partial(self._threader,
- self._start_authentication),
- username=username)
- d.addCallback(
- partial(self._threader,
- self._process_challenge),
- username=username)
- d.addCallback(
- partial(self._threader,
- self._extract_data))
- d.addCallback(partial(self._threader,
- self._verify_session))
+ def is_authenticated(self):
+ """
+ Return whether the user is authenticated or not.
+
+ :rtype: bool
+ """
+ user = self._srp_user
+ if user is not None:
+ return user.authenticated()
+
+ return False
+
+
+class SRPAuth(object):
+ """
+ SRPAuth singleton
+ """
+ class __impl(SRPAuthImpl):
+
+ def __init__(self, provider_config, signaler=None):
+ SRPAuthImpl.__init__(self, provider_config)
+ self._signaler = signaler
+ def authenticate(self, username, password):
+ d = SRPAuthImpl.authenticate(self, username, password)
d.addCallback(self._authenticate_ok)
d.addErrback(self._authenticate_error)
return d
@@ -630,82 +671,53 @@ class SRPAuth(object):
self._signaler.signal(signal)
- def logout(self):
+ def change_password(self, current_password, new_password):
+ """
+ Changes the password for the currently logged user if the current
+ password match.
+ It requires to be authenticated.
+
+ :param current_password: the current password for the logged user.
+ :type current_password: str
+ :param new_password: the new password for the user
+ :type new_password: str
"""
- Logs out the current session.
- Expects a session_id to exists, might raise AssertionError
+ d = SRPAuthImpl.change_password(self, current_password,
+ new_password)
+ d.addCallback(self._change_password_ok)
+ d.addErrback(self._change_password_error)
+ return d
+
+ def _change_password_ok(self, _):
"""
- logger.debug("Starting logout...")
+ Password change callback.
+ """
+ if self._signaler is not None:
+ self._signaler.signal(self._signaler.srp_password_change_ok)
- if self.get_session_id() is None:
- logger.debug("Already logged out")
+ def _change_password_error(self, failure):
+ """
+ Password change errback.
+ """
+ logger.debug(
+ "Error changing password. Failure: {0}".format(failure))
+ if self._signaler is None:
return
- logout_url = "%s/%s/%s/" % (self._provider_config.get_api_uri(),
- self._provider_config.
- get_api_version(),
- "logout")
+ if failure.check(SRPAuthBadUserOrPassword):
+ self._signaler.signal(self._signaler.srp_password_change_badpw)
+ else:
+ self._signaler.signal(self._signaler.srp_password_change_error)
+
+ def logout(self):
try:
- self._session.delete(logout_url,
- data=self.get_session_id(),
- verify=self._provider_config.
- get_ca_cert_path(),
- timeout=REQUEST_TIMEOUT)
- except Exception as e:
- logger.warning("Something went wrong with the logout: %r" %
- (e,))
+ SRPAuthImpl.logout(self)
+ if self._signaler is not None:
+ self._signaler.signal(self._signaler.srp_logout_ok)
+ except Exception:
if self._signaler is not None:
self._signaler.signal(self._signaler.srp_logout_error)
raise
- else:
- self.set_session_id(None)
- self.set_uuid(None)
- self.set_token(None)
- # Also reset the session
- self._session = self._fetcher.session()
- logger.debug("Successfully logged out.")
- if self._signaler is not None:
- self._signaler.signal(self._signaler.srp_logout_ok)
-
- def set_session_id(self, session_id):
- with self._session_id_lock:
- self._session_id = session_id
-
- def get_session_id(self):
- with self._session_id_lock:
- return self._session_id
-
- def set_uuid(self, uuid):
- with self._uuid_lock:
- full_uid = "%s@%s" % (
- self._username, self._provider_config.get_domain())
- if uuid is not None: # avoid removing the uuid from settings
- self._settings.set_uuid(full_uid, uuid)
- self._uuid = uuid
-
- def get_uuid(self):
- with self._uuid_lock:
- return self._uuid
-
- def set_token(self, token):
- with self._token_lock:
- self._token = token
-
- def get_token(self):
- with self._token_lock:
- return self._token
-
- def is_authenticated(self):
- """
- Return whether the user is authenticated or not.
-
- :rtype: bool
- """
- user = self._srp_user
- if user is not None:
- return user.authenticated()
-
- return False
__instance = None
diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py
index 86510de1..e3007b6c 100644
--- a/src/leap/bitmask/crypto/srpregister.py
+++ b/src/leap/bitmask/crypto/srpregister.py
@@ -33,40 +33,18 @@ from leap.common.check import leap_assert, leap_assert_type
logger = logging.getLogger(__name__)
-class SRPRegister(QtCore.QObject):
- """
- Registers a user to a specific provider using SRP
- """
+class SRPRegisterImpl:
USER_LOGIN_KEY = 'user[login]'
USER_VERIFIER_KEY = 'user[password_verifier]'
USER_SALT_KEY = 'user[password_salt]'
-
- STATUS_OK = (200, 201)
- STATUS_TAKEN = 422
STATUS_ERROR = -999 # Custom error status
- def __init__(self, signaler=None,
- provider_config=None, register_path="users"):
- """
- Constructor
-
- :param signaler: Signaler object used to receive notifications
- from the backend
- :type signaler: Signaler
- :param provider_config: provider configuration instance,
- properly loaded
- :type privider_config: ProviderConfig
- :param register_path: webapp path for registering users
- :type register_path; str
- """
- QtCore.QObject.__init__(self)
+ def __init__(self, provider_config, register_path):
leap_assert(provider_config, "Please provide a provider")
leap_assert_type(provider_config, ProviderConfig)
self._provider_config = provider_config
- self._signaler = signaler
-
# **************************************************** #
# Dependency injection helpers, override this for more
# granular testing
@@ -83,25 +61,8 @@ class SRPRegister(QtCore.QObject):
self._port = "443"
self._register_path = register_path
-
self._session = self._fetcher.session()
- def _get_registration_uri(self):
- """
- Returns the URI where the register request should be made for
- the provider
-
- :rtype: str
- """
-
- uri = "https://%s:%s/%s/%s" % (
- self._provider,
- self._port,
- self._provider_config.get_api_version(),
- self._register_path)
-
- return uri
-
def register_user(self, username, password):
"""
Registers a user with the validator based on the password provider
@@ -152,7 +113,6 @@ class SRPRegister(QtCore.QObject):
status_code = self.STATUS_ERROR
if req is not None:
status_code = req.status_code
- self._emit_result(status_code)
if not ok:
try:
@@ -165,6 +125,66 @@ class SRPRegister(QtCore.QObject):
except Exception as e:
logger.error("Unknown error: %r" % (e, ))
+ return ok, status_code
+
+ def _get_registration_uri(self):
+ """
+ Returns the URI where the register request should be made for
+ the provider
+
+ :rtype: str
+ """
+
+ uri = "https://%s:%s/%s/%s" % (
+ self._provider,
+ self._port,
+ self._provider_config.get_api_version(),
+ self._register_path)
+
+ return uri
+
+
+class SRPRegister(QtCore.QObject):
+ """
+ Registers a user to a specific provider using SRP
+ """
+
+ STATUS_OK = (200, 201)
+ STATUS_TAKEN = 422
+
+ def __init__(self, signaler=None,
+ provider_config=None, register_path="users"):
+ """
+ Constructor
+
+ :param signaler: Signaler object used to receive notifications
+ from the backend
+ :type signaler: Signaler
+ :param provider_config: provider configuration instance,
+ properly loaded
+ :type privider_config: ProviderConfig
+ :param register_path: webapp path for registering users
+ :type register_path; str
+ """
+ self._srp_register = SRPRegisterImpl(provider_config, register_path)
+ QtCore.QObject.__init__(self)
+
+ self._signaler = signaler
+
+ def register_user(self, username, password):
+ """
+ Registers a user with the validator based on the password provider
+
+ :param username: username to register
+ :type username: str
+ :param password: password for this username
+ :type password: str
+
+ :returns: if the registration went ok or not.
+ :rtype: bool
+ """
+ ok, status_code = self._srp_register.register_user(username, password)
+ self._emit_result(status_code)
return ok
def _emit_result(self, status_code):