ACL 的数据库架构

发布于 2024-11-04 13:03:23 字数 906 浏览 1 评论 0原文

我想为 ACL 创建一个架构;然而,我在几种实现它的方法之间左右为难。

我很确定我不想处理级联权限,因为这会导致后端和站点管理员产生很多混乱。

我想我也可以接受用户一次只扮演一种角色的情况。这样的设置将允许随着站点的增长根据需要添加角色和权限,而不会影响现有的角色/规则。

首先,我打算对数据进行标准化,并使用三个表来表示关系。

ROLES { id, name }
RESOURCES { id, name }
PERMISSIONS { id, role_id, resource_id }

确定某处是否允许某个用户的查询如下所示:

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

然后我意识到我只有大约 20 个资源,每个资源最多有 5 个操作(创建、更新、查看等),也许还有另外 8 个角色。这意味着我可以公然无视数据标准化,因为我永远不会有超过几百条可能的记录。

所以也许这样的模式会更有意义。

ROLES { id, name }
PERMISSIONS { id, role_id, resource_name }

这将允许我在单个查询中查找记录

SELECT * FROM permissions WHERE role_id = ? AND permission  = ? ($user_role_id, 'post.update')

那么哪个更正确? ACL 是否还有其他架构布局?

I want to create a schema for a ACL; however, I'm torn between a couple of ways of implementing it.

I am pretty sure I don't want to deal with cascading permissions as that leads to a lot of confusion on the backend and for site administrators.

I think I can also live with users only being in one role at a time. A setup like this will allow roles and permissions to be added as needed as the site grows without affecting existing roles/rules.

At first I was going to normalize the data and have three tables to represent the relations.

ROLES { id, name }
RESOURCES { id, name }
PERMISSIONS { id, role_id, resource_id }

A query to figure out whether a user was allowed somewhere would look like this:

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

Then I realized that I will only have about 20 resources, each with up to 5 actions (create, update, view, etc..) and perhaps another 8 roles. This means that I can exercise blatant disregard for data normalization as I will never have more than a couple of hundred possible records.

So perhaps a schema like this would make more sense.

ROLES { id, name }
PERMISSIONS { id, role_id, resource_name }

which would allow me to lookup records in a single query

SELECT * FROM permissions WHERE role_id = ? AND permission  = ? ($user_role_id, 'post.update')

So which of these is more correct? Are there other schema layouts for ACL?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

数理化全能战士 2024-11-11 13:03:23

根据我的经验,真正的问题主要在于是否会发生任何数量的特定于用户的访问限制。

例如,假设您正在设计社区的架构,并且允许用户切换其个人资料的可见性。

一种选择是坚持公共/私人个人资料标志,并坚持广泛的、先发制人的权限检查:“users.view”(查看公共用户)与“users.view_all”(查看所有用户,供版主使用) 。

另一个涉及更精细的权限,您可能希望他们能够配置一些东西,以便他们可以使自己(a)可供所有人查看,(b)可供他们精心挑选的好友查看,(c)完全保密,也许(d) )除了他们精心挑选的笨蛋之外,所有人都可以看到。在这种情况下,您需要存储各个行的所有者/访问相关数据,并且您需要对其中一些内容进行大量抽象,以避免实现密集的、有向图的传递闭包。

无论采用哪种方法,我发现角色编辑/分配中增加的复杂性都被为各个数据分配权限所带来的轻松/灵活性所抵消,并且以下方法效果最佳:

  1. 用户可以具有多个角色
  2. 角色和权限合并在同一个表中,并使用标志来区分两者(在编辑角色/权限时有用)
  3. 角色可以分配其他角色,角色和权限可以分配权限(但权限不能分配角色),从同一张桌子。

然后,可以将生成的定向图提取到两个查询中,使用您使用的任何语言在合理的时间内一次性构建,并缓存到 Memcache 或类似的缓存中以供后续使用。

从这里开始,提取用户的权限就是检查他拥有哪些角色,并使用权限图对其进行处理以获得最终权限。通过验证用户是否具有指定的角色/权限来检查权限。然后根据该权限检查运行查询/发出错误。

您可以扩展对单个节点的检查(即“可以编辑此节点”的 check_perms($user, 'users.edit', $node)check_perms($user, 'users. edit') 表示“可以编辑节点”)如果需要的话,您将为最终用户提供非常灵活/易于使用的东西。

正如开头的示例所示,请注意不要过度转向行级权限。检查单个节点的权限的性能瓶颈比提取有效节点列表(即仅用户可以查看或编辑的节点)要少。如果您(非常)不熟悉查询优化,我建议您不要使用行本身中除标志和 user_id 字段之外的任何内容。

In my experience, the real question mostly breaks down to whether or not any amount of user-specific access-restriction is going to occur.

Suppose, for instance, that you're designing the schema of a community and that you allow users to toggle the visibility of their profile.

One option is to stick to a public/private profile flag and stick to broad, pre-emptive permission checks: 'users.view' (views public users) vs, say, 'users.view_all' (views all users, for moderators).

Another involves more refined permissions, you might want them to be able to configure things so they can make themselves (a) viewable by all, (b) viewable by their hand-picked buddies, (c) kept private entirely, and perhaps (d) viewable by all except their hand-picked bozos. In this case you need to store owner/access-related data for individual rows, and you'll need to heavily abstract some of these things in order to avoid materializing the transitive closure of a dense, oriented graph.

With either approach, I've found that added complexity in role editing/assignment is offset by the resulting ease/flexibility in assigning permissions to individual pieces of data, and that the following to worked best:

  1. Users can have multiple roles
  2. Roles and permissions merged in the same table with a flag to distinguish the two (useful when editing roles/perms)
  3. Roles can assign other roles, and roles and perms can assign permissions (but permissions cannot assign roles), from within the same table.

The resulting oriented graph can then be pulled in two queries, built once and for all in a reasonable amount of time using whichever language you're using, and cached into Memcache or similar for subsequent use.

From there, pulling a user's permissions is a matter of checking which roles he has, and processing them using the permission graph to get the final permissions. Check permissions by verifying that a user has the specified role/permission or not. And then run your query/issue an error based on that permission check.

You can extend the check for individual nodes (i.e. check_perms($user, 'users.edit', $node) for "can edit this node" vs check_perms($user, 'users.edit') for "may edit a node") if you need to, and you'll have something very flexible/easy to use for end users.

As the opening example should illustrate, be wary of steering too much towards row-level permissions. The performance bottleneck is less in checking an individual node's permissions than it is in pulling a list of valid nodes (i.e. only those that the user can view or edit). I'd advise against anything beyond flags and user_id fields within the rows themselves if you're not (very) well versed in query optimization.

留一抹残留的笑 2024-11-11 13:03:23

这意味着我可以明目张胆地锻炼了
忽略数据标准化,因为我
永远不会超过一对
一百个可能的记录。

您期望的行数并不是选择目标范式的标准。
规范化与数据完整性有关。它通常通过减少冗余来提高数据完整性。

真正要问的问题不是“我将有多少行?”,而是“数据库始终为我提供正确答案有多重要?”对于将用于实现 ACL 的数据库,我会说“非常重要”。

如果有什么不同的话,那就是行数少表明您不需要关心性能,因此 5NF 应该是一个很容易做出的选择。在添加任何 ID 号之前,您需要先点击 5NF。

用于确定用户是否是的查询
允许某处看起来像
这个:

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions 
WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

您将其写为两个查询而不是使用内部联接,这表明您可能会不知所措。 (这是一个观察,而不是批评。)

SELECT p.* 
FROM permissions p
INNER JOIN resources r ON (r.id = p.resource_id AND 
                           r.name = ?)

This means that I can exercise blatant
disregard for data normalization as I
will never have more than a couple
hundred possible records.

The number of rows you expect isn't a criterion for choosing which normal form to aim for.
Normalization is concerned with data integrity. It generally increases data integrity by reducing redundancy.

The real question to ask isn't "How many rows will I have?", but "How important is it for the database to always give me the right answers?" For a database that will be used to implement an ACL, I'd say "Pretty danged important."

If anything, a low number of rows suggests you don't need to be concerned with performance, so 5NF should be an easy choice to make. You'll want to hit 5NF before you add any id numbers.

A query to figure out if a user was
allowed somewhere would look like
this:

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions 
WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

That you wrote that as two queries instead of using an inner join suggests that you might be in over your head. (That's an observation, not a criticism.)

SELECT p.* 
FROM permissions p
INNER JOIN resources r ON (r.id = p.resource_id AND 
                           r.name = ?)
楠木可依 2024-11-11 13:03:23

您可以使用 SET 来分配角色。

CREATE TABLE permission (
  id integer primary key autoincrement
  ,name varchar
  ,perm SET('create', 'edit', 'delete', 'view')
  ,resource_id integer );

You can use a SET to assign the roles.

CREATE TABLE permission (
  id integer primary key autoincrement
  ,name varchar
  ,perm SET('create', 'edit', 'delete', 'view')
  ,resource_id integer );
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文