Exemple #1
0
def generate_password(credential_id, user):
    """
    Generate a new password credential and add it to the VCCS authentication backend.

    The salt returned needs to be saved for use in subsequent authentications using
    this password. The password is returned so that it can be conveyed to the user.

    :param credential_id: VCCS credential_id as string
    :param user: user data as dict
    :return: (password, salt) both strings
    """
    user_id = str(user.user_id)
    config = current_app.config
    password = pwgen(int(config.get('PASSWORD_LENGTH')),
                     no_capitalize=True,
                     no_symbols=True)
    factor = vccs_client.VCCSPasswordFactor(password, credential_id)
    current_app.logger.info("Adding VCCS password factor for user {}, "
                            "credential_id {!r}".format(user, credential_id))

    vccs = vccs_client.VCCSClient(base_url=config.get('VCCS_URL'))
    try:
        result = vccs.add_credentials(user_id, [factor])
    except vccs_client.VCCSClientHTTPError as e:
        current_app.logger.error(
            'There was an error adding credentials for user {} '
            ': {!r}'.format(user, e))
        raise e
    current_app.logger.debug("VCCS password (id {!r}) creation result: "
                             "{!r}".format(credential_id, result))

    return _human_readable(password), factor.salt
Exemple #2
0
def provision_credentials(vccs_url, new_password, user):
    """
    This function should be used by tests only
    Provision new password to a user.

    Returns True on success.

    :param vccs_url: URL to VCCS authentication backend
    :param old_password: plaintext current password
    :param new_password: plaintext new password
    :param user: user object

    :type vccs_url: string
    :type old_password: string
    :type user: User
    :rtype: bool
    """
    password_id = ObjectId()
    vccs = get_vccs_client(vccs_url)
    new_factor = vccs_client.VCCSPasswordFactor(new_password,
                                                credential_id=str(password_id))

    if not vccs.add_credentials(str(user.user_id), [new_factor]):
        return False  # something failed

    new_password = Password(
        credential_id=password_id,
        salt=new_factor.salt,
        application='dashboard',
    )
    user.passwords.add(new_password)

    return True
 def test_add_creds1_utf8(self):
     """
     Test parsing of successful add_creds response with a password in UTF-8.
     """
     credential_id = '4711'
     userid = '*****@*****.**'
     password = '******'
     resp = {
         'add_creds_response': {
             'version': 1,
             'success': True,
         },
     }
     c = FakeVCCSClient(json.dumps(resp))
     f = vccs_client.VCCSPasswordFactor(password, credential_id,
                                        '$NDNv1H1$aaaaaaaaaaaaaaaa$12$32$')
     add_result = c.add_credentials(userid, [f])
     self.assertTrue(add_result)
     self.assertEqual(c.last_service, 'add_creds')
     values = json.loads(c.last_values['request'])
     expected = {
         'add_creds': {
             'version':
             1,
             'user_id':
             userid,
             'factors': [{
                 'credential_id': credential_id,
                 'H1': '80e6759a26bb9d439bc77d52',
                 'type': 'password'
             }],
         }
     }
     self.assertEqual(expected, values)
 def test_add_creds1(self):
     """
     Test parsing of successful add_creds response.
     """
     credential_id = '4711'
     userid = '*****@*****.**'
     password = '******'
     resp = {
         'add_creds_response': {
             'version': 1,
             'success': True,
         },
     }
     c = FakeVCCSClient(json.dumps(resp))
     f = vccs_client.VCCSPasswordFactor(password, credential_id,
                                        '$NDNv1H1$aaaaaaaaaaaaaaaa$12$32$')
     add_result = c.add_credentials(userid, [f])
     self.assertTrue(add_result)
     self.assertEqual(c.last_service, 'add_creds')
     values = json.loads(c.last_values['request'])
     expected = {
         'add_creds': {
             'version':
             1,
             'user_id':
             userid,
             'factors': [{
                 'credential_id': credential_id,
                 'H1': '6520c816376fd8ee6299ff31',
                 'type': 'password'
             }],
         }
     }
     self.assertEqual(expected, values)
Exemple #5
0
 def test_authn_expired_credential(self, mock_add_credentials,
                                   mock_authenticate):
     mock_add_credentials.return_value = False
     mock_authenticate.return_value = True
     assert isinstance(self.test_user, eduid_userdb.User)
     passwords = self.test_user.credentials.to_list()
     factor = vccs_client.VCCSPasswordFactor('foo',
                                             str(passwords[0].key),
                                             salt=passwords[0].salt)
     self.idp_app.authn.auth_client.add_credentials(
         str(self.test_user.user_id), [factor])
     data = {
         'username': self.test_user.mail_addresses.primary.email,
         'password': '******',
     }
     # Store a successful authentication using this credential three year ago
     three_years_ago = datetime.datetime.now() - datetime.timedelta(days=3 *
                                                                    365)
     self.idp_app.authn.authn_store.credential_success([passwords[0].key],
                                                       three_years_ago)
     with self.assertRaises(exceptions.EduidForbidden):
         self.assertTrue(self.idp_app.authn.password_authn(data))
     # Do the same thing again to make sure we didn't accidentally update the
     # 'last successful login' timestamp when it was a successful login with an
     # expired credential.
     with self.assertRaises(exceptions.EduidForbidden):
         self.assertTrue(self.idp_app.authn.password_authn(data))
Exemple #6
0
    def _authn_passwords(self, user, username, password, credentials):
        """
        Perform the final actual authentication of a user based on a list of (password) credentials.

        :param user: User object
        :param username: Username provided
        :param password: Password provided
        :param credentials: Authn credentials to try
        :return: User | None

        :type user: IdPUser
        :type username: string
        :type password: string
        :type credentials: [Password]
        :rtype: IdPUser | None
        """
        for cred in credentials:
            if isinstance(cred, Password):
                try:
                    factor = vccs_client.VCCSPasswordFactor(
                        password, str(cred.id), str(cred.salt))
                except ValueError as exc:
                    self.logger.info(
                        "User {!r} password factor {!s} unusable: {!r}".format(
                            username, cred.id, exc))
                    continue
                self.logger.debug(
                    "Password-authenticating {!r}/{!r} with VCCS: {!r}".format(
                        username, str(cred.id), factor))
                user_id = str(user.user_id)
                try:
                    if self.auth_client.authenticate(user_id, [factor]):
                        self.logger.debug(
                            "VCCS authenticated user {!r} (user_id {!r})".
                            format(user, user_id))
                        # Verify that the credential had been successfully used in the last 18 monthts
                        # (Kantara AL2_CM_CSM#050).
                        if self.credential_expired(cred):
                            self.logger.info(
                                'User {!r} credential {!s} has expired'.format(
                                    user, cred.key))
                            raise eduid_idp.error.Forbidden(
                                'CREDENTIAL_EXPIRED')
                        self.log_authn(user, success=[cred.id], failure=[])
                        return user
                except vccs_client.VCCSClientHTTPError as exc:
                    if exc.http_code == 500:
                        self.logger.debug(
                            "VCCS credential {!r} might be revoked".format(
                                cred.id))
                        continue
            else:
                self.logger.debug("Unknown credential: {!s}".format(cred))
        self.logger.debug(
            "VCCS username-password authentication FAILED for user {!r}".
            format(user))
        self.log_authn(user,
                       success=[],
                       failure=[cred.id for cred in user.passwords.to_list()])
        return None
 def test_generate_salt1(self):
     """ Test salt generation. """
     f = vccs_client.VCCSPasswordFactor('anything', '4711')
     self.assertEqual(len(f.salt), 80)
     random, length, rounds = f._decode_parameters(f.salt)
     self.assertEqual(length, 32)
     self.assertEqual(rounds, 32)
     self.assertEqual(len(random), length)
Exemple #8
0
 def test_authn_known_user_wrong_password(self, mock_add_credentials):
     mock_add_credentials.return_value = False
     assert isinstance(self.test_user, eduid_userdb.User)
     cred_id = ObjectId()
     factor = vccs_client.VCCSPasswordFactor('foo', str(cred_id), salt=None)
     self.idp_app.authn.auth_client.add_credentials(
         str(self.test_user.user_id), [factor])
     data = {
         'username': self.test_user.mail_addresses.primary.email,
         'password': '******',
     }
     self.assertFalse(self.idp_app.authn.password_authn(data))
 def test_password_factor(self):
     """
     Test creating a VCCSPasswordFactor instance.
     """
     # XXX need to find test vectors created with another implementation!
     f = vccs_client.VCCSPasswordFactor('plaintext', '4711',
                                        '$NDNv1H1$aaaaaaaaaaaaaaaa$12$32$')
     self.assertEqual(
         f.to_dict('auth'), {
             'type': 'password',
             'credential_id': '4711',
             'H1': '0b9ba6497c08106032a3337b',
         })
 def test_utf8_password_factor(self):
     """
     Test creating a VCCSPasswordFactor instance.
     """
     # XXX need to find test vectors created with another implementation!
     f = vccs_client.VCCSPasswordFactor('plaintextåäöхэж', '4711',
                                        '$NDNv1H1$aaaaaaaaaaaaaaaa$12$32$')
     self.assertEqual(
         f.to_dict('auth'), {
             'type': 'password',
             'credential_id': '4711',
             'H1': 'bbcebc158aa37039e0fa3294',
         })
 def test_authenticate1_utf8(self):
     """
     Test parsing of successful authentication response with a password in UTF-8.
     """
     resp = {
         'auth_response': {
             'version': 1,
             'authenticated': True,
         },
     }
     c = FakeVCCSClient(json.dumps(resp))
     f = vccs_client.VCCSPasswordFactor('passwordåäöхэж', '4711',
                                        '$NDNv1H1$aaaaaaaaaaaaaaaa$12$32$')
     self.assertTrue(c.authenticate('*****@*****.**', [f]))
 def test_add_creds2_utf8(self):
     """
     Test parsing of unsuccessful add_creds response with a password in UTF-8.
     """
     resp = {
         'add_creds_response': {
             'version': 1,
             'success': False,
         },
     }
     c = FakeVCCSClient(json.dumps(resp))
     f = vccs_client.VCCSPasswordFactor('passwordåäöхэж', '4711',
                                        '$NDNv1H1$aaaaaaaaaaaaaaaa$12$32$')
     self.assertFalse(c.add_credentials('*****@*****.**', [f]))
 def test_authenticate2_utf8(self):
     """
     Test unknown response version with a password in UTF-8.
     """
     resp = {
         'auth_response': {
             'version': 999,
         },
     }
     c = FakeVCCSClient(json.dumps(resp))
     f = vccs_client.VCCSPasswordFactor('passwordåäöхэж', '4711',
                                        '$NDNv1H1$aaaaaaaaaaaaaaaa$12$32$')
     with self.assertRaises(AssertionError):
         c.authenticate('*****@*****.**', [f])
Exemple #14
0
 def test_authn_known_user_right_password(self, mock_add_credentials,
                                          mock_authenticate):
     mock_add_credentials.return_value = True
     mock_authenticate.return_value = True
     assert isinstance(self.test_user, eduid_userdb.User)
     passwords = self.test_user.credentials.to_list()
     factor = vccs_client.VCCSPasswordFactor('foo',
                                             str(passwords[0].key),
                                             salt=passwords[0].salt)
     self.idp_app.authn.auth_client.add_credentials(
         str(self.test_user.user_id), [factor])
     data = {
         'username': self.test_user.mail_addresses.primary.email,
         'password': '******',
     }
     self.assertTrue(self.idp_app.authn.password_authn(data))
Exemple #15
0
def provision_credentials(vccs_url,
                          new_password,
                          user,
                          vccs=None,
                          source='dashboard'):
    """
    This function should be used by tests only
    Provision new password to a user.
    Returns True on success.

    :param vccs_url: URL to VCCS authentication backend
    :param old_password: plaintext current password
    :param new_password: plaintext new password
    :param user: user object
    :type vccs_url: str
    :type old_password: str
    :type user: User
    :rtype: bool
    """
    password_id = ObjectId()
    if vccs is None:
        vccs = get_vccs_client(vccs_url)
    # upgrade DashboardLegacyUser to DashboardUser
    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)

    new_factor = vccs_client.VCCSPasswordFactor(new_password,
                                                credential_id=str(password_id))

    if not vccs.add_credentials(str(user.user_id), [new_factor]):
        return False  # something failed

    new_password = Password(
        credential_id=password_id,
        salt=new_factor.salt,
        application=source,
    )
    user.passwords.add(new_password)

    return user
Exemple #16
0
def check_password(vccs_url, password, user, vccs=None):
    """ Try to validate a user provided password.

    Returns False or a dict with data about the credential that validated.

    :param vccs_url: URL to VCCS authentication backend
    :param password: plaintext password
    :param user: user dict
    :param vccs: optional vccs client instance

    :type vccs_url: string
    :type password: string
    :type user: User | DashboardLegacyUser
    :type vccs: None or VCCSClient
    :rtype: bool or dict
    """
    if vccs is None:
        vccs = get_vccs_client(vccs_url)

    # upgrade DashboardLegacyUser to DashboardUser
    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)

    for cred in user.passwords.to_list():
        factor = vccs_client.VCCSPasswordFactor(
            password,
            credential_id=str(cred.id),
            salt=cred.salt,
        )
        try:
            if vccs.authenticate(str(user.user_id), [factor]):
                return cred
        except Exception as exc:
            logger.warning(
                "VCCS authentication threw exception: {!s}".format(exc))
    return False
Exemple #17
0
def add_credentials(vccs_url,
                    old_password,
                    new_password,
                    user,
                    source='dashboard',
                    vccs=None):
    """
    Add a new password to a user. Revokes the old one, if one is given.

    Returns True on success.

    :param vccs_url: URL to VCCS authentication backend
    :param old_password: plaintext current password
    :param new_password: plaintext new password
    :param user: user object

    :type vccs_url: string
    :type old_password: string
    :type user: User | DashboardLegacyUser
    :rtype: bool
    """
    password_id = ObjectId()
    if vccs is None:
        vccs = get_vccs_client(vccs_url)
    new_factor = vccs_client.VCCSPasswordFactor(new_password,
                                                credential_id=str(password_id))

    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)

    old_factor = None
    checked_password = None
    # remember if an old password was supplied or not, without keeping it in
    # memory longer than we have to
    old_password_supplied = bool(old_password)
    if user.passwords.count > 0 and old_password:
        # Find the old credential to revoke
        checked_password = check_password(vccs_url,
                                          old_password,
                                          user,
                                          vccs=vccs)
        del old_password  # don't need it anymore, try to forget it
        if not checked_password:
            return False
        old_factor = vccs_client.VCCSRevokeFactor(
            str(checked_password.id),
            'changing password',
            reference=source,
        )

    if not vccs.add_credentials(str(user.user_id), [new_factor]):
        logger.warning("Failed adding password credential "
                       "{!r} for user {!r}".format(new_factor.credential_id,
                                                   user))
        return False  # something failed
    logger.debug("Added password credential {!s} for user {!s}".format(
        new_factor.credential_id, user))

    if old_factor:
        vccs.revoke_credentials(str(user.user_id), [old_factor])
        user.passwords.remove(checked_password.id)
        logger.debug("Revoked old credential {!s} (user {!s})".format(
            old_factor.credential_id, user))

    if not old_password_supplied:
        # TODO: Revoke all current credentials on password reset for now
        revoked = []
        for password in user.passwords.to_list():
            revoked.append(
                vccs_client.VCCSRevokeFactor(str(password.id),
                                             'reset password',
                                             reference=source))
            logger.debug("Revoking old credential (password reset) "
                         "{!s} (user {!s})".format(password.id, user))
            user.passwords.remove(password.id)
        if revoked:
            try:
                vccs.revoke_credentials(str(user.user_id), revoked)
            except vccs_client.VCCSClientHTTPError:
                # Password already revoked
                # TODO: vccs backend should be changed to return something more informative than
                # TODO: VCCSClientHTTPError when the credential is already revoked or just return success.
                logger.warning("VCCS failed to revoke all passwords for "
                               "user {!s}".format(user))

    new_password = Password(
        credential_id=password_id,
        salt=new_factor.salt,
        application=source,
    )
    user.passwords.add(new_password)

    return user
 def test_unknown_salt_version(self):
     """ Test unknown salt version """
     with self.assertRaises(ValueError):
         vccs_client.VCCSPasswordFactor('anything', '4711',
                                        '$NDNvFOO$aaaaaaaaaaaaaaaa$12$32$')