├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── flask_permissions ├── __init__.py ├── core.py ├── decorators.py ├── models.py ├── tests.py └── utils.py ├── requirements.txt ├── requirements_test.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | dist 3 | cover 4 | .coverage 5 | *.pyc 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | install: 6 | - pip install -r requirements.txt 7 | - pip install -r requirements_test.txt 8 | script: nosetests --with-coverage --cover-package=flask_permissions 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Devon Campbell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-Permissions 2 | 3 | [![Build Status](https://travis-ci.org/raddevon/flask-permissions.png?branch=master)](https://travis-ci.org/raddevon/flask-permissions) 4 | 5 | Flask-Permissions is a simple Flask permissions extension that works with [Flask-SQLAlchemy](https://github.com/mitsuhiko/flask-sqlalchemy). It also plays nicely with [Flask-Login](https://github.com/maxcountryman/flask-login) although that isn't a strict requirement. 6 | 7 | ## Installation 8 | 9 | Installs quickly and easily using PIP: 10 | 11 | pip install Flask-Permissions 12 | 13 | ## Getting Started 14 | 15 | 1. Import Flask, Flask-SQLAlchemy, and, if you want, Flask-Login. 16 | 17 | from flask import Flask 18 | from flask.ext.login import LoginManager, current_user 19 | from flask.ext.sqlalchemy import SQLAlchemy 20 | 21 | 2. Import the `Permissions` object. 22 | 23 | from flask.ext.permissions.core import Permissions 24 | 25 | 3. Instantiate the `Permissions` object passing in your Flask app, SQLAlchemy database, and a proxy for the current user. 26 | 27 | perms = Permissions(app, db, current_user) 28 | 29 | 4. Sub-class the Flask-Permissions UserMixin. Call the UserMixin's `__init__` in your own `__init__`. 30 | 31 | from app import db 32 | from flask.ext.permissions.models import UserMixin 33 | 34 | 35 | class User(UserMixin): 36 | # Add whatever fields you need for your user class here. 37 | 38 | def __init__(self, ...): 39 | # Do your user init 40 | UserMixin.__init__(self, roles) 41 | 42 | 5. Add roles to your users and abilities to your roles. This can be done using convenience methods on the `UserMixin` and `Role` classes. 43 | 44 | You'll need a role to start adding abilities. 45 | 46 | my_role = Role('admin') 47 | 48 | Add abilities by passing string ability names to `role.add_abilities()`. You may pass existing or new abilities in this way. New abilities will be created for you. Add the role to the session and commit when you're done. 49 | 50 | my_role.add_abilities('create_users', 'delete_users', 'bring_about_world_peace') 51 | db.session.add(my_role) 52 | db.session.commit() 53 | 54 | Add roles on an instance of your `UserMixin` sub-class. 55 | 56 | my_user = User() 57 | 58 | The `user.add_roles()` method works just like `role.add_abilities()`. Pass in a string name or a series of string names. New roles will be created for you. Existing roles will simply be applied to the user. Don't forget to add and commit to the database! 59 | 60 | my_user.add_roles('admin', 'superadmin') 61 | db.session.add(my_user) 62 | db.session.commit() 63 | 64 | Similarly to the add methods, the classes also offer remove methods that work in the same way. Pass strings to `role.remove_abilities()` or `user.remove_roles()` to remove those attributes from the objects in question. 65 | 66 | 6. Put those decorators to work! Decorate any of your views with the `user_is` or `user_has` decorators from `flask.ext.permissions.decorators` to limit access. 67 | 68 | from flask.ext.permissions.decorators import user_is, user_has 69 | 70 | `@user_is` decorator: 71 | 72 | @app.route('/admin', methods=['GET', 'POST']) 73 | @user_is('admin') 74 | def admin(): 75 | return render_template('admin.html') 76 | 77 | `@user_has` decorator: 78 | 79 | @app.route('/delete-users', methods=['GET', 'POST']) 80 | @user_has('delete_users') 81 | def delete_users(): 82 | return render_template('delete-users.html') 83 | 84 | ## Example Implementation 85 | 86 | This is ripped almost directly from a project I'm working on that implements Flask-Permissions. Be sure to check out the code comments for help with what does what. 87 | 88 | #### \__init__.py 89 | 90 | # Import Flask, Flask-SQLAlchemy, and maybe Flask-Login 91 | from flask import Flask 92 | from flask.ext.login import LoginManager, current_user 93 | from flask.ext.sqlalchemy import SQLAlchemy 94 | 95 | # Import the Permissions object 96 | from flask.ext.permissions.core import Permissions 97 | 98 | # Here, you'll initialize your app with Flask and your database with 99 | # Flask-SQLAlchemy. It might look something like this: 100 | db = SQLAlchemy() 101 | 102 | app = Flask(__name__) 103 | app.config.from_object('config') 104 | 105 | db.init_app(app) 106 | with app.test_request_context(): 107 | db.create_all() 108 | 109 | # If you're using Flask-Login, this would be a good time to set that up. 110 | login_manager = LoginManager() 111 | login_manager.init_app(app) 112 | 113 | # Now, initialize a Permissions object. I've assigned it to a variable here, 114 | # but you don't have to do so. 115 | perms = Permissions(app, db, current_user) 116 | 117 | #### models.py 118 | 119 | # Import your database 120 | from app import db 121 | # I'm using these handy functions for my user's password. Flask is dependent 122 | # on Werkzeug, so you'll have access to these too. 123 | from werkzeug import generate_password_hash, check_password_hash 124 | # Import the mixin 125 | from flask.ext.permissions.models import UserMixin 126 | 127 | 128 | class User(UserMixin): 129 | # Add whatever fields you need for your user class. Here, I've added 130 | # an email field and a password field 131 | email = db.Column(db.String(120), unique=True) 132 | pwdhash = db.Column(db.String(100)) 133 | 134 | def __init__(self, email, password, roles=None): 135 | self.email = email.lower() 136 | self.set_password(password) 137 | # Be sure to call the UserMixin's constructor in your class constructor 138 | UserMixin.__init__(self, roles) 139 | 140 | def set_password(self, password): 141 | self.pwdhash = generate_password_hash(password) 142 | 143 | def check_password(self, password): 144 | return check_password_hash(self.pwdhash, password) 145 | 146 | def __str__(self): 147 | return self.email 148 | 149 | #### views.py 150 | 151 | # Import the decorators 152 | from flask.ext.permissions.decorators import user_is, user_has 153 | 154 | # Set up your route and decorate it 155 | @app.route('/admin', methods=['GET', 'POST']) 156 | # Pass the name of the role you want to test for to the decorator 157 | @user_is('admin') 158 | def admin(): 159 | return render_template('admin.html') 160 | 161 | # Here's an example of user_has 162 | @app.route('/delete-users', methods=['GET', 'POST']) 163 | # Pass the name of the ability you want to test for to the decorator 164 | @user_has('delete_users') 165 | def delete_users(): 166 | return render_template('delete-users.html') 167 | 168 | ## License 169 | 170 | This extension is available under the MIT license. See the LICENSE file for more details. 171 | 172 | ## Thank You 173 | 174 | I hope you enjoy this project. I built Flask-Permissions because I couldn't find a simple permissions system for Flask. This does everything I need, and I feel the implementation is very easy to understand. 175 | -------------------------------------------------------------------------------- /flask_permissions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raddevon/flask-permissions/a2f64c8e26b6b4807019794a68bad21b12ceeb71/flask_permissions/__init__.py -------------------------------------------------------------------------------- /flask_permissions/core.py: -------------------------------------------------------------------------------- 1 | class Permissions(object): 2 | 3 | def __init__(self, app, local_db, current_user): 4 | self.init_app(app, local_db, current_user) 5 | 6 | def init_app(self, app, local_db, current_user): 7 | self.app = app 8 | self.current_user = current_user 9 | global db 10 | db = local_db 11 | -------------------------------------------------------------------------------- /flask_permissions/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from werkzeug.exceptions import Forbidden 3 | 4 | 5 | def import_user(): 6 | try: 7 | from flask.ext.login import current_user 8 | return current_user 9 | except ImportError: 10 | raise ImportError( 11 | 'User argument not passed and Flask-Login current_user could not be imported.') 12 | 13 | 14 | def user_has(ability, get_user=import_user): 15 | """ 16 | Takes an ability (a string name of either a role or an ability) and returns the function if the user has that ability 17 | """ 18 | def wrapper(func): 19 | @wraps(func) 20 | def inner(*args, **kwargs): 21 | from .models import Ability 22 | desired_ability = Ability.query.filter_by( 23 | name=ability).first() 24 | user_abilities = [] 25 | current_user = get_user() 26 | for role in current_user._roles: 27 | user_abilities += role.abilities 28 | if desired_ability in user_abilities: 29 | return func(*args, **kwargs) 30 | else: 31 | raise Forbidden("You do not have access") 32 | return inner 33 | return wrapper 34 | 35 | 36 | def user_is(role, get_user=import_user): 37 | """ 38 | Takes an role (a string name of either a role or an ability) and returns the function if the user has that role 39 | """ 40 | def wrapper(func): 41 | @wraps(func) 42 | def inner(*args, **kwargs): 43 | from .models import Role 44 | current_user = get_user() 45 | if role in current_user.roles: 46 | return func(*args, **kwargs) 47 | raise Forbidden("You do not have access") 48 | return inner 49 | return wrapper 50 | -------------------------------------------------------------------------------- /flask_permissions/models.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | from .core import db 4 | except ImportError: 5 | raise Exception( 6 | 'Permissions app must be initialized before importing models') 7 | 8 | from werkzeug import generate_password_hash, check_password_hash 9 | from sqlalchemy.ext.hybrid import hybrid_property 10 | from sqlalchemy.ext.declarative import declared_attr 11 | from sqlalchemy.ext.associationproxy import association_proxy 12 | from .utils import is_sequence 13 | 14 | 15 | def _role_find_or_create(r): 16 | role = Role.query.filter_by(name=r).first() 17 | if not(role): 18 | role = Role(name=r) 19 | db.session.add(role) 20 | return role 21 | 22 | 23 | def make_user_role_table(table_name='user', id_column_name='id'): 24 | """ 25 | Create the user-role association table so that 26 | it correctly references your own UserMixin subclass. 27 | 28 | """ 29 | 30 | return db.Table('fp_user_role', 31 | db.Column( 32 | 'user_id', db.Integer, db.ForeignKey('{}.{}'.format( 33 | table_name, id_column_name))), 34 | db.Column( 35 | 'role_id', db.Integer, db.ForeignKey('fp_role.id')), 36 | extend_existing=True) 37 | 38 | 39 | role_ability_table = db.Table('fp_role_ability', 40 | db.Column( 41 | 'role_id', db.Integer, db.ForeignKey('fp_role.id')), 42 | db.Column( 43 | 'ability_id', db.Integer, db.ForeignKey('fp_ability.id')) 44 | ) 45 | 46 | 47 | class Role(db.Model): 48 | 49 | """ 50 | Subclass this for your roles 51 | """ 52 | __tablename__ = 'fp_role' 53 | id = db.Column(db.Integer, primary_key=True) 54 | name = db.Column(db.String(120), unique=True) 55 | abilities = db.relationship( 56 | 'Ability', secondary=role_ability_table, backref='roles') 57 | 58 | def __init__(self, name): 59 | self.name = name.lower() 60 | 61 | def add_abilities(self, *abilities): 62 | for ability in abilities: 63 | existing_ability = Ability.query.filter_by( 64 | name=ability).first() 65 | if not existing_ability: 66 | existing_ability = Ability(ability) 67 | db.session.add(existing_ability) 68 | db.session.commit() 69 | self.abilities.append(existing_ability) 70 | 71 | def remove_abilities(self, *abilities): 72 | for ability in abilities: 73 | existing_ability = Ability.query.filter_by(name=ability).first() 74 | if existing_ability and existing_ability in self.abilities: 75 | self.abilities.remove(existing_ability) 76 | 77 | def __repr__(self): 78 | return ''.format(self.name) 79 | 80 | def __str__(self): 81 | return self.name 82 | 83 | 84 | class Ability(db.Model): 85 | 86 | """ 87 | Subclass this for your abilities 88 | """ 89 | __tablename__ = 'fp_ability' 90 | id = db.Column(db.Integer, primary_key=True) 91 | name = db.Column(db.String(120), unique=True) 92 | 93 | def __init__(self, name): 94 | self.name = name.lower() 95 | 96 | def __repr__(self): 97 | return ''.format(self.name) 98 | 99 | def __str__(self): 100 | return self.name 101 | 102 | 103 | class UserMixin(db.Model): 104 | 105 | """ 106 | Subclass this for your user class 107 | """ 108 | 109 | __abstract__ = True 110 | 111 | @hybrid_property 112 | def _id_column_name(self): 113 | 114 | # the list of the class's columns (with attributes like 115 | # 'primary_key', etc.) is accessible in different places 116 | # before and after table definition. 117 | if self.__tablename__ in list(self.metadata.tables.keys()): 118 | # after definition, it's here 119 | columns = self.metadata.tables[self.__tablename__]._columns 120 | else: 121 | # before, it's here 122 | columns = self.__dict__ 123 | 124 | for k, v in list(columns.items()): 125 | if getattr(v, 'primary_key', False): 126 | return k 127 | 128 | @declared_attr 129 | def _roles(self): 130 | user_role_table = make_user_role_table(self.__tablename__, 131 | self._id_column_name.fget(self)) 132 | return db.relationship( 133 | 'Role', secondary=user_role_table, backref='users') 134 | 135 | @declared_attr 136 | def roles(self): 137 | return association_proxy('_roles', 'name', creator=_role_find_or_create) 138 | 139 | def __init__(self, roles=None, default_role='user'): 140 | # If only a string is passed for roles, convert it to a list containing 141 | # that string 142 | if roles and isinstance(roles, str): 143 | roles = [roles] 144 | 145 | # If a sequence is passed for roles (or if roles has been converted to 146 | # a sequence), fetch the corresponding database objects and make a list 147 | # of those. 148 | if roles and is_sequence(roles): 149 | self.roles = roles 150 | # Otherwise, assign the default 'user' role. Create that role if it 151 | # doesn't exist. 152 | elif default_role: 153 | self.roles = [default_role] 154 | 155 | def is_authenticated(self): 156 | return True 157 | 158 | def is_active(self): 159 | return True 160 | 161 | def is_anonymous(self): 162 | return False 163 | 164 | def add_roles(self, *roles): 165 | self.roles.extend([role for role in roles if role not in self.roles]) 166 | 167 | def remove_roles(self, *roles): 168 | self.roles = [role for role in self.roles if role not in roles] 169 | 170 | def get_id(self): 171 | return str(getattr(self, self._id_column_name)) 172 | 173 | def __repr__(self): 174 | return '<{} {}>'.format(self.__tablename__.capitalize(), self.get_id()) 175 | -------------------------------------------------------------------------------- /flask_permissions/tests.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | import unittest 3 | from flask_testing import TestCase as FlaskTestCase 4 | from flask_sqlalchemy import SQLAlchemy 5 | from sqlalchemy import Column, Integer 6 | from .core import Permissions 7 | from .utils import is_sequence 8 | from .decorators import user_has, user_is 9 | from werkzeug.exceptions import Forbidden 10 | import os 11 | 12 | app = Flask(__name__) 13 | app.config['TESTING'] = True 14 | 15 | db_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'app.db') 16 | 17 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + db_path 18 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 19 | 20 | db = SQLAlchemy(app) 21 | 22 | perms = Permissions(app, db, None) 23 | 24 | from .models import Role, Ability, UserMixin 25 | 26 | 27 | class User(UserMixin): 28 | id = Column(Integer, primary_key=True) 29 | 30 | 31 | class DatabaseTests(FlaskTestCase): 32 | 33 | def create_app(self): 34 | return app 35 | 36 | def setUp(self): 37 | db.create_all() 38 | 39 | def tearDown(self): 40 | os.remove(db_path) 41 | 42 | 43 | class ModelsTests(DatabaseTests): 44 | 45 | def test_user_mixin(self): 46 | user = User() 47 | db.session.add(user) 48 | db.session.commit() 49 | self.assertEqual(user.id, 1) 50 | 51 | def test_new_user_with_roles_assigned(self): 52 | roles = ['admin', 'superadmin'] 53 | for role in roles: 54 | new_role = Role(role) 55 | db.session.add(new_role) 56 | db.session.commit() 57 | user = User(roles=roles, default_role=None) 58 | db.session.add(user) 59 | db.session.commit() 60 | self.assertEqual(user.id, 1) 61 | 62 | def test_new_user_with_a_single_role_assigned(self): 63 | new_role = Role('admin') 64 | db.session.add(new_role) 65 | db.session.commit() 66 | user = User(roles='admin', default_role=None) 67 | db.session.add(user) 68 | db.session.commit() 69 | self.assertEqual(user.id, 1) 70 | 71 | def test_user_is_authenticated(self): 72 | user = User() 73 | self.assertTrue(user.is_authenticated()) 74 | 75 | def test_user_is_active(self): 76 | user = User() 77 | self.assertTrue(user.is_active()) 78 | 79 | def test_user_is_not_anonymous(self): 80 | user = User() 81 | self.assertFalse(user.is_anonymous()) 82 | 83 | def test_user_get_id(self): 84 | user = User() 85 | db.session.add(user) 86 | db.session.commit() 87 | self.assertEqual(user.get_id(), str(1)) 88 | 89 | def test_user_repr(self): 90 | user = User() 91 | db.session.add(user) 92 | db.session.commit() 93 | self.assertEqual(user.__repr__(), '') 94 | 95 | def test_role(self): 96 | role = Role('admin') 97 | db.session.add(role) 98 | db.session.commit() 99 | self.assertEqual(role.id, 1) 100 | 101 | def test_role_repr(self): 102 | role = Role('admin') 103 | self.assertEqual(role.__repr__(), '') 104 | 105 | def test_role_str(self): 106 | role = Role('admin') 107 | self.assertEqual(role.__str__(), 'admin') 108 | 109 | def test_ability(self): 110 | ability = Ability('create_users') 111 | db.session.add(ability) 112 | db.session.commit() 113 | self.assertEqual(ability.id, 1) 114 | 115 | def test_ability_repr(self): 116 | ability = Ability('create_users') 117 | self.assertEqual(ability.__repr__(), '') 118 | 119 | def test_ability_repr(self): 120 | ability = Ability('create_users') 121 | self.assertEqual(ability.__str__(), 'create_users') 122 | 123 | def test_add_roles_with_nonexisting_roles(self): 124 | user = User(default_role=None) 125 | new_roles = ['admin', 'superadmin', 'editor'] 126 | user.add_roles(*new_roles) 127 | db.session.add(user) 128 | db.session.commit() 129 | test_user = User.query.get(1) 130 | # self.assertItemsEqual(new_roles, test_user.roles) 131 | self.assertEqual(sorted(new_roles), sorted(test_user.roles)) 132 | 133 | def test_add_roles_with_existing_roles(self): 134 | user = User(default_role=None) 135 | new_roles = ['admin', 'superadmin', 'editor'] 136 | for role in new_roles: 137 | new_role = Role(role) 138 | db.session.add(new_role) 139 | db.session.commit() 140 | user.add_roles(*new_roles) 141 | db.session.add(user) 142 | db.session.commit() 143 | test_user = User.query.get(1) 144 | # self.assertItemsEqual(new_roles, test_user.roles) 145 | self.assertEqual(sorted(new_roles), sorted(test_user.roles)) 146 | 147 | def test_remove_roles(self): 148 | user = User() 149 | new_roles = ['admin', 'user', 'superadmin'] 150 | user.add_roles(*new_roles) 151 | db.session.add(user) 152 | db.session.commit() 153 | role_remove_user = User.query.get(1) 154 | role_remove_user.remove_roles('user', 'admin') 155 | db.session.add(user) 156 | db.session.commit() 157 | test_user = User.query.get(1) 158 | # self.assertItemsEqual(test_user.roles, ['superadmin']) 159 | self.assertEqual(sorted(test_user.roles), sorted(['superadmin'])) 160 | 161 | def test_add_abilities_with_nonexisting_abilities(self): 162 | role = Role('admin') 163 | new_abilities = ['create_users', 'set_roles', 'set_abilities'] 164 | role.add_abilities(*new_abilities) 165 | db.session.add(role) 166 | db.session.commit() 167 | test_role = Role.query.get(1) 168 | abilities = [ability.name for ability in test_role.abilities] 169 | # self.assertItemsEqual(new_abilities, abilities) 170 | self.assertEqual(sorted(new_abilities), sorted(abilities)) 171 | 172 | def test_add_abilities_with_existing_abilities(self): 173 | role = Role('admin') 174 | new_abilities = ['create_users', 'set_roles', 'set_abilities'] 175 | for ability in new_abilities: 176 | new_ability = Ability(ability) 177 | db.session.add(new_ability) 178 | db.session.commit() 179 | role.add_abilities(*new_abilities) 180 | db.session.add(role) 181 | db.session.commit() 182 | test_role = Role.query.get(1) 183 | abilities = [ability.name for ability in test_role.abilities] 184 | # self.assertItemsEqual(new_abilities, abilities) 185 | self.assertEqual(sorted(new_abilities), sorted(abilities)) 186 | 187 | def test_remove_abilities(self): 188 | role = Role('admin') 189 | new_abilities = ['create_users', 'set_roles', 'set_abilities'] 190 | role.add_abilities(*new_abilities) 191 | db.session.add(role) 192 | db.session.commit() 193 | ability_remove_role = Role.query.get(1) 194 | ability_remove_role.remove_abilities('set_roles', 'set_abilities') 195 | db.session.add(ability_remove_role) 196 | db.session.commit() 197 | test_role = Role.query.get(1) 198 | abilities = [ability.name for ability in test_role.abilities] 199 | # self.assertItemsEqual(abilities, ['create_users']) 200 | self.assertEqual(sorted(abilities), sorted(['create_users'])) 201 | 202 | 203 | class DecoratorsTests(DatabaseTests): 204 | 205 | def mock_function(self): 206 | return True 207 | 208 | def create_user(self): 209 | role = Role('admin') 210 | new_abilities = ['create_users', 'set_roles', 'set_abilities'] 211 | for ability in new_abilities: 212 | new_ability = Ability(ability) 213 | db.session.add(new_ability) 214 | db.session.commit() 215 | role.add_abilities(*new_abilities) 216 | db.session.add(role) 217 | db.session.commit() 218 | 219 | user = User(roles='admin') 220 | db.session.add(user) 221 | db.session.commit() 222 | 223 | def return_user(self): 224 | user = User.query.get(1) 225 | return user 226 | 227 | def setUp(self): 228 | super(DecoratorsTests, self).setUp() 229 | self.create_user() 230 | 231 | def test_user_has_pass(self): 232 | wrapped_function = user_has( 233 | 'create_users', self.return_user)(self.mock_function) 234 | self.assertTrue(wrapped_function()) 235 | 236 | def test_user_has_fail(self): 237 | wrapped_function = user_has( 238 | 'edit', self.return_user)(self.mock_function) 239 | self.assertRaises(Forbidden, wrapped_function) 240 | 241 | def test_user_is_pass(self): 242 | wrapped_function = user_is( 243 | 'admin', self.return_user)(self.mock_function) 244 | self.assertTrue(wrapped_function()) 245 | 246 | def test_user_is_fail(self): 247 | wrapped_function = user_is( 248 | 'user', self.return_user)(self.mock_function) 249 | self.assertRaises(Forbidden, wrapped_function) 250 | 251 | 252 | class UtilsTests(unittest.TestCase): 253 | 254 | def test_is_string_a_sequence(self): 255 | string_var = 'This is a string. It is not a sequence.' 256 | self.assertFalse(is_sequence(string_var)) 257 | 258 | def test_is_integer_a_sequence(self): 259 | int_var = 1 260 | self.assertFalse(is_sequence(int_var)) 261 | 262 | def test_is_bool_a_sequence(self): 263 | bool_var = True 264 | self.assertFalse(is_sequence(bool_var)) 265 | 266 | def test_is_list_a_sequence(self): 267 | list_var = [1, 2, 3] 268 | self.assertTrue(is_sequence(list_var)) 269 | 270 | def test_is_set_a_sequence(self): 271 | set_var = set([1, 2, 3]) 272 | self.assertTrue(is_sequence(set_var)) 273 | 274 | def test_is_tuple_a_sequence(self): 275 | tuple_var = (1, 2, 3) 276 | self.assertTrue(is_sequence(tuple_var)) 277 | 278 | def test_is_dict_a_sequence(self): 279 | dict_var = {'a': 1, 'b': 2, 'c': 3} 280 | self.assertTrue(is_sequence(dict_var)) 281 | 282 | 283 | if __name__ == '__main__': # optional, but makes import and reuse easier 284 | unittest.main() 285 | -------------------------------------------------------------------------------- /flask_permissions/utils.py: -------------------------------------------------------------------------------- 1 | def is_sequence(arg): 2 | if hasattr(arg, "strip"): 3 | return False 4 | return (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__")) 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.7 2 | Flask==0.12.1 3 | Flask-SQLAlchemy==2.2 4 | itsdangerous==0.24 5 | Jinja2==2.9.6 6 | MarkupSafe==1.0 7 | SQLAlchemy==1.1.9 8 | Werkzeug==0.12.1 9 | 10 | -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | coverage==4.4 2 | Flask-Testing==0.6.2 3 | nose==1.3.7 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple permissions system for developers using Flask and SQLAlchemy. 3 | """ 4 | from setuptools import setup 5 | 6 | 7 | setup( 8 | name='Flask-Permissions', 9 | version='0.2.3', 10 | url='https://github.com/raddevon/flask-permissions', 11 | license='MIT', 12 | author='Devon Campbell', 13 | author_email='devon@raddevon.com', 14 | description='Simple user permissions for Flask', 15 | long_description=__doc__, 16 | packages=['flask_permissions'], 17 | zip_safe=False, 18 | include_package_data=True, 19 | platforms='any', 20 | install_requires=[ 21 | 'Flask', 22 | 'Flask-SQLAlchemy' 23 | ], 24 | classifiers=[ 25 | 'Framework :: Flask', 26 | 'Natural Language :: English', 27 | 'Environment :: Web Environment', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Operating System :: OS Independent', 31 | 'Programming Language :: Python', 32 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 33 | 'Topic :: Software Development :: Libraries :: Python Modules' 34 | ] 35 | ) 36 | --------------------------------------------------------------------------------