app-cloudep-permission-server/pkg/usecase/permission_tree.go

223 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package usecase
import (
"sync"
"code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// PermissionTree 用來管理權限節點,包含快速查詢 map 與輔助 map例如名稱對應的 ID 列表)
type PermissionTree struct {
// dummy root 節點
root *PermissionNode
// permission id => node
nodes map[string]*PermissionNode
// permission name => permission id
names map[string][]string
mu sync.RWMutex // 保護樹的並發存取
}
type PermissionNode struct {
Data entity.Permission
Parent *PermissionNode
Children []*PermissionNode
}
const rootName = "root"
// GeneratePermissionTree 根據扁平權限資料建立樹,並掛在 dummy root 下
func GeneratePermissionTree(permissions []entity.Permission) *PermissionTree {
tree := &PermissionTree{
nodes: make(map[string]*PermissionNode),
names: make(map[string][]string),
}
// 1. 建立所有節點
for _, perm := range permissions {
node := &PermissionNode{
Data: perm,
Children: []*PermissionNode{},
}
tree.nodes[perm.ID.Hex()] = node
tree.names[perm.Name] = append(tree.names[perm.Name], perm.ID.Hex())
}
// 2. 建立 dummy root 節點
tree.root = &PermissionNode{
Data: entity.Permission{ID: primitive.NewObjectID(), Name: rootName},
Children: []*PermissionNode{},
}
// 3. 建立父子連結:若找不到父節點或 Parent 為 0則掛在 dummy root 下
for _, node := range tree.nodes {
if node.Data.Parent == "" {
node.Parent = tree.root
tree.root.Children = append(tree.root.Children, node)
} else if parent, ok := tree.nodes[node.Data.Parent]; ok {
node.Parent = parent
parent.Children = append(parent.Children, node)
} else {
// 若父節點不存在,預設掛在 dummy root 下
node.Parent = tree.root
tree.root.Children = append(tree.root.Children, node)
}
}
return tree
}
// getNode 輔助函數:根據 ID 從樹中查找節點
func (tree *PermissionTree) getNode(id string) *PermissionNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.nodes[id]
}
//nolint:unused
func (tree *PermissionTree) put(node entity.Permission) {
parentNode := tree.getNode(node.Parent)
if parentNode == nil {
parentNode = tree.root
}
thisNode := &PermissionNode{
Data: node,
Parent: parentNode,
Children: make([]*PermissionNode, 0),
}
parentNode.Children = append(parentNode.Children, thisNode)
tree.names[node.Name] = append(tree.names[node.Name], node.ID.Hex())
tree.nodes[node.ID.Hex()] = thisNode
}
// filterOpenNodes 走訪整棵樹,列出有被打開的節點(父節點沒開,則底下的都不會開)
// 如果某個節點為非葉節點,則會檢查其子節點是否有啟用,否則該節點不會被展開。
// [permissionID] entity.Permission
func (tree *PermissionTree) filterOpenNodes() (map[string]entity.Permission, error) {
tree.mu.RLock()
defer tree.mu.RUnlock()
result := make(map[string]entity.Permission)
// dfs 為內部閉包,可存取 result
// 返回值 bool 表示目前節點或其子孫中是否存在有效 open 節點
var dfs func(node *PermissionNode) bool
dfs = func(node *PermissionNode) bool {
// 若本身狀態非 open則整個分支不展開
if node.Data.Status != permission.Open {
return false
}
// 節點本身是 open不論子節點狀態如何先將該節點加入結果
result[node.Data.ID.Hex()] = node.Data
// 遞迴處理子節點
for _, child := range node.Children {
dfs(child)
}
return true
}
// 從 dummy root 的 Children 開始走訪dummy root 本身不納入結果)
for _, child := range tree.root.Children {
dfs(child)
}
return result, nil
}
// getFullParentPermission 根據 role permission 找出完整 path permission status
func (tree *PermissionTree) getFullParentPermission(rolePermissions []*entity.RolePermission) permission.Permissions {
status := permission.Permissions{}
for _, v := range rolePermissions {
node := tree.getNode(v.PermissionID)
if node == nil {
return nil
}
if _, ok := status[node.Data.Name]; ok {
continue
}
status[node.Data.Name] = permission.StatusCode(node.Data.Status.String())
// 往上找node(父)
for node.Parent != nil {
np := node.Parent
if np == nil || np.Data.Name == rootName {
break
}
parent := tree.getNode(np.Data.ID.Hex())
if parent == nil {
continue
}
status[parent.Data.Name] = permission.StatusCode(parent.Data.Status.String())
}
}
return status
}
// getFullParentPermissionIDs 根據 permissions 找出所有完整 permission
// 比如B的父權限是A給B權限就要回傳A與B權限id
func (tree *PermissionTree) getFullParentPermissionIDs(permissions permission.Permissions) ([]string, error) {
exist := make(map[string]bool)
ids := make([]string, 0)
for name, status := range permissions {
if status != permission.OpenPermission {
continue
}
pIDs, ok := tree.names[name]
if !ok {
return nil, errs.ResourceNotFound("failed to get node")
}
for _, pID := range pIDs {
node := tree.getNode(pID)
if node == nil {
return nil, errs.ResourceNotFound("failed to get node")
}
// 如果有子node代表不是最底層權限而是父node就必須檢查此父node底下子node權限是否有開啟
if len(node.Children) > 0 {
var can bool
for _, ch := range node.Children {
if v, ok := permissions[ch.Data.Name]; ok && v == permission.OpenPermission {
can = true
break
}
}
if !can {
continue
}
}
for node.Parent != nil {
np := node.Parent
if np == nil || np.Data.Name == rootName {
break
}
if _, ok := exist[np.Data.ID.Hex()]; !ok {
ids = append(ids, np.Data.ID.Hex())
exist[np.Data.ID.Hex()] = true
}
}
}
}
return ids, nil
}