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 verify_mail(request, user, new_mail): log.info('Trying to verify mail address for user {!r}.'.format(user)) log.debug('Mail address: {!s}.'.format(new_mail)) # Start by removing mail address from any other user old_user_docs = request.db.profiles.find({ 'mailAliases': {'$elemMatch': {'email': new_mail, 'verified': True}} }) steal_count = 0 for old_user_doc in old_user_docs: old_user = User(old_user_doc) if old_user: log.debug('Found old user {!r} with mail address ({!s}) already verified.'.format(old_user, new_mail)) log.debug('Old user mail BEFORE: {!s}.'.format(old_user.get_mail())) log.debug('Old user mail aliases BEFORE: {!r}.'.format(old_user.get_mail_aliases())) if old_user.get_mail() == new_mail: old_user.set_mail('') mails = [m for m in old_user.get_mail_aliases() if m['email'] != new_mail] old_user.set_mail_aliases(mails) log.debug('Old user mail AFTER: {!s}.'.format(old_user.get_mail())) log.debug('Old user mail aliases AFTER: {!r}.'.format(old_user.get_mail_aliases())) old_user.retrieve_modified_ts(request.db.profiles) old_user.save(request) steal_count += 1 # Add the verified mail address to the requesting user user.add_verified_email(new_mail) 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 user, _('Email {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('verify_mail_stolen', steal_count) request.stats.count('verify_mail_completed') return _('Email {obj} verified')
def send_reset_password_mail(request, user, reset_password_link): """ Send an email with the instructions for resetting password """ mailer = get_mailer(request) site_name = request.registry.settings.get("site.name", "eduID") password_reset_timeout = int( request.registry.settings.get("password_reset_timeout", "120")) / 60 if user.mail_addresses.primary is not None: email = user.mail_addresses.primary.email elif user.mail_addresses.count > 0: email = user.mail_addresses.to_list()[0].email else: log.info( 'User {!r} has no email address, not possible to send a message'. format(user)) return context = { "email": email, "reset_password_link": reset_password_link, "password_reset_timeout": password_reset_timeout, "site_url": request.route_url("home"), "site_name": site_name, } message = Message( subject=_("Reset your {site_name} password").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[email], body=render( "templates/reset-password-email.txt.jinja2", context, request, ), html=render( "templates/reset-password-email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug( "Sent reset password mail to user {!r} with address {!s}.".format( user, email)) request.stats.count('email_send_pwreset_mail')
def logout(request): """ Destroy session information when a user logs out. :param request: :return: """ if 'user' in request.session: user = request.session['user'] log.info("User {!r} logging out".format(user['_id'])) if request.session is not None: request.session.delete() headers = forget(request) return headers
def logout(request): """ Destroy session information when a user logs out. :param request: :return: """ if request.session is not None: user = get_logged_in_user(request, raise_on_not_logged_in = False, legacy_user = True) if user: log.info("User {!r} logging out".format(user.get_id())) request.session.delete() headers = forget(request) return headers
def send_reset_password_mail(request, user, reset_password_link): """ Send an email with the instructions for resetting password """ mailer = get_mailer(request) site_name = request.registry.settings.get("site.name", "eduID") password_reset_timeout = int(request.registry.settings.get("password_reset_timeout", "120")) / 60 if user.mail_addresses.primary is not None: email = user.mail_addresses.primary.email elif user.mail_addresses.count > 0: email = user.mail_addresses.to_list()[0].email else: log.info('User {!r} has no email address, not possible to send a message'.format(user)) return context = { "email": email, "reset_password_link": reset_password_link, "password_reset_timeout": password_reset_timeout, "site_url": request.route_url("home"), "site_name": site_name, } message = Message( subject=_("Reset your {site_name} password").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[email], body=render( "templates/reset-password-email.txt.jinja2", context, request, ), html=render( "templates/reset-password-email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug("Sent reset password mail to user {!r} with address {!s}.".format(user, email)) request.stats.count('email_send_pwreset_mail')
def login(request, session_info, user): """ Update session with information about a user that has just logged in. :param request: Request object :param session_info: Session info received by pysaml2 client :param user: Information about user as returned by authenticate() :return: """ log.info("User {!r} logging in (eduPersonPrincipalName: {!r})".format(user['_id'], user['eduPersonPrincipalName'])) request.session['eduPersonPrincipalName'] = user['eduPersonPrincipalName'] store_session_user(request, user) request.session['eduPersonAssurance'] = get_loa( request.registry.settings.get('available_loa'), session_info ) remember_headers = remember(request, user['eduPersonPrincipalName']) return remember_headers
def send_termination_mail(request, user): user = get_session_user(request) # XXX remove when context users are new users mailer = get_mailer(request) support_email = request.registry.settings.get('mail.support_email', '*****@*****.**') site_name = request.registry.settings.get("site.name", "eduID") context = { 'support_mail': support_email, 'displayName': user.display_name } if user.mail_addresses.primary is not None: address = user.mail_addresses.primary.email elif user.mail_addresses.count > 0: for a in user.mail_addresses.to_list(): address = a.email break else: log.info('User {!r} has no email address, not possible to send a message'.format(user)) return message = Message( subject=_("{site_name} account termination").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[address], body=render( "templates/termination_email.txt.jinja2", context, request, ), html=render( "templates/termination_email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug("Sent termination mail to user {!r} with address {!s}.".format(user, address)) request.stats.count('dashboard/email_send_termination_mail', 1)
def login(request, session_info, user): """ Update session with information about a user that has just logged in. :param request: Request object :param session_info: Session info received by pysaml2 client :param user: Information about user as returned by authenticate() :return: """ main_attribute = request.registry.settings.get('saml2.user_main_attribute') log.info("User {!r} logging in ({!r}: {!r})".format(user['_id'], main_attribute, user[main_attribute])) request.session[main_attribute] = user[main_attribute] request.session['user'] = user request.session['eduPersonAssurance'] = get_loa( request.registry.settings.get('available_loa'), session_info ) remember_headers = remember(request, user[main_attribute]) return remember_headers
def send_termination_mail(request, user): mailer = get_mailer(request) support_email = request.registry.settings.get('mail.support_email', '*****@*****.**') site_name = request.registry.settings.get("site.name", "eduID") context = {'support_mail': support_email, 'displayName': user.display_name} if user.mail_addresses.primary is not None: address = user.mail_addresses.primary.email elif user.mail_addresses.count > 0: address = user.mail_addresses.to_list()[0].email else: log.info( 'User {!r} has no email address, not possible to send a message'. format(user)) return message = Message( subject=_("{site_name} account termination").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[address], body=render( "templates/termination_email.txt.jinja2", context, request, ), html=render( "templates/termination_email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug("Sent termination mail to user {!r} with address {!s}.".format( user, address)) request.stats.count('email_send_termination_mail')
def new_verification_code(request, model_name, obj_id, user, hasher=None): """ Match a user supplied code (`code') against an actual entry in the database. :param request: The HTTP request :param model_name: 'norEduPersonNIN', 'phone', or 'mailAliases' :param obj_id: The data covered by the verification, like the phone number or nin or ... :param user: The user :param hasher: Callable used to generate the code :type request: pyramid.request.Request :type model_name: str | unicode :type obj_id: str | unicode :type user: User | OldUser :type hasher: callable """ assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases'] try: userid = user.user_id except AttributeError: userid = user.get_id() if hasher is None: hasher = get_unique_hash code = hasher() obj = { 'model_name': model_name, 'obj_id': obj_id, 'user_oid': userid, 'code': code, 'verified': False, 'timestamp': datetime.now(utc), } doc_id = request.db.verifications.insert(obj) reference = unicode(doc_id) session_verifications = request.session.get('verifications', []) session_verifications.append(code) request.session['verifications'] = session_verifications log.info('Created new {!s} verification code for user {!r}.'.format(model_name, user)) log.debug('Verification object id {!s}. Code: {!s}.'.format(obj_id, code)) return reference, code
def new_verification_code(request, model_name, obj_id, user, hasher=None): if hasher is None: hasher = get_unique_hash code = hasher() obj = { "model_name": model_name, "obj_id": obj_id, "user_oid": user.get_id(), "code": code, "verified": False, "timestamp": datetime.now(utc), } doc_id = request.db.verifications.insert(obj) reference = unicode(doc_id) session_verifications = request.session.get('verifications', []) session_verifications.append(code) request.session['verifications'] = session_verifications log.info('Created new {!s} verification code for user {!r}.'.format(model_name, user)) log.debug('Verification object id {!s}. Code: {!s}.'.format(obj_id, code)) return reference, code
def add_by_mobile_success(self, ninform): """ This method is bounded to the "add_by_mobile"-button by it's name """ newnin = self.schema.serialize(ninform) newnin = newnin['norEduPersonNIN'] newnin = normalize_nin(newnin) self.user = get_session_user(self.request) message = set_nin_verified(self.request, self.user, newnin) try: self.request.context.save_dashboard_user(self.user) except UserOutOfSync: log.info("Failed to save user {!r} after mobile phone vetting. User out of sync.".format(self.user)) raise log.info("Saved user {!r} after NIN vetting using mobile phone".format(self.user)) self.request.session.flash( get_localizer(self.request).translate(message), queue='forms') self.request.stats.count('nin_add_mobile')
def add_by_mobile_success(self, ninform): """ This method is bounded to the "add_by_mobile"-button by it's name """ newnin = self.schema.serialize(ninform) newnin = newnin['norEduPersonNIN'] newnin = normalize_nin(newnin) self.user = get_session_user(self.request) message = set_nin_verified(self.request, self.user, newnin) try: self.request.context.save_dashboard_user(self.user) except UserOutOfSync: log.info("Failed to save user {!r} after mobile phone vetting. User out of sync.".format(self.user)) raise log.info("Saved user {!r} after NIN vetting using mobile phone".format(self.user)) self.request.session.flash( get_localizer(self.request).translate(message), queue='forms') self.request.stats.count('dashboard/nin_add_mobile', 1)
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 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 verify_mobile(request, user, new_mobile): log.info('Trying to verify mobile number for user {!r}.'.format(user)) log.debug('Mobile number: {!s}.'.format(new_mobile)) # Start by removing mobile number from any other user old_user_docs = request.db.profiles.find({ 'mobile': {'$elemMatch': {'mobile': new_mobile, 'verified': True}} }) steal_count = 0 for old_user_doc in old_user_docs: old_user = User(old_user_doc) if old_user: log.debug('Found old user {!r} with mobile number ({!s}) already verified.'.format(old_user, new_mobile)) log.debug('Old user mobile numbers BEFORE: {!r}.'.format(old_user.get_mobiles())) mobiles = [m for m in old_user.get_mobiles() if m['mobile'] != new_mobile] old_user.set_mobiles(mobiles) log.debug('Old user mobile numbers AFTER: {!r}.'.format(old_user.get_mobiles())) old_user.retrieve_modified_ts(request.db.profiles) old_user.save(request) log.info('Removed mobile number from user {!r}.'.format(old_user)) steal_count += 1 # Add the verified mobile number to the requesting user user.add_verified_mobile(new_mobile) log.info('Mobile 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 user, _('Mobile {obj} verified')
def verify_nin(request, user, new_nin, reference): 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_docs = request.db.profiles.find({ 'norEduPersonNIN': new_nin }) steal_count = 0 for old_user_doc in old_user_docs: old_user = User(old_user_doc) if old_user: log.debug('Found old user {!r} with NIN ({!s}) already verified.'.format(old_user, new_nin)) log.debug('Old user NINs BEFORE: {!r}.'.format(old_user.get_nins())) nins = [nin for nin in old_user.get_nins() if nin != new_nin] old_user.set_nins(nins) log.debug('Old user NINs AFTER: {!r}.'.format(old_user.get_nins())) log.debug('Old user addresses BEFORE: {!r}.'.format(old_user.get_addresses())) addresses = [a for a in old_user.get_addresses() if not a['verified']] old_user.set_addresses(addresses) log.debug('Old user addresses AFTER: {!r}.'.format(old_user.get_addresses())) old_user.retrieve_modified_ts(request.db.profiles) old_user.save(request) log.info('Removed NIN and associated addresses from user {!r}.'.format(old_user)) steal_count += 1 # Add the verified nin to the requesting user user.add_verified_nin(new_nin) user.retrieve_address(request, new_nin) # Connect the verification to the transaction audit log request.msgrelay.postal_address_to_transaction_audit_log(reference) # Reset session eduPersonIdentityProofing on NIN verification request.session['eduPersonIdentityProofing'] = None 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 user, _('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 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 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 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: webob.request.BaseRequest :return: string of verified data """ 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 if 'edit-user' in request.session: # non personal mode user = request.session['edit-user'] elif 'user' in request.session: # personal mode user = request.session['user'] assert_error_msg = 'Requesting users ID does not match verifications user ID' assert user.get_id() == this_verification['user_oid'], assert_error_msg if model_name == 'norEduPersonNIN': user, msg = verify_nin(request, user, obj_id, reference) elif model_name == 'mobile': user, msg = verify_mobile(request, user, obj_id) elif model_name == 'mailAliases': user, msg = verify_mail(request, user, obj_id) else: raise NotImplementedError('Unknown validation model_name') try: user.save(request) 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 validate_nin_by_mobile(request, user, nin): """ :param request: The pyramid request :param user: The eduid user :param nin: The NIN :type request: pyramid.request.Request :type user: eduid_userdb.User :type nin: str :return:dict """ log.info( 'Trying to verify nin via mobile number for user {!r}.'.format(user)) log.debug('NIN: {!s}.'.format(nin)) from eduid_lookup_mobile.utilities import format_NIN # Get list of verified mobile numbers verified_mobiles = [ x.number for x in user.phone_numbers.to_list() if x.is_verified ] national_identity_number = format_NIN(nin) status = 'no_phone' valid_mobile = None registered_to_nin = None age = _get_age(national_identity_number) try: for mobile_number in verified_mobiles: status = 'no_match' # Get the registered owner of the mobile number registered_to_nin = request.lookuprelay.find_NIN_by_mobile( mobile_number) registered_to_nin = format_NIN(registered_to_nin) if registered_to_nin == national_identity_number: # Check if registered nin was the given nin valid_mobile = mobile_number status = 'match' log.info('Mobile number matched for user {!r}.'.format(user)) log.debug('Mobile {!s} registered to NIN: {!s}.'.format( valid_mobile, registered_to_nin)) request.stats.count('validate_nin_by_mobile_exact_match') break elif registered_to_nin is not None and age < 18: # Check if registered nin is related to given nin relation = request.msgrelay.get_relations_to( national_identity_number, registered_to_nin) # TODO All relations? #valid_relations = ['M', 'B', 'FA', 'MO', 'VF'] valid_relations = ['FA', 'MO'] if any(r in relation for r in valid_relations): valid_mobile = mobile_number status = 'match_by_navet' log.info('Mobile number matched for user {!r} via navet.'. format(user)) log.debug('Mobile {!s} registered to NIN: {!s}.'.format( valid_mobile, registered_to_nin)) log.debug( 'Person with NIN {!s} have relation {!s} to user: {!r}.' .format(registered_to_nin, relation, user)) request.stats.count( 'validate_nin_by_mobile_relative_match') break except request.lookuprelay.TaskFailed: status = 'error_lookup' except request.msgrelay.TaskFailed: status = 'error_navet' msg = None if status == 'no_phone': msg = _('You have no confirmed mobile phone') log.info('User {!r} has no verified mobile phone number.'.format(user)) elif status == 'no_match': log.info( 'User {!r} NIN is not associated with any verified mobile phone number.' .format(user)) msg = _( 'A company subscription or protected phone number cannot be used with this service.' ) request.stats.count('validate_nin_by_mobile_no_match') elif status == 'error_lookup' or status == 'error_navet': log.error( 'Validate NIN via mobile failed with status "{!s}" for user {!r}.'. format(status, user)) msg = _('Sorry, we are experiencing temporary technical ' 'problem with ${service_name}, please try again ' 'later.') request.stats.count('validate_nin_by_mobile_error') if status == 'match' or status == 'match_by_navet': log.info( 'Validate NIN via mobile succeeded with status "{!s}" for user {!r}.' .format(status, user)) msg = _('Validate NIN via mobile with succeeded') user_postal_address = request.msgrelay.get_full_postal_address( national_identity_number) if status == 'match': proofing_data = TeleAdressProofing(user, status, national_identity_number, valid_mobile, user_postal_address) else: registered_postal_address = request.msgrelay.get_full_postal_address( registered_to_nin) proofing_data = TeleAdressProofingRelation( user, status, national_identity_number, valid_mobile, user_postal_address, registered_to_nin, relation, registered_postal_address) log.info('Logging of mobile proofing data for user {!r}.'.format(user)) if not request.idproofinglog.log_verification(proofing_data): log.error( 'Logging of mobile proofing data for user {!r} failed.'.format( user)) valid_mobile = None msg = _('Sorry, we are experiencing temporary technical ' 'problem with ${service_name}, please try again ' 'later.') validation_result = { 'success': valid_mobile is not None, 'message': msg, 'mobile': valid_mobile } return validation_result
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 validate_nin_by_mobile(request, user, nin): """ :param request: The pyramid request :param user: The eduid user :param nin: The NIN :type request: pyramid.request.Request :type user: eduid_userdb.User :type nin: str :return:dict """ log.info('Trying to verify nin via mobile number for user {!r}.'.format(user)) log.debug('NIN: {!s}.'.format(nin)) from eduid_lookup_mobile.utilities import format_NIN # Get list of verified mobile numbers verified_mobiles = [x.number for x in user.phone_numbers.to_list() if x.is_verified] national_identity_number = format_NIN(nin) status = 'no_phone' valid_mobile = None registered_to_nin = None age = _get_age(national_identity_number) try: for mobile_number in verified_mobiles: status = 'no_match' # Get the registered owner of the mobile number registered_to_nin = request.lookuprelay.find_NIN_by_mobile(mobile_number) registered_to_nin = format_NIN(registered_to_nin) if registered_to_nin == national_identity_number: # Check if registered nin was the given nin valid_mobile = mobile_number status = 'match' log.info('Mobile number matched for user {!r}.'.format(user)) log.debug('Mobile {!s} registered to NIN: {!s}.'.format(valid_mobile, registered_to_nin)) request.stats.count('dashboard/validate_nin_by_mobile_exact_match', 1) break elif registered_to_nin is not None and age < 18: # Check if registered nin is related to given nin relation = request.msgrelay.get_relations_to(national_identity_number, registered_to_nin) # TODO All relations? #valid_relations = ['M', 'B', 'FA', 'MO', 'VF'] valid_relations = ['FA', 'MO'] if any(r in relation for r in valid_relations): valid_mobile = mobile_number status = 'match_by_navet' log.info('Mobile number matched for user {!r} via navet.'.format(user)) log.debug('Mobile {!s} registered to NIN: {!s}.'.format(valid_mobile, registered_to_nin)) log.debug('Person with NIN {!s} have relation {!s} to user: {!r}.'.format(registered_to_nin, relation, user)) request.stats.count('dashboard/validate_nin_by_mobile_relative_match', 1) break except request.lookuprelay.TaskFailed: status = 'error_lookup' except request.msgrelay.TaskFailed: status = 'error_navet' msg = None if status == 'no_phone': msg = _('You have no confirmed mobile phone') log.info('User {!r} has no verified mobile phone number.'.format(user)) elif status == 'no_match': log.info('User {!r} NIN is not associated with any verified mobile phone number.'.format(user)) msg = _('The given mobile number was not associated to the given national identity number') request.stats.count('dashboard/validate_nin_by_mobile_no_match', 1) elif status == 'error_lookup' or status == 'error_navet': log.error('Validate NIN via mobile failed with status "{!s}" for user {!r}.'.format(status, user)) msg = _('Sorry, we are experiencing temporary technical ' 'problem with ${service_name}, please try again ' 'later.') request.stats.count('dashboard/validate_nin_by_mobile_error', 1) if status == 'match' or status == 'match_by_navet': log.info('Validate NIN via mobile succeeded with status "{!s}" for user {!r}.'.format(status, user)) msg = _('Validate NIN via mobile with succeeded') user_postal_address = request.msgrelay.get_full_postal_address(national_identity_number) if status == 'match': proofing_data = TeleAdressProofing(user, status, national_identity_number, valid_mobile, user_postal_address) else: registered_postal_address = request.msgrelay.get_full_postal_address(registered_to_nin) proofing_data = TeleAdressProofingRelation(user, status, national_identity_number, valid_mobile, user_postal_address, registered_to_nin, relation, registered_postal_address) log.info('Logging of mobile proofing data for user {!r}.'.format(user)) if not request.idproofinglog.log_verification(proofing_data): log.error('Logging of mobile proofing data for user {!r} failed.'.format(user)) valid_mobile = None msg = _('Sorry, we are experiencing temporary technical ' 'problem with ${service_name}, please try again ' 'later.') validation_result = {'success': valid_mobile is not None, 'message': msg, 'mobile': valid_mobile} return validation_result
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