轻量级、框架无关、基于上下文的RBAC授权库
项目描述
Authoritah - 框架无关的Python RBAC库
Authoritah是一个Python RBAC库。它被设计为框架无关,这意味着它与任何Web框架或ORM库都没有耦合。此外,Authoritah提供了一个基于上下文角色解析的独特方法,提供了一个高度细粒度的角色系统。
兼容性
我们在Python 3.5、3.6、3.7和Pypy 3上测试了Authoritah。可能较老的Authoritah版本也能与Python 2.7兼容,但低于Python 3.5的版本不支持。
安装
安装Authoritah最简单的方法是通过pip
pip install authoritah
概述 & 术语
以下术语在许多授权框架中都很常见,但在authoritah中可能有特定的含义,因此首先需要明确它们。
身份
简单来说,身份就是用户 - 使用系统的实体(无论是人、通过API密钥进行身份验证的机器、默认的“匿名”用户等)。身份可以有角色 - 在authoritah中,身份的角色通常与给定的上下文对象相关,尽管身份也可以有默认角色。
在authoritah中,不期望使用特定结构来定义身份对象 - 对于库来说,它们是透明的,只是在您提供的不同可调用对象之间传递。
角色
将角色分配给一个身份,并定义一组权限 - 用户被允许在对象或系统中执行的操作。
一个身份可以拥有多个角色(例如,一个用户可能既是招聘经理又是内容编辑)。此外,角色可以继承一个或多个其他角色的权限(例如,一个内容编辑可以继承自内容查看者)。
与其他许多授权框架不同,在authoritah中,角色不是全局的(尽管可以是),而是从上下文中派生的 - 例如,一个用户可能对所有文章都是内容编辑,或者可能仅对自己创建的文章是内容编辑,对所有其他文章是内容查看者。
权限
简单来说,权限是“基于给定角色的身份授予的权利”。例如,具有内容编辑角色的某人拥有文章编辑、文章发布和文章取消发布的权限。
在系统中实现授权检查通常涉及在执行操作之前检查用户是否被授予一个或多个权限。
严格模式
可以通过将Authorizer类实例化时使用strict=True(默认为False)来启用严格模式。
严格模式在两种情况下会引发异常
- 如果为未在任何定义的角色中定义的权限调用is_allowed。
- 如果提供给is_allowed的身份中的角色未定义
这有助于检查是否忘记添加角色或权限。
上下文对象
上下文对象是操作执行的对象。例如,当编辑文章时,上下文对象是文章。正如所述,在authoritah中,上下文对象比许多其他授权框架具有更中心的角色 - 在决定用户的角色时会考虑它们。
快速入门
以下是如何使用authoritah将授权应用到您的代码中的快速入门指南。
我们将以一个具有3个对象(文章、评论和用户)的虚构、简化的内容管理系统为例。
1. 定义角色和权限
与任何RBAC系统一样,建议从定义一些角色以及它们授予的权限开始。在Authoritah中,建议将角色与系统中的对象联系起来考虑。
您可以在配置文件中定义您的角色和权限,例如YAML
或JSON
,甚至是Python dict
。
---
viewer:
- article_list
- article_view
- comment_list
- comment_view
- user_create
user:
parents: [ 'viewer' ]
grants:
- comment_create
- comment_upvote
contributor:
parents: [ 'user' ]
grants:
- article_create
content_admin:
- comment_edit
- comment_delete
- article_edit
- article_delete
user_admin:
- user_edit
- user_delete
super_admin:
parents:
- contributor
- content_admin
- user_admin
需要注意的一些事项
- 每个
role
都有一个唯一的键(或名称),并可选地定义了一个权限列表(grants
)。 - 角色可以从其他角色继承权限。这是通过提供继承角色的列表作为
parents
来完成的。 - 作为一个快捷方式,您可以简单地提供授予的权限列表来定义一个角色,而跳过
grants
键。这种格式对于不继承任何其他角色的角色来说工作得很好。
最重要的是,角色是以尽可能细粒度的方式定义的,遵循最小访问原则。在authoritah中思考权限的正确方式是考虑具有给定角色的人是否应该在所有情况下或仅在特定上下文中拥有特定的权限。
在我们的例子中,contributor
可以创建任何文章,但不能删除任何文章 - 只能删除自己的文章。稍后,我们将看到如何使用动态角色解析将特定用户提升为特定上下文中的content_admin
,这样他们就可以编辑和删除自己的文章。
2. 初始化Authorizer并挂钩身份管理
您将使用authoritah所做的一切都始于创建一个Authorizer
对象。假设我们从名为authorization.yml
的YAML文件中读取角色和权限配置,以下是这样做的方法
import yaml
from authoritah import Authorizer
with open('authorization.yml') as f:
roles = yaml.safe_load(f)
authz = Authorizer(permissions=roles)
由于authoritah并未绑定到任何认证或身份管理实现,您需要告知您的授权者如何获取身份对象,以及如何从该对象中获取角色列表。
完成此操作最简单的方法是使用我们刚刚创建的Authorizer
对象的两个装饰器方法:authz.identity_provider
和authz.default_role_provider
@authz.identity_provider
def get_current_user(request):
"""This function returns the current authenticated user object
"""
return request.user
@authz.default_role_provider
def get_user_roles(user, context=None):
"""Get roles for the current user.
This function should always accept an identity object (as returned
by the defined identity provider) and return either a list of roles,
a string representing a single role, or None for a user with no roles.
Note that this function also receives the current context object. It
may be used, if needed, to infer roles - but this is usually not
recommended.
"""
return user.roles
在大多数情况下,这两个函数应该非常简单——它们只是将您的现有代码与authoritah集成的“胶水”。
3. 定义上下文特定角色解析
如果您的系统不需要任何动态角色解析(例如,权限是全局的,与您的上下文对象无关),则可以跳过此阶段,像使用其他任何RBAC库一样使用authoritah。
然而,在大多数情况下,您可能希望根据用户访问的对象(上下文对象)给予用户额外的角色。
这可以通过使用authz.context_role_provider
装饰器来实现。这个装饰器应该用来装饰类,指定当上下文对象为特定类型时,我们应该如何获取角色。
假设我们的CRM有一个ORM或实体类,它代表一篇文章
class Article:
title = None
content = None
created_by = None
您现在可以使用一个装饰器来告诉Authoritah,article_user_roles
是Article
类型或其任何子类型的角色提供者
@authz.role_provider(Article)
def article_user_roles(user, article):
if user.id == article.created_by:
return ['content_admin']
return []
因此,我们已经告诉我们的授权者,每当上下文对象是Article
对象时,就调用article_user_roles
。此可调用返回的角色列表将被附加到用户已有的现有全局角色列表中。这样,我们就知道如果用户是该文章的创建者,他们应该获得与拥有content_admin
角色相同的权限(这意味着他们可以编辑或删除这篇文章)。
另一种实现方式是在上下文对象类上使用class_role_provider
注解,并提供一个同一类中的方法名作为上下文角色提供者
@authz.class_role_provider('user_roles')
class ProtectedArticle(Article):
def user_roles(self, user):
if user.id == self.created_by:
return ['content_admin']
return []
请注意,此情况仅适用于ProtectedArticle
对象,而不是原始的Article
基类。然而,它确实适用于从ProtectedArticle
继承的任何类。
4. 在您的代码中应用授权检查
最后但非常重要的一点,在执行某些操作之前,开始检查权限。
有两种常见的方法可以这样做。一种是明确的
def modify_article(article_id, data):
"""Assume this is your Web framework's handler for article editing
"""
article = DB.article.get(article_id)
if not authz.is_allowed('article_edit', article):
return 'You are not allowed to edit this article', 403
# ... proceed to update the article
另一种是使用装饰器,这对于对象方法(其中对象是我们的上下文对象)效果很好。让我们更新之前的类定义
@authz.class_role_provider('user_roles')
class ProtectedArticle(Article):
@authz.require('article_edit')
def modify(self, new_data):
# ... proceed to modify my own attributes
pass
def user_roles(self, user):
if user.id == self.created_by:
return ['content_admin']
return []
在上面的示例中,如果用户没有article_edit
权限,调用modify()
将引发一个authoritah.NotAuthorized
异常,然后您需要捕获并处理它。
背景:为什么基于上下文的角色解析?
在大多数RBAC/ACL框架中,每个用户都会被分配一个或多个预定义的角色,这些角色反过来决定他们执行各种操作时的权限。这在许多情况下都很好,但当静态权限不足以决定用户是否应该被允许执行操作时,这种方法就不够了。
例如,在一个内容管理系统中,一个用户可能有一个“编辑”角色,允许他们编辑文章。在一个“平坦”系统中,所有编辑都可以编辑所有文章,这种情况效果很好。
但是,如果我们希望用户只能编辑他们创建的文章,或者如果我们希望用户能够为文章指定特定的编辑,那么授予全局的“编辑”角色就不够了。
现有解决方案:事后断言
大多数当前的RBAC库通过在静态角色和权限之上添加动态断言功能来解决这个问题。它们允许开发者为每个授予的权限指定额外的断言可调用。一旦用户被授予根据其角色执行某个操作的权利,就会执行额外的断言,实际上是在检查在用户和上下文对象(在我们的例子中是正在编辑的文章)的情况下,是否还应授予该权限。
遗憾的是,这有几个主要的缺点
- 随着权限变得更加粒度化以及系统中权限数量的增加,编写自定义断言很快就变得繁琐。
- 这种模型强制采用授予最大权限的角色的方法。将权限仅限于在特定上下文中应用是事后考虑的事情。
引入一种新的方法:基于上下文的角色解析
在authoritah中,用户的角色不是静态的,而是根据上下文对象而变化。本质上,我们不是问“这个用户的角色是什么?”,而是问“在这个对象的情况下,用户的角色是什么?”。一旦角色被动态决定,就很容易授予或拒绝执行某个操作的权利,而无需任何额外的断言。
此外,它倡导一个过程,即通过每个角色最初授予最小权限。在适当的上下文中,用户可能会通过分配给他们的额外角色获得额外的权限。
我们认为,这降低了权限泄露的风险,因为它鼓励一种更加粒度化和受限的方法来授予权限。
许可证
版权(c)2017 Shoppimon LTD
根据Apache许可证版本2.0(“许可证”);除非适用法律要求或经书面同意,否则您不得使用此文件,除非遵守许可证。您可以在以下位置获得许可证副本:
https://apache.ac.cn/licenses/LICENSE-2.0
除非适用法律要求或经书面同意,否则在许可证下分发的软件按“原样”分发,不提供任何明示或暗示的保证或条件。有关许可证中具体规定许可和限制的条款,请参阅许可证。
项目详情
下载文件
下载适用于您平台的应用程序文件。如果您不确定选择哪个,请了解有关安装包的更多信息。
源分布
构建分布
authoritah-0.2.0.tar.gz的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | 2542946de9b8ac978703b3bc732c6ef11538c610ce867e454fc8617282dc1742 |
|
MD5 | d59359898b03526db8be95cdd1c2171c |
|
BLAKE2b-256 | 20d9605b961bedf0dc446d80083d63aed78a5d4af402465e17f07dcb76f2a875 |