diff --git a/docs/index.rst b/docs/index.rst
index 0991e12..970e134 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -34,13 +34,13 @@ your Flask application. They include:
Many of these features are made possible by integrating various Flask extensions
and libraries. They include:
-1. Flask-Login
-2. Flask-Mail
-3. Flask-Principal
-4. Flask-Script
-5. Flask-WTF
-6. itsdangerous
-7. passlib
+1. `Flask-Login `_
+2. `Flask-Mail `_
+3. `Flask-Principal `_
+4. `Flask-Script `_
+5. `Flask-WTF `_
+6. `itsdangerous `_
+7. `passlib `_
Additionally, it assumes you'll be using a common library for your database
connections and model definitions. Flask-Security thus supports SQLAlchemy and
diff --git a/example/security.py b/example/security.py
deleted file mode 100644
index fe684d8..0000000
--- a/example/security.py
+++ /dev/null
@@ -1,316 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- Flask-Security
- ~~~~~~~~~~~~~~
-
- This module was generated by Flask-Security to give developers greater
- control over the various security mechanisms. For more information about
- using this feature see:
-
- TODO: Documentation URL
-"""
-
-
-from datetime import datetime
-
-from flask import current_app as app, redirect, request, session, \
- render_template, jsonify, Blueprint
-from flask.ext.login import login_user, logout_user
-from flask.ext.principal import Identity, AnonymousIdentity, identity_changed
-from werkzeug.datastructures import MultiDict
-from werkzeug.local import LocalProxy
-
-from flask_security.confirmable import confirm_by_token, reset_confirmation_token
-from flask_security.decorators import login_required
-from flask_security.exceptions import ConfirmationError, BadCredentialsError, \
- ResetPasswordError
-from flask_security.forms import LoginForm, RegisterForm, ForgotPasswordForm, \
- ResetPasswordForm, ResendConfirmationForm
-from flask_security.recoverable import reset_by_token, \
- reset_password_reset_token
-from flask_security.signals import user_registered
-from flask_security.tokens import generate_authentication_token
-from flask_security.utils import get_url, get_post_login_redirect, do_flash, \
- get_remember_token, get_message, config_value
-
-
-# Convenient references
-_security = LocalProxy(lambda: app.security)
-
-_datastore = LocalProxy(lambda: app.security.datastore)
-
-_logger = LocalProxy(lambda: app.logger)
-
-
-def _do_login(user, remember=True):
- """Performs the login and sends the appropriate signal."""
-
- if not login_user(user, remember):
- return False
-
- if user.authentication_token is None:
- user.authentication_token = generate_authentication_token(user)
-
- if remember:
- user.remember_token = get_remember_token(user.email, user.password)
-
- if _security.trackable:
- old_current, new_current = user.current_login_at, datetime.utcnow()
- user.last_login_at = old_current or new_current
- user.current_login_at = new_current
-
- old_current, new_current = user.current_login_ip, request.remote_addr
- user.last_login_ip = old_current or new_current
- user.current_login_ip = new_current
-
- user.login_count = user.login_count + 1 if user.login_count else 0
-
- _datastore._save_model(user)
-
- identity_changed.send(app._get_current_object(),
- identity=Identity(user.id))
-
- _logger.debug('User %s logged in' % user)
- return True
-
-
-def _json_auth_ok(user):
- return jsonify({
- "meta": {
- "code": 200
- },
- "response": {
- "user": {
- "id": str(user.id),
- "authentication_token": user.authentication_token
- }
- }
- })
-
-
-def _json_auth_error(msg):
- resp = jsonify({
- "meta": {
- "code": 400
- },
- "response": {
- "error": msg
- }
- })
- resp.status_code = 400
- return resp
-
-
-def authenticate():
- """View function which handles an authentication request."""
-
- form = LoginForm(MultiDict(request.json) if request.json else request.form)
-
- try:
- user = _security.auth_provider.authenticate(form)
-
- if _do_login(user, remember=form.remember.data):
- if request.json:
- return _json_auth_ok(user)
-
- return redirect(get_post_login_redirect())
-
- raise BadCredentialsError('Account is disabled')
-
- except BadCredentialsError, e:
- msg = str(e)
-
- _logger.debug('Unsuccessful authentication attempt: %s' % msg)
-
- if request.json:
- return _json_auth_error(msg)
-
- do_flash(msg, 'error')
-
- return redirect(request.referrer or _security.login_manager.login_view)
-
-
-def logout():
- """View function which handles a logout request."""
-
- for key in ('identity.name', 'identity.auth_type'):
- session.pop(key, None)
-
- identity_changed.send(app._get_current_object(),
- identity=AnonymousIdentity())
-
- logout_user()
- _logger.debug('User logged out')
-
- return redirect(request.args.get('next', None) or
- _security.post_logout_view)
-
-
-def register_user():
- """View function which handles a registration request."""
-
- form = RegisterForm(csrf_enabled=not app.testing)
-
- if form.validate_on_submit():
- # Create user
- u = _datastore.create_user(**form.to_dict())
-
- # Send confirmation instructions if necessary
- t = reset_confirmation_token(u) if _security.confirmable else None
-
- data = dict(user=u, confirm_token=t)
- user_registered.send(data, app=app._get_current_object())
-
- _logger.debug('User %s registered' % u)
-
- # Login the user if allowed
- if not _security.confirmable or _security.login_without_confirmation:
- _do_login(u)
-
- return redirect(_security.post_register_view or
- _security.post_login_view)
-
- return render_template('security/registrations/new.html',
- register_user_form=form)
-
-
-def send_confirmation():
- form = ResendConfirmationForm()
-
- if form.validate_on_submit():
- user = _datastore.find_user(email=form.email.data)
-
- reset_confirmation_token(user)
-
- _logger.debug('%s request confirmation instructions' % user)
-
- msg, cat = get_message('CONFIRMATION_REQUEST', email=user.email)
-
- do_flash(msg, cat)
-
- else:
- for key, value in form.errors.items():
- do_flash(value[0], 'error')
-
- return render_template('security/confirmations/new.html',
- reset_confirmation_form=form)
-
-
-def confirm_account(token):
- """View function which handles a account confirmation request."""
- try:
- user = confirm_by_token(token)
- _logger.debug('%s confirmed their account' % user)
-
- except ConfirmationError, e:
- msg, cat = str(e), 'error'
-
- _logger.debug('Confirmation error: ' + msg)
-
- if e.user:
- reset_confirmation_token(e.user)
-
- msg, cat = get_message('CONFIRMATION_EXPIRED',
- within=_security.confirm_email_within,
- email=e.user.email)
-
- do_flash(msg, cat)
-
- return redirect(get_url(_security.confirm_error_view))
-
- do_flash(get_message('ACCOUNT_CONFIRMED'))
-
- return redirect(_security.post_confirm_view or _security.post_login_view)
-
-
-def forgot_password():
- """View function that handles a forgotten password request."""
-
- form = ForgotPasswordForm(csrf_enabled=not app.testing)
-
- if form.validate_on_submit():
- user = _datastore.find_user(**form.to_dict())
-
- reset_password_reset_token(user)
-
- _logger.debug('%s requested to reset their password' % user)
-
- msg, cat = get_message('PASSWORD_RESET_REQUEST', email=user.email)
-
- do_flash(msg, cat)
-
- return redirect(_security.post_forgot_view)
-
- else:
- _logger.debug('A reset password request was made for %s but '
- 'that email does not exist.' % form.email.data)
-
- for key, value in form.errors.items():
- do_flash(value[0], 'error')
-
- return render_template('security/passwords/new.html',
- forgot_password_form=form)
-
-
-def reset_password(token):
- """View function that handles a reset password request."""
-
- form = ResetPasswordForm(csrf_enabled=not app.testing)
-
- if form.validate_on_submit():
- try:
- user = reset_by_token(token=token, **form.to_dict())
- _logger.debug('%s reset their password' % user)
-
- except ResetPasswordError, e:
- msg, cat = str(e), 'error'
-
- _logger.debug('Password reset error: ' + msg)
-
- if e.user:
- reset_password_reset_token(e.user)
-
- msg, cat = get_message('PASSWORD_RESET_EXPIRED',
- within=_security.reset_password_within,
- email=e.user.email)
-
- do_flash(msg, cat)
-
- return render_template('security/passwords/edit.html',
- reset_password_form=form,
- password_reset_token=token)
-
-
-def create_blueprint(app, name, import_name, **kwargs):
- bp = Blueprint(name, import_name, **kwargs)
-
- bp.route(config_value('AUTH_URL', app=app),
- methods=['POST'],
- endpoint='authenticate')(authenticate)
-
- bp.route(config_value('LOGOUT_URL', app=app),
- endpoint='logout')(login_required(logout))
-
- if config_value('REGISTERABLE', app=app):
- bp.route(config_value('REGISTER_URL', app=app),
- methods=['GET', 'POST'],
- endpoint='register')(register_user)
-
- if config_value('RECOVERABLE', app=app):
- bp.route(config_value('RESET_URL', app=app),
- methods=['GET', 'POST'],
- endpoint='forgot_password')(forgot_password)
- bp.route(config_value('RESET_URL', app=app) + '/',
- methods=['GET', 'POST'],
- endpoint='reset_password')(reset_password)
-
- if config_value('CONFIRMABLE', app=app):
- bp.route(config_value('CONFIRM_URL', app=app),
- methods=['GET', 'POST'],
- endpoint='send_confirmation')(send_confirmation)
- bp.route(config_value('CONFIRM_URL', app=app) + '/',
- methods=['GET', 'POST'],
- endpoint='confirm_account')(confirm_account)
-
- return bp
diff --git a/flask_security/__init__.py b/flask_security/__init__.py
index cca9f3e..ceedf93 100644
--- a/flask_security/__init__.py
+++ b/flask_security/__init__.py
@@ -10,8 +10,6 @@
:license: MIT, see LICENSE for more details.
"""
-from flask.ext.login import login_user, logout_user
-
from .core import Security, RoleMixin, UserMixin, AnonymousUser, \
AuthenticationProvider, current_user
from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore
@@ -22,3 +20,4 @@ from .forms import ForgotPasswordForm, LoginForm, RegisterForm, \
from .signals import confirm_instructions_sent, password_reset, \
password_reset_requested, reset_instructions_sent, user_confirmed, \
user_registered
+from .utils import login_user, logout_user
diff --git a/flask_security/tokens.py b/flask_security/tokens.py
deleted file mode 100644
index f896469..0000000
--- a/flask_security/tokens.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- flask.ext.security.tokens
- ~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Flask-Security tokens module
-
- :copyright: (c) 2012 by Matt Wright.
- :license: MIT, see LICENSE for more details.
-"""
-
-from flask import current_app as app
-from werkzeug.local import LocalProxy
-
-from .utils import md5
-
-
-# Convenient references
-_security = LocalProxy(lambda: app.extensions['security'])
-
-_datastore = LocalProxy(lambda: _security.datastore)
-
-
-def generate_authentication_token(user):
- """Generates a unique authentication token for the specified user.
-
- :param user: The user to work with
- """
- data = [str(user.id), md5(user.email)]
- return _security.token_auth_serializer.dumps(data)
-
-
-def reset_authentication_token(user):
- """Resets a user's authentication token and returns the new token value.
-
- :param user: The user to work with
- """
- token = generate_authentication_token(user)
- user.authentication_token = token
- _datastore._save_model(user)
- return token
-
-
-def ensure_authentication_token(user):
- """Ensures that a user has an authentication token. If the user has an
- authentication token already, nothing is performed.
-
- :param user: The user to work with
- """
- if not user.authentication_token:
- return reset_authentication_token(user)
diff --git a/flask_security/utils.py b/flask_security/utils.py
index 24a54b9..f8a095e 100644
--- a/flask_security/utils.py
+++ b/flask_security/utils.py
@@ -17,7 +17,9 @@ from contextlib import contextmanager
from datetime import datetime, timedelta
from flask import url_for, flash, current_app, request, session, render_template
-from flask.ext.login import make_secure_token
+from flask.ext.login import make_secure_token, login_user as _login_user, \
+ logout_user as _logout_user
+from flask.ext.principal import Identity, AnonymousIdentity, identity_changed
from werkzeug.local import LocalProxy
from .signals import user_registered, password_reset_requested
@@ -26,8 +28,56 @@ from .signals import user_registered, password_reset_requested
# Convenient references
_security = LocalProxy(lambda: current_app.extensions['security'])
+_datastore = LocalProxy(lambda: _security.datastore)
+
_pwd_context = LocalProxy(lambda: _security.pwd_context)
+_logger = LocalProxy(lambda: current_app.logger)
+
+
+def login_user(user, remember=True):
+ """Performs the login and sends the appropriate signal."""
+
+ if not _login_user(user, remember):
+ return False
+
+ if user.authentication_token is None:
+ from .tokens import generate_authentication_token
+
+ user.authentication_token = generate_authentication_token(user)
+
+ if remember:
+ user.remember_token = get_remember_token(user.email, user.password)
+
+ if _security.trackable:
+ old_current, new_current = user.current_login_at, datetime.utcnow()
+ user.last_login_at = old_current or new_current
+ user.current_login_at = new_current
+
+ old_current, new_current = user.current_login_ip, request.remote_addr
+ user.last_login_ip = old_current or new_current
+ user.current_login_ip = new_current
+
+ user.login_count = user.login_count + 1 if user.login_count else 0
+
+ _datastore._save_model(user)
+
+ identity_changed.send(current_app._get_current_object(),
+ identity=Identity(user.id))
+
+ _logger.debug('User %s logged in' % user)
+ return True
+
+
+def logout_user():
+ for key in ('identity.name', 'identity.auth_type'):
+ session.pop(key, None)
+
+ identity_changed.send(current_app._get_current_object(),
+ identity=AnonymousIdentity())
+
+ _logout_user()
+
def get_hmac(msg, salt=None, digestmod=None):
digestmod = digestmod or hashlib.sha512
@@ -44,6 +94,15 @@ def encrypt_password(password, salt=None, use_hmac=False):
return _pwd_context.encrypt(hmac_value)
+def generate_authentication_token(user):
+ """Generates a unique authentication token for the specified user.
+
+ :param user: The user to work with
+ """
+ data = [str(user.id), md5(user.email)]
+ return _security.token_auth_serializer.dumps(data)
+
+
def md5(data):
return hashlib.md5(data).hexdigest()
diff --git a/flask_security/views.py b/flask_security/views.py
index f1d534c..a0eb415 100644
--- a/flask_security/views.py
+++ b/flask_security/views.py
@@ -9,12 +9,9 @@
:license: MIT, see LICENSE for more details.
"""
-from datetime import datetime
-
from flask import current_app as app, redirect, request, session, \
render_template, jsonify, Blueprint
-from flask.ext.login import login_user, logout_user
-from flask.ext.principal import Identity, AnonymousIdentity, identity_changed
+from flask.ext.principal import AnonymousIdentity, identity_changed
from werkzeug.datastructures import MultiDict
from werkzeug.local import LocalProxy
@@ -27,9 +24,8 @@ from flask_security.forms import LoginForm, RegisterForm, ForgotPasswordForm, \
from flask_security.recoverable import reset_by_token, \
reset_password_reset_token
from flask_security.signals import user_registered
-from flask_security.tokens import generate_authentication_token
from flask_security.utils import get_url, get_post_login_redirect, do_flash, \
- get_remember_token, get_message, config_value
+ get_message, config_value, login_user, logout_user
# Convenient references
@@ -40,38 +36,6 @@ _datastore = LocalProxy(lambda: _security.datastore)
_logger = LocalProxy(lambda: app.logger)
-def _do_login(user, remember=True):
- """Performs the login and sends the appropriate signal."""
-
- if not login_user(user, remember):
- return False
-
- if user.authentication_token is None:
- user.authentication_token = generate_authentication_token(user)
-
- if remember:
- user.remember_token = get_remember_token(user.email, user.password)
-
- if _security.trackable:
- old_current, new_current = user.current_login_at, datetime.utcnow()
- user.last_login_at = old_current or new_current
- user.current_login_at = new_current
-
- old_current, new_current = user.current_login_ip, request.remote_addr
- user.last_login_ip = old_current or new_current
- user.current_login_ip = new_current
-
- user.login_count = user.login_count + 1 if user.login_count else 0
-
- _datastore._save_model(user)
-
- identity_changed.send(app._get_current_object(),
- identity=Identity(user.id))
-
- _logger.debug('User %s logged in' % user)
- return True
-
-
def _json_auth_ok(user):
return jsonify({
"meta": {
@@ -107,7 +71,7 @@ def authenticate():
try:
user = _security.auth_provider.authenticate(form)
- if _do_login(user, remember=form.remember.data):
+ if login_user(user, remember=form.remember.data):
if request.json:
return _json_auth_ok(user)
@@ -131,13 +95,8 @@ def authenticate():
def logout():
"""View function which handles a logout request."""
- for key in ('identity.name', 'identity.auth_type'):
- session.pop(key, None)
-
- identity_changed.send(app._get_current_object(),
- identity=AnonymousIdentity())
-
logout_user()
+
_logger.debug('User logged out')
return redirect(request.args.get('next', None) or
@@ -163,7 +122,7 @@ def register_user():
# Login the user if allowed
if not _security.confirmable or _security.login_without_confirmation:
- _do_login(u)
+ login_user(u)
return redirect(_security.post_register_view or
_security.post_login_view)