def update_context_user(self): # XXX what is this context user??? logger.notice('UPDATE CONTEXT USER CALLED') raise NotImplementedError('update_context_user UN-implemented') eppn = self.user.eppn self.user = self.request.userdb_new.get_user_by_eppn(eppn) retrieve_modified_ts(self.user, self.request.dashboard_userdb)
def set_email_verified(request, user, new_mail): """ Mark an e-mail address as verified on a user. This process also includes *removing* the e-mail address from any other user that had it as a verified e-mail address. :param request: The HTTP request :param user: The user :param new_mail: The e-mail address to mark as verified :type request: pyramid.request.Request :type user: User :type new_mail: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify mail address for user {!r}.'.format(user)) log.debug('Mail address: {!s}.'.format(new_mail)) # Start by removing the email address from any other user that currently has it (verified) old_user = request.userdb_new.get_user_by_mail(new_mail, raise_on_missing=False) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) old_user = _remove_mail_from_user(new_mail, old_user) request.context.save_dashboard_user(old_user) steal_count = 1 # Add the verified mail address to the requesting user _add_mail_to_user(new_mail, user) log.info('Mail address verified for user {!r}.'.format(user)) request.stats.count('verify_mail_stolen', steal_count) request.stats.count('verify_mail_completed') return _('Email {obj} verified')
def set_phone_verified(request, user, new_number): """ Mark a phone number as verified on a user. This process also includes *removing* the phone number from any other user that had it as a verified phone number. :param request: The HTTP request :param user: The user :param new_number: The phone number to mark as verified :type request: pyramid.request.Request :type user: User :type new_number: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify phone number for user {!r}.'.format(user)) log.debug('Phone number: {!s}.'.format(new_number)) # Start by removing mobile number from any other user old_user = request.userdb_new.get_user_by_phone(new_number, raise_on_missing=False) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) _remove_phone_from_user(new_number, old_user) request.context.save_dashboard_user(old_user) log.info('Removed phone number from user {!r}.'.format(old_user)) steal_count = 1 # Add the verified mobile number to the requesting user _add_phone_to_user(new_number, user) log.info('Phone number verified for user {!r}.'.format(user)) request.stats.count('dashboard/verify_mobile_stolen', steal_count) request.stats.count('dashboard/verify_mobile_completed', 1) return _('Phone {obj} verified')
def set_email_verified(request, user, new_mail): """ Mark an e-mail address as verified on a user. This process also includes *removing* the e-mail address from any other user that had it as a verified e-mail address. :param request: The HTTP request :param user: The user :param new_mail: The e-mail address to mark as verified :type request: pyramid.request.Request :type user: User :type new_mail: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify mail address for user {!r}.'.format(user)) log.debug('Mail address: {!s}.'.format(new_mail)) # Start by removing the email address from any other user that currently has it (verified) old_user = request.userdb_new.get_user_by_mail(new_mail, raise_on_missing=False) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) old_user = _remove_mail_from_user(new_mail, old_user) request.context.save_dashboard_user(old_user) steal_count = 1 # Add the verified mail address to the requesting user _add_mail_to_user(new_mail, user) log.info('Mail address verified for user {!r}.'.format(user)) request.stats.count('dashboard/verify_mail_stolen', steal_count) request.stats.count('dashboard/verify_mail_completed', 1) return _('Email {obj} verified')
def reset_success(self, passwordform): form_data = self.schema.serialize(passwordform) hash_code = self.request.matchdict['code'] password_reset = self.request.db.reset_passwords.find_one( {'hash_code': hash_code}) if password_reset.get('eppn'): user = self.request.userdb_new.get_user_by_eppn( password_reset['eppn']) # Legacy password reset codes were connected to the user by email elif password_reset.get('email'): user = self.request.userdb_new.get_user_by_mail( password_reset['email']) log.debug("Loaded user {!s} from {!s}".format(user, self.request.userdb)) if form_data.get('use_custom_password') == 'true': log.debug( "Password change for user {!r} (custom password).".format( user)) new_password = form_data.get('custom_password') self.request.stats.count('pwreset_custom_password') else: log.debug( "Password change for user {!r} (suggested password).".format( user)) new_password = self.get_suggested_password() self.request.stats.count('pwreset_generated_password') # Make user AL1 if password was reset by e-mail only if password_reset['mechanism'] == 'email': nin_count = user.nins.count if nin_count: retrieve_modified_ts(user, self.request.dashboard_userdb) unverify_user_nins(self.request, user) self.request.stats.count('pwreset_downgraded_NINs', nin_count) if len(user.phone_numbers.to_list()): retrieve_modified_ts(user, self.request.dashboard_userdb) # We need to unverify a users phone numbers to make sure that an attacker can not # verify the account again without control over the users phone number # This should be changed to only unverify the phone numbers instead of removing them. unverify_user_mobiles(self.request, user) # Save new password new_password = new_password.replace(' ', '') ok = change_password(self.request, user, '', new_password) if ok: self.request.db.reset_passwords.remove( {'_id': password_reset['_id']}) self.request.stats.count('pwreset_changed_password') url = self.request.route_url('profile-editor') reset = True else: self.request.stats.count('pwreset_password_change_failed') url = self.request.route_url('reset-password') reset = False return {'url': url, 'reset': reset}
def set_phone_verified(request, user, new_number): """ Mark a phone number as verified on a user. This process also includes *removing* the phone number from any other user that had it as a verified phone number. :param request: The HTTP request :param user: The user :param new_number: The phone number to mark as verified :type request: pyramid.request.Request :type user: User :type new_number: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify phone number for user {!r}.'.format(user)) log.debug('Phone number: {!s}.'.format(new_number)) # Start by removing mobile number from any other user old_user = request.userdb_new.get_user_by_phone(new_number, raise_on_missing=False) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) _remove_phone_from_user(new_number, old_user) request.context.save_dashboard_user(old_user) log.info('Removed phone number from user {!r}.'.format(old_user)) steal_count = 1 # Add the verified mobile number to the requesting user _add_phone_to_user(new_number, user) log.info('Phone number verified for user {!r}.'.format(user)) request.stats.count('verify_mobile_stolen', steal_count) request.stats.count('verify_mobile_completed') return _('Phone {obj} verified')
def get_user(self): logger.debug("Looking for verification code {!r}".format(self.request.matchdict['code'])) verification_code = self.request.db.verifications.find_one({ 'code': self.request.matchdict['code'], }) if verification_code is None: raise HTTPNotFound() user = self.request.userdb_new.get_user_by_id(verification_code['user_oid']) retrieve_modified_ts(user, self.request.dashboard_userdb) return user
def save_success(self, passwordform): authn_ts = self.request.session.get('reauthn-for-chpass', None) if authn_ts is None: raise HTTPBadRequest(_('No authentication info')) now = datetime.utcnow() delta = now - datetime.fromtimestamp(authn_ts) # XXX put the reauthn timeout in the settings if int(delta.total_seconds()) > 600: msg = _('Stale authentication info. Please try again.') self.request.session.flash('error|' + msg) raise HTTPFound(self.context.route_url('profile-editor')) user = get_session_user(self.request) log.debug('Removing Authn ts for user {!r} before' ' changing the password'.format(user)) del self.request.session['reauthn-for-chpass'] passwords_data = self.schema.serialize(passwordform) if passwords_data.get('use_custom_password') == 'true': # The user has entered his own password and it was verified by # validators log.debug( "Password change for user {!r} (custom password).".format( user)) new_password = passwords_data.get('custom_password') else: # If the user has selected the suggested password, then it should # be in session log.debug( "Password change for user {!r} (suggested password).".format( user)) new_password = self.get_suggested_password() new_password = new_password.replace(' ', '') old_password = passwords_data['old_password'] # Load user from database to ensure we are working on an up-to-date set of credentials. # XXX this refresh is a bit redundant with the same thing being done in OldPasswordValidator. user = self.request.userdb_new.get_user_by_id(user.user_id) log.debug("Refreshed user {!s} from {!s}".format( user, self.request.userdb_new)) retrieve_modified_ts(user, self.request.dashboard_userdb) self.changed = change_password(self.request, user, old_password, new_password) if self.changed: message = 'success|' + _( 'Your password has been successfully updated') else: message = 'error|' + _( 'An error has occured while updating your password, ' 'please try again or contact support if the problem persists.') self.request.session.flash(message) raise HTTPFound(self.context.route_url('profile-editor'))
def reset_success(self, passwordform): form_data = self.schema.serialize(passwordform) hash_code = self.request.matchdict['code'] password_reset = self.request.db.reset_passwords.find_one({'hash_code': hash_code}) if password_reset.get('eppn'): user = self.request.userdb_new.get_user_by_eppn(password_reset['eppn']) # Legacy password reset codes were connected to the user by email elif password_reset.get('email'): user = self.request.userdb_new.get_user_by_mail(password_reset['email']) log.debug("Loaded user {!s} from {!s}".format(user, self.request.userdb)) if form_data.get('use_custom_password') == 'true': log.debug("Password change for user {!r} (custom password).".format(user)) new_password = form_data.get('custom_password') self.request.stats.count('pwreset_custom_password') else: log.debug("Password change for user {!r} (suggested password).".format(user)) new_password = self.get_suggested_password() self.request.stats.count('pwreset_generated_password') # Make user AL1 if password was reset by e-mail only if password_reset['mechanism'] == 'email': nin_count = user.nins.count if nin_count: retrieve_modified_ts(user, self.request.dashboard_userdb) unverify_user_nins(self.request, user) self.request.stats.count('pwreset_downgraded_NINs', nin_count) if len(user.phone_numbers.to_list()): retrieve_modified_ts(user, self.request.dashboard_userdb) # We need to unverify a users phone numbers to make sure that an attacker can not # verify the account again without control over the users phone number # This should be changed to only unverify the phone numbers instead of removing them. unverify_user_mobiles(self.request, user) # Save new password new_password = new_password.replace(' ', '') ok = change_password(self.request, user, '', new_password) if ok: self.request.db.reset_passwords.remove({'_id': password_reset['_id']}) self.request.stats.count('pwreset_changed_password') url = self.request.route_url('profile-editor') reset = True else: self.request.stats.count('pwreset_password_change_failed') url = self.request.route_url('reset-password') reset = False return { 'url': url, 'reset': reset }
def reset_success(self, passwordform): form_data = self.schema.serialize(passwordform) hash_code = self.request.matchdict['code'] password_reset = self.request.db.reset_passwords.find_one({'hash_code': hash_code}) user = self.request.userdb_new.get_user_by_mail(password_reset['email']) log.debug("Loaded user {!s} from {!s}".format(user, self.request.userdb)) if form_data.get('use_custom_password') == 'true': log.debug("Password change for user {!r} (custom password).".format(user)) new_password = form_data.get('custom_password') self.request.stats.count('dashboard/pwreset_custom_password', 1) else: log.debug("Password change for user {!r} (suggested password).".format(user)) new_password = self.get_suggested_password() self.request.stats.count('dashboard/pwreset_generated_password', 1) # Make user AL1 if password was reset by e-mail only if password_reset['mechanism'] == 'email': retrieve_modified_ts(user, self.request.dashboard_userdb) sync_user = False nin_count = len(user.nins.to_list()) if nin_count: unverify_user_nins(self.request, user) sync_user = True self.request.stats.count('dashboard/pwreset_downgraded_NINs', nin_count) if len(user.phone_numbers.to_list()): # We need to unverify a users phone numbers to make sure that an attacker can not # verify the account again without control over the users phone number unverify_user_mobiles(self.request, user) sync_user = True if sync_user: # Do not perform a sync if no changes where made, there is a corner case # where the user has not been created yet self.request.amrelay.request_sync(user) # Save new password new_password = new_password.replace(' ', '') ok = change_password(self.request, user, '', new_password) if ok: self.request.db.reset_passwords.remove({'_id': password_reset['_id']}) self.request.stats.count('dashboard/pwreset_changed_password', 1) url = self.request.route_url('profile-editor') reset = True else: self.request.stats.count('dashboard/pwreset_password_change_failed', 1) url = self.request.route_url('reset-password') reset = False return { 'url': url, 'reset': reset }
def sync_user(request, context, old_user): user = request.userdb.get_user_by_id(old_user.user_id) if isinstance(user, DashboardUser): retrieve_modified_ts(user, request.dashboard_userdb) else: user.retrieve_modified_ts(request.db.profiles) if context.workmode == 'personal': store_session_user(request, user) else: store_session_user(request, user, edit_user = True) return user
def sync_user(request, context, old_user): user = request.userdb.get_user_by_id(old_user.user_id) if isinstance(user, DashboardUser): retrieve_modified_ts(user, request.dashboard_userdb) else: user.retrieve_modified_ts(request.db.profiles) if context.workmode == 'personal': store_session_user(request, user) else: store_session_user(request, user, edit_user=True) return user
def get_user(self): logger.debug("Looking for verification code {!r}".format( self.request.matchdict['code'])) verification_code = self.request.db.verifications.find_one({ 'code': self.request.matchdict['code'], }) if verification_code is None: raise HTTPNotFound() user = self.request.userdb_new.get_user_by_id( verification_code['user_oid']) retrieve_modified_ts(user, self.request.dashboard_userdb) return user
def verify_mb_action(self, data, post_data): """ Verify a users identity using their mobile phone subscriber records. Only the active (the last one) NIN can be verified """ nin, index = data.split() index = int(index) nins = get_not_verified_nins_list(self.request, self.user) if len(nins) > index: new_nin = nins[index] if new_nin != nin: return self.sync_user() else: return self.sync_user() # if index != len(nins) - 1: # message = _("The provided nin can't be verified. You only " # 'can verify the last one') # return { # 'result': 'bad', # 'message': get_localizer(self.request).translate(message), # } validation = validate_nin_by_mobile(self.request, self.user, nin) result = validation['success'] and 'success' or 'error' model_name = 'norEduPersonNIN' if result == 'success': session_user = get_session_user(self.request, legacy_user = False) retrieve_modified_ts(session_user, self.request.dashboard_userdb) set_nin_verified(self.request, session_user, nin) try: #self.user.save(self.request) self.request.context.save_dashboard_user(session_user) logger.info("Verified by mobile, {!s} saved for user {!r}.".format(model_name, session_user)) # Save the state in the verifications collection save_as_verified(self.request, 'norEduPersonNIN', session_user, nin) except UserOutOfSync: logger.info("Verified {!s} NOT saved for user {!r}. User out of sync.".format(model_name, session_user)) raise settings = self.request.registry.settings msg = get_localizer(self.request).translate(validation['message'], mapping={ 'service_name': settings.get('mobile_service_name', 'Navet'), }) return { 'result': result, 'message': msg, }
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 save_success(self, passwordform): authn_ts = self.request.session.get('reauthn-for-chpass', None) if authn_ts is None: raise HTTPBadRequest(_('No authentication info')) now = datetime.utcnow() delta = now - datetime.fromtimestamp(authn_ts) # XXX put the reauthn timeout in the settings if int(delta.total_seconds()) > 600: msg = _('Stale authentication info. Please try again.') self.request.session.flash('error|' + msg) raise HTTPFound(self.context.route_url('profile-editor')) user = get_session_user(self.request) log.debug('Removing Authn ts for user {!r} before' ' changing the password'.format(user)) del self.request.session['reauthn-for-chpass'] passwords_data = self.schema.serialize(passwordform) if passwords_data.get('use_custom_password') == 'true': # The user has entered his own password and it was verified by # validators log.debug("Password change for user {!r} (custom password).".format(user)) new_password = passwords_data.get('custom_password') else: # If the user has selected the suggested password, then it should # be in session log.debug("Password change for user {!r} (suggested password).".format(user)) new_password = self.get_suggested_password() new_password = new_password.replace(' ', '') old_password = passwords_data['old_password'] # Load user from database to ensure we are working on an up-to-date set of credentials. # XXX this refresh is a bit redundant with the same thing being done in OldPasswordValidator. user = self.request.userdb_new.get_user_by_id(user.user_id) log.debug("Refreshed user {!s} from {!s}".format(user, self.request.userdb_new)) retrieve_modified_ts(user, self.request.dashboard_userdb) self.changed = change_password(self.request, user, old_password, new_password) if self.changed: message = 'success|' + _('Your password has been successfully updated') else: message = 'error|' + _('An error has occured while updating your password, ' 'please try again or contact support if the problem persists.') self.request.session.flash(message) raise HTTPFound(self.context.route_url('profile-editor'))
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 _search_user(self, text): """ Find a user matching email, mobile or NIN. @param text: search string @return: user object """ if validate_email_format(text): user = self.request.userdb_new.get_user_by_mail(normalize_email(text), raise_on_missing=True) elif text.startswith(u'0') or text.startswith(u'+'): text = normalize_to_e_164(self.request, text) user = self.request.userdb_new.get_user_by_phone(text) else: user = self.request.userdb_new.get_user_by_nin(text) log.debug("Found user {!r} using input {!s}.".format(user, text)) retrieve_modified_ts(user, self.request.dashboard_userdb) return user
def _search_user(self, text): """ Find a user matching email, mobile or NIN. @param text: search string @return: user object """ if validate_email_format(text): user = self.request.userdb_new.get_user_by_mail( normalize_email(text), raise_on_missing=True) elif text.startswith(u'0') or text.startswith(u'+'): text = normalize_to_e_164(self.request, text) user = self.request.userdb_new.get_user_by_phone(text) else: user = self.request.userdb_new.get_user_by_nin(text) log.debug("Found user {!r} using input {!s}.".format(user, text)) retrieve_modified_ts(user, self.request.dashboard_userdb) return user
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 _get_user_by_eppn(request, eppn, legacy_user): """ Fetch a user in either legacy format or the new eduid_userdb.User format. :param request: Pyramid request object :param eppn: eduPersonPrincipalName :param legacy_user: Use old format or not :type eppn: str | unicode :type legacy_user: bool :return: DashboardUser :rtype: eduid_userdb.User | eduid_userdb.dashboard.DashboardLegacyUser """ if legacy_user: user = request.userdb.get_user_by_eppn(eppn) logger.debug('Loading modified_ts from dashboard db (profiles) for user {!r}'.format(user)) user.retrieve_modified_ts(request.db.profiles) return user user = request.userdb_new.get_user_by_eppn(eppn) retrieve_modified_ts(user, request.dashboard_userdb) return user
def change_password(request, user, old_password, new_password): """ Change the user password, deleting old credentials @param request: Request object @param user: User object @param old_password: Old password, if supplied (i.e. not a password reset) @param new_password: New password @type request: Request @type user: User @type old_password: str or unicode or None """ vccs_url = request.registry.settings.get('vccs_url') log.debug("Changing password user {!s}\nTIMESTAMP 1 {!s}".format(user, user.modified_ts)) added = add_credentials(vccs_url, old_password, new_password, user) log.debug("Changing password user {!s}\nTIMESTAMP 2 {!s}".format(user, user.modified_ts)) if added: retrieve_modified_ts(user, request.dashboard_userdb) user.terminated = False request.context.save_dashboard_user(user) return added
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 set_nin_verified(request, user, new_nin, reference=None): """ Mark a National Identity Number (NIN) as verified on a user. This process also includes *removing* the NIN from any other user that had it as a verified NIN. :param request: The HTTP request :param user: The user :param new_nin: The National Identity Number to mark as verified :param reference: A reference to the verification code - used for audit logging :type request: pyramid.request.Request :type user: User :type new_nin: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify NIN for user {!r}.'.format(user)) log.debug('NIN: {!s}.'.format(new_nin)) # Start by removing nin from any other user old_user = request.userdb_new.get_user_by_nin(new_nin, raise_on_missing=False) log.debug('Searched for NIN {!r} in {!s}: {!r}'.format(new_nin, request.userdb_new, old_user)) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) _remove_nin_from_user(new_nin, old_user) request.context.save_dashboard_user(old_user) log.info('Removed NIN and associated addresses from user {!r}.'.format(old_user)) steal_count = 1 # Add the verified nin to the requesting user _add_nin_to_user(new_nin, user) _nin_verified_transaction_audit(request, reference) log.info('NIN verified for user {!r}.'.format(user)) request.stats.count('dashboard/verify_nin_stolen', steal_count) request.stats.count('dashboard/verify_nin_completed', 1) return _('National identity number {obj} verified')
def set_nin_verified(request, user, new_nin, reference=None): """ Mark a National Identity Number (NIN) as verified on a user. This process also includes *removing* the NIN from any other user that had it as a verified NIN. :param request: The HTTP request :param user: The user :param new_nin: The National Identity Number to mark as verified :param reference: A reference to the verification code - used for audit logging :type request: pyramid.request.Request :type user: User :type new_nin: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify NIN for user {!r}.'.format(user)) log.debug('NIN: {!s}.'.format(new_nin)) # Start by removing nin from any other user old_user = request.userdb_new.get_user_by_nin(new_nin, raise_on_missing=False) log.debug('Searched for NIN {!r} in {!s}: {!r}'.format(new_nin, request.userdb_new, old_user)) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) _remove_nin_from_user(new_nin, old_user) request.context.save_dashboard_user(old_user) log.info('Removed NIN and associated addresses from user {!r}.'.format(old_user)) steal_count = 1 # Add the verified nin to the requesting user _add_nin_to_user(new_nin, user) _nin_verified_transaction_audit(request, reference) log.info('NIN verified for user {!r}.'.format(user)) request.stats.count('verify_nin_stolen', steal_count) request.stats.count('verify_nin_completed') return _('National identity number {obj} verified')
def change_password(request, user, old_password, new_password): """ Change the user password, deleting old credentials @param request: Request object @param user: User object @param old_password: Old password, if supplied (i.e. not a password reset) @param new_password: New password @type request: Request @type user: User @type old_password: str or unicode or None """ vccs_url = request.registry.settings.get('vccs_url') log.debug("Changing password user {!s}\nTIMESTAMP 1 {!s}".format( user, user.modified_ts)) added = add_credentials(vccs_url, old_password, new_password, user) log.debug("Changing password user {!s}\nTIMESTAMP 2 {!s}".format( user, user.modified_ts)) if added: retrieve_modified_ts(user, request.dashboard_userdb) user.terminated = False request.context.save_dashboard_user(user) return added
def _get_user_by_eppn(request, eppn, legacy_user): """ Fetch a user in either legacy format or the new eduid_userdb.User format. :param request: Pyramid request object :param eppn: eduPersonPrincipalName :param legacy_user: Use old format or not :type eppn: str | unicode :type legacy_user: bool :return: DashboardUser :rtype: eduid_userdb.User | eduid_userdb.dashboard.DashboardLegacyUser """ if legacy_user: user = request.userdb.get_user_by_eppn(eppn) logger.debug( 'Loading modified_ts from dashboard db (profiles) for user {!r}'. format(user)) user.retrieve_modified_ts(request.db.profiles) return user user = request.userdb_new.get_user_by_eppn(eppn) retrieve_modified_ts(user, request.dashboard_userdb) return user
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 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 verify_code(request, model_name, code): """ Verify a code and act accordingly to the model_name ('norEduPersonNIN', 'mobile', or 'mailAliases'). This is what turns an unconfirmed NIN/mobile/e-mail into a confirmed one. :param request: The HTTP request :param model_name: 'norEduPersonNIN', 'mobile', or 'mailAliases' :param code: The user supplied code :type request: pyramid.request.Request :return: string of verified data """ assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases'] this_verification = request.db.verifications.find_one( { "model_name": model_name, "code": code, }) if not this_verification: log.error("Could not find verification record for code {!r}, model {!r}".format(code, model_name)) return reference = unicode(this_verification['_id']) obj_id = this_verification['obj_id'] if not obj_id: return None user = get_session_user(request, legacy_user=False) retrieve_modified_ts(user, request.dashboard_userdb) assert_error_msg = 'Requesting users ID does not match verifications user ID' assert user.user_id == this_verification['user_oid'], assert_error_msg if model_name == 'norEduPersonNIN': msg = set_nin_verified(request, user, obj_id, reference) elif model_name == 'phone': msg = set_phone_verified(request, user, obj_id) elif model_name == 'mailAliases': msg = set_email_verified(request, user, obj_id) else: raise NotImplementedError('Unknown validation model_name: {!r}'.format(model_name)) try: request.context.save_dashboard_user(user) log.info("Verified {!s} saved for user {!r}.".format(model_name, user)) verified = { 'verified': True, 'verified_timestamp': datetime.utcnow() } this_verification.update(verified) request.db.verifications.update({'_id': this_verification['_id']}, this_verification) log.info("Code {!r} ({!s}) marked as verified".format(code, obj_id)) except UserOutOfSync: log.info("Verified {!s} NOT saved for user {!r}. User out of sync.".format(model_name, user)) raise else: msg = get_localizer(request).translate(msg) request.session.flash(msg.format(obj=obj_id), queue='forms') request.stats.count('dashboard/verify_code_completed', 1) return obj_id
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 verify_code(request, model_name, code): """ Verify a code and act accordingly to the model_name ('norEduPersonNIN', 'phone', or 'mailAliases'). This is what turns an unconfirmed NIN/mobile/e-mail into a confirmed one. :param request: The HTTP request :param model_name: 'norEduPersonNIN', 'phone', or 'mailAliases' :param code: The user supplied code :type request: pyramid.request.Request :return: string of verified data """ assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases'] this_verification = request.db.verifications.find_one( { "model_name": model_name, "code": code, }) if not this_verification: log.error("Could not find verification record for code {!r}, model {!r}".format(code, model_name)) return reference = unicode(this_verification['_id']) obj_id = this_verification['obj_id'] if not obj_id: return None user = get_session_user(request, legacy_user=False) retrieve_modified_ts(user, request.dashboard_userdb) assert_error_msg = 'Requesting users ID does not match verifications user ID' assert user.user_id == this_verification['user_oid'], assert_error_msg if model_name == 'norEduPersonNIN': msg = set_nin_verified(request, user, obj_id, reference) elif model_name == 'phone': msg = set_phone_verified(request, user, obj_id) elif model_name == 'mailAliases': msg = set_email_verified(request, user, obj_id) else: raise NotImplementedError('Unknown validation model_name: {!r}'.format(model_name)) try: request.context.save_dashboard_user(user) log.info("Verified {!s} saved for user {!r}.".format(model_name, user)) verified = { 'verified': True, 'verified_timestamp': datetime.utcnow() } this_verification.update(verified) request.db.verifications.update({'_id': this_verification['_id']}, this_verification) log.info("Code {!r} ({!s}) marked as verified".format(code, obj_id)) except UserOutOfSync: log.info("Verified {!s} NOT saved for user {!r}. User out of sync.".format(model_name, user)) raise else: msg = get_localizer(request).translate(msg) request.session.flash(msg.format(obj=obj_id), queue='forms') request.stats.count('verify_code_completed') return obj_id