Added a bunch of code documentation

This commit is contained in:
Matt Wright
2012-03-12 17:07:21 -04:00
parent e8d41ad4b5
commit 65eac687b7
8 changed files with 244 additions and 41 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "docs/_themes"]
path = docs/_themes
url = git://github.com/mitsuhiko/flask-sphinx-themes.git
Submodule
+1
Submodule docs/_themes added at 0269f3d188
+98 -30
View File
@@ -1,15 +1,14 @@
# -*- coding: utf-8 -*-
"""
flask.ext.security
~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~
Flask-Security is a Flask extension module that aims to add quick and
simple security via Flask-Login and Flask-Principal.
Flask-Security is a Flask extension that aims to add quick and simple
security via Flask-Login, Flask-Principal, Flask-WTF, and passlib.
:copyright: (c) 2012 by Matt Wright.
:license: MIT, see LICENSE for more details.
"""
from __future__ import absolute_import
import sys
@@ -34,8 +33,11 @@ from passlib.context import CryptContext
from werkzeug.utils import import_string
from werkzeug.local import LocalProxy
#: User model
User = None
User, Role = None, None
#: Role model
Role = None
URL_PREFIX_KEY = 'SECURITY_URL_PREFIX'
AUTH_PROVIDER_KEY = 'SECURITY_AUTH_PROVIDER'
@@ -54,6 +56,7 @@ DEBUG_LOGOUT = 'User logged out, redirecting to: %s'
FLASH_INACTIVE = 'Inactive user'
FLASH_PERMISSIONS = 'You do not have permission to view this resource.'
#: Default Flask-Security configuration
default_config = {
URL_PREFIX_KEY: None,
PASSWORD_HASH_KEY: 'plaintext',
@@ -94,15 +97,15 @@ class UserIdNotFoundError(Exception):
"""
class UserDatastoreError(Exception):
"""Raise when a user datastore experiences an unexpected error
"""Raised when a user datastore experiences an unexpected error
"""
class UserCreationError(Exception):
"""Raise when an error occurs when creating a user
"""Raised when an error occurs when creating a user
"""
class RoleCreationError(Exception):
"""Raise when an error occurs when creating a role
"""Raised when an error occurs when creating a role
"""
@@ -118,11 +121,24 @@ login_manager = LocalProxy(lambda: current_app.login_manager)
#: Password encyption context
pwd_context = LocalProxy(lambda: current_app.pwd_context)
# User service
#: User datastore
user_datastore = LocalProxy(lambda: getattr(current_app,
current_app.config[USER_DATASTORE_KEY]))
def roles_required(*args):
"""View decorator which specifies that a user must have all the specified
roles. Example::
@app.route('/dashboard')
@roles_required('admin', 'editor')
def dashboard():
return 'Dashboard'
The current user must have both the `admin` role and `editor` role in order
to view the page.
:param args: The required roles.
"""
roles = args
perm = Permission(*[RoleNeed(role) for role in roles])
def wrapper(fn):
@@ -144,6 +160,19 @@ def roles_required(*args):
def roles_accepted(*args):
"""View decorator which specifies that a user must have at least one of the
specified roles. Example::
@app.route('/create_post')
@roles_accepted('editor', 'author')
def create_post():
return 'Create Post'
The current user must have either the `editor` role or `author` role in
order to view the page.
:param args: The possible roles.
"""
roles = args
perms = [Permission(RoleNeed(role)) for role in roles]
def wrapper(fn):
@@ -166,6 +195,7 @@ def roles_accepted(*args):
class RoleMixin(object):
"""Mixin for `Role` model definitions"""
def __eq__(self, other):
return self.name == other.name
@@ -177,10 +207,16 @@ class RoleMixin(object):
class UserMixin(BaseUserMixin):
"""Mixin for `User` model definitions"""
def is_active(self):
"""Returns `True` if the user is active."""
return self.active
def has_role(self, role):
"""Returns `True` if the user identifies with the specified role.
:param role: A role name or `Role` instance"""
if not isinstance(role, Role):
role = Role(name=role)
return role in self.roles
@@ -196,18 +232,25 @@ class AnonymousUser(AnonymousUserBase):
self.roles = [] # TODO: Make this immutable?
def has_role(self, *args):
"""Returns `False`"""
return False
class Security(object):
"""The :class:`Security` class initializes the Flask-Security extension.
:param app: The application.
:param datastore: An instance of a user datastore.
"""
def __init__(self, app=None, datastore=None):
self.init_app(app, datastore)
def init_app(self, app, datastore):
"""Initialize the application
:param app: An instance of an application
:param datastore: An instance of a datastore for your users
"""Initializes the Flask-Security extension for the specified
application and datastore implentation.
:param app: The application.
:param datastore: An instance of a user datastore.
"""
if app is None or datastore is None: return
@@ -220,9 +263,6 @@ class Security(object):
app.config.update(configured)
config = app.config
#config = default_config.copy()
#config.update(app.config.get(AUTH_CONFIG_KEY, {}))
#app.config[AUTH_CONFIG_KEY] = config
# setup the login manager extension
login_manager = LoginManager()
@@ -301,7 +341,7 @@ class Security(object):
class LoginForm(Form):
"""Default login form"""
"""The default login form"""
username = TextField("Username or Email",
validators=[Required(message="Username not provided")])
@@ -317,16 +357,27 @@ class LoginForm(Form):
class AuthenticationProvider(object):
"""Default authentication provider"""
"""The default authentication provider implementation.
:param login_form_class: The login form class to use when authenticating a
user
"""
def __init__(self, login_form_class=None):
self.login_form_class = login_form_class or LoginForm
def login_form(self, formdata=None):
"""Returns an instance of the login form with the provided form.
:param formdata: The incoming form data"""
return self.login_form_class(formdata)
def authenticate(self, form):
# first some basic validation
"""Processes an authentication request and returns a user instance if
authentication is successful.
:param form: An instance of a populated login form
"""
if not form.validate():
if form.username.errors:
raise BadCredentialsError(form.username.errors[0])
@@ -336,6 +387,13 @@ class AuthenticationProvider(object):
return self.do_authenticate(form.username.data, form.password.data)
def do_authenticate(self, user_identifier, password):
"""Returns the authenticated user if authentication is successfull. If
authentication fails an appropriate error is raised
:param user_identifier: The user's identifier, either an email address
or username
:param password: The user's unencrypted password
"""
try:
user = user_datastore.find_user(user_identifier)
except AttributeError, e:
@@ -355,11 +413,15 @@ class AuthenticationProvider(object):
raise BadCredentialsError("Password does not match")
def auth_error(self, msg):
"""Sends an error log message and raises an authentication error.
:param msg: An authentication error message"""
logger.error(msg)
raise AuthenticationError(msg)
def get_class_by_name(clazz):
"""Get a reference to a class by its string representation."""
parts = clazz.split('.')
module = ".".join(parts[:-1])
m = __import__( module )
@@ -368,6 +430,7 @@ def get_class_by_name(clazz):
return m
def get_class_from_config(key, config):
"""Get a reference to a class by its configuration key name."""
try:
return get_class_by_name(config[key])
except Exception, e:
@@ -375,22 +438,27 @@ def get_class_from_config(key, config):
"Could not get class '%s' for Auth setting '%s' >> %s" %
(config[key], key, e))
def get_url(value):
# try building the url or assume its a url already
try: return url_for(value)
except: return value
def get_url(endpoint_or_url):
"""Returns a URL if a valid endpoint is found. Otherwise, returns the
provided value."""
try:
return url_for(endpoint_or_url)
except:
return endpoint_or_url
def get_post_login_redirect():
"""Returns the URL to redirect to after a user logs in successfully"""
return (get_url(request.args.get('next')) or
get_url(request.form.get('next')) or
find_redirect(POST_LOGIN_KEY))
def find_redirect(key):
# Look in the session first, and if not there go to the config, and
# if its not there either just go to the root url
result = (get_url(session.get(key.lower(), None)) or
get_url(current_app.config[key] or None) or '/')
# Try and delete the session value if it was used
try: del session[key.lower()]
except: pass
"""Returns the URL to redirect to after a user logs in successfully"""
result = (get_url(session.pop(key.lower(), None)) or
get_url(current_app.config[key.upper()] or None) or '/')
try:
del session[key.lower()]
except:
pass
return result
+71 -4
View File
@@ -1,10 +1,34 @@
# -*- coding: utf-8 -*-
"""
flask.ext.security.datastore
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module contains an abstracted user datastore.
:copyright: (c) 2012 by Matt Wright.
:license: MIT, see LICENSE for more details.
"""
from datetime import datetime
from flask.ext import security
from flask.ext.security import UserCreationError, RoleCreationError, pwd_context
class UserDatastore(object):
"""Abstracted user datastore. Always extend this and implement
missing methods"""
"""Abstracted user datastore. Always extend this class and implement the
:attr:`get_models`, :attr:`_save_model`, :attr:`_do_with_id`,
:attr:`_do_find_user`, and :attr:`_do_find_role` methods.
:param db: An instance of a configured databse manager from a Flask
extension such as Flask-SQLAlchemy or Flask-MongoEngine"""
def __init__(self, db):
self.db = db
def get_models(self):
"""Returns configured `User` and `Role` models for the datastore
implementation"""
raise NotImplementedError(
"User datastore does not implement get_models method")
def _save_model(self, model, **kwargs):
raise NotImplementedError(
@@ -96,36 +120,79 @@ class UserDatastore(object):
return kwargs
def with_id(self, id):
"""Returns a user with the specified ID.
:param id: User ID"""
user = self._do_with_id(id)
if user: return user
raise security.UserIdNotFoundError()
def find_user(self, user):
"""Returns a user based on the specified identifier.
:param user: User identifier, usually a username or email address
"""
user = self._do_find_user(user)
if user: return user
raise security.UserNotFoundError()
def find_role(self, role):
"""Returns a role based on its name.
:param role: Role name
"""
role = self._do_find_role(role)
if role: return role
raise security.RoleNotFoundError()
def create_role(self, commit=True, **kwargs):
def create_role(self, **kwargs):
"""Creates and returns a new role.
:param name: Role name
:param description: Role description
"""
role = security.Role(**self._prepare_create_role_args(kwargs))
return self._save_model(role)
def create_user(self, commit=True, **kwargs):
def create_user(self, **kwargs):
"""Creates and returns a new user.
:param username: Username
:param email: Email address
:param password: Unencrypted password
:param active: The optional active state
"""
user = security.User(**self._prepare_create_user_args(kwargs))
return self._save_model(user)
def add_role_to_user(self, user, role):
"""Adds a role to a user if the user does not have it already. Returns
the modified user.
:param user: A User instance or a user identifier
:param role: A Role instance or a role name
"""
return self._save_model(self._do_add_role(user, role))
def remove_role_from_user(self, user, role, commit=True):
"""Removes a role from a user if the user has the role. Returns the
modified user.
:param user: A User instance or a user identifier
:param role: A Role instance or a role name
"""
return self._save_model(self._do_remove_role(user, role))
def deactivate_user(self, user):
"""Deactivates a user and returns the modified user.
:param user: A User instance or a user identifier
"""
return self._save_model(self._do_deactive_user(user))
def activate_user(self, user, commit=True):
"""Activates a user and returns the modified user.
:param user: A User instance or a user identifier
"""
return self._save_model(self._do_active_user(user))
+30 -3
View File
@@ -1,21 +1,48 @@
# -*- coding: utf-8 -*-
"""
flask.ext.security.datastore.mongoengine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module contains a Flask-Security MongoEngine datastore implementation
:copyright: (c) 2012 by Matt Wright.
:license: MIT, see LICENSE for more details.
"""
from flask.ext import security
from flask.ext.security import UserMixin, RoleMixin
from flask.ext.security.datastore import UserDatastore
class MongoEngineUserDatastore(UserDatastore):
"""MongoEngine datastore"""
"""A MongoEngine datastore implementation for Flask-Security. Example:
def __init__(self, db):
self.db = db
from flask import Flask
from flask.ext.mongoengine import MongoEngine
from flask.ext.security import Security
from flask.ext.security.datastore.mongoengine import MongoEngineUserDatastore
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
app.config['MONGODB_DB'] = 'flask_security_example'
app.config['MONGODB_HOST'] = 'localhost'
app.config['MONGODB_PORT'] = 27017
db = MongoEngine(app)
Security(app, MongoEngineUserDatastore(db))
"""
def get_models(self):
db = self.db
class Role(db.Document, RoleMixin):
"""MongoEngine Role model"""
name = db.StringField(required=True, unique=True, max_length=80)
description = db.StringField(max_length=255)
class User(db.Document, UserMixin):
"""MongoEngine User model"""
username = db.StringField(unique=True, max_length=255)
email = db.StringField(unique=True, max_length=255)
password = db.StringField(required=True, max_length=120)
+28 -3
View File
@@ -1,13 +1,34 @@
# -*- coding: utf-8 -*-
"""
flask.ext.security.datastore.sqlalchemy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module contains a Flask-Security SQLAlchemy datastore implementation
:copyright: (c) 2012 by Matt Wright.
:license: MIT, see LICENSE for more details.
"""
from flask.ext import security
from flask.ext.security import UserMixin, RoleMixin
from flask.ext.security.datastore import UserDatastore
class SQLAlchemyUserDatastore(UserDatastore):
"""SQLAlchemy datastore"""
"""A SQLAlchemy datastore implementation for Flask-Security. Example:
def __init__(self, db):
self.db = db
from flask import Flask
from flask.ext.security import Security
from flask.ext.security.datastore.sqlalchemy import SQLAlchemyUserDatastore
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/flask_security_example.sqlite'
db = SQLAlchemy(app)
Security(app, SQLAlchemyUserDatastore(db))
"""
def get_models(self):
db = self.db
@@ -16,6 +37,8 @@ class SQLAlchemyUserDatastore(UserDatastore):
db.Column('role_id', db.Integer(), db.ForeignKey('user.id')))
class Role(db.Model, RoleMixin):
"""SQLAlchemy Role model"""
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
@@ -25,6 +48,8 @@ class SQLAlchemyUserDatastore(UserDatastore):
self.description = description
class User(db.Model, UserMixin):
"""SQLAlchemy User model"""
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), unique=True)
email = db.Column(db.String(255), unique=True)
+11
View File
@@ -1,3 +1,14 @@
# -*- coding: utf-8 -*-
"""
flask.ext.security.script
~~~~~~~~~~~~~~~~~~~~~~~~~
This module contains commands for use with the Flask-Script extension
:copyright: (c) 2012 by Matt Wright.
:license: MIT, see LICENSE for more details.
"""
import json
import re
from flask.ext.script import Command, Option
+2 -1
View File
@@ -2,7 +2,8 @@
Flask-Security
--------------
Simple security for Flask apps
Flask-Security is a Flask extension that aims to add quick and simple security
via Flask-Login, Flask-Principal, Flask-WTF, and passlib.
Links
`````