Exemple #1
0
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
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #5
0
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)
Exemple #6
0
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
Exemple #9
0
    def finish_letter_action(self, data, post_data):
        """
        Contact the eduid-idproofing-letter service and give it the code the user supplied.

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

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

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

        code = post_data['verification_code']

        self.user = get_session_user(self.request)

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

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

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

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

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

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

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

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

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

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

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

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

        code = post_data['verification_code']

        self.user = get_session_user(self.request)

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

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

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

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

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

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

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

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

        return make_result('success', _('You have successfully verified your identity'))
Exemple #11
0
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