示例#1
0
def get_actions():
    user = current_app.central_userdb.get_user_by_eppn(
        session.get('user_eppn'))
    actions = get_next_action(user)
    if not actions['action']:
        return json.dumps({
            'action': False,
            'url': actions['idp_url'],
            'payload': {
                'csrf_token': session.new_csrf_token()
            }
        })
    action_type = session['current_plugin']
    plugin_obj = current_app.plugins[action_type]()
    old_format = 'user_oid' in session['current_action']
    action = Action(data=session['current_action'], old_format=old_format)
    current_app.logger.info('Starting pre-login action {} '
                            'for user {}'.format(action.action_type, user))
    try:
        url = plugin_obj.get_url_for_bundle(action)
        return json.dumps({
            'action': True,
            'url': url,
            'payload': {
                'csrf_token': session.new_csrf_token()
            }
        })
    except plugin_obj.ActionError as exc:
        _aborted(action, exc)
        abort(500)
示例#2
0
def get_actions():
    user = current_app.central_userdb.get_user_by_eppn(session.get('user_eppn'))
    actions = get_next_action(user)
    if not actions['action']:
        return json.dumps({'action': False,
                           'url': actions['idp_url'],
                           'payload': {
                               'csrf_token': session.new_csrf_token()
                               }
                           })
    action_type = session['current_plugin']
    plugin_obj = current_app.plugins[action_type]()
    old_format = 'user_oid' in session['current_action']
    action = Action(data=session['current_action'], old_format=old_format)
    current_app.logger.info('Starting pre-login action {} '
                            'for user {}'.format(action.action_type, user))
    try:
        url = plugin_obj.get_url_for_bundle(action)
        return json.dumps({'action': True,
                           'url': url,
                           'payload': {
                               'csrf_token': session.new_csrf_token()
                               }})
    except plugin_obj.ActionError as exc:
        _aborted(action, exc)
        abort(500)
示例#3
0
def registration_begin(user, authenticator):
    user_webauthn_tokens = user.credentials.filter(FidoCredential)
    if user_webauthn_tokens.count >= current_app.config.webauthn_max_allowed_tokens:
        current_app.logger.error(
            'User tried to register more than {} tokens.'.format(current_app.config.webauthn_max_allowed_tokens)
        )
        return error_response(message=SecurityMsg.max_webauthn)

    creds = make_credentials(user_webauthn_tokens.to_list())
    server = get_webauthn_server(current_app.config.fido2_rp_id)
    if user.given_name is None or user.surname is None or user.display_name is None:
        return error_response(message=SecurityMsg.no_pdata)

    registration_data, state = server.register_begin(
        {
            'id': str(user.eppn).encode('ascii'),
            'name': "{} {}".format(user.given_name, user.surname),
            'displayName': user.display_name,
        },
        credentials=creds,
        user_verification=USER_VERIFICATION.DISCOURAGED,
        authenticator_attachment=authenticator,
    )
    session['_webauthn_state_'] = state

    current_app.logger.info('User {} has started registration of a webauthn token'.format(user))
    current_app.logger.debug('Webauthn Registration data: {}.'.format(registration_data))
    current_app.stats.count(name='webauthn_register_begin')

    encoded_data = base64.urlsafe_b64encode(cbor.encode(registration_data)).decode('ascii')
    encoded_data = encoded_data.rstrip('=')
    return {'csrf_token': session.new_csrf_token(), 'registration_data': encoded_data}
示例#4
0
def post_action():
    if not request.data or session.get_csrf_token() != json.loads(request.data)['csrf_token']:
        abort(400)
    ret = _do_action()
    # Add a new csrf token as this is a POST request
    ret['csrf_token'] = session.new_csrf_token()
    return ret
示例#5
0
def extra_security_phone_number(state):
    current_app.logger.info('Password reset: verify_phone_number {}'.format(request.method))
    view_context = {
        'heading': _('Verify phone number'),
        'text': _('Enter the code you received via SMS'),
        'action': url_for('reset_password.extra_security_phone_number', email_code=state.email_code.code),
        'form_post_fail_msg': None,
        'retry_url': url_for('reset_password.choose_extra_security', email_code=state.email_code.code),
        'retry_url_txt': _('Resend code or try another way'),
        'csrf_token': session.get_csrf_token(),
        'errors': [],
    }
    if request.method == 'POST':
        form = ResetPasswordVerifyPhoneNumberSchema().load(request.form)
        if not form.errors and session.get_csrf_token() == form.data['csrf']:
            current_app.logger.info('Trying to verify phone code')
            if form.data.get('phone_code', '') == state.phone_code.code:
                if not verify_phone_number(state):
                    current_app.logger.info('Could not validated phone code for {}'.format(state.eppn))
                    view_context = {
                        'heading': _('Temporary technical problem'),
                        'text': _('Please try again.'),
                        'retry_url': url_for('reset_password.choose_extra_security', email_code=state.email_code.code),
                        'retry_url_txt': _('Try again'),
                    }
                    return render_template('error.jinja2', view_context=view_context)
                current_app.logger.info('Phone code verified redirecting user to set password view')
                return redirect(url_for('reset_password.new_password', email_code=state.email_code.code))
            view_context['form_post_fail_msg'] = _('Invalid code. Please try again.')
        view_context['errors'] = form.errors
    view_context['csrf_token'] = session.new_csrf_token()
    return render_template('reset_password_verify_phone.jinja2', view_context=view_context)
示例#6
0
def registration_begin(user, authenticator):
    user_webauthn_tokens = user.credentials.filter(FidoCredential)
    if user_webauthn_tokens.count >= current_app.config['WEBAUTHN_MAX_ALLOWED_TOKENS']:
        current_app.logger.error('User tried to register more than {} tokens.'.format(
            current_app.config['WEBAUTHN_MAX_ALLOWED_TOKENS']))
        return {'_status': 'error', 'message': 'security.webauthn.max_allowed_tokens'}
    creds = make_credentials(user_webauthn_tokens.to_list())
    server = get_webauthn_server(current_app.config['FIDO2_RP_ID'])
    if user.given_name is None or user.surname is None or user.display_name is None:
        return {'_status': 'error', 'message': 'security.webauthn-missing-pdata'}
    registration_data, state = server.register_begin(
            {
                'id': str(user.eppn).encode('ascii'),
                'name': "{} {}".format(user.given_name, user.surname),
                'displayName': user.display_name
            }, credentials=creds,
            user_verification=USER_VERIFICATION.DISCOURAGED,
            authenticator_attachment=authenticator,
            )
    session['_webauthn_state_'] = state

    current_app.logger.info('User {} has started registration of a webauthn token'.format(user))
    current_app.logger.debug('Webauthn Registration data: {}.'.format(registration_data))
    current_app.stats.count(name='webauthn_register_begin')

    encoded_data = base64.urlsafe_b64encode(cbor.dumps(registration_data)).decode('ascii')
    encoded_data = encoded_data.rstrip('=')
    return {
        'csrf_token': session.new_csrf_token(),
        'registration_data': encoded_data
    }
示例#7
0
def choose_extra_security(state):
    current_app.logger.info('Password reset: choose_extra_security {}'.format(request.method))
    view_context = {
        'heading': _('Extra security'),
        'text': _('Choose an option to enhance the security'),
        'action': url_for('reset_password.choose_extra_security', email_code=state.email_code.code),
        'csrf_token': session.get_csrf_token(),
        'form_post_success': False,
        'errors': [],
    }

    # Check that the email code has been validated
    if not state.email_code.is_verified:
        current_app.logger.info('User {} has not verified their email address'.format(state.eppn))
        view_context = {
            'heading': _('Email address not validated'),
            'text': _('Please use the password reset link that you have in your email.'),
        }
        return render_template('error.jinja2', view_context=view_context)

    if request.method == 'POST':
        form = ResetPasswordExtraSecuritySchema().load(request.form)
        if not form.errors and session.get_csrf_token() == form.data['csrf']:
            if form.data.get('no_extra_security'):
                current_app.logger.info('Redirecting user to reset password with NO extra security')
                return redirect(url_for('reset_password.new_password', email_code=state.email_code.code))
            if form.data.get('phone_number_index'):
                phone_number_index = int(form.data.get('phone_number_index'))
                phone_number = state.extra_security['phone_numbers'][phone_number_index]
                current_app.logger.info('Trying to send password reset sms to user {}'.format(state.eppn))
                send_verify_phone_code(state, phone_number)
                current_app.logger.info('Redirecting user to verify phone number view')
                return redirect(url_for('reset_password.extra_security_phone_number', email_code=state.email_code.code))

        view_context['errors'] = form.errors
    view_context['csrf_token'] = session.new_csrf_token()

    try:
        alternatives = get_extra_security_alternatives(state.eppn)
        state.extra_security = alternatives
        current_app.password_reset_state_db.save(state)
        view_context['security_alternatives'] = mask_alternatives(alternatives)
    except DocumentDoesNotExist:
        current_app.logger.error('User {} not found'.format(state.eppn))
        view_context = {
            'heading': _('Something went wrong'),
            'text': _('Please restart the password reset procedure.'),
            'retry_url': url_for('reset_password.reset_password'),
            'retry_url_txt': _('Reset your password'),
        }
        return render_template('error.jinja2', view_context=view_context)
    if not alternatives:
        # The user has no options for extra security, redirect to setting a new password
        return redirect(url_for('reset_password.new_password', email_code=state.email_code.code))
    return render_template('reset_password_extra_security.jinja2', view_context=view_context)
示例#8
0
def get_config():
    try:
        action_type = session['current_plugin']
    except KeyError:
        abort(403)
    plugin_obj = current_app.plugins[action_type]()
    action = Action(data=session['current_action'])
    try:
        config = plugin_obj.get_config_for_bundle(action)
        config['csrf_token'] = session.new_csrf_token()
        return config
    except plugin_obj.ActionError as exc:
        return error_response(message=exc.args[0])
示例#9
0
def get_config():
    try:
        action_type = session['current_plugin']
    except KeyError:
        abort(403)
    plugin_obj = current_app.plugins[action_type]()
    action = Action(data=session['current_action'])
    try:
        config = plugin_obj.get_config_for_bundle(action)
        config['csrf_token'] = session.new_csrf_token()
        return config
    except plugin_obj.ActionError as exc:
        return {
            '_status': 'error',
            'message':  exc.args[0]
            }
示例#10
0
def reset_password():
    current_app.logger.info('Password reset: reset_password {}'.format(
        request.method))
    view_context = {
        'heading':
        _('Reset password'),
        'text':
        _('Enter an email address registered to your account below'),
        'action':
        url_for('reset_password.reset_password'),
        'csrf_token':
        session.get_csrf_token(),
        'form_post_success':
        False,
        'form_post_success_msg':
        _('Reset password message sent. Check your email to continue.'),
        'errors': [],
    }
    if request.method == 'POST':
        try:
            form = ResetPasswordEmailSchema().load(request.form)
        except ValidationError as e:
            current_app.logger.error(e)
            view_context['errors'] = e.messages
        else:
            if session.get_csrf_token() == form['csrf']:
                current_app.logger.info(
                    'Trying to send password reset email to {}'.format(
                        form['email']))
                try:
                    send_password_reset_mail(form['email'])
                except MailTaskFailed as e:
                    current_app.logger.error(
                        'Sending e-mail failed: {}'.format(e))
                    view_context = {
                        'heading': _('Temporary technical problem'),
                        'text': _('Please try again.'),
                        'retry_url': url_for('reset_password.reset_password'),
                        'retry_url_txt': _('Try again'),
                    }
                    return render_template('error.jinja2',
                                           view_context=view_context)
                view_context['form_post_success'] = True
                view_context['text'] = ''

    view_context['csrf_token'] = session.new_csrf_token()
    return render_template('reset_password.jinja2', view_context=view_context)
示例#11
0
def new_password(state):
    current_app.logger.info('Password reset: new_password {}'.format(request.method))
    view_context = {
        'heading': _('New password'),
        'text': _('''
            Please choose a new password for your eduID account. A strong password has been generated for you.
            You can accept the generated password by clicking "Change password" or you can opt to choose your
            own password by clicking "Custom Password".
        '''),
        'action': url_for('reset_password.new_password', email_code=state.email_code.code),
        'csrf_token': session.get_csrf_token(),
        'active_pane': 'generated',
        'zxcvbn_terms': json.dumps(get_zxcvbn_terms(state.eppn)),
        'errors': [],
    }
    if request.method == 'POST':
        min_entropy = current_app.config['PASSWORD_ENTROPY']
        form = ResetPasswordNewPasswordSchema(
            zxcvbn_terms=view_context['zxcvbn_terms'], min_entropy=int(min_entropy)).load(request.form)
        current_app.logger.debug(form)
        if not form.errors and session.get_csrf_token() == form.data['csrf']:
            if form.data.get('use_generated_password'):
                password = state.generated_password
                current_app.logger.info('Generated password used')
            else:
                password = form.data.get('custom_password')
                current_app.logger.info('Custom password used')
            current_app.logger.info('Resetting password for user {}'.format(state.eppn))
            reset_user_password(state, password)
            current_app.logger.info('Password reset done removing state for user {}'.format(state.eppn))
            current_app.password_reset_state_db.remove_state(state)
            view_context['form_post_success'] = True
            view_context['login_url'] = current_app.config['EDUID_SITE_URL']
            return render_template('reset_password_new_password.jinja2', view_context=view_context)

        view_context['errors'] = form.errors
        view_context['active_pane'] = 'custom'

    # Generate a random good password
    # TODO: Hash the password using VCCSPasswordFactor before saving it to db
    state.generated_password = generate_suggested_password()
    view_context['generated_password'] = state.generated_password
    current_app.password_reset_state_db.save(state)

    view_context['csrf_token'] = session.new_csrf_token()
    return render_template('reset_password_new_password.jinja2', view_context=view_context)
示例#12
0
def reset_password():
    current_app.logger.info('Password reset: reset_password {}'.format(request.method))
    view_context = {
        'heading': _('Reset password'),
        'text': _('Enter an email address registered to your account below'),
        'action': url_for('reset_password.reset_password'),
        'csrf_token': session.get_csrf_token(),
        'form_post_success': False,
        'form_post_success_msg': _('Reset password message sent. Check your email to continue.'),
        'errors': [],
    }
    if request.method == 'POST':
        form = ResetPasswordEmailSchema().load(request.form)
        if not form.errors and session.get_csrf_token() == form.data['csrf']:
            current_app.logger.info('Trying to send password reset email to {}'.format(form.data['email']))
            send_password_reset_mail(form.data['email'])
            view_context['form_post_success'] = True
            view_context['text'] = ''
        view_context['errors'] = form.errors
    view_context['csrf_token'] = session.new_csrf_token()
    return render_template('reset_password.jinja2', view_context=view_context)
示例#13
0
def new_password(state):
    current_app.logger.info('Password reset: new_password {}'.format(
        request.method))
    view_context = {
        'heading':
        _('New password'),
        'text':
        _('''
            Please choose a new password for your eduID account. A strong password has been generated for you.
            You can accept the generated password by clicking "Change password" or you can opt to choose your
            own password by clicking "Custom Password".
        '''),
        'action':
        url_for('reset_password.new_password',
                email_code=state.email_code.code),
        'csrf_token':
        session.get_csrf_token(),
        'active_pane':
        'generated',
        'zxcvbn_terms':
        json.dumps(get_zxcvbn_terms(state.eppn)),
        'errors': [],
    }
    if request.method == 'POST':
        min_entropy = current_app.config.password_entropy
        try:
            form = ResetPasswordNewPasswordSchema(
                zxcvbn_terms=view_context['zxcvbn_terms'],
                min_entropy=int(min_entropy)).load(request.form)
            current_app.logger.debug(form)
        except ValidationError as e:
            current_app.logger.error(e)
            view_context['errors'] = e.messages
            view_context['active_pane'] = 'custom'
        else:
            if session.get_csrf_token() == form['csrf']:
                if form.get('use_generated_password'):
                    password = state.generated_password
                    current_app.logger.info('Generated password used')
                    current_app.stats.count(
                        name='reset_password_generated_password_used')
                else:
                    password = form.get('custom_password')
                    current_app.logger.info('Custom password used')
                    current_app.stats.count(
                        name='reset_password_custom_password_used')
                current_app.logger.info(
                    'Resetting password for user {}'.format(state.eppn))
                reset_user_password(state, password)
                current_app.logger.info(
                    'Password reset done removing state for user {}'.format(
                        state.eppn))
                current_app.password_reset_state_db.remove_state(state)
                view_context['form_post_success'] = True
                view_context['login_url'] = current_app.config.eduid_site_url
                return render_template('reset_password_new_password.jinja2',
                                       view_context=view_context)

    # Generate a random good password
    # TODO: Hash the password using VCCSPasswordFactor before saving it to db
    state.generated_password = generate_suggested_password()
    view_context['generated_password'] = state.generated_password
    current_app.password_reset_state_db.save(state)

    view_context['csrf_token'] = session.new_csrf_token()
    return render_template('reset_password_new_password.jinja2',
                           view_context=view_context)
示例#14
0
def extra_security_phone_number(state):
    current_app.logger.info('Password reset: verify_phone_number {}'.format(
        request.method))
    view_context = {
        'heading':
        _('Verify phone number'),
        'text':
        _('Enter the code you received via SMS'),
        'action':
        url_for('reset_password.extra_security_phone_number',
                email_code=state.email_code.code),
        'form_post_fail_msg':
        None,
        'retry_url':
        url_for('reset_password.choose_extra_security',
                email_code=state.email_code.code),
        'retry_url_txt':
        _('Resend code or try another way'),
        'csrf_token':
        session.get_csrf_token(),
        'errors': [],
    }
    if request.method == 'POST':
        try:
            form = ResetPasswordVerifyPhoneNumberSchema().load(request.form)
        except ValidationError as e:
            current_app.logger.error(e)
            view_context['errors'] = e.messages
        else:
            if session.get_csrf_token() == form['csrf']:
                current_app.logger.info('Trying to verify phone code')

                phone_code = form.get('phone_code', '')

                if phone_code == state.phone_code.code:
                    if not verify_phone_number(state):
                        current_app.logger.info(
                            'Could not validated phone code for {}'.format(
                                state.eppn))
                        view_context = {
                            'heading':
                            _('Temporary technical problem'),
                            'text':
                            _('Please try again.'),
                            'retry_url':
                            url_for('reset_password.choose_extra_security',
                                    email_code=state.email_code.code),
                            'retry_url_txt':
                            _('Try again'),
                        }
                        return render_template('error.jinja2',
                                               view_context=view_context)
                    current_app.logger.info(
                        'Phone code verified redirecting user to set password view'
                    )
                    current_app.stats.count(
                        name='reset_password_extra_security_phone_success')
                    return redirect(
                        url_for('reset_password.new_password',
                                email_code=state.email_code.code))
                view_context['form_post_fail_msg'] = _(
                    'Invalid code. Please try again.')
    view_context['csrf_token'] = session.new_csrf_token()
    return render_template('reset_password_verify_phone.jinja2',
                           view_context=view_context)
示例#15
0
def choose_extra_security(state):
    current_app.logger.info('Password reset: choose_extra_security {}'.format(
        request.method))
    view_context = {
        'heading':
        _('Extra security'),
        'text':
        _('Choose an option to enhance the security'),
        'action':
        url_for('reset_password.choose_extra_security',
                email_code=state.email_code.code),
        'csrf_token':
        session.get_csrf_token(),
        'form_post_success':
        False,
        'errors': [],
    }

    # Check that the email code has been validated
    if not state.email_code.is_verified:
        current_app.logger.info(
            'User {} has not verified their email address'.format(state.eppn))
        view_context = {
            'heading':
            _('Email address not validated'),
            'text':
            _('Please use the password reset link that you have in your email.'
              ),
        }
        return render_template('error.jinja2', view_context=view_context)

    if request.method == 'POST':
        try:
            form = ResetPasswordExtraSecuritySchema().load(request.form)
        except ValidationError as e:
            current_app.logger.error(e)
            view_context['errors'] = e.messages
        else:
            if session.get_csrf_token() == form['csrf']:
                if form.get('no_extra_security'):
                    current_app.logger.info(
                        'Redirecting user to reset password with NO extra security'
                    )
                    current_app.stats.count(
                        name='reset_password_no_extra_security')
                    return redirect(
                        url_for('reset_password.new_password',
                                email_code=state.email_code.code))
                if form.get('phone_number_index'):
                    phone_number_index = int(form.get('phone_number_index'))
                    phone_number = state.extra_security['phone_numbers'][
                        phone_number_index]
                    current_app.logger.info(
                        'Trying to send password reset sms to user {}'.format(
                            state.eppn))
                    try:
                        send_verify_phone_code(state, phone_number)
                    except MsgTaskFailed as e:
                        current_app.logger.error(
                            'Sending sms failed: {}'.format(e))
                        view_context = {
                            'heading':
                            _('Temporary technical problem'),
                            'text':
                            _('Please try again.'),
                            'retry_url':
                            url_for('reset_password.choose_extra_security',
                                    email_code=state.email_code.code),
                            'retry_url_txt':
                            _('Try again'),
                        }
                        return render_template('error.jinja2',
                                               view_context=view_context)
                    current_app.logger.info(
                        'Redirecting user to verify phone number view')
                    current_app.stats.count(
                        name='reset_password_extra_security_phone')
                    return redirect(
                        url_for('reset_password.extra_security_phone_number',
                                email_code=state.email_code.code))

    view_context['csrf_token'] = session.new_csrf_token()

    try:
        alternatives = get_extra_security_alternatives(state.eppn)
        state.extra_security = alternatives
        current_app.password_reset_state_db.save(state)
        view_context['security_alternatives'] = mask_alternatives(alternatives)
    except DocumentDoesNotExist:
        current_app.logger.error('User {} not found'.format(state.eppn))
        view_context = {
            'heading': _('Something went wrong'),
            'text': _('Please restart the password reset procedure.'),
            'retry_url': url_for('reset_password.reset_password'),
            'retry_url_txt': _('Reset your password'),
        }
        return render_template('error.jinja2', view_context=view_context)
    if not alternatives:
        # The user has no options for extra security, redirect to setting a new password
        return redirect(
            url_for('reset_password.new_password',
                    email_code=state.email_code.code))
    return render_template('reset_password_extra_security.jinja2',
                           view_context=view_context)