Files
ai_site/platform/internal/tenantperm/store.go
2026-07-31 10:19:22 +08:00

196 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package tenantperm
import (
"context"
"database/sql"
"sync"
"aijianzhan/platform/internal/authx"
)
// Store 公司权限额度:超管授予,公司内分配不得越界。
// configured=false 表示从未配置 → 读时视为全量默认configured=true 时允许空额度。
type Store interface {
Get(ctx context.Context, tenantID int64) ([]string, error)
Set(ctx context.Context, tenantID int64, perms []string) error
Allows(ctx context.Context, tenantID int64, perm string) (bool, error)
EnsureDefault(ctx context.Context, tenantID int64) error
Configured(ctx context.Context, tenantID int64) (bool, error)
}
type MemoryStore struct {
mu sync.Mutex
by map[int64][]string // key 存在即已配置
}
func NewMemoryStore() *MemoryStore {
return &MemoryStore{by: map[int64][]string{}}
}
func (s *MemoryStore) Configured(_ context.Context, tenantID int64) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.by[tenantID]
return ok, nil
}
func (s *MemoryStore) Get(_ context.Context, tenantID int64) ([]string, error) {
s.mu.Lock()
defer s.mu.Unlock()
if ps, ok := s.by[tenantID]; ok {
return append([]string{}, ps...), nil
}
return authx.CompanyPermCatalog(), nil
}
func (s *MemoryStore) Set(_ context.Context, tenantID int64, perms []string) error {
s.mu.Lock()
defer s.mu.Unlock()
s.by[tenantID] = intersectCatalog(perms) // 可为空切片
return nil
}
func (s *MemoryStore) Allows(ctx context.Context, tenantID int64, perm string) (bool, error) {
ps, err := s.Get(ctx, tenantID)
if err != nil {
return false, err
}
want := authx.NormalizePerm(perm)
for _, p := range ps {
if p == want {
return true, nil
}
}
return false, nil
}
func (s *MemoryStore) EnsureDefault(ctx context.Context, tenantID int64) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.by[tenantID]; ok {
return nil
}
s.by[tenantID] = authx.CompanyPermCatalog()
return nil
}
type PostgresStore struct{ DB *sql.DB }
func NewPostgresStore(db *sql.DB) *PostgresStore { return &PostgresStore{DB: db} }
func (s *PostgresStore) Configured(ctx context.Context, tenantID int64) (bool, error) {
var n int
err := s.DB.QueryRowContext(ctx, `
SELECT COUNT(*) FROM platform_meta.tenant_entitlement_state WHERE tenant_id=$1`, tenantID).Scan(&n)
return n > 0, err
}
func (s *PostgresStore) Get(ctx context.Context, tenantID int64) ([]string, error) {
ok, err := s.Configured(ctx, tenantID)
if err != nil {
return nil, err
}
if !ok {
return authx.CompanyPermCatalog(), nil
}
rows, err := s.DB.QueryContext(ctx, `
SELECT perm FROM platform_meta.tenant_permissions WHERE tenant_id=$1 ORDER BY perm`, tenantID)
if err != nil {
return nil, err
}
defer rows.Close()
out := make([]string, 0)
for rows.Next() {
var p string
if err := rows.Scan(&p); err != nil {
return nil, err
}
out = append(out, authx.NormalizePerm(p))
}
if err := rows.Err(); err != nil {
return nil, err
}
return authx.NormalizePerms(out), nil
}
func (s *PostgresStore) Set(ctx context.Context, tenantID int64, perms []string) error {
allowed := intersectCatalog(perms)
tx, err := s.DB.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
if _, err := tx.ExecContext(ctx, `DELETE FROM platform_meta.tenant_permissions WHERE tenant_id=$1`, tenantID); err != nil {
return err
}
for _, p := range allowed {
if _, err := tx.ExecContext(ctx, `
INSERT INTO platform_meta.tenant_permissions(tenant_id, perm) VALUES($1,$2)`, tenantID, p); err != nil {
return err
}
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO platform_meta.tenant_entitlement_state(tenant_id, updated_at)
VALUES($1, now())
ON CONFLICT (tenant_id) DO UPDATE SET updated_at=now()`, tenantID); err != nil {
return err
}
return tx.Commit()
}
func (s *PostgresStore) Allows(ctx context.Context, tenantID int64, perm string) (bool, error) {
ps, err := s.Get(ctx, tenantID)
if err != nil {
return false, err
}
want := authx.NormalizePerm(perm)
for _, p := range ps {
if p == want {
return true, nil
}
}
return false, nil
}
func (s *PostgresStore) EnsureDefault(ctx context.Context, tenantID int64) error {
ok, err := s.Configured(ctx, tenantID)
if err != nil {
return err
}
if ok {
return nil
}
return s.Set(ctx, tenantID, authx.CompanyPermCatalog())
}
func intersectCatalog(perms []string) []string {
cat := map[string]struct{}{}
for _, p := range authx.CompanyPermCatalog() {
cat[p] = struct{}{}
}
out := make([]string, 0, len(perms))
seen := map[string]struct{}{}
for _, p := range authx.NormalizePerms(perms) {
if _, ok := cat[p]; !ok {
continue
}
if _, ok := seen[p]; ok {
continue
}
seen[p] = struct{}{}
out = append(out, p)
}
return out
}
func MustAllow(ctx context.Context, st Store, tenantID int64, want []string) error {
if st == nil || tenantID <= 0 {
return nil
}
allowance, err := st.Get(ctx, tenantID)
if err != nil {
return err
}
return authx.AssertWithinEntitlement(want, allowance)
}