Skip to content

Proposal: Flatten groups #106

Description

@nevivurn

현제 그룹-permission 구조가 너무 복잡하다고 생각합니다. 조금 더 쉽게 관리를 하기 위해 다음과 같이 그룹 구조를 바꾸는 것을 제안합니다.

Proposal

  1. group-group 관계 삭제. (DROP TABLE group_relations)
  2. permission 관계 inversion
    • 그룹은 여러 permission을 가지고
    • 사용자는 속한 모든 그룹의 permission의 union을 가진다

좋은 점

  • group_reachable_cache를 없애도 된다
    • 그룹 relation을 변경한 후 재시작할 필요가 없어진다
  • 그룹 관리 페이지 등에서 사용자가 어느 그룹에 직접 들어가 있는지 혹은 상위 그룹에 들어가 있는지 확인하지 않아도 된다
  • 그룹 간 관계가 없어짐으로써 관리하고 이해하기 편해진다
  • 앞으로 만들 관리자 페이지에 그룹 간 관계 설정을 추가하지 않아도 된다
    • 지금 상태로 이걸 추가한다면 그룹을 변경할 때마다 group_reachable_cache를 다시 생성해야 한다
  • 몇 그룹들은 이미 이 제안의 permission 역할을 하고 있다
    • 현제 모든 permission들은 하나의 permission requirement을 가진다 ("permission groups")
      • 예: "000 실습실 사용자"
    • 이 "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"

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions