Files
Agent-System/internal/authz/service.go
T
WeiCHEN d49c8f8cfc feat(sysadmin): system permission checks and settings audit list
Expose fine-grained system:* checks (admin grants all) and list
system_setting_policy_audits for settings change visibility.
2026-09-23 10:11:46 +08:00

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)
}