Merge commit '47296980495f8bbfc9493e93de85dd62de6fa6b9' as 'paste-framework'
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import select, delete
|
||||
|
||||
from paste.rbac.rbac_models import RbacAssignmentModel
|
||||
|
||||
|
||||
class RbacAssignment(RbacAssignmentModel):
|
||||
"""
|
||||
权限分配器,负责用户权限的分配。
|
||||
允许为用户分配角色,也允许为用户直接分配权限。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def new(cls, user_id: Union[str, int], item_name: str):
|
||||
"""
|
||||
新建角色分配对象。不检查用户是否存在,也不保存数据库。
|
||||
|
||||
:param user_id: 用户 ID
|
||||
:param item_name: 授权项名称
|
||||
:return: 新的分配对象
|
||||
"""
|
||||
return cls(user_id=user_id, item_name=item_name).before_save()
|
||||
|
||||
@classmethod
|
||||
async def new_batch(cls, user_id: int, item_names: set[str]):
|
||||
"""
|
||||
为用户创建角色或权限,自动剔除已经分配的角色或授权项。注意:仅创建不保存。
|
||||
|
||||
:param user_id: 用户名
|
||||
:param item_names: 授权项名称列表,这里允许是角色名称,也允许是权限名称
|
||||
"""
|
||||
from paste.rbac.rbac_user import RbacUser, Supervisors
|
||||
|
||||
# 确认用户存在,且不是超级用户
|
||||
_mod_user: RbacUser = await RbacUser(**{RbacUser.id.key: user_id}).async_find_first()
|
||||
assert _mod_user is not None, f"ID 为 {user_id} 的用户不存在."
|
||||
assert _mod_user.username not in Supervisors, f"ID 为 {user_id} 的用户 {_mod_user.username} 禁止设置权限."
|
||||
|
||||
# 查库,过滤已经存在的权限分配
|
||||
_query = select(cls.item_name).where(cls.user_id == user_id, cls.item_name.in_(item_names))
|
||||
_rows = await cls.query_all(_query)
|
||||
_exist_names: set[str] = set([_r[cls.item_name.key] for _r in _rows])
|
||||
item_names = set([_name for _name in item_names if _name not in _exist_names])
|
||||
|
||||
# 创建模型列表
|
||||
_new_assignments = [cls(**{cls.user_id.key: user_id, cls.item_name.key: name}) for name in item_names]
|
||||
return _new_assignments
|
||||
|
||||
@classmethod
|
||||
async def assign(cls, user_id: int, item_names: set[str]):
|
||||
"""
|
||||
为用户分配角色或权限,自动剔除已经分配的角色或授权项。
|
||||
|
||||
:param user_id: 用户名
|
||||
:param item_names: 授权项名称列表,这里允许是角色名称,也允许是权限名称
|
||||
"""
|
||||
# 创建模型列表
|
||||
_new_assignments = await cls.new_batch(user_id=user_id, item_names=item_names)
|
||||
|
||||
# 保存数据
|
||||
_session = cls.get_aio_session()
|
||||
try:
|
||||
_session.add_all(_new_assignments)
|
||||
await _session.commit()
|
||||
except Exception as e:
|
||||
await _session.rollback()
|
||||
raise e
|
||||
finally:
|
||||
await _session.close()
|
||||
|
||||
@classmethod
|
||||
async def delete(cls, user_id: Union[str, int], item_name: str):
|
||||
"""
|
||||
删除授权。
|
||||
|
||||
:param user_id: 用户 ID
|
||||
:param item_name: 授权项
|
||||
:return: 操作状态,游标返回对象
|
||||
"""
|
||||
assert user_id not in ('', None), '必须提供用户 ID.'
|
||||
assert item_name not in ('', None), '必须提供权限或角色名称.'
|
||||
|
||||
_query = delete(cls).where(cls.user_id == user_id, cls.item_name == item_name)
|
||||
_result = await cls.raw_execute(query=_query)
|
||||
_rowcount = _result.rowcount if isinstance(_result.rowcount, int) else 0
|
||||
return _rowcount > 0, _result
|
||||
@@ -0,0 +1,134 @@
|
||||
from sqlalchemy import select, delete
|
||||
|
||||
from paste.rbac.rbac_item_child import RbacItemChild
|
||||
from paste.rbac.rbac_models import RbacItemModel
|
||||
|
||||
|
||||
class RbacItem(RbacItemModel):
|
||||
"""
|
||||
授权项。
|
||||
分为角色和权限两类,分别由对应子类实现。
|
||||
"""
|
||||
|
||||
TYPE_ROLE = 1
|
||||
"""
|
||||
角色类型。
|
||||
"""
|
||||
|
||||
TYPE_PERMISSION = 2
|
||||
"""
|
||||
权限类型。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def all_parents(cls, item_names: set[str]):
|
||||
"""
|
||||
按层次,递归查询授权项列表中所有授权项的父授权项名称。
|
||||
|
||||
:param item_names: 授权项名称列表
|
||||
:return: 所有各层级的父授权项
|
||||
"""
|
||||
_query = select(RbacItemChild.parent).where(RbacItemChild.child.in_(item_names))
|
||||
_rows = await cls.query_all(_query)
|
||||
_item_names: set[str] = set(_rows)
|
||||
|
||||
if _item_names:
|
||||
_parents = await cls.all_parents(_item_names)
|
||||
_item_names = _item_names.union(_parents)
|
||||
|
||||
return _item_names
|
||||
|
||||
@classmethod
|
||||
async def all_children(cls, item_names: set[str]):
|
||||
"""
|
||||
按层次,递归查询授权项列表中所有授权项的子授权项名称。
|
||||
|
||||
:param item_names: 授权项名称列表
|
||||
:return: 所有各层级的子授权项
|
||||
"""
|
||||
_query = select(RbacItemChild.child).where(RbacItemChild.parent.in_(item_names))
|
||||
_rows = await cls.query_all(_query)
|
||||
_item_names: set[str] = set(_rows)
|
||||
|
||||
if _item_names:
|
||||
_children = await cls.all_children(_item_names)
|
||||
_item_names = _item_names.union(_children)
|
||||
|
||||
return _item_names
|
||||
|
||||
@classmethod
|
||||
async def find_by_name(cls, name: str):
|
||||
"""
|
||||
根据授权项名称查找授权项。
|
||||
|
||||
:param name: 授权项名称
|
||||
:return: 授权项
|
||||
"""
|
||||
_query = select(cls).where(cls.name == name)
|
||||
_model: cls = await cls.query_first(_query)
|
||||
return _model
|
||||
|
||||
async def add_children(self, item_names: set[str]):
|
||||
"""
|
||||
增加子授权项,自动剔除已包含的角色或授权项。角色和权限都能增加子授权项。
|
||||
|
||||
:param item_names: 待分配的子授权项名称列表
|
||||
"""
|
||||
# 首先根据授权项名称列表查出所有的授权项,剔除错误的名称
|
||||
_query = select(RbacItem.name).where(RbacItem.name.in_(item_names))
|
||||
_rows = await self.query_all(_query)
|
||||
item_names: set[str] = set(_rows)
|
||||
|
||||
# 取得所有祖先,确保所有子授权项,没有出现在祖先中,防止循环授权
|
||||
_all_parents = await self.all_parents(item_names=item_names)
|
||||
|
||||
# 查库,过滤已经存在的子授权项
|
||||
_query = select(RbacItemChild.child).where(
|
||||
RbacItemChild.parent == self.name,
|
||||
RbacItemChild.child.in_(item_names)
|
||||
)
|
||||
_rows = await self.query_all(_query)
|
||||
_exist_children: set[str] = set(_rows)
|
||||
|
||||
# 创建模型列表,剔除已包含的角色或授权项,剔除出现在祖先中的授权项,以及剔除自身
|
||||
_new_children = [
|
||||
RbacItemChild(**{RbacItemChild.parent.key: self.name, RbacItemChild.child.key: _name})
|
||||
for _name in item_names
|
||||
if _name not in _exist_children and _name not in _all_parents and _name != self.name
|
||||
]
|
||||
|
||||
# 保存数据
|
||||
_session = self.get_aio_session()
|
||||
try:
|
||||
_session.add_all(_new_children)
|
||||
await _session.commit()
|
||||
except Exception as e:
|
||||
await _session.rollback()
|
||||
raise e
|
||||
finally:
|
||||
await _session.close()
|
||||
|
||||
async def get_children(self):
|
||||
"""
|
||||
通过中间关系查询所有子授权项。注意:查询的是直接子授权项,不包含继承获得的授权项。
|
||||
|
||||
:return: 子权限项列表
|
||||
"""
|
||||
_query = select(RbacItem).join(
|
||||
RbacItemChild, RbacItemChild.child == RbacItem.name
|
||||
).where(
|
||||
RbacItemChild.parent == self.name,
|
||||
)
|
||||
_item_model_list: list[RbacItem] = await self.query_all(_query)
|
||||
return _item_model_list
|
||||
|
||||
async def remove_children(self, item_names: set[str]):
|
||||
"""
|
||||
删除子授权项。
|
||||
|
||||
:param item_names: 子授权项名称集合
|
||||
:return: 成功删除的项数
|
||||
"""
|
||||
_delete = delete(RbacItemChild).where(RbacItemChild.parent == self.name, RbacItemChild.child.in_(item_names))
|
||||
_result = await self.raw_execute(_delete)
|
||||
return _result.rowcount
|
||||
@@ -0,0 +1,8 @@
|
||||
from paste.rbac.rbac_models import RbacItemChildModel
|
||||
|
||||
|
||||
class RbacItemChild(RbacItemChildModel):
|
||||
"""
|
||||
授权项目关系。
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,93 @@
|
||||
#
|
||||
# 数据模型配置文件,注意:与数据模型对应的表名称来自配置文件。
|
||||
# 若使用 <paste.gen_models> 生成代码,则这部分表将不会自动生成。
|
||||
#
|
||||
|
||||
from sqlalchemy import Column, String, DateTime, LargeBinary, text, BigInteger, Integer, SmallInteger, Text, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from paste.core import config
|
||||
from paste.db.basemodel import BaseModel
|
||||
|
||||
|
||||
class RbacRuleModel(BaseModel):
|
||||
__tablename__ = config.get_config('rbac.table.rule')
|
||||
__table_args__ = {'comment': '规则'}
|
||||
|
||||
name = Column(String(64, 'utf8mb4_unicode_ci'), primary_key=True, comment='名称')
|
||||
data = Column(LargeBinary, comment='规则对象')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment='更新时间')
|
||||
|
||||
|
||||
class RbacUserModel(BaseModel):
|
||||
__tablename__ = config.get_config('rbac.table.user')
|
||||
__table_args__ = {'comment': '用户'}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, comment='系统编号')
|
||||
username = Column(String(255, 'utf8mb4_unicode_ci'), nullable=False, unique=True, comment='用户名')
|
||||
password_hash = Column(String(255, 'utf8mb4_unicode_ci'), nullable=False, comment='密码')
|
||||
password_reset_token = Column(String(255, 'utf8mb4_unicode_ci'), comment='重置标记')
|
||||
auth_key = Column(String(255, 'utf8mb4_unicode_ci'), comment='授权码')
|
||||
status = Column(Integer, nullable=False, server_default=text("'0'"), comment='用户状态')
|
||||
type = Column(String(64, 'utf8mb4_unicode_ci'), nullable=False, server_default=text("'user'"), comment='用户类型')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment='更新时间')
|
||||
|
||||
def before_save(self):
|
||||
super().before_save()
|
||||
if self.is_new:
|
||||
self.status = 1
|
||||
|
||||
|
||||
class RbacItemModel(BaseModel):
|
||||
__tablename__ = config.get_config('rbac.table.item')
|
||||
__table_args__ = {'comment': '授权项(角色/权限)'}
|
||||
|
||||
name = Column(String(64, 'utf8mb4_unicode_ci'), primary_key=True, comment='名称')
|
||||
type = Column(SmallInteger, nullable=False, comment='类型,1角色,2权限')
|
||||
description = Column(Text(collation='utf8mb4_unicode_ci'), comment='描述')
|
||||
rule_name = Column(
|
||||
ForeignKey(f"{config.get_config('rbac.table.rule')}.name", ondelete='SET NULL', onupdate='CASCADE'),
|
||||
index=True, comment='规则名称'
|
||||
)
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment='更新时间')
|
||||
|
||||
rbac_rule = relationship(RbacRuleModel)
|
||||
|
||||
|
||||
class RbacAssignmentModel(BaseModel):
|
||||
__tablename__ = config.get_config('rbac.table.assignment')
|
||||
__table_args__ = {'comment': '权限分配'}
|
||||
|
||||
item_name = Column(
|
||||
ForeignKey(f"{config.get_config('rbac.table.item')}.name", ondelete='CASCADE', onupdate='CASCADE'),
|
||||
primary_key=True, nullable=False, comment='授权项(角色/权限)'
|
||||
)
|
||||
user_id = Column(
|
||||
ForeignKey(f"{config.get_config('rbac.table.user')}.id", ondelete='CASCADE', onupdate='CASCADE'),
|
||||
primary_key=True, nullable=False, index=True, comment='用户'
|
||||
)
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment='创建时间')
|
||||
|
||||
rbac_item = relationship(RbacItemModel)
|
||||
rbac_user = relationship(RbacUserModel)
|
||||
|
||||
|
||||
class RbacItemChildModel(BaseModel):
|
||||
__tablename__ = config.get_config('rbac.table.item_child')
|
||||
__table_args__ = {'comment': '授权关系'}
|
||||
|
||||
parent = Column(
|
||||
ForeignKey(f"{config.get_config('rbac.table.item')}.name", ondelete='CASCADE', onupdate='CASCADE'),
|
||||
primary_key=True, nullable=False, comment='角色/权限'
|
||||
)
|
||||
child = Column(
|
||||
ForeignKey(f"{config.get_config('rbac.table.item')}.name", ondelete='CASCADE', onupdate='CASCADE'),
|
||||
primary_key=True, nullable=False, index=True, comment='授权项(角色/权限)'
|
||||
)
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment='创建时间')
|
||||
|
||||
child_item = relationship(RbacItemModel, primaryjoin='RbacItemChildModel.child == RbacItemModel.name')
|
||||
parent_item = relationship(RbacItemModel, primaryjoin='RbacItemChildModel.parent == RbacItemModel.name')
|
||||
@@ -0,0 +1,149 @@
|
||||
import importlib
|
||||
|
||||
from sqlalchemy import delete
|
||||
|
||||
from paste.core import config
|
||||
from paste.rbac.rbac_item import RbacItem
|
||||
from paste.web.application import Application
|
||||
|
||||
|
||||
class RbacPermission(RbacItem):
|
||||
"""
|
||||
权限。
|
||||
大多数情况下,权限都对应着一个具体的请求操作,且经由导入期自动导入。
|
||||
权限的 name 属性为请求控制器 RequestHandler 的 route_pattern 属性值,
|
||||
权限的 description 属性对应于 RequestHandler 类的文档注释的第一行。
|
||||
权限可以分配给用户,也可以分配给角色或其他权限。
|
||||
|
||||
此外允许手动创建权限,主要是为规则创建一个权限载体,当其他的权限属于这个权限的子权限时,相当于同时拥有了这个权限的规则。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def create(cls, name: str, description: str = None, rule_name: str = None):
|
||||
"""
|
||||
创建权限。
|
||||
|
||||
:param name: 权限名称
|
||||
:param rule_name: 规则名称
|
||||
:param description: 权限描述
|
||||
:return: 权限对象
|
||||
"""
|
||||
assert name not in ('', None), '必须提供权限名称.'
|
||||
_permission = cls(name=name, description=description, type=cls.TYPE_PERMISSION)
|
||||
_permission.rule_name = rule_name if rule_name else _permission.rule_name
|
||||
await _permission.async_save()
|
||||
return _permission
|
||||
|
||||
@classmethod
|
||||
async def delete(cls, name: str):
|
||||
"""
|
||||
删除权限。
|
||||
|
||||
:param name: 授权项名称
|
||||
:return: 操作状态,游标返回对象
|
||||
"""
|
||||
assert name not in ('', None), '必须提供权限名称.'
|
||||
_query = delete(cls).where(cls.name == name)
|
||||
_result = await cls.raw_execute(query=_query)
|
||||
_rowcount = _result.rowcount if isinstance(_result.rowcount, int) else 0
|
||||
return _rowcount > 0, _result
|
||||
|
||||
@classmethod
|
||||
async def modify(cls, name: str, description: str = None, rule_name: str = None):
|
||||
"""
|
||||
编辑权限。
|
||||
|
||||
:param name: 名称
|
||||
:param description: 描述
|
||||
:param rule_name: 规则名称
|
||||
:return: 权限对象
|
||||
"""
|
||||
assert name not in ('', None), '必须提供权限名称.'
|
||||
_permission: cls = await cls(name=name).async_find_first()
|
||||
assert _permission, f"未找到名称为:{name} 的权限."
|
||||
|
||||
_permission.description = description if description else _permission.description
|
||||
_permission.rule_name = rule_name if rule_name else _permission.rule_name
|
||||
await _permission.async_save()
|
||||
return _permission
|
||||
|
||||
@classmethod
|
||||
def identify_permission(cls):
|
||||
"""
|
||||
根据应用程序配置,从应用程序目录中识别所有的控制器,及其对应的路由。
|
||||
读取配置文件中关于 tornado 部分的配置,扫描配置包中的所有 Handler 类。
|
||||
识别需要授权的接口,即包含 auth_permission 装饰器的接口。
|
||||
忽略无需授权的接口,如:部分 OpenAPI 或 FrontendAPI。
|
||||
|
||||
:return: 识别到的控制器列表,注意列表中是 tuple(route, handler_type)
|
||||
"""
|
||||
apps_config: dict = config.get_config('tornado')
|
||||
_handlers: list[tuple[str, type]] = []
|
||||
|
||||
for _n, _app_cfgs in apps_config.items():
|
||||
for app_cfg in _app_cfgs:
|
||||
_handlers_pkg = app_cfg.get('handlers_pkg')
|
||||
_modules_itr = Application.modules_iterator(package=_handlers_pkg)
|
||||
for file_finder, handler_name, is_package in _modules_itr:
|
||||
if is_package:
|
||||
continue
|
||||
|
||||
_module = importlib.import_module(handler_name)
|
||||
_hls_list = Application.fetch_handlers(module=_module)
|
||||
for _hls in _hls_list:
|
||||
_uri, _hdl = _hls
|
||||
# 检查 post 或 get 是否被 auth_permission 装饰
|
||||
for method_name in ['post', 'get']:
|
||||
method = getattr(_hdl, method_name, None)
|
||||
if method and callable(method):
|
||||
if getattr(method, '__auth_permission__', False):
|
||||
_handlers.append(_hls)
|
||||
break
|
||||
|
||||
return _handlers
|
||||
|
||||
@classmethod
|
||||
async def import_permissions(cls):
|
||||
"""
|
||||
导入所有的可分配权限到数据库,若已经在数据库存在,则更新。
|
||||
"""
|
||||
_handlers = cls.identify_permission()
|
||||
|
||||
# 根据所有的路由信息,查出已经有的权限数据
|
||||
_routes: list[str] = [rc[0] for rc in _handlers]
|
||||
_item_model_list: list[cls] = await cls(**{cls.name.key: _routes}).async_find()
|
||||
|
||||
# 利用路由 Key 建立索引
|
||||
_item_model_dict: dict[str: cls] = {_item.name: _item for _item in _item_model_list}
|
||||
|
||||
_permissions: list[cls] = []
|
||||
for _route, _cls in _handlers:
|
||||
# 取得类描述
|
||||
_desc = f"{_cls.__doc__}".strip().split('\n')[0].strip(),
|
||||
# 利用路由取出模型
|
||||
_perm_item: cls = _item_model_dict.get(_route, None)
|
||||
if _perm_item is None:
|
||||
# 未得到模型,创建
|
||||
_perm_item = cls(**{cls.name.key: _route, cls.description.key: _desc})
|
||||
else:
|
||||
# 已得到模型,更新
|
||||
_perm_item.description = _desc
|
||||
_perm_item.close_session()
|
||||
|
||||
# 加入列表,批量保存
|
||||
_permissions.append(_perm_item)
|
||||
|
||||
# 保存数据
|
||||
_session = cls.get_aio_session()
|
||||
try:
|
||||
_session.add_all(_permissions)
|
||||
await _session.commit()
|
||||
except Exception as e:
|
||||
await _session.rollback()
|
||||
raise e
|
||||
finally:
|
||||
await _session.close()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.type = self.TYPE_PERMISSION
|
||||
@@ -0,0 +1,63 @@
|
||||
from sqlalchemy import delete
|
||||
|
||||
from paste.rbac.rbac_item import RbacItem
|
||||
|
||||
|
||||
class RbacRole(RbacItem):
|
||||
"""
|
||||
角色。
|
||||
是一系列关联角色或权限的组合。可以包含权限,也可以包含其他角色。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def create(cls, name: str, description: str = None, rule_name: str = None):
|
||||
"""
|
||||
创建角色。
|
||||
|
||||
:param name: 角色名称
|
||||
:param description: 角色描述
|
||||
:param rule_name: 规则名称
|
||||
:return: 角色对象
|
||||
"""
|
||||
assert name not in ('', None), '必须提供角色名称.'
|
||||
_role = cls(name=name, description=description, type=cls.TYPE_ROLE)
|
||||
_role.rule_name = rule_name if rule_name else _role.rule_name
|
||||
await _role.async_save()
|
||||
return _role
|
||||
|
||||
@classmethod
|
||||
async def delete(cls, name: str):
|
||||
"""
|
||||
删除角色。
|
||||
|
||||
:param name: 授权项名称
|
||||
:return: 操作状态,游标返回对象
|
||||
"""
|
||||
assert name not in ('', None), '必须提供角色名称.'
|
||||
_query = delete(cls).where(cls.name == name)
|
||||
_result = await cls.raw_execute(query=_query)
|
||||
_rowcount = _result.rowcount if isinstance(_result.rowcount, int) else 0
|
||||
return _rowcount > 0, _result
|
||||
|
||||
@classmethod
|
||||
async def modify(cls, name: str, description: str = None, rule_name: str = None):
|
||||
"""
|
||||
编辑角色。
|
||||
|
||||
:param name: 角色名称
|
||||
:param description: 描述
|
||||
:param rule_name: 规则名称
|
||||
:return: 角色对象
|
||||
"""
|
||||
assert name not in ('', None), '必须提供角色名称.'
|
||||
_role: cls = await cls(name=name).async_find_first()
|
||||
assert _role, f"未找到名称为:{name} 的角色."
|
||||
|
||||
_role.description = description if description else _role.description
|
||||
_role.rule_name = rule_name if rule_name else _role.rule_name
|
||||
await _role.async_save()
|
||||
return _role
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.type = self.TYPE_ROLE
|
||||
@@ -0,0 +1,147 @@
|
||||
import importlib
|
||||
import pickle
|
||||
|
||||
from sqlalchemy import select, text, delete
|
||||
|
||||
from paste.rbac.rbac_item import RbacItem
|
||||
from paste.rbac.rbac_models import RbacRuleModel
|
||||
from paste.rbac.rbac_permission import RbacPermission
|
||||
from paste.rbac.rbac_user import RbacUser
|
||||
|
||||
|
||||
class RbacRule(RbacRuleModel):
|
||||
"""
|
||||
规则是授权项的附带验证条件。在验证授权项时,只能判断是否具有某个授权项,无法对具体数据执行进一步验证。比如
|
||||
验证某一项数据是否允许某个用户执行某操作,此时就需要用到规则。
|
||||
|
||||
规则是在单独定义的验证方法,这个方法被持久化保存在数据库中,具体执行某个需要鉴权的操作时,若该权限配置了规
|
||||
则,那么规则方法会一并参与到鉴权过程中去,以确定用户是否有对具体数据执行操作的权限。
|
||||
|
||||
必须允许多个规则应用于一个授权项,但事实上是一个授权项自身只能绑定一个规则,解决方案是将一系列需要规则鉴权
|
||||
的操作作为规则权限的子授权项。这样,在对这个权限进行鉴权操作时,会自底向上逐个检查父权限是否有规则,若有规
|
||||
则,那么会进入这个父权限的规则方法,执行,并校验其返回值。
|
||||
|
||||
因此要允许手动创建授权项,且允许手动为授权项绑定规则,然后将其他同样需要执行该规则的权限配置为该权限的子权
|
||||
限。
|
||||
|
||||
由于在规则执行系统中,不知道未来将会编写和配置的规则,因此无法调用到对应的规则,只能将来编写好规则后,通过
|
||||
持久化对象到数据库中,通过查库还原将来的规则对象后再执行 run 方法。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def create(cls, full_class_name: str):
|
||||
"""
|
||||
添加规则。
|
||||
|
||||
:param full_class_name: 规则类
|
||||
:return: 保存状态、当前规则
|
||||
"""
|
||||
_rule_cls = cls.load_rule_class(full_class_name)
|
||||
assert _rule_cls, f"未找到名称为:{full_class_name} 的规则类."
|
||||
|
||||
_rule_model = _rule_cls()
|
||||
_rule_model.data = _rule_model.dumps()
|
||||
if not _rule_model.name:
|
||||
_rule_model.name = _rule_cls.__name__
|
||||
|
||||
await _rule_model.async_save()
|
||||
return _rule_model
|
||||
|
||||
@classmethod
|
||||
async def delete(cls, name: str):
|
||||
"""
|
||||
删除规则。
|
||||
|
||||
:param name: 要删除的规则名称
|
||||
:return: 是否删除成功、删除的行数
|
||||
"""
|
||||
assert name not in ('', None), '必须提供规则名称.'
|
||||
_query = delete(cls).where(cls.name == name)
|
||||
_result = await cls.raw_execute(query=_query)
|
||||
_rowcount = _result.rowcount if isinstance(_result.rowcount, int) else 0
|
||||
return _rowcount > 0, _rowcount
|
||||
|
||||
@classmethod
|
||||
async def modify(cls, name: str, full_class_name: str):
|
||||
"""
|
||||
编辑规则。
|
||||
|
||||
:param name: 名称
|
||||
:param full_class_name: 规则类
|
||||
:return: 保存状态、当前规则
|
||||
"""
|
||||
_rule_cls = cls.load_rule_class(full_class_name)
|
||||
assert _rule_cls, f"未找到名称为:{full_class_name} 的规则类."
|
||||
_rule_model = _rule_cls()
|
||||
|
||||
assert name not in ('', None), '必须提供规则名称.'
|
||||
_rule: cls = await cls(name=name).async_find_first()
|
||||
assert _rule, f"未找到名称为:{name} 的规则."
|
||||
|
||||
_rule.data = _rule_model.dumps()
|
||||
_rule.name = _rule_model.name
|
||||
if not _rule.name:
|
||||
_rule.name = _rule_cls.__name__
|
||||
|
||||
await _rule.async_save()
|
||||
return _rule
|
||||
|
||||
@classmethod
|
||||
async def find_by_item_names(cls, item_names: set[str]):
|
||||
"""
|
||||
根据授权项名称(权限名称或角色名称)取得所有角色。
|
||||
|
||||
:param item_names: 授权项名称列表
|
||||
:return: 规则列表
|
||||
"""
|
||||
# 取出所有授权项中的规则,忽略没有规则的
|
||||
_query = select(cls).join(
|
||||
RbacItem, RbacItem.rule_name == cls.name
|
||||
).where(
|
||||
RbacItem.name.in_(item_names),
|
||||
text(f"ifnull({RbacItem.rule_name.key},'')!=''")
|
||||
)
|
||||
_rule_list: list[cls] = await cls.query_all(_query)
|
||||
return _rule_list
|
||||
|
||||
@classmethod
|
||||
def load_rule_class(cls, full_class_name: str):
|
||||
"""
|
||||
通过规则类名称,加载规则类。若类所在的模块不存在,则报异常。
|
||||
|
||||
:param full_class_name: 完整规则名称,从顶层模块名称开始,直到类名称。
|
||||
:return: 找到的规则类,找不到返回 None
|
||||
"""
|
||||
_full_paths = full_class_name.split('.')
|
||||
_cls_name = _full_paths[-1]
|
||||
_mod_name = '.'.join(_full_paths[:-1])
|
||||
|
||||
try:
|
||||
_module = importlib.import_module(_mod_name)
|
||||
# 迭代模块成员
|
||||
for _n in dir(_module):
|
||||
_cls = getattr(_module, _n)
|
||||
if isinstance(_cls, type) and issubclass(_cls, RbacRule) and _cls.__name__ == _cls_name:
|
||||
return _cls
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def run(self, rbac_user: RbacUser, rbac_permission: RbacPermission, *args, **kwargs) -> bool:
|
||||
"""
|
||||
运行规则。当用户在执行具体操作时,该规则会自动被唤起执行。
|
||||
|
||||
:param rbac_user: 用户
|
||||
:param rbac_permission: 权限对象
|
||||
:return: 允许执行返回 True, 否则返回 False
|
||||
"""
|
||||
return True
|
||||
|
||||
def dumps(self):
|
||||
"""
|
||||
序列化为可持久化文本。
|
||||
|
||||
:return: 可持久化文本
|
||||
"""
|
||||
return pickle.dumps(self)
|
||||
@@ -0,0 +1,259 @@
|
||||
import pickle
|
||||
from typing import Optional, Awaitable
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from paste.rbac.rbac_assignment import RbacAssignment
|
||||
from paste.rbac.rbac_item import RbacItem
|
||||
from paste.rbac.rbac_models import RbacUserModel
|
||||
from paste.rbac.rbac_permission import RbacPermission
|
||||
from paste.rbac.rbac_role import RbacRole
|
||||
from paste.security import shash
|
||||
|
||||
Supervisors = ('administrator', 'root', 'supervisor')
|
||||
"""
|
||||
超级管理员名称,这些名称不能用于一般用户。
|
||||
"""
|
||||
|
||||
|
||||
class RbacUser(RbacUserModel):
|
||||
"""
|
||||
RBAC 用户类。
|
||||
"""
|
||||
|
||||
STATUS_DEFAULT = 0b00000000000000000000000000000
|
||||
"""
|
||||
用户默认状态:0。
|
||||
"""
|
||||
STATUS_ENABLED = 0b00000000000000000000000000001
|
||||
"""
|
||||
用户激活状态:1。
|
||||
"""
|
||||
STATUS_DISABLED = 0b00000000000000000000000000010
|
||||
"""
|
||||
用户禁用状态:2。
|
||||
"""
|
||||
STATUS_DELETED = 0b00000000000000000000000000100
|
||||
"""
|
||||
用户删除状态:4。
|
||||
"""
|
||||
|
||||
STATUS_LIST = [STATUS_DEFAULT, STATUS_ENABLED, STATUS_DISABLED, STATUS_DELETED]
|
||||
"""
|
||||
允许的所有用户状态。
|
||||
"""
|
||||
|
||||
STATUS_DESCRIPTION = {
|
||||
STATUS_DEFAULT: '默认',
|
||||
STATUS_ENABLED: '激活',
|
||||
STATUS_DISABLED: '已禁用',
|
||||
STATUS_DELETED: '已删除',
|
||||
}
|
||||
"""
|
||||
用户状态描述。
|
||||
"""
|
||||
|
||||
TYPE_USER = 'user'
|
||||
"""
|
||||
用户类型:用户。
|
||||
"""
|
||||
|
||||
TYPE_ADMINISTRATOR = 'admin'
|
||||
"""
|
||||
用户类型:管理员。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def import_supervisors(cls):
|
||||
"""
|
||||
导入超级用户。
|
||||
|
||||
:return: 初始化状态
|
||||
"""
|
||||
# 查出已经有的超级用户
|
||||
_user_model_list: list[cls] = await cls(**{cls.username.key: Supervisors}).async_find()
|
||||
_user_model_dict: dict[str: cls] = {_user.username: _user for _user in _user_model_list}
|
||||
|
||||
init_status: bool = True
|
||||
for _username in Supervisors:
|
||||
_user: cls = _user_model_dict.get(_username, None)
|
||||
if _user is None:
|
||||
# 用户不存在,创建超级用户
|
||||
_save_status, _ = await cls.create(username=_username, password=_username)
|
||||
|
||||
return init_status
|
||||
|
||||
@classmethod
|
||||
async def create(cls, username: str, password: str):
|
||||
"""
|
||||
创建用户。
|
||||
|
||||
:param username: 用户名
|
||||
:param password: 密码
|
||||
:return: 保存状态
|
||||
"""
|
||||
assert username is not None and password is not None, '必须提供用户名和密码.'
|
||||
_usr_model = cls()
|
||||
_usr_model.before_save()
|
||||
_usr_model.username = username
|
||||
_usr_model.password_hash = shash.generate_password_hash(pwd=password)
|
||||
_status = await _usr_model.async_save()
|
||||
return _status, _usr_model
|
||||
|
||||
@classmethod
|
||||
async def find_by_username(cls, username: str):
|
||||
"""
|
||||
根据用户名查找用户。
|
||||
|
||||
:param username: 用户名
|
||||
:return: 用户对象
|
||||
"""
|
||||
query = select(cls).where(cls.username == username)
|
||||
model = await cls.query_first(query)
|
||||
return model
|
||||
|
||||
@classmethod
|
||||
def status_description(cls, status):
|
||||
"""
|
||||
取得状态描述。
|
||||
|
||||
:param status:
|
||||
:return:
|
||||
"""
|
||||
if status in cls.STATUS_DESCRIPTION:
|
||||
return cls.STATUS_DESCRIPTION[status]
|
||||
else:
|
||||
return '状态未知'
|
||||
|
||||
async def assign(self, item_names: set[str]):
|
||||
"""
|
||||
为用户分配权限。这里调用了权限分配器的分配方法,自动剔除已经分配过的角色或权限。
|
||||
|
||||
:param item_names: 授权项名称列表
|
||||
"""
|
||||
await RbacAssignment.assign(user_id=self.id, item_names=item_names)
|
||||
|
||||
async def can(self, permission_name: str, **kwargs) -> bool:
|
||||
"""
|
||||
验证用户是否具有 permission_name 权限。
|
||||
|
||||
该方法主要是调用 :class:`RbacRule` 的 run() 方法,执行规则检验。
|
||||
|
||||
在执行规则验证的时候是自底向上,查询父 RbacItem 中的规则,并调用其 run 方法。调用时不分先后,随机执行。
|
||||
|
||||
只要有一个规则返回 False,则后续规则方法不再继续执行。
|
||||
|
||||
:param permission_name: 权限名称
|
||||
:param kwargs: 可选参数,传递给规则的 run 方法的参数
|
||||
:return: 验证状态
|
||||
"""
|
||||
from paste.rbac.rbac_rule import RbacRule
|
||||
|
||||
# 如果用户没有初始化,直接禁止
|
||||
if self.username is None:
|
||||
return False
|
||||
|
||||
# 如果是超级用户,直接返回 True
|
||||
if self.is_supervisors():
|
||||
return True
|
||||
|
||||
# 取得所有授权项,然后取得所有对应规则
|
||||
_item_names = await self.get_all_permissions()
|
||||
_rule_list: list[RbacRule] = await RbacRule.find_by_item_names(_item_names)
|
||||
|
||||
# 取出授权项
|
||||
_permission: RbacPermission = await RbacPermission.find_by_name(permission_name)
|
||||
|
||||
# 遍历执行 run 方法
|
||||
for _rule in _rule_list:
|
||||
# 还原持久化数据到对象
|
||||
_rule: RbacRule = pickle.loads(_rule.data)
|
||||
|
||||
# 执行 run 方法,若是协程,则继续等待协程完成
|
||||
_result = _rule.run(rbac_user=self, rbac_permission=_permission, **kwargs)
|
||||
if isinstance(_result, Awaitable):
|
||||
_result = await _result
|
||||
|
||||
if not _result:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def get_all_roles(self):
|
||||
"""
|
||||
取得所有角色名称。
|
||||
|
||||
:return: 角色名称列表
|
||||
"""
|
||||
_roles = await self.roles()
|
||||
_role_names = [_r.name for _r in _roles]
|
||||
return _role_names
|
||||
|
||||
async def get_all_permissions(self):
|
||||
"""
|
||||
取得所有授权项的名称,包含角色和权限。
|
||||
|
||||
:return: 授权项名称列表
|
||||
"""
|
||||
_dir_perms = await self.get_direct_permissions()
|
||||
_inh_perms = await self.get_inherit_permissions(direct_perms=_dir_perms)
|
||||
_dir_perms = _dir_perms.union(_inh_perms)
|
||||
return _dir_perms
|
||||
|
||||
async def get_direct_permissions(self):
|
||||
"""
|
||||
通过权限分配器查询用户直接拥有的角色和权限。
|
||||
|
||||
:return: 授权项名称列表
|
||||
"""
|
||||
_query = select(RbacAssignment.item_name, RbacAssignment.user_id).where(RbacAssignment.user_id == self.id)
|
||||
_item_names = await RbacAssignment.query_all(_query)
|
||||
return set(_item_names)
|
||||
|
||||
async def get_inherit_permissions(self, direct_perms: Optional[set[str]] = None):
|
||||
"""
|
||||
通过直接拥有的权限查询继承而来的角色和权限。
|
||||
继承而来的权限包括用户所有角色的权限及其子权限。
|
||||
|
||||
:param direct_perms: 用户直接拥有的权限名称列表
|
||||
:return: 授权项名称列表
|
||||
"""
|
||||
# 若不提供用户直接拥有的权限,则查询
|
||||
if direct_perms is None:
|
||||
direct_perms = await self.get_direct_permissions()
|
||||
|
||||
_perm_names = await RbacItem.all_children(item_names=direct_perms)
|
||||
return _perm_names
|
||||
|
||||
async def has_permission(self, permission_name: str):
|
||||
"""
|
||||
验证用户是否有执行某个路由的权限。
|
||||
|
||||
:param permission_name: 路由模式,一般为 route_pattern
|
||||
:return: 是否有权限
|
||||
"""
|
||||
_permissions = await self.get_all_permissions()
|
||||
return permission_name in _permissions
|
||||
|
||||
async def roles(self):
|
||||
"""
|
||||
通过中间关系直接查询用户的所有角色。
|
||||
|
||||
:return: 角色列表
|
||||
"""
|
||||
_query = select(RbacRole).join(
|
||||
RbacAssignment, RbacAssignment.item_name == RbacRole.name
|
||||
).where(
|
||||
RbacRole.type == RbacRole.TYPE_ROLE,
|
||||
RbacAssignment.user_id == self.id
|
||||
)
|
||||
_roles: list[RbacRole] = await RbacRole.query_all(_query)
|
||||
return _roles
|
||||
|
||||
def is_supervisors(self):
|
||||
"""
|
||||
检查是否是超级用户。
|
||||
|
||||
:return: 是超级用户放回 True,否则返回 False
|
||||
"""
|
||||
return self.username in Supervisors
|
||||
@@ -0,0 +1,19 @@
|
||||
from paste.core import logging
|
||||
from paste.rbac.rbac_permission import RbacPermission
|
||||
from paste.rbac.rbac_rule import RbacRule
|
||||
from paste.rbac.rbac_user import RbacUser
|
||||
|
||||
|
||||
class TestRule(RbacRule):
|
||||
"""
|
||||
测试规则类。实际编写时要注意父类继承关系。
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.name = '测试规则'
|
||||
self.data = self.dumps()
|
||||
|
||||
def run(self, rbac_user: RbacUser, rbac_permission: RbacPermission, *args, **kwargs):
|
||||
logging.echo_log(f"正在运行规则:{self.name}.")
|
||||
return True
|
||||
Reference in New Issue
Block a user