1个不稳定版本
0.1.0 | 2020年8月10日 |
---|
#14 in #acl
51KB
494 行
zorq-acl
轻量级且灵活的权限管理访问控制列表(ACL)实现。
这是对laminas-permissions-acl的Rust实现。
入门指南
zorq-acl可在crates.io上找到。在那里查找最新发布的版本以及文档链接。
将以下依赖项添加到您的Cargo清单...
[dependencies]
zorq-acl = "0.1.0"
lib.rs
:
轻量级且灵活的权限管理访问控制列表(ACL)实现。
这是对laminas-permissions-acl的Rust实现。以下文档是对原版文档的复制和采用。有关Laminas项目的版权声明,请参阅CREDITS文件。
原实现中缺少什么?
- 删除访问控制。这将在未来的版本中通过
revoke
方法实现。 - 所有权断言和角色和资源接口。所有权断言可能通过定义角色和资源接口的特质和在将来扩展API来实现。
- 表达式断言。这可能在未来的版本中实现。
介绍
通常,应用程序可以利用ACL通过请求对象来允许或拒绝对资源的访问。
在这个实现的意义上
- 一个资源是受访问控制的对象。
- 一个角色是可以请求访问资源的对象。
- 一个权限是可能授予角色在资源上执行的操作。
资源
资源组织在树状结构中,并且必须具有唯一的名称。由于资源存储在树状结构中,因此可以从一般(树根)到具体(树叶)进行组织。对特定资源的查询将自动搜索分配给祖先资源的规则,允许规则的简单继承。例如,如果要对城市中的每座建筑应用默认规则,只需将该规则分配给城市,而不是将相同的规则分配给每座建筑。某些建筑可能需要对此类规则的例外,可以通过将此类例外规则分配给需要此类例外的每座建筑来实现。资源可能只从单个父资源继承,尽管该父资源可以有自己的父资源等。
资源上的权限(例如,“创建”,“读取”,“更新”,“删除”)也得到支持,因此可以分配影响所有权限或一个或多个资源的特定权限的规则。
角色
一个角色可以继承一个或多个角色。这是为了支持角色之间规则的继承。例如,一个用户角色,如“sally”,可能属于一个或多个父角色,如“editor”和“administrator”。开发者可以分别对“editor”和“administrator”分配规则,“sally”将继承这两者的规则,而无需直接对“sally”分配规则。
虽然从多个角色中继承的能力非常有用,但多重继承也引入了一定程度的复杂性。以下示例说明了模糊条件以及如何通过 Acl
解决它。
角色间的多重继承
以下代码定义了三个基本角色 - "guest"、"member" 和 "admin" - 其他角色可以从这些角色继承。然后,创建了一个名为 "someUser" 的角色,并从这三个角色中继承。这些角色在 parents
数组中出现的顺序很重要。当需要时,搜索访问不仅包括为查询的角色(在此处为 "someUser")定义的规则,还包括从这些角色继承的角色(在此处为 "guest"、"member" 和 "admin")的规则。
acl.add_role("guest", vec![]);
acl.add_role("member", vec![]);
acl.add_role("admin", vec![]);
let parents = vec!["guest", "member", "admin"];
acl.add_role("someUser", parents);
acl.add_resource("someResource", None);
acl.deny(Some("guest"), Some("someResource"), None);
acl.allow(Some("member"), Some("someResource"), None);
assert!(acl.is_allowed(Some("someUser"), Some("someResource"), None));
由于没有为 "someUser" 角色和 "someResource" 特定定义规则,因此 Acl
必须搜索可能为 "someUser" 继承的角色定义的规则。首先,访问 "admin" 角色时,没有为其定义访问规则。接下来,访问 "member" 角色时,Acl
发现存在一个规则指定 "member" 可以访问 "someResource"。
然而,如果 Acl
继续检查其他父角色的规则,它将发现 "guest" 被拒绝访问 "someResource"。这一事实引入了歧义,因为现在 "someUser" 由于从不同的父角色继承冲突规则,既被拒绝又被允许访问 "someResource"。
Acl
通过在找到第一个适用于查询的直接适用规则时完成查询来解决这个问题。在这种情况下,由于 "member" 角色在 "guest" 角色之前被检查,因此示例代码断言得到满足,因此允许访问。
角色查询的 LIFO 顺序:在指定角色的多个父级时,请记住,最后一个列出的父级是首先搜索适用于授权查询的规则的。
创建访问控制列表
访问控制列表(ACL)可以代表您希望表示的任何物理或虚拟对象集。但是,为了演示目的,我们将创建一个基本的基于内容的系统(CMS)ACL,该ACL维护广泛的区域中的多个层级组。要创建新的ACL对象,我们实例化ACL。构造函数没有参数。
use zorq_acl::Acl;
let mut acl = Acl::new();
默认拒绝
直到开发人员指定一个 "允许" 规则,Acl
将拒绝每个角色对每个资源的每个权限的访问。
注册角色
CMS系统几乎总是需要层次化的权限来确定用户的创作能力。可能有 "Guest" 组允许演示的有限访问,"Staff" 组用于大多数CMS用户,他们执行大部分日常操作,"Editor" 组负责发布、审阅、存档和删除内容,最后是 "Administrator" 组,其任务可能包括其他组的所有任务,以及维护敏感信息、用户管理、后端配置数据、备份和导出。这组权限可以用角色注册表示,允许每个组从 "父" 组继承权限,同时也为它们的唯一组提供不同的权限。权限可以表示如下:
名称 | 唯一权限 | 从以下权限继承 |
---|---|---|
Guest | 查看 | 无 |
Staff | 编辑、提交、修订 | Guest |
编辑器 | 发布、存档、删除 | Staff |
管理员 | (授予所有访问权限) | 无 |
这些组可以按如下方式添加到角色注册表中
acl.add_role("guest", vec![]);
acl.add_role("staff", vec!["guest"]);
acl.add_role("editor", vec!["staff"]);
acl.add_role("admin", vec![]);
定义访问控制
现在ACL包含了相关角色,可以建立规则来定义角色如何访问资源。你可能已经注意到,我们没有为这个例子定义任何特定的资源,这是为了简化示例,说明规则适用于所有资源。《code>Acl提供了一个实现,其中规则只需从通用分配到特定,以最小化所需规则的数量,因为资源和角色继承了它们祖先上定义的规则。
具体性:一般来说,《code>Acl遵循给定的规则,当且仅当没有更具体的规则适用时。
因此,我们可以用最少的代码定义一组相当复杂的规则。为了应用上述定义的基本权限
// guest may only view content
acl.allow(Some("guest"), None, Some("view"));
// staff inherits view privilege from guest, but also needs additional privileges
acl.allow(Some("staff"), None, Some("edit"));
acl.allow(Some("staff"), None, Some("submit"));
acl.allow(Some("staff"), None, Some("revise"));
// editor inherits view, edit, submit, and revise privileges from staff, but also needs
// additional privileges
acl.allow(Some("editor"), None, Some("publish"));
acl.allow(Some("editor"), None, Some("archive"));
acl.allow(Some("editor"), None, Some("delete"));
// admin inherits nothing, but is allowed all privileges
acl.allow(Some("admin"), None, None);
上述《code>None值在《code>allow()调用中用于表示允许规则适用于所有资源。《code>None等于通配符。
查询ACL
我们现在有一个灵活的ACL,可以用来确定请求者是否有权在整个Web应用程序中执行功能。使用《code>is_allowed或《code>is_denied方法进行查询非常简单
// allowed
assert!( acl.is_allowed(Some("guest"), None, Some("view")));
assert!(!acl.is_denied (Some("guest"), None, Some("view")));
// denied
assert!(!acl.is_allowed(Some("staff"), None, Some("publish")));
assert!( acl.is_denied (Some("staff"), None, Some("publish")));
// allowed
assert!( acl.is_allowed(Some("staff"), None, Some("revise")));
assert!(!acl.is_denied (Some("staff"), None, Some("revise")));
// allowed because of inheritance from guest
assert!( acl.is_allowed(Some("editor"), None, Some("view")));
assert!(!acl.is_denied (Some("editor"), None, Some("view")));
// denied because no allow rule for 'update'
assert!(!acl.is_allowed(Some("editor"), None, Some("update")));
assert!( acl.is_denied (Some("editor"), None, Some("update")));
// allowed because admin is allowed all privileges
assert!( acl.is_allowed(Some("admin"), None, Some("view")));
assert!(!acl.is_denied (Some("admin"), None, Some("view")));
// allowed because admin is allowed all privileges
assert!( acl.is_allowed(Some("admin"), None, None));
assert!(!acl.is_denied (Some("admin"), None, None));
// allowed because admin is allowed all privileges
assert!( acl.is_allowed(Some("admin"), None, Some("update")));
assert!(!acl.is_denied (Some("admin"), None, Some("update")));
精确访问控制
在上一节中定义的基本ACL显示了如何在整个ACL(所有资源)上允许各种权限。然而,在实际应用中,访问控制往往有例外,并且具有不同的复杂程度。《code>Acl允许你以直接和灵活的方式完成这些改进。
对于示例CMS,已经确定虽然“staff”组满足了绝大多数用户的需求,但还需要一个新的“marketing”组来访问CMS中的时事通讯和最新新闻。该组相对独立,将能够发布和存档时事通讯和最新新闻。
此外,还要求“staff”组可以查看新闻故事,但不能修改最新新闻。最后,应确保任何人(包括管理员)都无法存档任何“公告”新闻故事,因为它们的寿命只有1-2天。
首先,我们修改角色注册表以反映这些变化。我们已经确定“marketing”组与“staff”组具有相同的基本权限,因此我们以继承“staff”权限的方式定义“marketing”
acl.add_role("marketing", vec!["staff"]);
接下来,注意上述访问控制涉及特定的资源(例如,“时事通讯”,“最新新闻”,“公告新闻”)。现在我们添加这些资源
acl.add_resource("newsletter", None);
acl.add_resource("news", None);
acl.add_resource("latest", Some("news"));
acl.add_resource("anouncement", Some("news"));
然后,只需在ACL的目标区域定义这些更具体的规则
// marketing must be able to publish and archive newsletters and the latest news
acl.allow(Some("marketing"), Some("newsletter"), Some("publish"));
acl.allow(Some("marketing"), Some("newsletter"), Some("archive"));
acl.allow(Some("marketing"), Some("latest"), Some("publish"));
acl.allow(Some("marketing"), Some("latest"), Some("archive"));
// staff (and marketing, by inheritance), are denied permission
// to revise the latest news
acl.deny(Some("staff"), Some("latest"), Some("revise"));
// everyone (including admins) are denied permission to archive news announcements
acl.deny(None, Some("anouncement"), Some("archive"));
现在我们可以根据最新的变化查询ACL
// denied
assert!(!acl.is_allowed(Some("staff"), Some("newsletter"), Some("publish")));
assert!( acl.is_denied (Some("staff"), Some("newsletter"), Some("publish")));
// allowed
assert!( acl.is_allowed(Some("marketing"), Some("newsletter"), Some("publish")));
assert!(!acl.is_denied (Some("marketing"), Some("newsletter"), Some("publish")));
// denied
assert!(!acl.is_allowed(Some("staff"), Some("latest"), Some("publish")));
assert!( acl.is_denied (Some("staff"), Some("latest"), Some("publish")));
// allowed
assert!( acl.is_allowed(Some("marketing"), Some("latest"), Some("publish")));
assert!(!acl.is_denied (Some("marketing"), Some("latest"), Some("publish")));
// allowed
assert!( acl.is_allowed(Some("marketing"), Some("latest"), Some("archive")));
assert!(!acl.is_denied (Some("marketing"), Some("latest"), Some("archive")));
// denied
assert!(!acl.is_allowed(Some("marketing"), Some("latest"), Some("revise")));
assert!( acl.is_denied (Some("marketing"), Some("latest"), Some("revise")));
// denied
assert!(!acl.is_allowed(Some("editor"), Some("anouncement"), Some("archive")));
assert!( acl.is_denied (Some("editor"), Some("anouncement"), Some("archive")));
// denied
assert!(!acl.is_allowed(Some("admin"), Some("anouncement"), Some("archive")));
assert!( acl.is_denied (Some("admin"), Some("anouncement"), Some("archive")));
依赖关系
~90KB