Expose fine-grained system:* checks (admin grants all) and list system_setting_policy_audits for settings change visibility.
140 lines
3.7 KiB
Go
140 lines
3.7 KiB
Go
package authz
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
const PermManage = "perm:manage"
|
|
const PermManageProjectMembers = "project:manage_members"
|
|
|
|
// Service 统一系统 RBAC 与项目成员授权判定。
|
|
type Service struct {
|
|
pool *pgxpool.Pool
|
|
authorizer *Authorizer
|
|
}
|
|
|
|
func NewService(pool *pgxpool.Pool) *Service {
|
|
return &Service{pool: pool, authorizer: NewAuthorizer(pool)}
|
|
}
|
|
|
|
func (s *Service) enabled() bool {
|
|
return s != nil && s.pool != nil
|
|
}
|
|
|
|
// Enabled 暴露 RBAC 是否已连接数据库。
|
|
func (s *Service) Enabled() bool {
|
|
return s.enabled()
|
|
}
|
|
|
|
// ListCapabilities 返回用户系统级 permission key 列表。
|
|
func (s *Service) ListCapabilities(ctx context.Context, userID int64) ([]string, error) {
|
|
if userID <= 0 {
|
|
return nil, errors.New("authz: invalid user id")
|
|
}
|
|
if !s.enabled() {
|
|
return nil, nil
|
|
}
|
|
rows, err := s.pool.Query(ctx, `
|
|
SELECT DISTINCT p.perm_key
|
|
FROM user_role_bindings b
|
|
JOIN system_roles sr ON sr.id = b.role_id AND sr.status = 'active'
|
|
JOIN system_role_permissions rp ON rp.role_id = b.role_id AND rp.effect = 'allow'
|
|
JOIN system_permissions p ON p.id = rp.permission_id
|
|
WHERE b.user_id = $1
|
|
AND b.scope_type = 'system'
|
|
AND b.scope_id = 0
|
|
AND b.valid_from <= now()
|
|
AND (b.valid_until IS NULL OR b.valid_until > now())
|
|
ORDER BY p.perm_key
|
|
`, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
out := make([]string, 0)
|
|
for rows.Next() {
|
|
var key string
|
|
if err := rows.Scan(&key); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, key)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// hasCapabilityBinding 仅查 DB 角色绑定(不含 role_id 兜底)。
|
|
func (s *Service) hasCapabilityBinding(ctx context.Context, userID int64, permKey string) (bool, error) {
|
|
if userID <= 0 || permKey == "" {
|
|
return false, nil
|
|
}
|
|
caps, err := s.ListCapabilities(ctx, userID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, k := range caps {
|
|
if k == permKey || k == PermManage {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// HasSystemPermission 判定用户是否具备系统级 permission。
|
|
//
|
|
// Phase-2 system:*:系统管理员授予全部;非管理员 fail-closed false。
|
|
// 本阶段不改 authz schema — 管理员 all-or-nothing,无按角色拆分绑定。
|
|
// 其它历史键(project:*、perm:manage 等):管理员或 DB 绑定任一即可。
|
|
func (s *Service) HasSystemPermission(ctx context.Context, userID int64, roleID, permKey string) (bool, error) {
|
|
if userID <= 0 || permKey == "" {
|
|
return false, nil
|
|
}
|
|
admin, err := s.IsSystemAdmin(ctx, userID, roleID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if admin {
|
|
return true, nil
|
|
}
|
|
if IsSystemConsolePermission(permKey) {
|
|
return false, nil
|
|
}
|
|
return s.hasCapabilityBinding(ctx, userID, permKey)
|
|
}
|
|
|
|
// IsSystemAdmin 系统管理员(role-admin 或 perm:manage)。
|
|
func (s *Service) IsSystemAdmin(ctx context.Context, userID int64, roleID string) (bool, error) {
|
|
if roleID == "role-admin" {
|
|
return true, nil
|
|
}
|
|
return s.hasCapabilityBinding(ctx, userID, PermManage)
|
|
}
|
|
|
|
// CanManageProjectMembers 项目 owner/创建人、系统管理员,或持有 project:manage_members 的用户可管理成员。
|
|
func (s *Service) CanManageProjectMembers(ctx context.Context, projectID, userID int64, roleID string) (bool, error) {
|
|
if userID <= 0 || projectID <= 0 {
|
|
return false, nil
|
|
}
|
|
admin, err := s.IsSystemAdmin(ctx, userID, roleID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if admin {
|
|
return true, nil
|
|
}
|
|
hasPerm, err := s.HasSystemPermission(ctx, userID, roleID, PermManageProjectMembers)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if hasPerm {
|
|
return true, nil
|
|
}
|
|
if s.authorizer == nil {
|
|
return !s.enabled() && userID > 0, nil
|
|
}
|
|
return s.authorizer.IsProjectOwner(ctx, projectID, userID)
|
|
}
|