def logout_service(request): """SAML Logout Response endpoint The IdP will send the logout response to this view, which will process it with pysaml2 help and log the user out. Note that the IdP can request a logout even when we didn't initiate the process as a single logout request started by another SP. """ log.debug('Logout service started') state = StateCache(request.session) client = Saml2Client(request.saml2_config, state_cache=state, identity_cache=IdentityCache(request.session)) settings = request.registry.settings logout_redirect_url = settings.get('saml2.logout_redirect_url') next_page = request.GET.get('next_page', logout_redirect_url) if 'SAMLResponse' in request.GET: # we started the logout log.debug('Receiving a logout response from the IdP') response = client.parse_logout_request_response( request.GET['SAMLResponse'], BINDING_HTTP_REDIRECT ) state.sync() if response and response.status_ok(): headers = logout(request) return HTTPFound(next_page, headers=headers) else: log.error('Unknown error during the logout') return HTTPBadRequest('Error during logout') elif 'SAMLRequest' in request.GET: # logout started by the IdP log.debug('Receiving a logout request from the IdP') subject_id = _get_name_id(request.session) if subject_id is None: log.warning( 'The session does not contain the subject id for user {0} ' 'Performing local logout'.format( authenticated_userid(request) ) ) headers = logout(request) return HTTPFound(location=next_page, headers=headers) else: http_info = client.handle_logout_request( request.GET['SAMLRequest'], subject_id, BINDING_HTTP_REDIRECT, relay_state=request.GET['RelayState'] ) state.sync() location = get_location(http_info) headers = logout(request) return HTTPFound(location=location, headers=headers) else: log.error('No SAMLResponse or SAMLRequest parameter found') raise HTTPNotFound('No SAMLResponse or SAMLRequest parameter found')
def get_authn_request(request, came_from, selected_idp, required_loa=None, force_authn=False): # Request the right AuthnContext for workmode # (AL1 for 'personal', AL2 for 'helpdesk' and AL3 for 'admin' by default) if required_loa is None: required_loa = request.registry.settings.get('required_loa', {}) workmode = request.registry.settings.get('workmode') required_loa = required_loa.get(workmode, '') log.debug('Requesting AuthnContext {!r}'.format(required_loa)) kwargs = { "requested_authn_context": RequestedAuthnContext( authn_context_class_ref=AuthnContextClassRef( text=required_loa ) ), "force_authn": str(force_authn).lower(), } client = Saml2Client(request.saml2_config) try: (session_id, info) = client.prepare_for_authenticate( entityid=selected_idp, relay_state=came_from, binding=BINDING_HTTP_REDIRECT, **kwargs ) except TypeError: log.error('Unable to know which IdP to use') raise oq_cache = OutstandingQueriesCache(request.session) oq_cache.set(session_id, came_from) return info
def login_view(request): login_redirect_url = request.registry.settings.get( 'saml2.login_redirect_url', '/') came_from = request.GET.get('next', login_redirect_url) if authenticated_userid(request): return HTTPFound(location=came_from) selected_idp = request.GET.get('idp', None) idps = request.saml2_config.getattr('idp') if selected_idp is None and len(idps) > 1: log.debug('A discovery process is needed') return render_to_response('templates/wayf.jinja2', { 'available_idps': idps.items(), 'came_from': came_from, 'login_url': request.route_url('saml2-login'), }) # Request the right AuthnContext for workmode # (AL1 for 'personal', AL2 for 'helpdesk' and AL3 for 'admin' by default) required_loa = request.registry.settings.get('required_loa', {}) workmode = request.registry.settings.get('workmode') required_loa = required_loa.get(workmode, '') log.debug('Requesting AuthnContext {!r} for workmode {!r}'.format(required_loa, workmode)) kwargs = { "requested_authn_context": RequestedAuthnContext( authn_context_class_ref=AuthnContextClassRef( text=required_loa ) ) } client = Saml2Client(request.saml2_config) try: (session_id, result) = client.prepare_for_authenticate( entityid=selected_idp, relay_state=came_from, binding=BINDING_HTTP_REDIRECT, **kwargs ) except TypeError: log.error('Unable to know which IdP to use') raise oq_cache = OutstandingQueriesCache(request.session) oq_cache.set(session_id, came_from) log.debug('Redirecting the user to the IdP') if not request.is_xhr: return HTTPFound(location=get_location(result)) else: loginurl = request.route_url('saml2-login', _query=(('next', request.path),)) return HTTPXRelocate(loginurl)
def assertion_consumer_service(request): ''' ''' action = get_action(request.session) if sanitize_post_key(request, 'SAMLResponse') is None: raise HTTPBadRequest("Couldn't find 'SAMLResponse' in POST data.") xmlstr = request.POST['SAMLResponse'] session_info = get_authn_response(request.registry.settings, request.session, xmlstr) log.debug('Trying to locate the user authenticated by the IdP') user = authenticate(request, session_info) if user is None: log.error('Could not find the user identified by the IdP') raise HTTPUnauthorized("Access not authorized") return action(request, session_info, user)
def assertion_consumer_service(request): if 'SAMLResponse' not in request.POST: return HTTPBadRequest("Couldn't find 'SAMLResponse' in POST data.") xmlstr = request.POST['SAMLResponse'] client = Saml2Client(request.saml2_config, identity_cache=IdentityCache(request.session)) oq_cache = OutstandingQueriesCache(request.session) outstanding_queries = oq_cache.outstanding_queries() try: # process the authentication response response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries) except AssertionError: log.error('SAML response is not verified') return HTTPBadRequest( """SAML response is not verified. May be caused by the response was not issued at a reasonable time or the SAML status is not ok. Check the IDP datetime setup""") if response is None: log.error('SAML response is None') return HTTPBadRequest( "SAML response has errors. Please check the logs") session_id = response.session_id() oq_cache.delete(session_id) # authenticate the remote user session_info = response.session_info() log.debug('Trying to locate the user authenticated by the IdP') log.debug('Session info:\n{!s}\n\n'.format(pprint.pformat(session_info))) user = authenticate(request, session_info) if user is None: log.error('Could not find the user identified by the IdP') return HTTPUnauthorized("Access not authorized") headers = login(request, session_info, user) _set_name_id(request.session, session_info['name_id']) # redirect the user to the view where he came from relay_state = request.POST.get('RelayState', '/') log.debug('Redirecting to the RelayState: ' + relay_state) return HTTPFound(location=relay_state, headers=headers)
def authenticate(request, session_info): """ Locate a user using the identity found in the SAML assertion. :param request: Request object :param session_info: Session info received by pysaml2 client :returns: User :type request: Request() :type session_info: dict() :rtype: User or None """ if session_info is None: raise TypeError('Session info is None') user_main_attribute = request.registry.settings.get('saml2.user_main_attribute') attribute_values = get_saml_attribute(session_info, user_main_attribute) if not attribute_values: log.error('Could not find attribute {!r} in the SAML assertion'.format(user_main_attribute)) return None saml_user = attribute_values[0] # If user_main_attribute is eduPersonPrincipalName, there value might be scoped # and the scope (e.g. "@example.com") might have to be removed before looking # for the user in the database. strip_suffix = request.registry.settings.get('saml2.strip_saml_user_suffix') if strip_suffix: if saml_user.endswith(strip_suffix): saml_user = saml_user[:-len(strip_suffix)] log.debug('Looking for user with {!r} == {!r}'.format(user_main_attribute, saml_user)) try: user = request.userdb.get_user(saml_user) except request.userdb.exceptions.UserDoesNotExist: log.error('No user with {!r} = {!r} found'.format(user_main_attribute, saml_user)) except request.userdb.exceptions.MultipleUsersReturned: log.error("There are more than one user with {!r} = {!r}".format(user_main_attribute, saml_user)) else: user.retrieve_modified_ts(request.db.profiles) return user return None
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', '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 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 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('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 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