Ejemplo n.º 1
0
def locale_negotiator(request):
    # Import here to avoid circular dependencies
    from eduiddashboard.utils import sanitize_cookies_get

    settings = request.registry.settings
    available_languages = settings['available_languages'].keys()
    cookie_name = settings['lang_cookie_name']

    cookie_lang = sanitize_cookies_get(request, cookie_name, None)
    if cookie_lang and cookie_lang in available_languages:
        return cookie_lang

    user = get_session_user(request,
                            legacy_user=False,
                            raise_on_not_logged_in=False)
    if user:
        preferredLanguage = user.language
        if preferredLanguage:
            return preferredLanguage

    locale_name = request.accept_language.best_match(available_languages)

    if locale_name not in available_languages:
        locale_name = settings.get('default_locale_name', 'sv')
    return locale_name
Ejemplo n.º 2
0
    def setprimary_action(self, index, post_data):

        self.user = get_session_user(self.request)
        try:
            mail = self.user.mail_addresses.to_list()[index]
        except IndexError:
            return self.sync_user()

        if not mail.is_verified:
            message = _('You need to confirm your email address '
                        'before it can become primary')
            return {
                'result': 'bad',
                'message': get_localizer(self.request).translate(message),
            }

        self.user.mail_addresses.primary = mail.email
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            return self.sync_user()

        self.request.stats.count('email_set_primary')
        message = _('Your primary email address was ' 'successfully changed')
        return {
            'result': 'success',
            'message': get_localizer(self.request).translate(message)
        }
Ejemplo n.º 3
0
    def add_success(self, emailform):
        newemail = self.schema.serialize(emailform)

        new_email = MailAddress(email=newemail['mail'],
                                application='dashboard',
                                verified=False,
                                primary=False)

        self.user = get_session_user(self.request)
        self.user.mail_addresses.add(new_email)
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            self.sync_user()

        else:
            message = _('Changes saved')
            self.request.session.flash(get_localizer(
                self.request).translate(message),
                                       queue='forms')

            send_verification_mail(self.request, newemail['mail'])

            second_msg = _(
                'A confirmation email has been sent to your email '
                'address. Please enter your confirmation code '
                '<a href="#" class="verifycode" '
                'data-identifier="${id}">here</a>.',
                mapping={'id': self.user.mail_addresses.count})
            self.request.session.flash(get_localizer(
                self.request).translate(second_msg),
                                       queue='forms')
Ejemplo n.º 4
0
    def remove_action(self, index, post_data):
        self.user = get_session_user(self.request)
        emails = self.user.mail_addresses.to_list()
        if len(emails) == 1:
            message = _('Error: You only have one email address and it  '
                        'can not be removed')
            return {
                'result': 'error',
                'message': get_localizer(self.request).translate(message),
            }

        try:
            remove_email = emails[index].email
        except IndexError:
            return self.sync_user()

        try:
            self.user.mail_addresses.remove(remove_email)
        except PrimaryElementViolation:
            new_index = 0 if index != 0 else 1
            self.user.mail_addresses.primary = emails[new_index].email
            self.user.mail_addresses.remove(remove_email)

        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            return self.sync_user()

        self.request.stats.count('email_removed')
        message = _('Email address was successfully removed')
        return {
            'result': 'success',
            'message': get_localizer(self.request).translate(message),
        }
Ejemplo n.º 5
0
    def verify_action(self, index, post_data):
        """ Common action to verify some given data.
            You can override in subclasses
        """
        self.user = get_session_user(self.request)

        # Catch the unlikely event when the user have e.g. removed all entries
        # in a separate tab, or one in the middle and then tries to resend the
        # code for a non-existing entry.
        # This is an incomplete fix since it is better if we can get the list
        # from the UI and then check that the entry which the client want to
        # resend the code for corresponds to the same entry we get from
        # data[index].
        try:
            _data = self.user.to_dict().get(self.data_attribute, [])
            data_to_verify = _data[index]
        except IndexError:
            log.warning('Index error in verify_action, user {!s}'.format(
                self.user))
            message = self.verify_messages['out_of_sync']
            return {
                'result': 'out_of_sync',
                'message': get_localizer(self.request).translate(message),
            }

        data_id = self.get_verification_data_id(data_to_verify)
        return self._verify_action(data_id, post_data)
Ejemplo n.º 6
0
    def __call__(self, node, value):
        """
        Validator which makes sure that:
        1. the NiN has not already been added by the user
        2. the user does not already have a confirmed NiN.
        """

        from eduiddashboard.models import normalize_nin
        value = normalize_nin(copy(value))

        request = node.bindings.get('request')
        user = get_session_user(request)
        user_nins = user.nins

        unverified_user_nins = request.db.verifications.find({
            'obj_id': value,
            'model_name': 'norEduPersonNIN',
            'user_oid': user.user_id,
            'verified': False
        })

        # Search the request.POST for any post that starts with "add".
        for post_value in request.POST:
            if post_value.startswith('add') and (user_nins.find(value) or
                                      unverified_user_nins.count() > 0):
                err = _('National identity number already added')
                raise colander.Invalid(node, get_localizer(request).translate(err))

            elif post_value.startswith('add') and user_nins.count > 0:
                err = _('You already have a confirmed national identity number')
                raise colander.Invalid(node, get_localizer(request).translate(err))
Ejemplo n.º 7
0
    def verify_action(self, index, post_data):
        """ Common action to verify some given data.
            You can override in subclasses
        """
        self.user = get_session_user(self.request)

        # Catch the unlikely event when the user have e.g. removed all entries
        # in a separate tab, or one in the middle and then tries to resend the
        # code for a non-existing entry.
        # This is an incomplete fix since it is better if we can get the list
        # from the UI and then check that the entry which the client want to
        # resend the code for corresponds to the same entry we get from
        # data[index].
        try:
            _data = self.user.to_dict().get(self.data_attribute, [])
            data_to_verify = _data[index]
        except IndexError:
            log.warning('Index error in verify_action, user {!s}'.format(self.user))
            message = self.verify_messages['out_of_sync']
            return {
                'result': 'out_of_sync',
                'message': get_localizer(self.request).translate(message),
            }

        data_id = self.get_verification_data_id(data_to_verify)
        return self._verify_action(data_id, post_data)
Ejemplo n.º 8
0
    def add_success(self, emailform):
        newemail = self.schema.serialize(emailform)

        new_email = MailAddress(email=newemail['mail'],
                application='dashboard',
                verified=False, primary=False)

        self.user = get_session_user(self.request)
        self.user.mail_addresses.add(new_email)
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            self.sync_user()

        else:
            message = _('Changes saved')
            self.request.session.flash(get_localizer(self.request).translate(message), queue='forms')

            send_verification_mail(self.request, newemail['mail'])

            second_msg = _('A confirmation email has been sent to your email '
                    'address. Please enter your confirmation code '
                    '<a href="#" class="verifycode" '
                    'data-identifier="${id}">here</a>.', mapping={'id': self.user.mail_addresses.count})
            self.request.session.flash(get_localizer(self.request).translate(second_msg), queue='forms')
Ejemplo n.º 9
0
    def setprimary_action(self, index, post_data):

        self.user = get_session_user(self.request)
        try:
            mail = self.user.mail_addresses.to_list()[index]
        except IndexError:
            return self.sync_user()

        if not mail.is_verified:
            message = _('You need to confirm your email address '
                        'before it can become primary')
            return {
                'result': 'bad',
                'message': get_localizer(self.request).translate(message),
            }

        self.user.mail_addresses.primary = mail.email
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            return self.sync_user()

        self.request.stats.count('dashboard/email_set_primary', 1)
        message = _('Your primary email address was '
                    'successfully changed')
        return {'result': 'success',
                'message': get_localizer(self.request).translate(message)}
Ejemplo n.º 10
0
    def remove_action(self, index, post_data):
        self.user = get_session_user(self.request)
        emails = self.user.mail_addresses.to_list()
        if len(emails) == 1:
            message = _('Error: You only have one email address and it  '
                        'can not be removed')
            return {
                'result': 'error',
                'message': get_localizer(self.request).translate(message),
            }

        try:
            remove_email = emails[index].email
        except IndexError:
            return self.sync_user()

        try:
            self.user.mail_addresses.remove(remove_email)
        except PrimaryElementViolation:
            new_index = 0 if index != 0 else 1
            self.user.mail_addresses.primary = emails[new_index].email
            self.user.mail_addresses.remove(remove_email)

        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            return self.sync_user()

        self.request.stats.count('dashboard/email_removed', 1)
        message = _('Email address was successfully removed')
        return {
            'result': 'success',
            'message': get_localizer(self.request).translate(message),
        }
Ejemplo n.º 11
0
    def add_success(self, mobileform):
        mobile_number = self.schema.serialize(mobileform)['mobile']
        mobile_number = normalize_to_e_164(self.request, mobile_number)
        mobile = PhoneNumber(
            data={
                'number': mobile_number,
                'verified': False,
                'primary': False,
                'created_ts': datetime.utcnow()
            })
        self.user = get_session_user(self.request)
        self.user.phone_numbers.add(mobile)
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            self.sync_user()

        else:
            send_verification_code(self.request, self.user, mobile_number)

            self.request.session.flash(_('Changes saved'), queue='forms')
            msg = _(
                'A confirmation code has been sent to your mobile phone. '
                'Please click on the "Pending confirmation" link below and enter your confirmation code.'
            )
            msg = get_localizer(self.request).translate(msg)
            self.request.session.flash(msg, queue='forms')
Ejemplo n.º 12
0
def get_available_tabs(context, request):
    from eduiddashboard.views import (emails, personal,
                                      mobiles, nins, permissions,
                                      get_dummy_status)

    default_tabs = [
        personal.get_tab(request),
        nins.get_tab(request),
        emails.get_tab(request),
        permissions.get_tab(request),
        mobiles.get_tab(request),
        # postal_address.get_tab(request),
        {
            'label': _('Security'),
            'status': get_dummy_status,
            'id': 'security',
        },
    ]
    if context.workmode == 'personal':
        tabs = filter_tabs(default_tabs, ['permissions'])
    elif context.workmode == 'helpdesk':
        tabs = filter_tabs(default_tabs, ['passwords', 'authorization'])
    else:
        tabs = default_tabs
    # Import here to avoid circular import
    from eduiddashboard.session import get_session_user
    user = get_session_user(request, legacy_user = False, raise_on_not_logged_in = False)
    for tab in tabs:
        tab['status'] = tab['status'](request, user)
    return tabs
Ejemplo n.º 13
0
    def add_success(self, mobileform):
        mobile_number = self.schema.serialize(mobileform)['mobile']
        mobile_number = normalize_to_e_164(self.request, mobile_number)
        mobile = PhoneNumber(data={'number':  mobile_number,
                                   'verified': False,
                                   'primary': False,
                                   'created_ts': datetime.utcnow()
                                   })
        self.user = get_session_user(self.request)
        self.user.phone_numbers.add(mobile)
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            self.sync_user()

        else:
            send_verification_code(self.request, self.user, mobile_number)

            self.request.session.flash(_('Changes saved'),
                                   queue='forms')
            msg = _('A confirmation code has been sent to your mobile phone. '
                'Please click on the "Pending confirmation" link below and enter your confirmation code.')
            msg = get_localizer(self.request).translate(msg)
            self.request.session.flash(msg,
                                   queue='forms')
Ejemplo n.º 14
0
    def setprimary_action(self, index, post_data):
        self.user = get_session_user(self.request)
        mobiles = self.user.phone_numbers.to_list()

        try:
            mobile = mobiles[index]
        except IndexError:
            return self.sync_user()

        if not mobile.is_verified:
            message = _('You need to confirm your mobile number '
                        'before it can become primary')
            return {
                'result': 'bad',
                'message': get_localizer(self.request).translate(message),
            }

        self.user.phone_numbers.primary = mobile.number
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            return self.sync_user()

        self.request.stats.count('mobile_number_set_primary')
        message = _('Mobile phone number was successfully made primary')
        return {
            'result': 'success',
            'message': get_localizer(self.request).translate(message),
        }
Ejemplo n.º 15
0
def get_authn_info(request):
    """
    Get credential information for the current user.

    :param request: the request object
    :return: a list of dicts [{'type': string, 'created_ts': timestamp, 'success_ts': timestamp }]
    """
    user = get_session_user(request)

    authninfo = []

    for credential in user.credentials.filter(Password).to_list():
        auth_entry = request.authninfodb.authn_info.find_one(
            {'_id': ObjectId(credential.credential_id)})
        log.debug("get_authn_info {!s}: cred id: {!r} auth entry: {!r}".format(
            user, credential.credential_id, auth_entry))
        if auth_entry:
            created_dt = convert_to_localtime(credential.created_ts)
            success_dt = convert_to_localtime(auth_entry['success_ts'])
            data_type = _('Password')
            data = {
                'type': get_localizer(request).translate(data_type),
                'created_ts': created_dt.strftime('%Y-%b-%d %H:%M'),
                'success_ts': success_dt.strftime('%Y-%b-%d %H:%M')
            }
            authninfo.append(data)

    return authninfo
Ejemplo n.º 16
0
    def save_success(self, user_modified):
        person = self.schema.serialize(user_modified)
        del (person['csrf'])  # Don't save the CSRF token in the user database

        self.user = get_session_user(self.request)
        new_preferred_language = person.get('preferredLanguage')
        old_preferred_language = self.user.language

        # Insert the new/updated user object
        self.user.given_name = person.get('givenName')
        self.user.display_name = person.get('displayName')
        self.user.surname = person.get('surname')
        self.user.language = person.get('preferredLanguage')
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            self.sync_user()
        else:
            message = _('Changes saved')
            self.request.session.flash(get_localizer(
                self.request).translate(message),
                                       queue='forms')
            self.request.stats.count('personal_data_saved')

        if new_preferred_language != old_preferred_language:
            self.full_page_reload()
Ejemplo n.º 17
0
    def setprimary_action(self, index, post_data):
        self.user = get_session_user(self.request)
        mobiles = self.user.phone_numbers.to_list()

        try:
            mobile = mobiles[index]
        except IndexError:
            return self.sync_user()

        if not mobile.is_verified:
            message = _('You need to confirm your mobile number '
                        'before it can become primary')
            return {
                'result': 'bad',
                'message': get_localizer(self.request).translate(message),
            }

        self.user.phone_numbers.primary = mobile.number
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            return self.sync_user()

        self.request.stats.count('mobile_number_set_primary')
        message = _('Mobile phone number was successfully made primary')
        return {
            'result': 'success',
            'message': get_localizer(self.request).translate(message),
        }
Ejemplo n.º 18
0
    def verify_action(self, data, post_data):
        """
        Only the active (the last one) NIN can be verified
        """
        nin, index = data.split()
        index = int(index)
        self.user = get_session_user(self.request, raise_on_not_logged_in = False)
        nins = get_not_verified_nins_list(self.request, self.user)

        if len(nins) > index:
            new_nin = nins[index]
            if new_nin != nin:
                return self.sync_user()
        else:
            return self.sync_user()

#        if index != len(nins) - 1:
#            message = _("The provided nin can't be verified. You only "
#                        'can verify the last one')
#            return {
#                'result': 'bad',
#                'message': get_localizer(self.request).translate(message),
#            }

        return self._verify_action(new_nin, post_data)
Ejemplo n.º 19
0
    def __call__(self, node, value):

        request = node.bindings.get('request')
        user = get_session_user(request)
        mobile = normalize_to_e_164(request, value)

        # The debug logs below were added to help figure out how a user
        # managed to get by past this function with a duplicated phone number,
        # which was caught by userdb while saving the user.

        log.debug("User {!r} tried to add a phone number {!r}: "
                  "entering 1st code-section that will verify that it's unique."
                  .format(user.eppn, mobile))

        if sanitize_post_key(request, 'add') is not None:
            log.debug("User {!r} tried to add a phone number {!r}: "
                      "1st code-section evaluated, POST request OK."
                      .format(user.eppn, mobile))

            if user.phone_numbers.find(mobile):
                log.debug("User {!r} tried to add a phone number {!r}: "
                          "2nd code-section evaluated, the user had already "
                          "added the phone number."
                          .format(user.eppn, mobile))

                err = _("This mobile phone was already registered")
                raise colander.Invalid(node, get_localizer(request).translate(err))

            else:
                log.debug("User {!r} tried to add a phone number {!r}: "
                          "2nd code-section evaluated, the phone number "
                          "has not previously been added by the user."
                          .format(user.eppn, mobile))
Ejemplo n.º 20
0
    def __init__(self, context, request):
        super(BaseFormView, self).__init__(request)
        self.user = get_session_user(self.request)
        self.context = context
        self.response = request.response

        # Stop Internet Explorer from caching view fragments
        self.response.headers['Cache-Control'] = 'no-cache'

        self.classname = self.__class__.__name__.lower()

        self.ajax_options = json.dumps({
            'replaceTarget':
            True,
            'url':
            context.route_url(self.route),
            'target':
            "div.{classname}-form-container".format(classname=self.classname),
        })

        self.form_options = {
            'formid': "{classname}-form".format(classname=self.classname),
            'action': context.route_url(self.route),
        }

        bootstrap_form_style = getattr(self, 'bootstrap_form_style', None)
        if bootstrap_form_style is not None:
            self.form_options['bootstrap_form_style'] = bootstrap_form_style
Ejemplo n.º 21
0
    def resend_code_action(self, index, post_data):
        self.user = get_session_user(self.request)
        data = self.user.to_dict().get(self.data_attribute, [])

        # Catch the unlikely event when the user have e.g. removed all entries
        # in a separate tab, or one in the middle and then tries to resend the
        # code for a non-existing entry.
        # This is an incomplete fix since it is better if we can get the list
        # from the UI and then check that the entry which the client want to
        # resend the code for corresponds to the same entry we get from
        # data[index].
        try:
            data_to_resend = data[index]
        except IndexError:
            log.warning('Index error in resend_code_action, user {!s}'.format(self.user))
            message = self.verify_messages['out_of_sync']
            return {
                'result': 'out_of_sync',
                'message': get_localizer(self.request).translate(message),
            }

        data_id = self.get_verification_data_id(data_to_resend)
        reference, code = new_verification_code(
            self.request, self.data_attribute, data_id,
            self.user, hasher=get_short_hash,
        )
        self.send_verification_code(data_id, reference, code)
        msg = self.verify_messages['new_code_sent']
        return {
            'result': 'success',
            'message': msg,
        }
Ejemplo n.º 22
0
    def save_success(self, user_modified):
        person = self.schema.serialize(user_modified)
        del(person['csrf'])  # Don't save the CSRF token in the user database

        self.user = get_session_user(self.request)
        new_preferred_language = person.get('preferredLanguage')
        old_preferred_language = self.user.language

        # Insert the new/updated user object
        self.user.given_name = person.get('givenName')
        self.user.display_name = person.get('displayName')
        self.user.surname = person.get('surname')
        self.user.language = person.get('preferredLanguage')
        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            self.sync_user()
        else:
            message = _('Changes saved')
            self.request.session.flash(
                    get_localizer(self.request).translate(message),
                    queue='forms')
            self.request.stats.count('personal_data_saved')

        if new_preferred_language != old_preferred_language:
            self.full_page_reload()
Ejemplo n.º 23
0
    def get_user(self):
        """
        Get the current user.

        In 'personal' mode, this is the logged in user. In 'admin' mode, it is the
        'edit-user' user from the session, or the user indicated by the matchdict
        'userid'. Matchdict is a part of the URL extracted by Pyramid with routes like
        '/users/{userid}/'.

        :return: User object
        :rtype: DashboardUser
        """
        if self.workmode == 'personal':
            user = get_logged_in_user(self.request, legacy_user = False, raise_on_not_logged_in = False)
            self._logged_in_user = user
        elif self.workmode == 'admin' or self.workmode == 'helpdesk':
            if not has_edit_user(self.request):
                user = None
                userid = self.request.matchdict.get('userid', '')
                logger.debug('get_user() looking for user matching {!r}'.format(userid))
                if EMAIL_RE.match(userid):
                    user = self.request.userdb_new.get_user_by_mail(userid)
                elif OID_RE.match(userid):
                    user = self.request.userdb_new.get_user_by_id(userid)
                if not user:
                    raise HTTPNotFound()
                logger.debug('get_user() storing user {!r}'.format(user))
                store_session_user(self.request, user, edit_user = True)
            user = get_session_user(self.request)
        else:
            raise NotImplementedError("Unknown workmode: {!s}".format(self.workmode))

        return user
Ejemplo n.º 24
0
    def __init__(self, context, request):
        super(BaseFormView, self).__init__(request)
        self.user = get_session_user(self.request)
        self.context = context
        self.response = request.response

        # Stop Internet Explorer from caching view fragments
        self.response.headers['Cache-Control'] = 'no-cache'

        self.classname = self.__class__.__name__.lower()

        self.ajax_options = json.dumps({
            'replaceTarget': True,
            'url': context.route_url(self.route),
            'target': "div.{classname}-form-container".format(
                classname=self.classname),

        })

        self.form_options = {
            'formid': "{classname}-form".format(classname=self.classname),
            'action': context.route_url(self.route),
        }

        bootstrap_form_style = getattr(self, 'bootstrap_form_style', None)
        if bootstrap_form_style is not None:
            self.form_options['bootstrap_form_style'] = bootstrap_form_style
Ejemplo n.º 25
0
    def __call__(self, node, value):
        request = node.bindings.get('request')
        localizer = get_localizer(request)
        settings = request.registry.settings
        value = value.replace(" ", "")
        password_min_entropy = int(settings.get('password_entropy', 60))

        # We accept a 10% of variance in password_min_entropy because
        # we have calculated the entropy by javascript too and the results
        # may vary.
        password_min_entropy = (0.90 * password_min_entropy)

        generated_password = request.session.get('last_generated_password', '')
        if len(generated_password) > 0 and generated_password == value:
            # Don't validate the password if it is the generated password
            # That is, the user has filled out the form with the suggested
            # password
            return

        # Get a users e-mail addresses to make sure a user does not use one of those as password
        user = get_session_user(request, raise_on_not_logged_in = False)
        if not user:
            # User is resetting a forgotten password
            hash_code = request.matchdict['code']
            password_reset = request.db.reset_passwords.find_one({'hash_code': hash_code})
            user = request.userdb_new.get_user_by_mail(password_reset['email'])
        mail_addresses = [item.email for item in user.mail_addresses.to_list()]

        veredict = zxcvbn.password_strength(value, user_inputs=mail_addresses)

        if veredict.get('entropy', 0) < password_min_entropy:
            err = _('The password complexity is too weak.')
            raise colander.Invalid(node, localizer.translate(err))
Ejemplo n.º 26
0
    def remove_action(self, data, post_data):
        """ Only not verified nins can be removed """
        raise HTTPNotImplemented  # Temporary remove the functionality
        self.user = get_session_user(self.request)

        nin, index = data.split()
        index = int(index)
        nins = get_not_verified_nins_list(self.request, self.user)

        if len(nins) > index:
            remove_nin = nins[index]
            if remove_nin != nin:
                return self.sync_user()
        else:
            return self.sync_user()

        verifications = self.request.db.verifications
        verifications.remove({
            'model_name': self.data_attribute,
            'obj_id': remove_nin,
            'user_oid': self.user.user_id,
            'verified': False,
        })

        self.request.stats.count('dashboard/nin_remove', 1)
        message = _('National identity number has been removed')
        return {
            'result': 'success',
            'message': get_localizer(self.request).translate(message),
        }
Ejemplo n.º 27
0
    def remove_action(self, data, post_data):
        """ Only not verified nins can be removed """
        raise HTTPNotImplemented  # Temporary remove the functionality
        self.user = get_session_user(self.request)

        nin, index = data.split()
        index = int(index)
        nins = get_not_verified_nins_list(self.request, self.user)

        if len(nins) > index:
            remove_nin = nins[index]
            if remove_nin != nin:
                return self.sync_user()
        else:
            return self.sync_user()

        verifications = self.request.db.verifications
        verifications.remove({
            'model_name': self.data_attribute,
            'obj_id': remove_nin,
            'user_oid': self.user.user_id,
            'verified': False,
        })

        self.request.stats.count('nin_remove')
        message = _('National identity number has been removed')
        return {
            'result': 'success',
            'message': get_localizer(self.request).translate(message),
        }
Ejemplo n.º 28
0
 def get_template_context(self):
     tempcontext = super(PermissionsView, self).get_template_context()
     session_user = get_session_user(self.request, raise_on_not_logged_in = False)
     if self.context.user.eppn == session_user.eppn:
         tempcontext['confirmation_required'] = True
     else:
         tempcontext['confirmation_required'] = False
     return tempcontext
Ejemplo n.º 29
0
 def get_template_context(self):
     context = {'intro_message': self.intro_message}
     # Collect the users mail addresses for use with zxcvbn
     user = get_session_user(self.request, raise_on_not_logged_in=False)
     if user:
         mail_addresses = []
         for item in user.mail_addresses.to_list():
             mail_addresses.append(item.email)
         context['user_input'] = json.dumps(mail_addresses)
     return context
Ejemplo n.º 30
0
    def save_success(self, passwordform):
        authn_ts = self.request.session.get('reauthn-for-chpass', None)
        if authn_ts is None:
            raise HTTPBadRequest(_('No authentication info'))
        now = datetime.utcnow()
        delta = now - datetime.fromtimestamp(authn_ts)
        # XXX put the reauthn timeout in the settings
        if int(delta.total_seconds()) > 600:
            msg = _('Stale authentication info. Please try again.')
            self.request.session.flash('error|' + msg)
            raise HTTPFound(self.context.route_url('profile-editor'))
        user = get_session_user(self.request)
        log.debug('Removing Authn ts for user {!r} before'
                  ' changing the password'.format(user))
        del self.request.session['reauthn-for-chpass']
        passwords_data = self.schema.serialize(passwordform)

        if passwords_data.get('use_custom_password') == 'true':
            # The user has entered his own password and it was verified by
            # validators
            log.debug(
                "Password change for user {!r} (custom password).".format(
                    user))
            new_password = passwords_data.get('custom_password')

        else:
            # If the user has selected the suggested password, then it should
            # be in session
            log.debug(
                "Password change for user {!r} (suggested password).".format(
                    user))
            new_password = self.get_suggested_password()

        new_password = new_password.replace(' ', '')

        old_password = passwords_data['old_password']

        # Load user from database to ensure we are working on an up-to-date set of credentials.
        # XXX this refresh is a bit redundant with the same thing being done in OldPasswordValidator.
        user = self.request.userdb_new.get_user_by_id(user.user_id)
        log.debug("Refreshed user {!s} from {!s}".format(
            user, self.request.userdb_new))
        retrieve_modified_ts(user, self.request.dashboard_userdb)

        self.changed = change_password(self.request, user, old_password,
                                       new_password)
        if self.changed:
            message = 'success|' + _(
                'Your password has been successfully updated')
        else:
            message = 'error|' + _(
                'An error has occured while updating your password, '
                'please try again or contact support if the problem persists.')
        self.request.session.flash(message)
        raise HTTPFound(self.context.route_url('profile-editor'))
Ejemplo n.º 31
0
    def __call__(self, node, value):
        request = node.bindings.get('request')
        settings = request.registry.settings
        user = get_session_user(request)
        result = validate_nin_by_mobile(request, user, value)

        if not result['success']:
            localizer = get_localizer(request)
            raise colander.Invalid(node, localizer.translate(result['message'], mapping={
                'service_name': settings.get('mobile_service_name', 'TeleAdress'),
            }))
Ejemplo n.º 32
0
 def get_template_context(self):
     context = {
         'intro_message': self.intro_message
     }
     # Collect the users mail addresses for use with zxcvbn
     user = get_session_user(self.request, raise_on_not_logged_in = False)
     if user:
         mail_addresses = []
         for item in user.mail_addresses.to_list():
             mail_addresses.append(item.email)
         context['user_input'] = json.dumps(mail_addresses)
     return context
Ejemplo n.º 33
0
 def save_success(self, permform):
     data = self.schema.serialize(permform)
     self.user = get_session_user(self.request, raise_on_not_logged_in = False)
     self.user.entitlements = data[self.attribute]
     try:
         self.context.save_dashboard_user(self.user)
     except UserOutOfSync:
         self.sync_user()
     else:
         message = _('Changes saved.')
         self.request.session.flash(
                 get_localizer(self.request).translate(message),
                 queue='forms')
Ejemplo n.º 34
0
    def verify_mb_action(self, data, post_data):
        """
        Verify a users identity using their mobile phone subscriber records.

        Only the active (the last one) NIN can be verified
        """
        nin, index = data.split()
        index = int(index)
        nins = get_not_verified_nins_list(self.request, self.user)

        if len(nins) > index:
            new_nin = nins[index]
            if new_nin != nin:
                return self.sync_user()
        else:
            return self.sync_user()

#        if index != len(nins) - 1:
#            message = _("The provided nin can't be verified. You only "
#                        'can verify the last one')
#            return {
#                'result': 'bad',
#                'message': get_localizer(self.request).translate(message),
#            }

        validation = validate_nin_by_mobile(self.request, self.user, nin)
        result = validation['success'] and 'success' or 'error'
        model_name = 'norEduPersonNIN'
        if result == 'success':
            session_user = get_session_user(self.request, legacy_user = False)
            retrieve_modified_ts(session_user, self.request.dashboard_userdb)
            set_nin_verified(self.request, session_user, nin)
            try:
                #self.user.save(self.request)
                self.request.context.save_dashboard_user(session_user)
                logger.info("Verified by mobile, {!s} saved for user {!r}.".format(model_name, session_user))
                # Save the state in the verifications collection
                save_as_verified(self.request, 'norEduPersonNIN', session_user, nin)
            except UserOutOfSync:
                logger.info("Verified {!s} NOT saved for user {!r}. User out of sync.".format(model_name, session_user))
                raise
        settings = self.request.registry.settings
        msg = get_localizer(self.request).translate(validation['message'],
                mapping={
                'service_name': settings.get('mobile_service_name', 'Navet'),
                })
        return {
            'result': result,
            'message': msg,
            }
Ejemplo n.º 35
0
 def add_nin_external(self, data):
     try:
         validated = self.validate_post_data()
     except deform.exception.ValidationFailure as e:
         return {
             'status': 'failure',
             'data': e.error.asdict()
         }
     self.user = get_session_user(self.request)
     self.addition_with_code_validation(validated)
     self.request.stats.count('nin_add_external')
     return {
         'status': 'success'
     }
Ejemplo n.º 36
0
 def add_nin_external(self, data):
     try:
         validated = self.validate_post_data()
     except deform.exception.ValidationFailure as e:
         return {
             'status': 'failure',
             'data': e.error.asdict()
         }
     self.user = get_session_user(self.request)
     self.addition_with_code_validation(validated)
     self.request.stats.count('dashboard/nin_add_external', 1)
     return {
         'status': 'success'
     }
Ejemplo n.º 37
0
    def get_template_context(self):
        context = super(PasswordsView, self).get_template_context()
        # Collect the users mail addresses for use with zxcvbn
        mail_addresses = []
        user = get_session_user(self.request)
        for item in user.mail_addresses.to_list():
            mail_addresses.append(item.email)

        context.update({
            'message': getattr(self, 'message', ''),
            'changed': getattr(self, 'changed', False),
            'authninfo': get_authn_info(self.request),
            'user_input': json.dumps(mail_addresses)
        })
        return context
Ejemplo n.º 38
0
    def get_template_context(self):
        context = super(PasswordsView, self).get_template_context()
        # Collect the users mail addresses for use with zxcvbn
        mail_addresses = []
        user = get_session_user(self.request)
        for item in user.mail_addresses.to_list():
            mail_addresses.append(item.email)

        context.update({
            'message': getattr(self, 'message', ''),
            'changed': getattr(self, 'changed', False),
            'authninfo': get_authn_info(self.request),
            'user_input': json.dumps(mail_addresses)
        })
        return context
Ejemplo n.º 39
0
def account_terminated(context, request):
    """
    The account termination action,
    removes all credentials for the terminated account
    from the VCCS service,
    flags the account as terminated,
    sends an email to the address in the terminated account,
    and logs out the session.

    :type user: eduid_userdb.dashboard.DashboardLegacyUser
    """
    settings = request.registry.settings

    authn_ts = request.session.get('reauthn-for-termination', None)
    if authn_ts is None:
        raise HTTPBadRequest(_('No authentication info'))
    now = datetime.utcnow()
    delta = now - datetime.fromtimestamp(authn_ts)
    # XXX put the reauthn timeout in the settings
    if int(delta.total_seconds()) > 600:
        msg = _('Stale authentication info. Please try again.')
        request.session.flash('error|' + msg)
        raise HTTPFound(context.route_url('profile-editor'))

    user = get_session_user(request)
    logger.debug('Removing Authn ts for user {!r} before'
            ' changing the password'.format(user))
    del request.session['reauthn-for-termination']

    logged_user = get_logged_in_user(request, legacy_user = False)
    if logged_user.user_id != user.user_id:
        raise HTTPUnauthorized("Wrong user")

    logger.info("Terminating user {!s}".format(user))

    # revoke all user credentials
    revoke_all_credentials(settings.get('vccs_url'), user)
    for p in logged_user.credentials.filter(Password).to_list():
        logged_user.passwords.remove(p.key)

    # flag account as terminated
    logged_user.terminated = True
    request.context.save_dashboard_user(logged_user)

    # email the user
    send_termination_mail(request, logged_user)

    return {}
Ejemplo n.º 40
0
    def __call__(self, node, value):
        request = node.bindings.get('request')
        settings = request.registry.settings
        user = get_session_user(request)
        result = validate_nin_by_mobile(request, user, value)

        if not result['success']:
            localizer = get_localizer(request)
            raise colander.Invalid(
                node,
                localizer.translate(result['message'],
                                    mapping={
                                        'service_name':
                                        settings.get('mobile_service_name',
                                                     'TeleAdress'),
                                    }))
Ejemplo n.º 41
0
    def save_success(self, passwordform):
        authn_ts = self.request.session.get('reauthn-for-chpass', None)
        if authn_ts is None:
            raise HTTPBadRequest(_('No authentication info'))
        now = datetime.utcnow()
        delta = now - datetime.fromtimestamp(authn_ts)
        # XXX put the reauthn timeout in the settings
        if int(delta.total_seconds()) > 600:
            msg = _('Stale authentication info. Please try again.')
            self.request.session.flash('error|' + msg)
            raise HTTPFound(self.context.route_url('profile-editor'))
        user = get_session_user(self.request)
        log.debug('Removing Authn ts for user {!r} before'
                ' changing the password'.format(user))
        del self.request.session['reauthn-for-chpass']
        passwords_data = self.schema.serialize(passwordform)

        if passwords_data.get('use_custom_password') == 'true':
            # The user has entered his own password and it was verified by
            # validators
            log.debug("Password change for user {!r} (custom password).".format(user))
            new_password = passwords_data.get('custom_password')

        else:
            # If the user has selected the suggested password, then it should
            # be in session
            log.debug("Password change for user {!r} (suggested password).".format(user))
            new_password = self.get_suggested_password()

        new_password = new_password.replace(' ', '')

        old_password = passwords_data['old_password']

        # Load user from database to ensure we are working on an up-to-date set of credentials.
        # XXX this refresh is a bit redundant with the same thing being done in OldPasswordValidator.
        user = self.request.userdb_new.get_user_by_id(user.user_id)
        log.debug("Refreshed user {!s} from {!s}".format(user, self.request.userdb_new))
        retrieve_modified_ts(user, self.request.dashboard_userdb)

        self.changed = change_password(self.request, user, old_password, new_password)
        if self.changed:
            message = 'success|' + _('Your password has been successfully updated')
        else:
            message = 'error|' + _('An error has occured while updating your password, '
                        'please try again or contact support if the problem persists.')
        self.request.session.flash(message)
        raise HTTPFound(self.context.route_url('profile-editor'))
Ejemplo n.º 42
0
    def verify_lp_action(self, data, post_data):
        '''
        verify by letter
        '''
        nin, index = data.split()
        index = int(index)
        self.user = get_session_user(self.request)
        nins = get_not_verified_nins_list(self.request, self.user)

        if len(nins) > index:
            new_nin = nins[index]
            if new_nin != nin:
                return self.sync_user()
        else:
            return self.sync_user()

        return letter_status(self.request, self.user, nin)
Ejemplo n.º 43
0
    def verify_lp_action(self, data, post_data):
        '''
        verify by letter
        '''
        nin, index = data.split()
        index = int(index)
        self.user = get_session_user(self.request)
        nins = get_not_verified_nins_list(self.request, self.user)

        if len(nins) > index:
            new_nin = nins[index]
            if new_nin != nin:
                return self.sync_user()
        else:
            return self.sync_user()

        return letter_status(self.request, self.user, nin)
Ejemplo n.º 44
0
    def __call__(self, node, value):

        request = node.bindings.get('request')

        if not request.registry.settings.get('use_vccs', True):
            return

        old_password = value.replace(" ", "")

        user = get_session_user(request)

        vccs_url = request.registry.settings.get('vccs_url')
        password = check_password(vccs_url, old_password, user)
        if not password:
            err = _('Current password is incorrect')
            localizer = get_localizer(request)
            raise colander.Invalid(node, localizer.translate(err))
Ejemplo n.º 45
0
    def __call__(self, node, value):

        request = node.bindings.get('request')

        if not request.registry.settings.get('use_vccs', True):
            return

        old_password = value.replace(" ", "")

        user = get_session_user(request)

        vccs_url = request.registry.settings.get('vccs_url')
        password = check_password(vccs_url, old_password, user)
        if not password:
            err = _('Current password is incorrect')
            localizer = get_localizer(request)
            raise colander.Invalid(node, localizer.translate(err))
Ejemplo n.º 46
0
    def __call__(self, node, value):

        request = node.bindings.get('request')
        user = get_session_user(request)
        user_emails = [e.email for e in user.mail_addresses.to_list()]
        if user.mail_addresses.primary:
            user_emails.append(user.mail_addresses.primary.email)
        localizer = get_localizer(request)

        if sanitize_post_key(request, 'add') is not None:
            if value in user_emails:
                err = _("You already have this email address")
                raise colander.Invalid(node, localizer.translate(err))

        elif set(['verify', 'setprimary', 'remove']).intersection(set(request.POST)):
            if value not in user_emails:
                err = _("This email address is unavailable")
                raise colander.Invalid(node, localizer.translate(err))
Ejemplo n.º 47
0
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)
Ejemplo n.º 48
0
    def __call__(self, node, value):
        request = node.bindings.get('request')
        localizer = get_localizer(request)
        settings = request.registry.settings
        value = value.replace(" ", "")
        password_min_entropy = int(settings.get('password_entropy', 60))

        # We accept a 10% of variance in password_min_entropy because
        # we have calculated the entropy by javascript too and the results
        # may vary.
        password_min_entropy = (0.90 * password_min_entropy)

        generated_password = request.session.get('last_generated_password', '')
        if len(generated_password) > 0 and generated_password == value:
            # Don't validate the password if it is the generated password
            # That is, the user has filled out the form with the suggested
            # password
            return

        # Get a users e-mail addresses to make sure a user does not use one of those as password
        user = get_session_user(request, raise_on_not_logged_in=False)
        if not user:
            # User is resetting a forgotten password
            hash_code = request.matchdict['code']
            password_reset = request.db.reset_passwords.find_one(
                {'hash_code': hash_code})

            if password_reset.get('eppn'):
                user = request.userdb_new.get_user_by_eppn(
                    password_reset['eppn'])

            # Legacy password reset codes were connected to the user by email
            elif password_reset.get('email'):
                user = request.userdb_new.get_user_by_mail(
                    password_reset['email'])

        mail_addresses = [item.email for item in user.mail_addresses.to_list()]

        veredict = zxcvbn.password_strength(value, user_inputs=mail_addresses)

        if veredict.get('entropy', 0) < password_min_entropy:
            err = _('The password complexity is too weak.')
            raise colander.Invalid(node, localizer.translate(err))
Ejemplo n.º 49
0
    def resend_code_action(self, data, post_data):
        self.user = get_session_user(self.request)
        nin, index = data.split()
        index = int(index)
        nins = get_not_verified_nins_list(self.request, self.user)

        if len(nins) > index:
            nin = nins[index]
        else:
            raise HTTPNotFound(_("No pending national identity numbers found."))

        send_verification_code(self.request, self.context.user, nin)

        self.request.stats.count('nin_code_resend')
        message = self.verify_messages['new_code_sent']
        return {
            'result': 'success',
            'message': message,
        }
Ejemplo n.º 50
0
    def resend_code_action(self, data, post_data):
        self.user = get_session_user(self.request)
        nin, index = data.split()
        index = int(index)
        nins = get_not_verified_nins_list(self.request, self.user)

        if len(nins) > index:
            nin = nins[index]
        else:
            raise HTTPNotFound(_("No pending national identity numbers found."))

        send_verification_code(self.request, self.context.user, nin)

        self.request.stats.count('dashboard/nin_code_resend', 1)
        message = self.verify_messages['new_code_sent']
        return {
            'result': 'success',
            'message': message,
        }
Ejemplo n.º 51
0
    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')
Ejemplo n.º 52
0
    def add_by_letter_success(self, ninform):
        """
        This method is bound to the "add_by_letter"-button by it's name
        """
        form = self.schema.serialize(ninform)
        nin = normalize_nin(form['norEduPersonNIN'])
        session_user = get_session_user(self.request)

        # self.user needs to be a new user in get_template_context
        self.user = session_user

        result = letter_status(self.request, session_user, nin)
        if result['result'] == 'success':
            result2 = send_letter(self.request, session_user, nin)
            if result2['result'] == 'success':
                new_verification_code(self.request, 'norEduPersonNIN',
                                      nin, session_user)
            msg = result2['message']
        else:
            msg = result['message']
        self.request.session.flash(msg, queue='forms')
Ejemplo n.º 53
0
    def get_user(self):
        """
        Get the current user.

        In 'personal' mode, this is the logged in user. In 'admin' mode, it is the
        'edit-user' user from the session, or the user indicated by the matchdict
        'userid'. Matchdict is a part of the URL extracted by Pyramid with routes like
        '/users/{userid}/'.

        :return: User object
        :rtype: DashboardUser
        """
        if self.workmode == 'personal':
            user = get_logged_in_user(self.request,
                                      legacy_user=False,
                                      raise_on_not_logged_in=False)
            self._logged_in_user = user
        elif self.workmode == 'admin' or self.workmode == 'helpdesk':
            if not has_edit_user(self.request):
                user = None
                userid = self.request.matchdict.get('userid', '')
                logger.debug(
                    'get_user() looking for user matching {!r}'.format(userid))
                if EMAIL_RE.match(userid):
                    user = self.request.userdb_new.get_user_by_mail(userid)
                elif OID_RE.match(userid):
                    user = self.request.userdb_new.get_user_by_id(userid)
                if not user:
                    raise HTTPNotFound()
                logger.debug('get_user() storing user {!r}'.format(user))
                store_session_user(self.request, user, edit_user=True)
            user = get_session_user(self.request)
        else:
            raise NotImplementedError("Unknown workmode: {!s}".format(
                self.workmode))

        return user
Ejemplo n.º 54
0
    def remove_action(self, index, post_data):
        self.user = get_session_user(self.request)
        mobiles = self.user.phone_numbers.to_list()

        try:
            mobile_to_remove = mobiles[index]
        except IndexError:
            return self.sync_user()

        try:
            self.user.phone_numbers.remove(mobile_to_remove.number)
        except PrimaryElementViolation:
            # The exception was raised because it would result in zero
            # primary mobiles while there still exits verified ones.
            # Therefore we first we have to set one of the other
            # verified mobiles as primary before we can try to
            # remove the previous one again.
            verified_mobiles = self.user.phone_numbers.verified.to_list()

            for mobile in verified_mobiles:
                if mobile.number != mobile_to_remove.number:
                    self.user.phone_numbers.primary = mobile.number
                    break

            self.user.phone_numbers.remove(mobile_to_remove.number)

        try:
            self.context.save_dashboard_user(self.user)
        except UserOutOfSync:
            return self.sync_user()

        self.request.stats.count('mobile_number_removed')
        message = _('Mobile phone number was successfully removed')
        return {
            'result': 'success',
            'message': get_localizer(self.request).translate(message),
        }
Ejemplo n.º 55
0
    def resend_code_action(self, index, post_data):
        self.user = get_session_user(self.request)
        data = self.user.to_dict().get(self.data_attribute, [])

        # Catch the unlikely event when the user have e.g. removed all entries
        # in a separate tab, or one in the middle and then tries to resend the
        # code for a non-existing entry.
        # This is an incomplete fix since it is better if we can get the list
        # from the UI and then check that the entry which the client want to
        # resend the code for corresponds to the same entry we get from
        # data[index].
        try:
            data_to_resend = data[index]
        except IndexError:
            log.warning('Index error in resend_code_action, user {!s}'.format(
                self.user))
            message = self.verify_messages['out_of_sync']
            return {
                'result': 'out_of_sync',
                'message': get_localizer(self.request).translate(message),
            }

        data_id = self.get_verification_data_id(data_to_resend)
        reference, code = new_verification_code(
            self.request,
            self.data_attribute,
            data_id,
            self.user,
            hasher=get_short_hash,
        )
        self.send_verification_code(data_id, reference, code)
        msg = self.verify_messages['new_code_sent']
        return {
            'result': 'success',
            'message': msg,
        }
Ejemplo n.º 56
0
    def add_success_personal(self, ninform, msg):
        self.user = get_session_user(self.request)
        self.addition_with_code_validation(ninform)

        msg = get_localizer(self.request).translate(msg)
        self.request.session.flash(msg, queue='forms')
Ejemplo n.º 57
0
 def send_letter_action(self, data, post_data):
     self.user = get_session_user(self.request)
     nin, index = data.split()
     return send_letter(self.request, self.user, nin)
Ejemplo n.º 58
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'))