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