Files
Agent-System/internal/authz/service.go
T
weichenandCursor 5cd22c7327 fix(authz): harden permissions, chat gate, and project create idempotency
Add project:manage_members, CSP frame-src, task-run member view, CreateUser
tx error handling, ERR_NO_PROJECT chat gate, and creator-scoped project
name/client_key uniqueness to stop duplicate creates and auth gaps.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 07:04:57 +00:00

118 lines
3.0 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()
}
// HasSystemPermission 判定用户是否具备系统级 permission。
func (s *Service) HasSystemPermission(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
}
// 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.HasSystemPermission(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, 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)
}