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)
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)
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)
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)
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)
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
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 } } )
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()
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)
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)
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)
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)
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
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
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')
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 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'))
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'))
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)
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)