def letter_proof_user():
    description = """\
    Apply verification returned by eduid-idproofing-letter after failure. The JSON data is found in the
    eduid-idproofing-letter log after a users successful verification.
    Example:
    'letter_proof_user eppn idproofing-letter-json-data'
    """
    usage = "usage: %prog eppn idproofing-letter-json-data"
    parser = optparse.OptionParser(
        usage=usage,
        description=textwrap.dedent(description)
        )

    options, args = parser.parse_args(sys.argv[1:])
    if not len(args) == 2:
        print('Two arguments required')
        print(usage)
        return 2

    env = bootstrap(default_config_file)
    eppn = args[0]
    data = args[1]
    rdata = json.loads(data)
    user = _get_user_by_eppn(env['request'], eppn, legacy_user=False)

    if not user.nins.to_list() and rdata.get('verified', False):
        # Save data from successful verification call for later addition to user proofing collection
        rdata['created_ts'] = datetime.utcfromtimestamp(int(rdata['created_ts']))
        rdata['verified_ts'] = datetime.utcfromtimestamp(int(rdata['verified_ts']))
        user = DashboardUser(data = user.to_dict())
        user.add_letter_proofing_data(rdata)

        # Look up users official address at the time of verification per Kantara requirements
        print "Looking up address via Navet for user {!r}.".format(user)
        user_postal_address = env['request'].msgrelay.get_full_postal_address(rdata['number'])
        print "Finished looking up address via Navet for user {!r}.".format(user)
        proofing_data = LetterProofing(user, rdata['number'], rdata['official_address'],
                                       rdata['transaction_id'], user_postal_address)
        # Log verification event and fail if that goes wrong
        print "Logging proofing data for user {!r}.".format(user)
        if env['request'].idproofinglog.log_verification(proofing_data):
            print "Finished logging proofing data for user {!r}.".format(user)
            try:
                # This is a hack to reuse the existing proofing functionality, the users code is
                # verified by the micro service
                set_nin_verified(env['request'], user, rdata['number'])
                try:
                    env['request'].context.save_dashboard_user(user)
                except UserOutOfSync:
                    print 'Verified norEduPersonNIN NOT saved for user {!r}. User out of sync.'.format(user)
                    raise
                save_as_verified(env['request'], 'norEduPersonNIN', user, rdata['number'])
                print "Verified NIN by physical letter saved for user {!r}.".format(user)
            except UserOutOfSync:
                print "Verified NIN by physical letter NOT saved for user {!r}. User out of sync.".format(user)
            else:
                print 'You have successfully verified the identity for user {!r}'.format(user)
    else:
        print 'User {!r} already has verified NIN ({!s}).'.format(user, user.nins)
Пример #2
0
    def save_dashboard_user(self, user):
        """
        Save (new) user objects to the dashboard db in the new format,
        and propagate the changes to the central user db.

        May raise UserOutOfSync exception

        :param user: the modified user
        :type user: eduid_userdb.dashboard.user.DashboardUser
        """
        if isinstance(user, User):
            # turn it into a DashboardUser before saving it in the dashboard private db
            user = DashboardUser(data = user.to_dict())
        self.request.dashboard_userdb.save(user, old_format=False)
        self.propagate_user_changes(user)
Пример #3
0
    def save_dashboard_user(self, user):
        """
        Save (new) user objects to the dashboard db in the new format,
        and propagate the changes to the central user db.

        May raise UserOutOfSync exception

        :param user: the modified user
        :type user: eduid_userdb.dashboard.user.DashboardUser
        """
        if isinstance(user, User):
            # turn it into a DashboardUser before saving it in the dashboard private db
            user = DashboardUser(data=user.to_dict())
        self.request.dashboard_userdb.save(user, old_format=False)
        self.propagate_user_changes(user)
Пример #4
0
def save_dashboard_user(user):
    """
    Save (new) user objects to the dashboard db in the new format,
    and propagate the changes to the central user db.

    May raise UserOutOfSync exception

    :param user: the modified user
    :type user: eduid_userdb.dashboard.user.DashboardUser
    """
    if isinstance(user, User) and not isinstance(user, DashboardUser):
        # turn it into a DashboardUser before saving it in the dashboard private db
        user = DashboardUser(data = user.to_dict())
    current_app.dashboard_userdb.save(user)
    return current_app.am_relay.request_user_sync(user)
Пример #5
0
def save_dashboard_user(user):
    """
    Save (new) user objects to the dashboard db in the new format,
    and propagate the changes to the central user db.

    May raise UserOutOfSync exception

    :param user: the modified user
    :type user: eduid_userdb.dashboard.user.DashboardUser
    """
    if isinstance(user, User) and not isinstance(user, DashboardUser):
        # turn it into a DashboardUser before saving it in the dashboard private db
        user = DashboardUser(data=user.to_dict())
    current_app.dashboard_userdb.save(user)
    return current_app.am_relay.request_user_sync(user)
Пример #6
0
 def set_logged(self, email='*****@*****.**', extra_session_data={}):
     request = self.set_user_cookie(email)
     user_obj = self.userdb_new.get_user_by_mail(email,
                                                 raise_on_missing=True)
     if not user_obj:
         logging.error(
             "User {!s} not found in database {!r}. Users:".format(
                 email, self.userdb))
         for this in self.userdb_new._get_all_userdocs():
             this_user = DashboardUser(this)
             logging.debug("  User: {!s}".format(this_user))
     # user only exists in eduid-userdb, so need to clear modified-ts to be able
     # to save it to eduid-dashboard.profiles
     user_obj.modified_ts = None
     dummy = DummyRequest()
     dummy.session = {
         'eduPersonAssurance': loa(3),
         'eduPersonIdentityProofing': loa(3),
         'eduPersonPrincipalName': 'hubba-bubba',
         'user_eppn': 'hubba-bubba',
     }
     store_session_user(dummy, user_obj)
     # XXX ought to set self.user = user_obj
     self.logged_in_user = self.userdb_new.get_user_by_id(user_obj.user_id)
     dummy.session.update(extra_session_data)
     request = self.add_to_session(dummy.session)
     return request
Пример #7
0
 def test_terminated_unset(self):
     _data = {
         'eduPersonPrincipalName': 'test-test',
         'mailAliases': [{
             'email': '*****@*****.**',
             'verified': True,
             'primary': True
         }],
         'mobile': [],
         'nins': [{'number': '123456781235', 'verified': True, 'primary': True}],
         'passwords': [{
             'id': bson.ObjectId('112345678901234567890123'),
             'salt': '$NDNv1H1$9c810d852430b62a9a7c6159d5d64c41c3831846f81b6799b54e1e8922f11545$32$32$',
         }],
         'terminated': datetime.now(tz=bson.tz_util.FixedOffset(0, 'UTC')),
     }
     user = DashboardUser(data=_data)
     self.plugin_context.dashboard_userdb.save(user)
     user.terminated = False
     self.plugin_context.dashboard_userdb.save(user)
     attributes = attribute_fetcher(self.plugin_context, user.user_id)
     self.assertDictEqual(
         attributes,
         {
             '$set': {
                 'mailAliases': [{'email': '*****@*****.**', 'verified': True, 'primary': True}],
                 'passwords': [{
                     'credential_id': u'112345678901234567890123',
                     'salt': '$NDNv1H1$9c810d852430b62a9a7c6159d5d64c41c3831846f81b6799b54e1e8922f11545$32$32$',
                 }],
                 'nins': [{'number': '123456781235', 'verified': True, 'primary': True}],
             },
             '$unset': {
                 'norEduPersonNIN': None,
                 'mail': None,
                 'mobile': None,
                 'phone': None,
                 'sn': None,
                 'terminated': False
             }
         }
     )
Пример #8
0
 def tearDown(self):
     super(LoggedInRequestTests, self).tearDown()
     logger.debug(
         "tearDown: Dropping profiles, verifications and reset_passwords from {!s}"
         .format(self.db))
     for userdoc in self.db.profiles.find({}):
         assert DashboardUser(data=userdoc)
     self.db.profiles.drop()
     self.db.verifications.drop()
     self.db.reset_passwords.drop()
     self.testapp.reset()
Пример #9
0
    def test_verify_nin_by_mobile(self):
        email = self.no_nin_user_email
        self.set_logged(email)
        user = self.userdb_new.get_user_by_mail(email)
        dashboard_user = DashboardUser(data=user.to_dict())
        self.assertEqual(user.nins.count, 0)

        # Add a verified phone number to the user in the central userdb
        dashboard_user.phone_numbers.add(
            PhoneNumber(data={
                'number': '666666666',
                'primary': True,
                'verified': True
            }))
        dashboard_user.modified_ts = None
        self.userdb_new.save(dashboard_user)
        retrieve_modified_ts(dashboard_user, self.dashboard_db)
        self.dashboard_db.save(dashboard_user)

        # First we add a nin...
        new_nin = '200010100001'

        response_form = self.testapp.get('/profile/nins/')
        form = response_form.forms[self.formname]
        from eduiddashboard.lookuprelay import LookupMobileRelay
        with patch.object(LookupMobileRelay, 'find_NIN_by_mobile', clear=True):
            LookupMobileRelay.find_NIN_by_mobile.return_value = new_nin
            from eduiddashboard.msgrelay import MsgRelay
            with patch.object(MsgRelay, 'get_full_postal_address', clear=True):
                MsgRelay.get_full_postal_address.return_value = {
                    'Address2': u'StreetName 104',
                    'PostalCode': u'74142',
                    'City': u'STOCKHOLM',
                }
                form['norEduPersonNIN'].value = new_nin
                form.submit('add_by_mobile')

        user = self.dashboard_db.get_user_by_mail(email)
        self.assertEqual(user.nins.count, 1)
        self.assertEqual(user.nins.primary.number, new_nin)
Пример #10
0
    def test_verify_nin_by_mobile(self):
        email = self.no_nin_user_email
        self.set_logged(email)
        user = self.userdb_new.get_user_by_mail(email)
        dashboard_user = DashboardUser(data=user.to_dict())
        self.assertEqual(user.nins.count, 0)

        # Add a verified phone number to the user in the central userdb
        dashboard_user.phone_numbers.add(PhoneNumber(data={
            'number': '666666666',
            'primary': True,
            'verified': True
            }))
        dashboard_user.modified_ts = None
        self.userdb_new.save(dashboard_user)
        retrieve_modified_ts(dashboard_user, self.dashboard_db)
        self.dashboard_db.save(dashboard_user)

        # First we add a nin...
        new_nin = '200010100001'

        response_form = self.testapp.get('/profile/nins/')
        form = response_form.forms[self.formname]
        from eduiddashboard.lookuprelay import LookupMobileRelay
        with patch.object(LookupMobileRelay, 'find_NIN_by_mobile', clear=True):
            LookupMobileRelay.find_NIN_by_mobile.return_value = new_nin
            from eduiddashboard.msgrelay import MsgRelay
            with patch.object(MsgRelay, 'get_full_postal_address', clear=True):
                MsgRelay.get_full_postal_address.return_value = {
                    'Address2': u'StreetName 104',
                    'PostalCode': u'74142',
                    'City': u'STOCKHOLM',
                }
                form['norEduPersonNIN'].value = new_nin
                form.submit('add_by_mobile')

        user = self.dashboard_db.get_user_by_mail(email)
        self.assertEqual(user.nins.count, 1)
        self.assertEqual(user.nins.primary.number, new_nin)
Пример #11
0
    def test_verify_existant_nin_by_mobile(self):
        email = self.no_nin_user_email
        self.set_logged(email)
        user = self.userdb_new.get_user_by_mail(email)
        dashboard_user = DashboardUser(data=user.to_dict())
        retrieve_modified_ts(dashboard_user, self.dashboard_db)

        self.assertEqual(dashboard_user.nins.count, 0)

        # Add a verified phone number to the user in the central userdb
        dashboard_user.phone_numbers.add(
            PhoneNumber(data={
                'number': '666666666',
                'primary': True,
                'verified': True
            }))
        self.dashboard_db.save(dashboard_user)

        # First we add a nin...
        nin = '200010100001'

        response_form = self.testapp.get('/profile/nins/')
        form = response_form.forms[self.formname]
        from eduiddashboard.msgrelay import MsgRelay
        with patch.multiple(MsgRelay,
                            nin_validator=return_true,
                            nin_reachable=return_true):
            form['norEduPersonNIN'].value = nin
            form.submit('add')

        # and then we verify it
        self.testapp.get('/profile/nins/')
        from eduiddashboard.views import nins
        with patch.object(nins, 'validate_nin_by_mobile', clear=True):
            nins.validate_nin_by_mobile.return_value = {
                'success': True,
                'message': u'Ok',
            }
            response = self.testapp.post('/profile/nins-actions/', {
                'identifier': nin + ' 0',
                'action': 'verify_mb'
            })
        response_json = json.loads(response.body)
        self.assertEqual(response_json['message'], 'Ok')
        user = self.dashboard_db.get_user_by_mail(email)
        self.assertEqual(user.nins.count, 1)
        self.assertEqual(user.nins.to_list_of_dicts()[0]['number'], nin)
Пример #12
0
def revoke_all_credentials(vccs_url, user, source='dashboard', vccs=None):
    if vccs is None:
        vccs = get_vccs_client(vccs_url)
    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)
    to_revoke = []
    for passwd in user.passwords.to_list():
        credential_id = str(passwd.id)
        factor = vccs_client.VCCSRevokeFactor(
            credential_id,
            'subscriber requested termination',
            reference=source)
        logger.debug("Revoked old credential (account termination)"
                     " {!s} (user {!s})".format(credential_id, user))
        to_revoke.append(factor)
    userid = str(user.user_id)
    vccs.revoke_credentials(userid, to_revoke)
Пример #13
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
Пример #14
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
Пример #15
0
    def add_success_other(self, ninform):
        newnin = self.schema.serialize(ninform)
        newnin = newnin['norEduPersonNIN']

        newnin = normalize_nin(newnin)

        old_user = self.request.db.profiles.find_one({
            'norEduPersonNIN': newnin
            })

        if old_user:
            old_user = DashboardUser(data=old_user)
            retrieve_modified_ts(old_user, self.request.dashboard_userdb)
            old_user.nins.remove(newnin)
            self.context.save_dashboard_user(old_user)

        primary = False
        if self.user.nins.count == 0:
            primary = True
        newnin_obj = Nin(number=newnin, application='dashboard',
                verified=True, primary=primary)
        self.user.nins.add(newnin_obj)

        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            message = _('Your user profile is out of sync. Please '
                        'reload the page and try again.')
        else:
            message = _('Your national identity number has been confirmed')
        # Save the state in the verifications collection
        save_as_verified(self.request, 'norEduPersonNIN', self.user, newnin)
        self.request.session.flash(
                get_localizer(self.request).translate(message),
                queue='forms')
        self.request.stats.count('nin_add_other')
Пример #16
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
Пример #17
0
    def finish_letter_action(self, data, post_data):
        """
        Contact the eduid-idproofing-letter service and give it the code the user supplied.

        If the letter proofing service approves of the code, this code does the following:
          * Put together some LetterProofing data with information about the user, the vetting, the
            users registered address etc. (Kantara requirement)
          * Log what the letter proofing service returned on the user (we put it there for now...)
          * Upgrade the NIN in question to verified=True
          * Mark the verification code as used

        :returns: status, message in a dict
        :rtype: dict
        """
        nin, index = data.split()
        index = int(index)

        settings = self.request.registry.settings
        letter_url = settings.get('letter_service_url')
        verify_letter_url = urlappend(letter_url, 'verify-code')

        code = post_data['verification_code']

        self.user = get_session_user(self.request)

        # small helper function to make rest of the function more readable
        def make_result(result, msg):
            return dict(result = result, message = msg)

        data = {'eppn': self.user.eppn,
                'verification_code': code}
        logger.info("Posting letter verification code for user {!r}.".format(self.user))
        response = requests.post(verify_letter_url, data=data)
        logger.info("Received response from idproofing-letter after posting verification code "
                    "for user {!r}.".format(self.user))
        if response.status_code != 200:
            # Do nothing, just return above error message and log microservice return code
            logger.info("Received status code {!s} from idproofing-letter after posting verification code "
                        "for user {!r}.".format(response.status_code, self.user))
            return make_result('error', _('There was a problem with the letter service. '
                                          'Please try again later.'))

        rdata = response.json().get('data', {})
        if not (rdata.get('verified', False) and nin == rdata.get('number', None)):
            log.info('User {!r} supplied wrong letter verification code or nin did not match.'.format(
                self.user))
            log.debug('NIN in dashboard: {!s}, NIN in idproofing-letter: {!s}'.format(
                nin, rdata.get('number', None)))
            return make_result('error', _('Your verification code seems to be wrong, please try again.'))

        # Save data from successful verification call for later addition to user proofing collection.
        # Convert self.user to a DashboardUser manually instead of letting save_dashboard_user do
        # it to get access to add_letter_proofing_data().
        user = DashboardUser(data = self.user.to_dict())
        rdata['created_ts'] = datetime.utcfromtimestamp(int(rdata['created_ts']))
        rdata['verified_ts'] = datetime.utcfromtimestamp(int(rdata['verified_ts']))
        user.add_letter_proofing_data(rdata)

        # Look up users official address at the time of verification per Kantara requirements
        logger.info("Looking up address via Navet for user {!r}.".format(self.user))
        user_postal_address = self.request.msgrelay.get_full_postal_address(rdata['number'])
        logger.info("Finished looking up address via Navet for user {!r}.".format(self.user))
        proofing_data = LetterProofing(self.user, rdata['number'], rdata['official_address'],
                                       rdata['transaction_id'], user_postal_address)

        # Log verification event and fail if that goes wrong
        logger.info("Logging proofing data for user {!r}.".format(self.user))
        if not self.request.idproofinglog.log_verification(proofing_data):
            log.error('Logging of letter proofing data for user {!r} failed.'.format(self.user))
            return make_result('error', _('Sorry, we are experiencing temporary technical '
                                          'problems, please try again later.'))

        logger.info("Finished logging proofing data for user {!r}.".format(self.user))
        # This is a hack to reuse the existing proofing functionality, the users code has
        # already been verified by the micro service but we decided the dashboard could
        # continue 'upgrading' the users until we've made the planned proofing consumer
        set_nin_verified(self.request, user, nin)
        try:
            self.request.context.save_dashboard_user(user)
        except UserOutOfSync:
            log.error("Verified norEduPersonNIN NOT saved for user {!r}. User out of sync.".format(
                self.user))
            return self.sync_user()
        self.user = user

        # Finally mark the verification as used
        save_as_verified(self.request, 'norEduPersonNIN', self.user, nin)
        logger.info("Verified NIN by physical letter saved for user {!r}.".format(
            self.user))

        return make_result('success', _('You have successfully verified your identity'))
Пример #18
0
    def finish_letter_action(self, data, post_data):
        """
        Contact the eduid-idproofing-letter service and give it the code the user supplied.

        If the letter proofing service approves of the code, this code does the following:
          * Put together some LetterProofing data with information about the user, the vetting, the
            users registered address etc. (Kantara requirement)
          * Log what the letter proofing service returned on the user (we put it there for now...)
          * Upgrade the NIN in question to verified=True
          * Mark the verification code as used

        :returns: status, message in a dict
        :rtype: dict
        """
        nin, index = data.split()
        index = int(index)

        settings = self.request.registry.settings
        letter_url = settings.get('letter_service_url')
        verify_letter_url = urlparse.urljoin(letter_url, 'verify-code')

        code = post_data['verification_code']

        self.user = get_session_user(self.request)

        # small helper function to make rest of the function more readable
        def make_result(result, msg):
            return dict(result = result, message = msg)

        data = {'eppn': self.user.eppn,
                'verification_code': code}
        logger.info("Posting letter verification code for user {!r}.".format(self.user))
        response = requests.post(verify_letter_url, data=data)
        logger.info("Received response from idproofing-letter after posting verification code "
                    "for user {!r}.".format(self.user))
        if response.status_code != 200:
            # Do nothing, just return above error message and log microservice return code
            logger.info("Received status code {!s} from idproofing-letter after posting verification code "
                        "for user {!r}.".format(response.status_code, self.user))
            return make_result('error', _('There was a problem with the letter service. '
                                          'Please try again later.'))

        rdata = response.json().get('data', {})
        if not (rdata.get('verified', False) and nin == rdata.get('number', None)):
            log.info('User {!r} supplied wrong letter verification code or nin did not match.'.format(
                self.user))
            log.debug('NIN in dashboard: {!s}, NIN in idproofing-letter: {!s}'.format(
                nin, rdata.get('number', None)))
            return make_result('error', _('Your verification code seems to be wrong, please try again.'))

        # Save data from successful verification call for later addition to user proofing collection.
        # Convert self.user to a DashboardUser manually instead of letting save_dashboard_user do
        # it to get access to add_letter_proofing_data().
        user = DashboardUser(data = self.user.to_dict())
        rdata['created_ts'] = datetime.utcfromtimestamp(int(rdata['created_ts']))
        rdata['verified_ts'] = datetime.utcfromtimestamp(int(rdata['verified_ts']))
        user.add_letter_proofing_data(rdata)

        # Look up users official address at the time of verification per Kantara requirements
        logger.info("Looking up address via Navet for user {!r}.".format(self.user))
        user_postal_address = self.request.msgrelay.get_full_postal_address(rdata['number'])
        logger.info("Finished looking up address via Navet for user {!r}.".format(self.user))
        proofing_data = LetterProofing(self.user, rdata['number'], rdata['official_address'],
                                       rdata['transaction_id'], user_postal_address)

        # Log verification event and fail if that goes wrong
        logger.info("Logging proofing data for user {!r}.".format(self.user))
        if not self.request.idproofinglog.log_verification(proofing_data):
            log.error('Logging of letter proofing data for user {!r} failed.'.format(self.user))
            return make_result('error', _('Sorry, we are experiencing temporary technical '
                                          'problems, please try again later.'))

        logger.info("Finished logging proofing data for user {!r}.".format(self.user))
        # This is a hack to reuse the existing proofing functionality, the users code has
        # already been verified by the micro service but we decided the dashboard could
        # continue 'upgrading' the users until we've made the planned proofing consumer
        set_nin_verified(self.request, user, nin)
        try:
            self.request.context.save_dashboard_user(user)
        except UserOutOfSync:
            log.error("Verified norEduPersonNIN NOT saved for user {!r}. User out of sync.".format(
                self.user))
            return self.sync_user()
        self.user = user

        # Finally mark the verification as used
        save_as_verified(self.request, 'norEduPersonNIN', self.user, nin)
        logger.info("Verified NIN by physical letter saved for user {!r}.".format(
            self.user))

        return make_result('success', _('You have successfully verified your identity'))
Пример #19
0
    def test_reset_password_unterminates_account(self):
        email = '*****@*****.**'
        # Set up a bunch of faked passwords to make sure they are all revoked
        user = self.userdb_new.get_user_by_mail(email)
        dashboard_user = DashboardUser(data=user.to_dict())
        retrieve_modified_ts(dashboard_user, self.dashboard_db)
        for i in range(7):
            pw = Password(
                credential_id=ObjectId(),
                salt=str(i) * 64,
                application='dashboard_unittest',
            )
            dashboard_user.passwords.add(pw)
        self.dashboard_db.save(dashboard_user)

        logging.log(logging.DEBUG,
                    "Fetching /profile/security\n\n" + ('-=-' * 30) + "\n\n")

        request = self.set_logged(email=email)
        response = self.testapp.get('/profile/security/')
        form = response.forms['terminate-account-form']

        # Verify the user has eight passwords
        user = self.dashboard_db.get_user_by_mail(email)
        self.assertEqual(
            len(user.credentials.filter(Password).to_list_of_dicts()), 8)

        logging.log(
            logging.DEBUG,
            "Submitting termination request\n\n" + ('-=-' * 30) + "\n\n")

        form_response = form.submit('submit')
        self.assertEqual(form_response.status, '302 Found')
        self.assertIn('authn.example.com', form_response.location)
        with patch('eduiddashboard.vccs.get_vccs_client'):
            from eduiddashboard.vccs import get_vccs_client
            get_vccs_client.return_value = FakeVCCSClient(fake_response={
                'revoke_creds_response': {
                    'version': 1,
                    'success': True,
                },
            })
            with patch('eduiddashboard.views.portal.send_termination_mail'):
                from eduiddashboard.views.portal import send_termination_mail
                send_termination_mail.return_value = None
                store_session_user(request, self.user)
                self.set_logged(email=self.user.mail_addresses.primary.email,
                                extra_session_data={
                                    'reauthn-for-termination': int(time.time())
                                })
                response = self.testapp.get('/profile/account-terminated/')

        # Verify the user doesn't have ANY passwords and IS terminated at this point
        user = self.dashboard_db.get_user_by_mail(email)
        self.assertEqual(user.passwords.count, 0)
        self.assertTrue(user.terminated)

        logging.log(
            logging.DEBUG,
            "Password reset of terminated user\n\n" + ('-=-' * 30) + "\n\n")

        # Do a password reset, which should resurrect the terminated user
        hash_code = '123456'
        date = datetime.now(pytz.utc)
        self.db.reset_passwords.insert({
            'email': email,
            'hash_code': hash_code,
            'mechanism': 'email',
            'created_at': date
        })
        response = self.testapp.get(
            '/profile/reset-password/{0}/'.format(hash_code))
        self.assertIn('Please choose a new password for your eduID account',
                      response.text)
        form = response.forms['resetpasswordstep2view-form']
        with patch('eduiddashboard.vccs.get_vccs_client'):
            from eduiddashboard.vccs import get_vccs_client
            get_vccs_client.return_value = FakeVCCSClient()
            form.submit('reset')

        # Verify the user has a password and is NOT terminated again
        user = self.dashboard_db.get_user_by_mail(email)
        self.assertEqual(
            len(user.credentials.filter(Password).to_list_of_dicts()), 1)
        self.assertFalse(user.terminated)
Пример #20
0
def letter_proof_user():
    description = """\
    Apply verification returned by eduid-idproofing-letter after failure. The JSON data is found in the
    eduid-idproofing-letter log after a users successful verification.
    Example:
    'letter_proof_user eppn idproofing-letter-json-data'
    """
    usage = "usage: %prog eppn idproofing-letter-json-data"
    parser = optparse.OptionParser(usage=usage,
                                   description=textwrap.dedent(description))

    options, args = parser.parse_args(sys.argv[1:])
    if not len(args) == 2:
        print('Two arguments required')
        print(usage)
        return 2

    env = bootstrap(default_config_file)
    eppn = args[0]
    data = args[1]
    rdata = json.loads(data)
    user = _get_user_by_eppn(env['request'], eppn, legacy_user=False)

    if not user.nins.to_list() and rdata.get('verified', False):
        # Save data from successful verification call for later addition to user proofing collection
        rdata['created_ts'] = datetime.utcfromtimestamp(
            int(rdata['created_ts']))
        rdata['verified_ts'] = datetime.utcfromtimestamp(
            int(rdata['verified_ts']))
        user = DashboardUser(data=user.to_dict())
        user.add_letter_proofing_data(rdata)

        # Look up users official address at the time of verification per Kantara requirements
        print "Looking up address via Navet for user {!r}.".format(user)
        user_postal_address = env['request'].msgrelay.get_full_postal_address(
            rdata['number'])
        print "Finished looking up address via Navet for user {!r}.".format(
            user)
        proofing_data = LetterProofing(user, rdata['number'],
                                       rdata['official_address'],
                                       rdata['transaction_id'],
                                       user_postal_address)
        # Log verification event and fail if that goes wrong
        print "Logging proofing data for user {!r}.".format(user)
        if env['request'].idproofinglog.log_verification(proofing_data):
            print "Finished logging proofing data for user {!r}.".format(user)
            try:
                # This is a hack to reuse the existing proofing functionality, the users code is
                # verified by the micro service
                set_nin_verified(env['request'], user, rdata['number'])
                try:
                    env['request'].context.save_dashboard_user(user)
                except UserOutOfSync:
                    print 'Verified norEduPersonNIN NOT saved for user {!r}. User out of sync.'.format(
                        user)
                    raise
                save_as_verified(env['request'], 'norEduPersonNIN', user,
                                 rdata['number'])
                print "Verified NIN by physical letter saved for user {!r}.".format(
                    user)
            except UserOutOfSync:
                print "Verified NIN by physical letter NOT saved for user {!r}. User out of sync.".format(
                    user)
            else:
                print 'You have successfully verified the identity for user {!r}'.format(
                    user)
    else:
        print 'User {!r} already has verified NIN ({!s}).'.format(
            user, user.nins)