388 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			388 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
# -*- coding: utf-8 -*-
 | 
						|
"""
 | 
						|
    flask_security.views
 | 
						|
    ~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
    Flask-Security views module
 | 
						|
 | 
						|
    :copyright: (c) 2012 by Matt Wright.
 | 
						|
    :license: MIT, see LICENSE for more details.
 | 
						|
"""
 | 
						|
 | 
						|
from flask import Blueprint, after_this_request, current_app, jsonify, \
 | 
						|
    redirect, request
 | 
						|
from flask_login import current_user
 | 
						|
from werkzeug.datastructures import MultiDict
 | 
						|
from werkzeug.local import LocalProxy
 | 
						|
 | 
						|
from .changeable import change_user_password
 | 
						|
from .confirmable import confirm_email_token_status, confirm_user, \
 | 
						|
    send_confirmation_instructions
 | 
						|
from .decorators import anonymous_user_required, login_required
 | 
						|
from .passwordless import login_token_status, send_login_instructions
 | 
						|
from .recoverable import reset_password_token_status, \
 | 
						|
    send_reset_password_instructions, update_password
 | 
						|
from .registerable import register_user
 | 
						|
from .utils import url_for_security as url_for
 | 
						|
from .utils import config_value, do_flash, get_message, \
 | 
						|
    get_post_login_redirect, get_post_logout_redirect, \
 | 
						|
    get_post_register_redirect, get_url, login_user, logout_user, \
 | 
						|
    slash_url_suffix
 | 
						|
 | 
						|
# Convenient references
 | 
						|
_security = LocalProxy(lambda: current_app.extensions['security'])
 | 
						|
 | 
						|
_datastore = LocalProxy(lambda: _security.datastore)
 | 
						|
 | 
						|
 | 
						|
def _render_json(form, include_user=True, include_auth_token=False):
 | 
						|
    has_errors = len(form.errors) > 0
 | 
						|
 | 
						|
    if has_errors:
 | 
						|
        code = 400
 | 
						|
        response = dict(errors=form.errors)
 | 
						|
    else:
 | 
						|
        code = 200
 | 
						|
        response = dict()
 | 
						|
        if include_user:
 | 
						|
            response['user'] = form.user.get_security_payload()
 | 
						|
 | 
						|
        if include_auth_token:
 | 
						|
            token = form.user.get_auth_token()
 | 
						|
            response['user']['authentication_token'] = token
 | 
						|
 | 
						|
    return jsonify(dict(meta=dict(code=code), response=response)), code
 | 
						|
 | 
						|
 | 
						|
def _commit(response=None):
 | 
						|
    _datastore.commit()
 | 
						|
    return response
 | 
						|
 | 
						|
 | 
						|
def _ctx(endpoint):
 | 
						|
    return _security._run_ctx_processor(endpoint)
 | 
						|
 | 
						|
 | 
						|
@anonymous_user_required
 | 
						|
def login():
 | 
						|
    """View function for login view"""
 | 
						|
 | 
						|
    form_class = _security.login_form
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        form = form_class(MultiDict(request.get_json()))
 | 
						|
    else:
 | 
						|
        form = form_class(request.form)
 | 
						|
 | 
						|
    if form.validate_on_submit():
 | 
						|
        login_user(form.user, remember=form.remember.data)
 | 
						|
        after_this_request(_commit)
 | 
						|
 | 
						|
        if not request.is_json:
 | 
						|
            return redirect(get_post_login_redirect(form.next.data))
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        return _render_json(form, include_auth_token=True)
 | 
						|
 | 
						|
    return _security.render_template(config_value('LOGIN_USER_TEMPLATE'),
 | 
						|
                                     login_user_form=form,
 | 
						|
                                     **_ctx('login'))
 | 
						|
 | 
						|
 | 
						|
def logout():
 | 
						|
    """View function which handles a logout request."""
 | 
						|
 | 
						|
    if current_user.is_authenticated:
 | 
						|
        logout_user()
 | 
						|
 | 
						|
    return redirect(get_post_logout_redirect())
 | 
						|
 | 
						|
 | 
						|
@anonymous_user_required
 | 
						|
def register():
 | 
						|
    """View function which handles a registration request."""
 | 
						|
 | 
						|
    if _security.confirmable or request.is_json:
 | 
						|
        form_class = _security.confirm_register_form
 | 
						|
    else:
 | 
						|
        form_class = _security.register_form
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        form_data = MultiDict(request.get_json())
 | 
						|
    else:
 | 
						|
        form_data = request.form
 | 
						|
 | 
						|
    form = form_class(form_data)
 | 
						|
 | 
						|
    if form.validate_on_submit():
 | 
						|
        user = register_user(**form.to_dict())
 | 
						|
        form.user = user
 | 
						|
 | 
						|
        if not _security.confirmable or _security.login_without_confirmation:
 | 
						|
            after_this_request(_commit)
 | 
						|
            login_user(user)
 | 
						|
 | 
						|
        if not request.is_json:
 | 
						|
            if 'next' in form:
 | 
						|
                redirect_url = get_post_register_redirect(form.next.data)
 | 
						|
            else:
 | 
						|
                redirect_url = get_post_register_redirect()
 | 
						|
 | 
						|
            return redirect(redirect_url)
 | 
						|
        return _render_json(form, include_auth_token=True)
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        return _render_json(form)
 | 
						|
 | 
						|
    return _security.render_template(config_value('REGISTER_USER_TEMPLATE'),
 | 
						|
                                     register_user_form=form,
 | 
						|
                                     **_ctx('register'))
 | 
						|
 | 
						|
 | 
						|
def send_login():
 | 
						|
    """View function that sends login instructions for passwordless login"""
 | 
						|
 | 
						|
    form_class = _security.passwordless_login_form
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        form = form_class(MultiDict(request.get_json()))
 | 
						|
    else:
 | 
						|
        form = form_class()
 | 
						|
 | 
						|
    if form.validate_on_submit():
 | 
						|
        send_login_instructions(form.user)
 | 
						|
        if not request.is_json:
 | 
						|
            do_flash(*get_message('LOGIN_EMAIL_SENT', email=form.user.email))
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        return _render_json(form)
 | 
						|
 | 
						|
    return _security.render_template(config_value('SEND_LOGIN_TEMPLATE'),
 | 
						|
                                     send_login_form=form,
 | 
						|
                                     **_ctx('send_login'))
 | 
						|
 | 
						|
 | 
						|
@anonymous_user_required
 | 
						|
def token_login(token):
 | 
						|
    """View function that handles passwordless login via a token"""
 | 
						|
 | 
						|
    expired, invalid, user = login_token_status(token)
 | 
						|
 | 
						|
    if invalid:
 | 
						|
        do_flash(*get_message('INVALID_LOGIN_TOKEN'))
 | 
						|
    if expired:
 | 
						|
        send_login_instructions(user)
 | 
						|
        do_flash(*get_message('LOGIN_EXPIRED', email=user.email,
 | 
						|
                              within=_security.login_within))
 | 
						|
    if invalid or expired:
 | 
						|
        return redirect(url_for('login'))
 | 
						|
 | 
						|
    login_user(user)
 | 
						|
    after_this_request(_commit)
 | 
						|
    do_flash(*get_message('PASSWORDLESS_LOGIN_SUCCESSFUL'))
 | 
						|
 | 
						|
    return redirect(get_post_login_redirect())
 | 
						|
 | 
						|
 | 
						|
def send_confirmation():
 | 
						|
    """View function which sends confirmation instructions."""
 | 
						|
 | 
						|
    form_class = _security.send_confirmation_form
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        form = form_class(MultiDict(request.get_json()))
 | 
						|
    else:
 | 
						|
        form = form_class()
 | 
						|
 | 
						|
    if form.validate_on_submit():
 | 
						|
        send_confirmation_instructions(form.user)
 | 
						|
        if not request.is_json:
 | 
						|
            do_flash(*get_message('CONFIRMATION_REQUEST',
 | 
						|
                     email=form.user.email))
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        return _render_json(form)
 | 
						|
 | 
						|
    return _security.render_template(
 | 
						|
        config_value('SEND_CONFIRMATION_TEMPLATE'),
 | 
						|
        send_confirmation_form=form,
 | 
						|
        **_ctx('send_confirmation')
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
def confirm_email(token):
 | 
						|
    """View function which handles a email confirmation request."""
 | 
						|
 | 
						|
    expired, invalid, user = confirm_email_token_status(token)
 | 
						|
 | 
						|
    if not user or invalid:
 | 
						|
        invalid = True
 | 
						|
        do_flash(*get_message('INVALID_CONFIRMATION_TOKEN'))
 | 
						|
 | 
						|
    already_confirmed = user is not None and user.confirmed_at is not None
 | 
						|
 | 
						|
    if expired and not already_confirmed:
 | 
						|
        send_confirmation_instructions(user)
 | 
						|
        do_flash(*get_message('CONFIRMATION_EXPIRED', email=user.email,
 | 
						|
                              within=_security.confirm_email_within))
 | 
						|
    if invalid or (expired and not already_confirmed):
 | 
						|
        return redirect(get_url(_security.confirm_error_view) or
 | 
						|
                        url_for('send_confirmation'))
 | 
						|
 | 
						|
    if user != current_user:
 | 
						|
        logout_user()
 | 
						|
        login_user(user)
 | 
						|
 | 
						|
    if confirm_user(user):
 | 
						|
        after_this_request(_commit)
 | 
						|
        msg = 'EMAIL_CONFIRMED'
 | 
						|
    else:
 | 
						|
        msg = 'ALREADY_CONFIRMED'
 | 
						|
 | 
						|
    do_flash(*get_message(msg))
 | 
						|
 | 
						|
    return redirect(get_url(_security.post_confirm_view) or
 | 
						|
                    get_url(_security.post_login_view))
 | 
						|
 | 
						|
 | 
						|
@anonymous_user_required
 | 
						|
def forgot_password():
 | 
						|
    """View function that handles a forgotten password request."""
 | 
						|
 | 
						|
    form_class = _security.forgot_password_form
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        form = form_class(MultiDict(request.get_json()))
 | 
						|
    else:
 | 
						|
        form = form_class()
 | 
						|
 | 
						|
    if form.validate_on_submit():
 | 
						|
        send_reset_password_instructions(form.user)
 | 
						|
        if not request.is_json:
 | 
						|
            do_flash(*get_message('PASSWORD_RESET_REQUEST',
 | 
						|
                     email=form.user.email))
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        return _render_json(form, include_user=False)
 | 
						|
 | 
						|
    return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'),
 | 
						|
                                     forgot_password_form=form,
 | 
						|
                                     **_ctx('forgot_password'))
 | 
						|
 | 
						|
 | 
						|
@anonymous_user_required
 | 
						|
def reset_password(token):
 | 
						|
    """View function that handles a reset password request."""
 | 
						|
 | 
						|
    expired, invalid, user = reset_password_token_status(token)
 | 
						|
 | 
						|
    if invalid:
 | 
						|
        do_flash(*get_message('INVALID_RESET_PASSWORD_TOKEN'))
 | 
						|
    if expired:
 | 
						|
        send_reset_password_instructions(user)
 | 
						|
        do_flash(*get_message('PASSWORD_RESET_EXPIRED', email=user.email,
 | 
						|
                              within=_security.reset_password_within))
 | 
						|
    if invalid or expired:
 | 
						|
        return redirect(url_for('forgot_password'))
 | 
						|
 | 
						|
    form = _security.reset_password_form()
 | 
						|
 | 
						|
    if form.validate_on_submit():
 | 
						|
        after_this_request(_commit)
 | 
						|
        update_password(user, form.password.data)
 | 
						|
        do_flash(*get_message('PASSWORD_RESET'))
 | 
						|
        login_user(user)
 | 
						|
        return redirect(get_url(_security.post_reset_view) or
 | 
						|
                        get_url(_security.post_login_view))
 | 
						|
 | 
						|
    return _security.render_template(
 | 
						|
        config_value('RESET_PASSWORD_TEMPLATE'),
 | 
						|
        reset_password_form=form,
 | 
						|
        reset_password_token=token,
 | 
						|
        **_ctx('reset_password')
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
@login_required
 | 
						|
def change_password():
 | 
						|
    """View function which handles a change password request."""
 | 
						|
 | 
						|
    form_class = _security.change_password_form
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        form = form_class(MultiDict(request.get_json()))
 | 
						|
    else:
 | 
						|
        form = form_class()
 | 
						|
 | 
						|
    if form.validate_on_submit():
 | 
						|
        after_this_request(_commit)
 | 
						|
        change_user_password(current_user._get_current_object(),
 | 
						|
                             form.new_password.data)
 | 
						|
        if not request.is_json:
 | 
						|
            do_flash(*get_message('PASSWORD_CHANGE'))
 | 
						|
            return redirect(get_url(_security.post_change_view) or
 | 
						|
                            get_url(_security.post_login_view))
 | 
						|
 | 
						|
    if request.is_json:
 | 
						|
        form.user = current_user
 | 
						|
        return _render_json(form)
 | 
						|
 | 
						|
    return _security.render_template(
 | 
						|
        config_value('CHANGE_PASSWORD_TEMPLATE'),
 | 
						|
        change_password_form=form,
 | 
						|
        **_ctx('change_password')
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
def create_blueprint(state, import_name):
 | 
						|
    """Creates the security extension blueprint"""
 | 
						|
 | 
						|
    bp = Blueprint(state.blueprint_name, import_name,
 | 
						|
                   url_prefix=state.url_prefix,
 | 
						|
                   subdomain=state.subdomain,
 | 
						|
                   template_folder='templates')
 | 
						|
 | 
						|
    bp.route(state.logout_url, endpoint='logout')(logout)
 | 
						|
 | 
						|
    if state.passwordless:
 | 
						|
        bp.route(state.login_url,
 | 
						|
                 methods=['GET', 'POST'],
 | 
						|
                 endpoint='login')(send_login)
 | 
						|
        bp.route(state.login_url + slash_url_suffix(state.login_url,
 | 
						|
                                                    '<token>'),
 | 
						|
                 endpoint='token_login')(token_login)
 | 
						|
    else:
 | 
						|
        bp.route(state.login_url,
 | 
						|
                 methods=['GET', 'POST'],
 | 
						|
                 endpoint='login')(login)
 | 
						|
 | 
						|
    if state.registerable:
 | 
						|
        bp.route(state.register_url,
 | 
						|
                 methods=['GET', 'POST'],
 | 
						|
                 endpoint='register')(register)
 | 
						|
 | 
						|
    if state.recoverable:
 | 
						|
        bp.route(state.reset_url,
 | 
						|
                 methods=['GET', 'POST'],
 | 
						|
                 endpoint='forgot_password')(forgot_password)
 | 
						|
        bp.route(state.reset_url + slash_url_suffix(state.reset_url,
 | 
						|
                                                    '<token>'),
 | 
						|
                 methods=['GET', 'POST'],
 | 
						|
                 endpoint='reset_password')(reset_password)
 | 
						|
 | 
						|
    if state.changeable:
 | 
						|
        bp.route(state.change_url,
 | 
						|
                 methods=['GET', 'POST'],
 | 
						|
                 endpoint='change_password')(change_password)
 | 
						|
 | 
						|
    if state.confirmable:
 | 
						|
        bp.route(state.confirm_url,
 | 
						|
                 methods=['GET', 'POST'],
 | 
						|
                 endpoint='send_confirmation')(send_confirmation)
 | 
						|
        bp.route(state.confirm_url + slash_url_suffix(state.confirm_url,
 | 
						|
                                                      '<token>'),
 | 
						|
                 methods=['GET', 'POST'],
 | 
						|
                 endpoint='confirm_email')(confirm_email)
 | 
						|
 | 
						|
    return bp
 |