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>
118 lines
3.0 KiB
Go
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)
|
|
}
|