コード例 #1
0
def send_termination_mail(request, user):
    mailer = get_mailer(request)
    support_email = request.registry.settings.get('mail.support_email', '*****@*****.**')
    site_name = request.registry.settings.get("site.name", "eduID")

    context = {
        'support_mail': support_email,
        'displayName': user.get_display_name()
    }

    message = Message(
        subject=_("{site_name} account termination").format(
            site_name=site_name),
        sender=request.registry.settings.get("mail.default_sender"),
        recipients=[user.get_mail()],
        body=render(
            "templates/termination_email.txt.jinja2",
            context,
            request,
        ),
        html=render(
            "templates/termination_email.html.jinja2",
            context,
            request,
        ),
    )

    # Development
    if request.registry.settings.get("development", '') == 'true':
        print message.body
    else:
        mailer.send(message)
    log.debug("Sent termination mail to user {!r} with address {!s}.".format(user, user.get_mail()))
    request.stats.count('dashboard/email_send_termination_mail', 1)
コード例 #2
0
ファイル: utils.py プロジェクト: Ratler/eduid-dashboard
def get_saml_attribute(session_info, attr_name):
    """
    Get value from a SAML attribute received from the SAML IdP.

    session_info is a pysaml2 response.session_info(). This is a dictionary like
        {'mail': ['*****@*****.**'],
         'eduPersonPrincipalName': ['*****@*****.**']
      }

    :param session_info: SAML attributes received by pysaml2 client.
    :param attr_name: The attribute to look up
    :returns: Attribute values

    :type session_info: dict()
    :type attr_name: string()
    :rtype: [string()]
    """
    if not 'ava' in session_info:
        raise ValueError('SAML attributes (ava) not found in session_info')

    attributes = session_info['ava']

    log.debug('SAML attributes received: %s' % attributes)

    attr_name = attr_name.lower()
    # Look for the canonicalized attribute in the SAML assertion attributes
    for saml_attr, local_fields in attributes.items():
        if saml_attr.lower() == attr_name:
            return attributes[saml_attr]
コード例 #3
0
ファイル: validators.py プロジェクト: Ratler/eduid-dashboard
    def __call__(self, node, value):
        request = node.bindings.get('request')
        token = request.session.get_csrf_token()

        if value != token:
            log.debug("CSRF token validation failed: Form {!r} != Session {!r}".format(value, token))
            raise colander.Invalid(node, _("Invalid CSRF token"))
コード例 #4
0
ファイル: security.py プロジェクト: Ratler/eduid-dashboard
    def save_success(self, passwordform):
        passwords_data = self.schema.serialize(passwordform)
        user = self.request.session['user']

        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.get_id()))
            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.get_id()))
            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.get_user_by_oid(user.get_id())

        self.changed = change_password(self.request, user, old_password, new_password)
        if self.changed:
            self.message = _('Your password has been successfully updated')
        else:
            self.message = _('An error has occured while updating your password, '
                             'please try again or contact support if the problem persists.')
コード例 #5
0
ファイル: security.py プロジェクト: Ratler/eduid-dashboard
    def reset_success(self, passwordform):
        passwords_data = self.schema.serialize(passwordform)
        email_or_username = passwords_data['email_or_username']

        # If input is a mail address we need to normalize it (ie lower case etc)
        if validate_email_format(email_or_username):
            email_or_username = normalize_email(email_or_username)
        elif email_or_username.startswith(u'0'):
            email_or_username = normalize_to_e_164(self.request, email_or_username)

        try:
            filter_dict = {'$or': []}
            for field in self.SEARCH_FIELDS:
                filter_dict['$or'].append({field: email_or_username})

            user = self.request.userdb.get_user_by_filter(filter_dict)
        except self.request.userdb.exceptions.UserDoesNotExist:
            log.debug("User {!r} does not exist".format(email_or_username))
            user = None

        if user is not None:
            nin = None
            nins = user.get_nins()
            if nins:
                nin = nins[-1]
            if nin is not None:
                reset_password_link = new_reset_password_code(self.request, user, mechanism='govmailbox')
                send_reset_password_gov_message(self.request, nin, user, reset_password_link)

        self.request.session['_reset_type'] = _('Myndighetspost')
        return HTTPFound(location=self.request.route_url('reset-password-sent'))
コード例 #6
0
ファイル: __init__.py プロジェクト: Ratler/eduid-dashboard
 def _verify_action(self, data_id, post_data):
     if 'code' in post_data:
         code_sent = post_data['code']
         verification_code = get_verification_code(self.request,
                                                   self.data_attribute,
                                                   obj_id=data_id,
                                                   user=self.user)
         if verification_code:
             if code_sent == verification_code['code']:
                 if verification_code['expired']:
                     log.debug("User {!r} verification code has expired".format(self.user))
                     return {
                         'result': 'error',
                         'message': self.verify_messages['expired'],
                     }
                 else:
                     verificate_code(self.request, self.data_attribute,
                                     code_sent)
                     return {
                         'result': 'ok',
                         'message': self.verify_messages['ok'],
                     }
             else:
                 log.debug("Incorrect code for user {!r}: {!r}".format(self.user, code_sent))
         return {
             'result': 'error',
             'message': self.verify_messages['error'],
         }
     else:
         message = self.verify_messages['request'].format(data=data_id)
         return {
             'result': 'getcode',
             'message': message,
             'placeholder': self.verify_messages['placeholder'],
         }
コード例 #7
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 }]
    """
    if 'edit-user' in request.session:
        user = request.session['edit-user']
    else:
        user = request.session['user']

    authninfo = []

    for credential in user.get_passwords():
        auth_entry = request.authninfodb.authn_info.find_one({'_id': ObjectId(credential['id'])})
        log.debug("cred id: {!r} auth entry: {!r}".format(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
コード例 #8
0
ファイル: views.py プロジェクト: enriquepablo/eduid-dashboard
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
コード例 #9
0
def set_phone_verified(request, user, new_number):
    """
    Mark a phone number as verified on a user.

    This process also includes *removing* the phone number from any other user
    that had it as a verified phone number.

    :param request: The HTTP request
    :param user: The user
    :param new_number: The phone number to mark as verified

    :type request: pyramid.request.Request
    :type user: User
    :type new_number: str | unicode

    :return: Status message
    :rtype: str | unicode
    """
    log.info('Trying to verify phone number for user {!r}.'.format(user))
    log.debug('Phone number: {!s}.'.format(new_number))
    # Start by removing mobile number from any other user
    old_user = request.userdb_new.get_user_by_phone(new_number, raise_on_missing=False)
    steal_count = 0
    if old_user and old_user.user_id != user.user_id:
        retrieve_modified_ts(old_user, request.dashboard_userdb)
        _remove_phone_from_user(new_number, old_user)
        request.context.save_dashboard_user(old_user)
        log.info('Removed phone number from user {!r}.'.format(old_user))
        steal_count = 1
    # Add the verified mobile number to the requesting user
    _add_phone_to_user(new_number, user)
    log.info('Phone number verified for user {!r}.'.format(user))
    request.stats.count('dashboard/verify_mobile_stolen', steal_count)
    request.stats.count('dashboard/verify_mobile_completed', 1)
    return _('Phone {obj} verified')
コード例 #10
0
def set_email_verified(request, user, new_mail):
    """
    Mark an e-mail address as verified on a user.

    This process also includes *removing* the e-mail address from any other user
    that had it as a verified e-mail address.

    :param request: The HTTP request
    :param user: The user
    :param new_mail: The e-mail address to mark as verified

    :type request: pyramid.request.Request
    :type user: User
    :type new_mail: str | unicode

    :return: Status message
    :rtype: str | unicode
    """
    log.info('Trying to verify mail address for user {!r}.'.format(user))
    log.debug('Mail address: {!s}.'.format(new_mail))
    # Start by removing the email address from any other user that currently has it (verified)
    old_user = request.userdb_new.get_user_by_mail(new_mail, raise_on_missing=False)
    steal_count = 0
    if old_user and old_user.user_id != user.user_id:
        retrieve_modified_ts(old_user, request.dashboard_userdb)
        old_user = _remove_mail_from_user(new_mail, old_user)
        request.context.save_dashboard_user(old_user)
        steal_count = 1
    # Add the verified mail address to the requesting user
    _add_mail_to_user(new_mail, user)
    log.info('Mail address verified for user {!r}.'.format(user))
    request.stats.count('verify_mail_stolen', steal_count)
    request.stats.count('verify_mail_completed')
    return _('Email {obj} verified')
コード例 #11
0
def set_phone_verified(request, user, new_number):
    """
    Mark a phone number as verified on a user.

    This process also includes *removing* the phone number from any other user
    that had it as a verified phone number.

    :param request: The HTTP request
    :param user: The user
    :param new_number: The phone number to mark as verified

    :type request: pyramid.request.Request
    :type user: User
    :type new_number: str | unicode

    :return: Status message
    :rtype: str | unicode
    """
    log.info('Trying to verify phone number for user {!r}.'.format(user))
    log.debug('Phone number: {!s}.'.format(new_number))
    # Start by removing mobile number from any other user
    old_user = request.userdb_new.get_user_by_phone(new_number, raise_on_missing=False)
    steal_count = 0
    if old_user and old_user.user_id != user.user_id:
        retrieve_modified_ts(old_user, request.dashboard_userdb)
        _remove_phone_from_user(new_number, old_user)
        request.context.save_dashboard_user(old_user)
        log.info('Removed phone number from user {!r}.'.format(old_user))
        steal_count = 1
    # Add the verified mobile number to the requesting user
    _add_phone_to_user(new_number, user)
    log.info('Phone number verified for user {!r}.'.format(user))
    request.stats.count('verify_mobile_stolen', steal_count)
    request.stats.count('verify_mobile_completed')
    return _('Phone {obj} verified')
コード例 #12
0
def set_email_verified(request, user, new_mail):
    """
    Mark an e-mail address as verified on a user.

    This process also includes *removing* the e-mail address from any other user
    that had it as a verified e-mail address.

    :param request: The HTTP request
    :param user: The user
    :param new_mail: The e-mail address to mark as verified

    :type request: pyramid.request.Request
    :type user: User
    :type new_mail: str | unicode

    :return: Status message
    :rtype: str | unicode
    """
    log.info('Trying to verify mail address for user {!r}.'.format(user))
    log.debug('Mail address: {!s}.'.format(new_mail))
    # Start by removing the email address from any other user that currently has it (verified)
    old_user = request.userdb_new.get_user_by_mail(new_mail, raise_on_missing=False)
    steal_count = 0
    if old_user and old_user.user_id != user.user_id:
        retrieve_modified_ts(old_user, request.dashboard_userdb)
        old_user = _remove_mail_from_user(new_mail, old_user)
        request.context.save_dashboard_user(old_user)
        steal_count = 1
    # Add the verified mail address to the requesting user
    _add_mail_to_user(new_mail, user)
    log.info('Mail address verified for user {!r}.'.format(user))
    request.stats.count('dashboard/verify_mail_stolen', steal_count)
    request.stats.count('dashboard/verify_mail_completed', 1)
    return _('Email {obj} verified')
コード例 #13
0
ファイル: views.py プロジェクト: digideskio/eduid-dashboard
def login_view(request):
    login_redirect_url = request.registry.settings.get(
        'saml2.login_redirect_url', '/')

    came_from = sanitize_get(request, 'next', login_redirect_url)

    if authenticated_userid(request):
        return HTTPFound(location=came_from)

    selected_idp = sanitize_get(request, 'idp', None)
    if selected_idp is not None:
        request.session['selected_idp'] = selected_idp

    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'),
        })

    result = get_authn_request(request.registry.settings, request.session,
                               came_from, selected_idp)

    schedule_action(request.session, 'login-action')

    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)
コード例 #14
0
ファイル: views.py プロジェクト: Ratler/eduid-dashboard
def forbidden_view(context, request):
    """
    View to trap all Forbidden errors and redirect any not logged in users to the login page.

    For logged in users, a template is rendered - this template probably won't be seen
    by the user though since there is Javascript handling 401 errors from form posts
    showing a small pop-up error message instead.
    :param context: Some object like HTTPForbidden()
    :param request: Request() object
    :return:
    """
    user = authenticated_userid(request)
    if user:
        # Return a plain forbbiden page
        try:
            reason = context.explanation
        except AttributeError:
            reason = 'unknown'
        log.debug("User {!r} tripped Forbidden view, request {!r}, reason {!r}".format(
            user, request, reason))
        response = Response(render('templates/forbidden.jinja2', {}))
        response.status_int = 401
        return response

    loginurl = request.route_url('saml2-login',
                                _query=(('next', request.path),))
    if not request.is_xhr:
        return HTTPFound(location=loginurl)
    else:
        return HTTPXRelocate(loginurl)
コード例 #15
0
    def reset_success(self, passwordform):
        form_data = self.schema.serialize(passwordform)
        hash_code = self.request.matchdict['code']
        password_reset = self.request.db.reset_passwords.find_one({'hash_code': hash_code})
        user = self.request.userdb.get_user_by_email(password_reset['email'])
        user.retrieve_modified_ts(self.request.db.profiles)

        if form_data.get('use_custom_password') == 'true':
            log.debug("Password change for user {!r} (custom password).".format(user.get_id()))
            new_password = form_data.get('custom_password')
            self.request.stats.count('dashboard/pwreset_custom_password', 1)
        else:
            log.debug("Password change for user {!r} (suggested password).".format(user.get_id()))
            new_password = self.get_suggested_password()
            self.request.stats.count('dashboard/pwreset_generated_password', 1)

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

        self.request.db.reset_passwords.remove({'_id': password_reset['_id']})
        ok = change_password(self.request, user, '', new_password)

        if ok:
            self.request.stats.count('dashboard/pwreset_changed_password', 1)
            if password_reset['mechanism'] == 'email':
                # TODO: Re-send verification code in advance?
                nins = user.get_nins()
                reset_nin_count = 0
                if nins:
                    # XXX shouldn't the downgrade of NIN to unverified be done to *ALL* the user's NINs?
                    nin = nins[-1]
                    if nin is not None:
                        self.request.db.profiles.update({
                            "_id": user.get_id()
                        }, {
                            "$set": {"norEduPersonNIN": []}
                        })
                        # Do not remove the verification as we no longer allow users to remove a already verified nin
                        # even if it gets unverified by a e-mail password reset.
                        self.request.db.verifications.update({
                            "user_oid": user.get_id(),
                            "model_name": "norEduPersonNIN",
                            "obj_id": nin
                        }, {
                            "$set": {"verified": False}
                        })
                        update_attributes('eduid_dashboard', str(user['_id']))
                        reset_nin_count += 1
                    self.request.stats.count('dashboard/pwreset_downgraded_NINs', reset_nin_count)
            url = self.request.route_url('profile-editor')
            reset = True
        else:
            self.request.stats.count('dashboard/pwreset_password_change_failed', 1)
            url = self.request.route_url('reset-password')
            reset = False
        return {
            'url': url,
            'reset': reset
        }
コード例 #16
0
ファイル: views.py プロジェクト: digideskio/eduid-dashboard
def login_action(request, session_info, user):

    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 = sanitize_post_key(request, 'RelayState', '/')
    log.debug('Redirecting to the RelayState: ' + relay_state)
    return HTTPFound(location=relay_state, headers=headers)
コード例 #17
0
ファイル: validators.py プロジェクト: SUNET/eduid-dashboard
    def __call__(self, node, value):
        request = node.bindings.get('request')
        token = request.session.get_csrf_token()

        if value != token:
            log.debug(
                "CSRF token validation failed: Form {!r} != Session {!r}".
                format(value, token))
            err = _("Invalid CSRF token")
            raise colander.Invalid(node, get_localizer(request).translate(err))
コード例 #18
0
def send_reset_password_mail(request, user, reset_password_link):
    """ Send an email with the instructions for resetting password """
    mailer = get_mailer(request)

    site_name = request.registry.settings.get("site.name", "eduID")
    password_reset_timeout = int(
        request.registry.settings.get("password_reset_timeout", "120")) / 60

    if user.mail_addresses.primary is not None:
        email = user.mail_addresses.primary.email
    elif user.mail_addresses.count > 0:
        email = user.mail_addresses.to_list()[0].email
    else:
        log.info(
            'User {!r} has no email address, not possible to send a message'.
            format(user))
        return

    context = {
        "email": email,
        "reset_password_link": reset_password_link,
        "password_reset_timeout": password_reset_timeout,
        "site_url": request.route_url("home"),
        "site_name": site_name,
    }

    message = Message(
        subject=_("Reset your {site_name} password").format(
            site_name=site_name),
        sender=request.registry.settings.get("mail.default_sender"),
        recipients=[email],
        body=render(
            "templates/reset-password-email.txt.jinja2",
            context,
            request,
        ),
        html=render(
            "templates/reset-password-email.html.jinja2",
            context,
            request,
        ),
    )

    # DEBUG
    if request.registry.settings.get('developer_mode', False):
        log.debug(message.body)
    else:
        mailer.send(message)
    log.debug(
        "Sent reset password mail to user {!r} with address {!s}.".format(
            user, email))
    request.stats.count('email_send_pwreset_mail')
コード例 #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))
コード例 #20
0
ファイル: validators.py プロジェクト: SUNET/eduid-dashboard
    def __call__(self, node, value):

        request = node.bindings.get('request')
        user = request.context.user
        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))
コード例 #21
0
def verify_mobile(request, user, new_mobile):
    log.info('Trying to verify mobile number for user {!r}.'.format(user))
    log.debug('Mobile number: {!s}.'.format(new_mobile))
    # Start by removing mobile number from any other user
    old_user_docs = request.db.profiles.find({
        'mobile': {'$elemMatch': {'mobile': new_mobile, 'verified': True}}
    })
    steal_count = 0
    for old_user_doc in old_user_docs:
        old_user = User(old_user_doc)
        if old_user:
            log.debug('Found old user {!r} with mobile number ({!s}) already verified.'.format(old_user, new_mobile))
            log.debug('Old user mobile numbers BEFORE: {!r}.'.format(old_user.get_mobiles()))
            mobiles = [m for m in old_user.get_mobiles() if m['mobile'] != new_mobile]
            old_user.set_mobiles(mobiles)
            log.debug('Old user mobile numbers AFTER: {!r}.'.format(old_user.get_mobiles()))
            old_user.retrieve_modified_ts(request.db.profiles)
            old_user.save(request)
            log.info('Removed mobile number from user {!r}.'.format(old_user))
            steal_count += 1
    # Add the verified mobile number to the requesting user
    user.add_verified_mobile(new_mobile)
    log.info('Mobile number verified for user {!r}.'.format(user))
    request.stats.count('dashboard/verify_mobile_stolen', steal_count)
    request.stats.count('dashboard/verify_mobile_completed', 1)
    return user, _('Mobile {obj} verified')
コード例 #22
0
ファイル: vccs.py プロジェクト: SUNET/eduid-dashboard
def revoke_all_credentials(vccs_url, user):
    vccs = get_vccs_client(vccs_url)
    passwords = user.credentials.filter(Password).to_list()
    to_revoke = []
    for passwd in passwords:
        credential_id = str(passwd.credential_id)
        factor = vccs_client.VCCSRevokeFactor(
            credential_id,
            'subscriber requested termination',
            reference='dashboard')
        log.debug("Revoked old credential (account termination)"
                  " {!s} (user {!r})".format(credential_id, user))
        to_revoke.append(factor)
    userid = str(user.user_id)
    vccs.revoke_credentials(userid, to_revoke)
コード例 #23
0
def _add_phone_to_user(new_number, user):
    """
    Add a phone number to a user.
    Part of set_phone_verified() above.
    """
    phone = PhoneNumber(data={'number': new_number,
                              'verified': True,
                              'primary': False})
    log.debug('User had phones BEFORE verification: {!r}'.format(user.phone_numbers.to_list()))
    if user.phone_numbers.primary is None:
        log.debug('Setting NEW phone number to primary: {}.'.format(phone))
        phone.is_primary = True
    try:
        user.phone_numbers.add(phone)
    except DuplicateElementViolation:
        user.phone_numbers.find(new_number).is_verified = True
コード例 #24
0
def _add_phone_to_user(new_number, user):
    """
    Add a phone number to a user.
    Part of set_phone_verified() above.
    """
    phone = PhoneNumber(data={'number': new_number,
                              'verified': True,
                              'primary': False})
    log.debug('User had phones BEFORE verification: {!r}'.format(user.phone_numbers.to_list()))
    if user.phone_numbers.primary is None:
        log.debug('Setting NEW phone number to primary: {}.'.format(phone))
        phone.is_primary = True
    try:
        user.phone_numbers.add(phone)
    except DuplicateElementViolation:
        user.phone_numbers.find(new_number).is_verified = True
コード例 #25
0
ファイル: emails.py プロジェクト: SUNET/eduid-dashboard
def send_reset_password_mail(request, user, reset_password_link):
    """ Send an email with the instructions for resetting password """
    mailer = get_mailer(request)

    site_name = request.registry.settings.get("site.name", "eduID")
    password_reset_timeout = int(request.registry.settings.get("password_reset_timeout", "120")) / 60

    if user.mail_addresses.primary is not None:
        email = user.mail_addresses.primary.email
    elif user.mail_addresses.count > 0:
        email = user.mail_addresses.to_list()[0].email
    else:
        log.info('User {!r} has no email address, not possible to send a message'.format(user))
        return

    context = {
        "email": email,
        "reset_password_link": reset_password_link,
        "password_reset_timeout": password_reset_timeout,
        "site_url": request.route_url("home"),
        "site_name": site_name,
    }

    message = Message(
        subject=_("Reset your {site_name} password").format(
            site_name=site_name),
        sender=request.registry.settings.get("mail.default_sender"),
        recipients=[email],
        body=render(
            "templates/reset-password-email.txt.jinja2",
            context,
            request,
        ),
        html=render(
            "templates/reset-password-email.html.jinja2",
            context,
            request,
        ),
    )

    # DEBUG
    if request.registry.settings.get('developer_mode', False):
        log.debug(message.body)
    else:
        mailer.send(message)
    log.debug("Sent reset password mail to user {!r} with address {!s}.".format(user, email))
    request.stats.count('email_send_pwreset_mail')
コード例 #26
0
def send_verification_mail(request, email, reference=None, code=None):
    mailer = get_mailer(request)
    if code is None or reference is None:
        reference, code = new_verification_code(request,
                                                'mailAliases',
                                                email,
                                                request.context.user,
                                                hasher=get_short_hash)

    verification_link = generate_verification_link(request, code,
                                                   'mailAliases')

    site_name = request.registry.settings.get("site.name", "eduID")

    context = {
        "email": email,
        "verification_link": verification_link,
        "site_url": request.context.safe_route_url("home"),
        "site_name": site_name,
        "code": code,
    }

    message = Message(
        subject=_("{site_name} confirmation email").format(
            site_name=site_name),
        sender=request.registry.settings.get("mail.default_sender"),
        recipients=[email],
        body=render(
            "templates/verification_email.txt.jinja2",
            context,
            request,
        ),
        html=render(
            "templates/verification_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 verification mail to user {!r} with address {!s}.".format(
        request.context.user, email))
    request.stats.count('email_send_verification_code')
コード例 #27
0
    def save_success(self, passwordform):
        authn_ts = self.request.session.get('re-authn-ts', None)
        if authn_ts is None:
            raise HTTPBadRequest(_('No authentication info'))
        else:
            now = datetime.utcnow()
            delta = now - authn_ts
            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'))
        del self.request.session['re-authn-ts']
        passwords_data = self.schema.serialize(passwordform)
        if 'edit-user' in self.request.session:
            user = self.request.session['edit-user']
        else:
            user = self.request.session['user']

        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.get_id()))
            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.get_id()))
            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.get_user_by_oid(user.get_id())
        user.retrieve_modified_ts(self.request.db.profiles)

        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'))
コード例 #28
0
ファイル: vccs.py プロジェクト: SUNET/eduid-dashboard
def revoke_all_credentials(vccs_url, user):
    vccs = get_vccs_client(vccs_url)
    passwords = user.credentials.filter(Password).to_list()
    to_revoke = []
    for passwd in passwords:
        credential_id = str(passwd.credential_id)
        factor = vccs_client.VCCSRevokeFactor(
            credential_id,
            'subscriber requested termination',
            reference='dashboard'
        )
        log.debug("Revoked old credential (account termination)"
                  " {!s} (user {!r})".format(
                      credential_id, user))
        to_revoke.append(factor)
    userid = str(user.user_id)
    vccs.revoke_credentials(userid, to_revoke)
コード例 #29
0
 def _verify_action(self, data_id, post_data):
     if 'code' in post_data:
         code_sent = post_data['code']
         verification_code = get_verification_code(self.request,
                                                   self.data_attribute,
                                                   obj_id=data_id,
                                                   code=code_sent,
                                                   user=self.user)
         if verification_code:
             if code_sent == verification_code['code']:
                 if verification_code['expired']:
                     log.debug(
                         "User {!r} verification code has expired".format(
                             self.user))
                     return {
                         'result': 'error',
                         'message': self.verify_messages['expired'],
                     }
                 else:
                     try:
                         verify_code(self.request, self.data_attribute,
                                     code_sent)
                     except UserOutOfSync:
                         self.sync_user()
                         return {
                             'result': 'out_of_sync',
                             'message': self.verify_messages['out_of_sync'],
                         }
                 return {
                     'result': 'success',
                     'message': self.verify_messages['success'],
                 }
             else:
                 log.debug("Incorrect code for user {!r}: {!r}".format(
                     self.user, code_sent))
         return {
             'result': 'error',
             'message': self.verify_messages['error'],
         }
     else:
         message = self.verify_messages['request'].format(data=data_id)
         return {
             'result': 'getcode',
             'message': message,
             'placeholder': self.verify_messages['placeholder'],
         }
コード例 #30
0
def get_verification_code(request, model_name, obj_id=None, code=None, user=None):
    """
    Match a user supplied code (`code') against an actual entry in the database.

    :param request: The HTTP request
    :param model_name: 'norEduPersonNIN', 'phone', or 'mailAliases'
    :param obj_id: The data covered by the verification, like the phone number or nin or ...
    :param code: User supplied code
    :param user: The user

    :type request: pyramid.request.Request
    :type model_name: str | unicode
    :type obj_id: str | unicode
    :type code: str | unicode
    :type user: User | OldUser

    :returns: Verification entry from the database
    :rtype: dict
    """
    assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases']

    userid = None
    if user is not None:
        try:
            userid = user.user_id
        except AttributeError:
            userid = user.get_id()

    filters = {
        'model_name': model_name,
    }
    if obj_id is not None:
        filters['obj_id'] = obj_id
    if code is not None:
        filters['code'] = code
    if userid is not None:
        filters['user_oid'] = userid
    log.debug("Verification code lookup filters : {!r}".format(filters))
    result = request.db.verifications.find_one(filters)
    if result:
        expiration_timeout = request.registry.settings.get('verification_code_timeout')
        expire_limit = datetime.now(utc) - timedelta(minutes=int(expiration_timeout))
        result['expired'] = result['timestamp'] < expire_limit
        log.debug("Verification lookup result : {!r}".format(result))
    return result
コード例 #31
0
def get_verification_code(request, model_name, obj_id=None, code=None, user=None):
    """
    Match a user supplied code (`code') against an actual entry in the database.

    :param request: The HTTP request
    :param model_name: 'norEduPersonNIN', 'phone', or 'mailAliases'
    :param obj_id: The data covered by the verification, like the phone number or nin or ...
    :param code: User supplied code
    :param user: The user

    :type request: pyramid.request.Request
    :type model_name: str | unicode
    :type obj_id: str | unicode
    :type code: str | unicode
    :type user: User | OldUser

    :returns: Verification entry from the database
    :rtype: dict
    """
    assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases']

    userid = None
    if user is not None:
        try:
            userid = user.user_id
        except AttributeError:
            userid = user.get_id()

    filters = {
        'model_name': model_name,
    }
    if obj_id is not None:
        filters['obj_id'] = obj_id
    if code is not None:
        filters['code'] = code
    if userid is not None:
        filters['user_oid'] = userid
    log.debug("Verification code lookup filters : {!r}".format(filters))
    result = request.db.verifications.find_one(filters)
    if result:
        expiration_timeout = request.registry.settings.get('verification_code_timeout')
        expire_limit = datetime.now(utc) - timedelta(minutes=int(expiration_timeout))
        result['expired'] = result['timestamp'] < expire_limit
        log.debug("Verification lookup result : {!r}".format(result))
    return result
コード例 #32
0
def get_verification_code(request, model_name, obj_id=None, code=None, user=None):
    filters = {
        'model_name': model_name,
    }
    if obj_id is not None:
        filters['obj_id'] = obj_id
    if code is not None:
        filters['code'] = code
    if user is not None:
        filters['user_oid'] = user.get_id()
    log.debug("Verification code lookup filters : {!r}".format(filters))
    result = request.db.verifications.find_one(filters)
    if result:
        expiration_timeout = request.registry.settings.get('verification_code_timeout')
        expire_limit = datetime.now(utc) - timedelta(minutes=int(expiration_timeout))
        result['expired'] = result['timestamp'] < expire_limit
        log.debug("Verification lookup result : {!r}".format(result))
    return result
コード例 #33
0
def new_reset_password_code(request, user, mechanism='email'):
    hash_code = get_unique_hash()
    date = datetime.now(pytz.utc)
    request.db.reset_passwords.remove({
        'email': user.get_mail()
    })
    reference = request.db.reset_passwords.insert({
        'email': user.get_mail(),
        'hash_code': hash_code,
        'mechanism': mechanism,
        'created_at': date,
    }, safe=True, manipulate=True)
    log.debug("New reset password code: {!s} via {!s} for user: {!r}.".format(hash_code, mechanism, user))
    reset_password_link = request.route_url(
        "reset-password-step2",
        code=hash_code,
    )
    return reference, reset_password_link
コード例 #34
0
ファイル: auth.py プロジェクト: enriquepablo/eduid-dashboard
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
コード例 #35
0
ファイル: emails.py プロジェクト: digideskio/eduid-dashboard
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)
コード例 #36
0
ファイル: views.py プロジェクト: enriquepablo/eduid-dashboard
def assertion_consumer_service(request):
    ''' '''
    action = get_action(request.session)

    if 'SAMLResponse' not in request.POST:
        raise 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')
        raise 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')
        raise 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')
        raise HTTPUnauthorized("Access not authorized")

    return action(request, session_info, user)
コード例 #37
0
ファイル: emails.py プロジェクト: digideskio/eduid-dashboard
def send_verification_mail(request, email, reference=None, code=None):
    mailer = get_mailer(request)
    if code is None or reference is None:
        reference, code = new_verification_code(request, 'mailAliases', email, request.context.user,
                                     hasher=get_short_hash)

    verification_link = generate_verification_link(request, code,
                                                   'mailAliases')

    site_name = request.registry.settings.get("site.name", "eduID")

    context = {
        "email": email,
        "verification_link": verification_link,
        "site_url": request.context.safe_route_url("home"),
        "site_name": site_name,
        "code": code,
    }

    message = Message(
        subject=_("{site_name} confirmation email").format(
            site_name=site_name),
        sender=request.registry.settings.get("mail.default_sender"),
        recipients=[email],
        body=render(
            "templates/verification_email.txt.jinja2",
            context,
            request,
        ),
        html=render(
            "templates/verification_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 verification mail to user {!r} with address {!s}.".format(request.context.user, email))
    request.stats.count('dashboard/email_send_verification_code', 1)
コード例 #38
0
ファイル: views.py プロジェクト: digideskio/eduid-dashboard
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)
コード例 #39
0
def new_verification_code(request, model_name, obj_id, user, hasher=None):
    """
    Match a user supplied code (`code') against an actual entry in the database.

    :param request: The HTTP request
    :param model_name: 'norEduPersonNIN', 'phone', or 'mailAliases'
    :param obj_id: The data covered by the verification, like the phone number or nin or ...
    :param user: The user
    :param hasher: Callable used to generate the code

    :type request: pyramid.request.Request
    :type model_name: str | unicode
    :type obj_id: str | unicode
    :type user: User | OldUser
    :type hasher: callable
    """
    assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases']

    try:
        userid = user.user_id
    except AttributeError:
        userid = user.get_id()

    if hasher is None:
        hasher = get_unique_hash
    code = hasher()
    obj = {
        'model_name': model_name,
        'obj_id': obj_id,
        'user_oid': userid,
        'code': code,
        'verified': False,
        'timestamp': datetime.now(utc),
    }
    doc_id = request.db.verifications.insert(obj)
    reference = unicode(doc_id)
    session_verifications = request.session.get('verifications', [])
    session_verifications.append(code)
    request.session['verifications'] = session_verifications
    log.info('Created new {!s} verification code for user {!r}.'.format(model_name, user))
    log.debug('Verification object id {!s}. Code: {!s}.'.format(obj_id, code))
    return reference, code
コード例 #40
0
def new_verification_code(request, model_name, obj_id, user, hasher=None):
    """
    Match a user supplied code (`code') against an actual entry in the database.

    :param request: The HTTP request
    :param model_name: 'norEduPersonNIN', 'phone', or 'mailAliases'
    :param obj_id: The data covered by the verification, like the phone number or nin or ...
    :param user: The user
    :param hasher: Callable used to generate the code

    :type request: pyramid.request.Request
    :type model_name: str | unicode
    :type obj_id: str | unicode
    :type user: User | OldUser
    :type hasher: callable
    """
    assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases']

    try:
        userid = user.user_id
    except AttributeError:
        userid = user.get_id()

    if hasher is None:
        hasher = get_unique_hash
    code = hasher()
    obj = {
        'model_name': model_name,
        'obj_id': obj_id,
        'user_oid': userid,
        'code': code,
        'verified': False,
        'timestamp': datetime.now(utc),
    }
    doc_id = request.db.verifications.insert(obj)
    reference = unicode(doc_id)
    session_verifications = request.session.get('verifications', [])
    session_verifications.append(code)
    request.session['verifications'] = session_verifications
    log.info('Created new {!s} verification code for user {!r}.'.format(model_name, user))
    log.debug('Verification object id {!s}. Code: {!s}.'.format(obj_id, code))
    return reference, code
コード例 #41
0
def send_termination_mail(request, user):
    mailer = get_mailer(request)
    support_email = request.registry.settings.get('mail.support_email',
                                                  '*****@*****.**')
    site_name = request.registry.settings.get("site.name", "eduID")

    context = {'support_mail': support_email, 'displayName': user.display_name}
    if user.mail_addresses.primary is not None:
        address = user.mail_addresses.primary.email
    elif user.mail_addresses.count > 0:
        address = user.mail_addresses.to_list()[0].email
    else:
        log.info(
            'User {!r} has no email address, not possible to send a message'.
            format(user))
        return

    message = Message(
        subject=_("{site_name} account termination").format(
            site_name=site_name),
        sender=request.registry.settings.get("mail.default_sender"),
        recipients=[address],
        body=render(
            "templates/termination_email.txt.jinja2",
            context,
            request,
        ),
        html=render(
            "templates/termination_email.html.jinja2",
            context,
            request,
        ),
    )

    # DEBUG
    if request.registry.settings.get('developer_mode', False):
        log.debug(message.body)
    else:
        mailer.send(message)
    log.debug("Sent termination mail to user {!r} with address {!s}.".format(
        user, address))
    request.stats.count('email_send_termination_mail')
コード例 #42
0
def new_verification_code(request, model_name, obj_id, user, hasher=None):
    if hasher is None:
        hasher = get_unique_hash
    code = hasher()
    obj = {
        "model_name": model_name,
        "obj_id": obj_id,
        "user_oid": user.get_id(),
        "code": code,
        "verified": False,
        "timestamp": datetime.now(utc),
    }
    doc_id = request.db.verifications.insert(obj)
    reference = unicode(doc_id)
    session_verifications = request.session.get('verifications', [])
    session_verifications.append(code)
    request.session['verifications'] = session_verifications
    log.info('Created new {!s} verification code for user {!r}.'.format(model_name, user))
    log.debug('Verification object id {!s}. Code: {!s}.'.format(obj_id, code))
    return reference, code
コード例 #43
0
def verifications(context, request):
    model_name = request.matchdict['model']
    code = request.matchdict['code']

    verification = get_verification_code(request, model_name, code=code)
    if verification and verification['expired']:
        log.debug("Verification code is expired: {!r}".format(verification))
        raise HTTPNotFound()  # the code is expired

    if code not in request.session.get('verifications', []):
        log.debug("Code {!r} not found in active sessions verifications: {!r}".
                  format(code, request.session.get('verifications', [])))
        raise HTTPNotFound(_("Can't locate the code in the active session"))
    try:
        obj_id = verify_code(request, model_name, code)
    except UserOutOfSync:
        msg = _('Your user profile is out of sync. Please '
                'reload the page and try again.')
        msg = get_localizer(request).translate(msg)
        request.session.flash(msg),
        raise HTTPFound(request.context.route_url('profile-editor'))

    if obj_id is not None:
        request.stats.count('verification_{!s}_ok'.format(model_name))
        return HTTPFound(location=request.route_url('home'))
    else:
        log.debug("Incorrect verification code {!r} for model {!r}".format(
            code, model_name))
        request.stats.count('verification_{!s}_fail'.format(model_name))
        raise HTTPNotFound()
コード例 #44
0
def set_nin_verified(request, user, new_nin, reference=None):
    """
    Mark a National Identity Number (NIN) as verified on a user.

    This process also includes *removing* the NIN from any other user
    that had it as a verified NIN.

    :param request: The HTTP request
    :param user: The user
    :param new_nin: The National Identity Number to mark as verified
    :param reference: A reference to the verification code - used for audit logging

    :type request: pyramid.request.Request
    :type user: User
    :type new_nin: str | unicode

    :return: Status message
    :rtype: str | unicode
    """
    log.info('Trying to verify NIN for user {!r}.'.format(user))
    log.debug('NIN: {!s}.'.format(new_nin))
    # Start by removing nin from any other user
    old_user = request.userdb_new.get_user_by_nin(new_nin, raise_on_missing=False)
    log.debug('Searched for NIN {!r} in {!s}: {!r}'.format(new_nin, request.userdb_new, old_user))
    steal_count = 0
    if old_user and old_user.user_id != user.user_id:
        retrieve_modified_ts(old_user, request.dashboard_userdb)
        _remove_nin_from_user(new_nin, old_user)
        request.context.save_dashboard_user(old_user)
        log.info('Removed NIN and associated addresses from user {!r}.'.format(old_user))
        steal_count = 1
    # Add the verified nin to the requesting user
    _add_nin_to_user(new_nin, user)
    _nin_verified_transaction_audit(request, reference)
    log.info('NIN verified for user {!r}.'.format(user))
    request.stats.count('verify_nin_stolen', steal_count)
    request.stats.count('verify_nin_completed')
    return _('National identity number {obj} verified')
コード例 #45
0
def _remove_nin_from_user(nin, user):
    """
    Remove a NIN from one user because it is being verified by another user.
    Part of set_nin_verified() above.
    """
    log.debug('Found old user {!r} with NIN ({!s}) already verified.'.format(user, nin))
    log.debug('Old user NINs BEFORE: {!r}.'.format(user.nins.to_list()))
    if user.nins.primary.number == nin:
        old_nins = user.nins.verified.to_list()
        for this in old_nins:
            if this.number != nin:
                user.nins.primary = this.number
                break
    user.nins.remove(nin)
    log.debug('Old user NINs AFTER: {!r}.'.format(user.nins.to_list()))
    return user
コード例 #46
0
def _remove_phone_from_user(number, user):
    """
    Remove a phone number from one user because it is being verified by another user.
    Part of set_phone_verified() above.
    """
    log.debug('Found old user {!r} with phone number ({!s}) already verified.'.format(user, number))
    log.debug('Old user phone numbers BEFORE: {!r}.'.format(user.phone_numbers.to_list()))
    if user.phone_numbers.primary.number == number:
        # Promote some other verified phone number to primary
        for phone in user.phone_numbers.verified.to_list():
            if phone.number != number:
                user.phone_numbers.primary = phone.number
                break
    user.phone_numbers.remove(number)
    log.debug('Old user phone numbers AFTER: {!r}.'.format(user.phone_numbers.to_list()))
    return user
コード例 #47
0
ファイル: nins.py プロジェクト: SUNET/eduid-dashboard
    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'))
コード例 #48
0
ファイル: validators.py プロジェクト: SUNET/eduid-dashboard
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
コード例 #49
0
def _remove_mail_from_user(email, user):
    """
    Remove an email address from one user because it is being verified by another user.
    Part of set_email_verified() above.
    """
    log.debug('Removing mail address {!s} from user {!s}'.format(email, user))
    if user.mail_addresses.primary:
        # only in the test suite could primary ever be None here
        log.debug('Old user mail BEFORE: {!s}'.format(user.mail_addresses.primary))
    log.debug('Old user mail aliases BEFORE: {!r}'.format(user.mail_addresses.to_list()))
    if user.mail_addresses.primary and user.mail_addresses.primary.email == email:
        # Promote some other verified e-mail address to primary
        for address in user.mail_addresses.to_list():
            if address.is_verified and address.email != email:
                user.mail_addresses.primary = address.email
                break
    user.mail_addresses.remove(email)
    if user.mail_addresses.primary is not None:
        log.debug('Old user mail AFTER: {!s}.'.format(user.mail_addresses.primary))
    if user.mail_addresses.count > 0:
        log.debug('Old user mail aliases AFTER: {!r}.'.format(user.mail_addresses.to_list()))
    else:
        log.debug('Old user has NO mail AFTER.')
    return user
コード例 #50
0
ファイル: vccs.py プロジェクト: SUNET/eduid-dashboard
def add_credentials(vccs_url, old_password, new_password, user):
    """
    Add a new password to a user. Revokes the old one, if one is given.

    Returns True on success.

    :param vccs_url: URL to VCCS authentication backend
    :param old_password: plaintext current password
    :param new_password: plaintext new password
    :param user: user object

    :type vccs_url: string
    :type old_password: string
    :type user: OldUser
    :rtype: bool
    """
    password_id = ObjectId()
    vccs = get_vccs_client(vccs_url)
    new_factor = vccs_client.VCCSPasswordFactor(new_password,
                                                credential_id=str(password_id))

    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)

    old_factor = None
    checked_password = None
    # remember if an old password was supplied or not, without keeping it in
    # memory longer than we have to
    old_password_supplied = bool(old_password)
    if user.passwords.count > 0 and old_password:
        # Find the old credential to revoke
        checked_password = check_password(vccs_url,
                                          old_password,
                                          user,
                                          vccs=vccs)
        del old_password  # don't need it anymore, try to forget it
        if not checked_password:
            return False
        old_factor = vccs_client.VCCSRevokeFactor(
            str(checked_password.credential_id),
            'changing password',
            reference='dashboard',
        )

    if not vccs.add_credentials(str(user.user_id), [new_factor]):
        log.warning(
            "Failed adding password credential {!r} for user {!r}".format(
                new_factor.credential_id, user))
        return False  # something failed
    log.debug("Added password credential {!s} for user {!s}".format(
        new_factor.credential_id, user))

    if old_factor:
        vccs.revoke_credentials(str(user.user_id), [old_factor])
        user.passwords.remove(checked_password.key)
        log.debug("Revoked old credential {!s} (user {!s})".format(
            old_factor.credential_id, user))

    if not old_password_supplied:
        # TODO: Revoke all current credentials on password reset for now
        revoked = []
        for password in user.credentials.filter(Password).to_list():
            revoked.append(
                vccs_client.VCCSRevokeFactor(str(password.credential_id),
                                             'reset password',
                                             reference='dashboard'))
            log.debug(
                "Revoking old credential (password reset) {!s} (user {!r})".
                format(password.credential_id, user))
            user.passwords.remove(password.key)
        if revoked:
            try:
                vccs.revoke_credentials(str(user.user_id), revoked)
            except vccs_client.VCCSClientHTTPError:
                # Password already revoked
                # TODO: vccs backend should be changed to return something more informative than
                # TODO: VCCSClientHTTPError when the credential is already revoked or just return success.
                log.warning(
                    "VCCS failed to revoke all passwords for user {!s}".format(
                        user))
                pass

    new_password = Password(
        credential_id=password_id,
        salt=new_factor.salt,
        application='dashboard',
    )
    user.passwords.add(new_password)

    return True
コード例 #51
0
def dummy_message(request, message):
    """
    This function is only for debugging purposes
    """
    log.debug('[DUMMY_MESSAGE]: {!s}'.format(message))