현제 그룹-permission 구조가 너무 복잡하다고 생각합니다. 조금 더 쉽게 관리를 하기 위해 다음과 같이 그룹 구조를 바꾸는 것을 제안합니다.
Proposal
- group-group 관계 삭제. (
DROP TABLE group_relations)
- permission 관계 inversion
- 그룹은 여러 permission을 가지고
- 사용자는 속한 모든 그룹의 permission의 union을 가진다
좋은 점
- group_reachable_cache를 없애도 된다
- 그룹 relation을 변경한 후 재시작할 필요가 없어진다
- 그룹 관리 페이지 등에서 사용자가 어느 그룹에 직접 들어가 있는지 혹은 상위 그룹에 들어가 있는지 확인하지 않아도 된다
- 그룹 간 관계가 없어짐으로써 관리하고 이해하기 편해진다
- 앞으로 만들 관리자 페이지에 그룹 간 관계 설정을 추가하지 않아도 된다
- 지금 상태로 이걸 추가한다면 그룹을 변경할 때마다 group_reachable_cache를 다시 생성해야 한다
- 몇 그룹들은 이미 이 제안의 permission 역할을 하고 있다
- 현제 모든 permission들은 하나의 permission requirement을 가진다 ("permission groups")
- 이 "permission group" 외에 다른 그룹을 subgroup으로 가지는 그룹은 없다
- 이 제안은 "permission group"들을 permission으로 바꾸는 것으로 생각할 수 있다
좋지 않은 점
- 개발을 해야 한다
- 대부분 core, 프론트는 그룹 관리 페이지에 있는 is_direct_member 관련 코드를 없애면 된다
- 그룹 목록 API 외에는 외부적으로 나타나는 차이는 없다
- 비슷한 그룹이 많을 경우 모든 그룹에 똑같은 permission을 주어야 한다
- contributions welcome
Changes
user permission check:
before:
- build user-reachable groups table in runtime (built and cached on startup)
- list all permission requirements
- list all groups reachable by user
- check if all permission requirements are satisfied
- currently requires two queries and a loop, doable in a single query with a CTE.
|
public async checkUserHavePermission(tr: Transaction, userIdx: number, permissionIdx: number): Promise<boolean> { |
|
const permissionRequirements = await this.getAllPermissionRequirements(tr, permissionIdx) |
|
if (permissionRequirements.length === 0) { |
|
return true |
|
} |
|
const userReachableGroups = Array.from(await this.model.users.getUserReachableGroups(tr, userIdx)) |
|
|
|
for (const pr of permissionRequirements) { |
|
if (!userReachableGroups.includes(pr)) { |
|
return false |
|
} |
|
} |
|
return true |
|
} |
after:
- list all groups the user is in
- list all permissions each group has
- check if permission is in said list
- efficient, doable in a single query with 3 joins
SELECT EXISTS (
SELECT 1 FROM users u
JOIN user_memberships um ON um.user_idx = u.idx
JOIN group_permissions gp ON gp.group_idx = um.group_idx
JOIN permissions p ON p.idx = gp.permission_idx
WHERE u.name = $1 AND p.name = $2
)
user membership list
before:
- if checking groups the user is directly in, query user_memberships
- if checking all reachable groups, query cache
- if both, see below
|
WITH |
|
umem AS (SELECT user_idx, group_idx FROM user_memberships WHERE user_idx = $1), |
|
pend_umem AS (SELECT user_idx, group_idx FROM pending_user_memberships WHERE user_idx = $1) |
|
SELECT DISTINCT ON (g.idx) |
|
g.idx, |
|
g.name_ko, |
|
g.name_en, |
|
g.description_ko, |
|
g.description_en, |
|
(umem.user_idx IS NOT NULL) AS is_member, |
|
(dir.user_idx IS NOT NULL) AS is_direct_member, |
|
(pend_umem.user_idx IS NOT NULL) AS is_pending, |
|
(EXISTS (SELECT 1 FROM umem WHERE umem.group_idx = g.owner_group_idx)) AS is_owner |
|
FROM umem |
|
RIGHT JOIN group_reachable_cache gr ON umem.group_idx = gr.supergroup_idx |
|
RIGHT JOIN groups g ON g.idx = gr.subgroup_idx |
|
LEFT JOIN umem dir ON dir.group_idx = g.idx |
|
LEFT JOIN pend_umem ON pend_umem.group_idx = g.idx |
|
WHERE g.owner_group_idx IS NOT NULL |
|
ORDER BY g.idx, umem.user_idx |
after
- similar to above, but we don't need joins anymore, no more indirect groups
- owner checking is no longer confusing
WITH
umem AS (SELECT group_idx FROM user_memberships WHERE user_idx = $1),
pend_umem AS (SELECT group_idx FROM pending_user_memberships WHERE user_idx = $1)
SELECT
g.idx,
g.name_ko,
g.name_en,
g.description_ko,
g.description_en,
(EXISTS (SELECT 1 FROM umem WHERE umem.group_idx = g.idx)) AS is_member,
(EXISTS (SELECT 1 FROM pend_umem WHERE pend_umem.group_idx = g.idx)) AS is_pending,
(EXISTS (SELECT 1 FROM umem WHERE umem.group_idx = g.owner_group_idx)) AS is_owner
FROM groups g ORDER BY g.idx;
restrict permission to a single group
before:
- set the new group to be a supergroup of all other permission requirements
- add the new group as a permission requirement
- to restore:
- remove the new group, relations are removed automatically
after:
- set the new group to have the permission
- remove the permission from all other groups
- to restore:
- remove the new group
- restore permission to other groups
adding a temp group
before:
- create a temp group
- make the temp group a supergroup of appropriate groups
after:
- create a temp group
- add permissions to temp group
organization
before:
- we have groups like "server users" and "computer users" with no semantic meaning ("permission groups")
after:
- remove all "permission groups"
- remaining groups will have meaningful names, like "staff", "majors"
현제 그룹-permission 구조가 너무 복잡하다고 생각합니다. 조금 더 쉽게 관리를 하기 위해 다음과 같이 그룹 구조를 바꾸는 것을 제안합니다.
Proposal
DROP TABLE group_relations)좋은 점
좋지 않은 점
Changes
user permission check:
before:
id/core/src/model/permissions.ts
Lines 49 to 62 in 437ff3a
after:
user membership list
before:
id/core/src/model/groups.ts
Lines 80 to 99 in 437ff3a
after
restrict permission to a single group
before:
after:
adding a temp group
before:
after:
organization
before:
after: