介绍的用户角色实现方式结合了分立的角色和权限: 即赋予用户分立的角色,但角色使用权限定义。
1. 角色在数据库中的表示
采用如下的权限
上述权限采用如下代码表示
app/models.py:权限常量
class Permission: FOLLOW = 0x01 COMMENT = 0x02 WRITE_ARTICLES = 0x04 MODERATE_COMMENTS = 0x08 ADMINISTER = 0x80
采用如下的用户角色
在Role模型中增加跟权限有关的列
app/models.py: 角色的权限
class Role(db.Model): __tablename__ = ‘roles‘ id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) default = db.Column(db.Boolean, default=False, index=True) permissions = db.Column(db.Integer) users = db.relationship(‘User‘, backref=‘role‘, lazy=‘dynamic‘)
只有一个角色的default字段要设为True,其他都设为False。
用户注册时,其角色会被设为默认角色。
permissions字段其值是一个整数,表示位标志。用来表示角色对应的权限操作
然后在Role类中添加一个类方法,完成添加用户角色的操作
app/models.py: 在数据库中添加用户角色
class Role(db.Model): #... @staticmethod def insert_roles(): roles = { ‘User‘: (Permission.FOLLOW | Permission.COMMENT | Permission.WRITE_ARTICLES, True), ‘Moderator‘: (Permission.FOLLOW | Permission.COMMENT | Permission.WRITE_ARTICLES | Permission.MODERATE_COMMENTS, False), ‘Administrator‘: (0xff, False) } for r in roles: role = Role.query.filter_by(name=r).first() if role is None: role = Role(name=r) role.permissions = roles[r][0] role.default = roles[r][1] db.session.add(role) db.session.commit()
nsert_roles() 函数并不直接创建新角色对象,而是通过角色名查找现有的角色,然后再进行更新。只有当数据库中没有某个角色名时才会创建新角色对象。
如此一来,如果以后更新了角色,就可以执行更新操作了。(要想更新角色,只要修改roles 字典,再运行函数即可。)
2. 赋予用户角色
app/models.py: 定义默认的用户角色
class User(UserMixin, db.Model): #... def __init__(self, **kwargs): super(User, self).__init__(**kwargs) if self.role is None: if self.email == current_app.config[‘FLASKY_ADMIN‘]: self.role = Role.query.filter_by(permissions=0xff).first() else: self.role = Role.query.filter_by(default=True).first()
User 类的构造函数首先调用基类的构造函数。如果创建基类对象后还没定义角色,则根据电子邮件地址觉得将其设为管理员还是默认角色
3. 角色验证
app/models.py:检查用户是否有指定的权限
from flask.ext.login import UserMixin, AnonymousUserMixin class User(UserMixin, db.Model): #... def can(self, permissions): return self.role is not None and (self.role.permissions & permissions) == permissions def is_administrator(self): return self.can(Permission.ADMINISTER) class AnonymousUser(AnonymousUserMixin): def can(self, permissions): return False def is_administrator(self): return False login_manager.anonymous_user = AnonymousUser
can()方法在角色所拥有的权限以及要比较的权限之间进行位与操作。如果角色中包含要比较的权限,则返回True,表示允许用户执行此项操作
出于一致性考虑,还定义了 AnonymousUser 类,并实现了 can() 方法和 is_administrator()方法。
这个对象继承自 Flask-Login 中的 AnonymousUserMixin 类,并将其设为用户未登录时current_user 的值。
这样程序不用先检查用户是否登录,就能自由调用 current_user.can() 和current_user.is_administrator()。
如果想让某些视图函数只对具有特定权限的用户开发,可自定义修饰器实现
app/decorators.py: 检查用户权限的自定义修饰器
from functools import warps from flask import abort from flask.ext.login import current_user from .models import Permission def permission_required(permission): def decorator(func): @warps(func) def warpper(*args, **kwargs): if not current_user.can(permission): abort(403) return func(*args, **kwargs) return wrapper return decorator def admin_required(func): return permission_required(Permission.ADMINISTER)(func)
举两个例子使用这些装饰器
from decorators import admin_required, permission_required from .models import Permission @main.route(‘/admin‘) @login_required @admin_required def for_admins_only(): return ‘For Administrator‘ @main.route(‘/moderator‘) @login_required @permission_required(Permission.MODERATE_COMMENTS) def for_moderators_only(): return ‘For comment moderator‘
在模板中可能也需要检查权限,为了避免每次调用 render_template() 时都多添加一个模板参数,可以使用上下文处理器。
下文处理器能让变量在所有模板中全局可访问。
app/main/__init__.py: 把Permission类加入模板上下文 @main.app_context_processor def inject_permissions(): return dict(Permission=Permission)
2015-05-24