Changeset 0.24.2 (#158)
This commit is contained in:
@@ -409,7 +409,7 @@ func (p *OIDCProvider) findOrCreateOIDCGroup(ctx context.Context, name, createdB
|
||||
Description: "Synced from OIDC",
|
||||
Scope: "global",
|
||||
Source: "oidc",
|
||||
CreatedBy: createdBy,
|
||||
CreatedBy: &createdBy,
|
||||
}
|
||||
if err := stores.Groups.Create(ctx, g); err != nil {
|
||||
log.Printf("[auth/oidc] warn: could not create group %s: %v", name, err)
|
||||
|
||||
164
server/auth/permissions.go
Normal file
164
server/auth/permissions.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
)
|
||||
|
||||
// EveryoneGroupID is the stable ID of the implicit "Everyone" group seeded in
|
||||
// migration 020. Every authenticated user receives its permissions without an
|
||||
// explicit membership row.
|
||||
const EveryoneGroupID = "00000000-0000-0000-0000-000000000001"
|
||||
|
||||
// Permission constants — domain.action convention.
|
||||
const (
|
||||
PermModelUse = "model.use" // use models for completion
|
||||
PermModelSelectAny = "model.select_any" // use any enabled model (vs. group allowlist)
|
||||
PermKBRead = "kb.read" // search KBs through personas
|
||||
PermKBWrite = "kb.write" // upload/delete KB documents
|
||||
PermKBCreate = "kb.create" // create new knowledge bases
|
||||
PermChannelCreate = "channel.create" // create group/channel conversations
|
||||
PermChannelInvite = "channel.invite" // invite users to channels
|
||||
PermPersonaCreate = "persona.create" // create new personas
|
||||
PermPersonaManage = "persona.manage" // edit/delete team personas
|
||||
PermWorkflowCreate = "workflow.create" // create workflow definitions (v0.25.0)
|
||||
PermAdminView = "admin.view" // read-only admin panel access
|
||||
PermTokenUnlimited = "token.unlimited" // bypass token budgets
|
||||
)
|
||||
|
||||
// AllPermissions is the complete set of valid permission strings.
|
||||
// Used for validation in handlers and rendering checkboxes in admin UI.
|
||||
var AllPermissions = []string{
|
||||
PermModelUse,
|
||||
PermModelSelectAny,
|
||||
PermKBRead,
|
||||
PermKBWrite,
|
||||
PermKBCreate,
|
||||
PermChannelCreate,
|
||||
PermChannelInvite,
|
||||
PermPersonaCreate,
|
||||
PermPersonaManage,
|
||||
PermWorkflowCreate,
|
||||
PermAdminView,
|
||||
PermTokenUnlimited,
|
||||
}
|
||||
|
||||
// ── Resolution ──────────────────────────────
|
||||
|
||||
// ResolvePermissions returns the effective permission set for a user.
|
||||
// Always includes the Everyone group regardless of membership.
|
||||
// Callers should short-circuit for role=admin before calling this.
|
||||
func ResolvePermissions(ctx context.Context, stores store.Stores, userID string) (map[string]bool, error) {
|
||||
perms := make(map[string]bool)
|
||||
|
||||
// Always apply Everyone group — no membership row required.
|
||||
if everyone, err := stores.Groups.GetByID(ctx, EveryoneGroupID); err == nil {
|
||||
for _, p := range everyone.Permissions {
|
||||
perms[p] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Union all explicit group memberships.
|
||||
groups, err := stores.Groups.ListForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return perms, err
|
||||
}
|
||||
for _, g := range groups {
|
||||
for _, p := range g.Permissions {
|
||||
perms[p] = true
|
||||
}
|
||||
}
|
||||
|
||||
return perms, nil
|
||||
}
|
||||
|
||||
// ── Token Budget ────────────────────────────
|
||||
|
||||
// TokenBudget holds the resolved ceiling for a user and the time window it covers.
|
||||
type TokenBudget struct {
|
||||
Limit int64
|
||||
Period string // "daily" or "monthly"
|
||||
}
|
||||
|
||||
// ResolveTokenBudget returns the most restrictive token budget across all of the
|
||||
// user's groups. Returns nil if no group has a budget set (unrestricted).
|
||||
// Callers should skip budget enforcement when providerScope == "personal" (BYOK).
|
||||
func ResolveTokenBudget(ctx context.Context, stores store.Stores, userID string) (*TokenBudget, error) {
|
||||
groups, err := stores.Groups.ListForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
isNewDay := now.Hour() < 1
|
||||
_ = isNewDay // used by callers for cache invalidation hints if needed
|
||||
|
||||
var daily, monthly *int64
|
||||
for _, g := range groups {
|
||||
if g.TokenBudgetDaily != nil {
|
||||
if daily == nil || *g.TokenBudgetDaily < *daily {
|
||||
daily = g.TokenBudgetDaily
|
||||
}
|
||||
}
|
||||
if g.TokenBudgetMonthly != nil {
|
||||
if monthly == nil || *g.TokenBudgetMonthly < *monthly {
|
||||
monthly = g.TokenBudgetMonthly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the most restrictive window. Daily wins when both are set
|
||||
// and daily is the binding constraint.
|
||||
if daily != nil {
|
||||
return &TokenBudget{Limit: *daily, Period: "daily"}, nil
|
||||
}
|
||||
if monthly != nil {
|
||||
return &TokenBudget{Limit: *monthly, Period: "monthly"}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ── Model Allowlist ─────────────────────────
|
||||
|
||||
// ResolveModelAllowlist returns the union of allowed model IDs across all of the
|
||||
// user's groups. Returns nil if the user is unrestricted (any group has a nil
|
||||
// allowlist, which means "no restriction").
|
||||
func ResolveModelAllowlist(ctx context.Context, stores store.Stores, userID string) (map[string]bool, error) {
|
||||
groups, err := stores.Groups.ListForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If any group has nil AllowedModels, the user is unrestricted.
|
||||
for _, g := range groups {
|
||||
if g.AllowedModels == nil {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// All groups have explicit allowlists — union them.
|
||||
allowed := make(map[string]bool)
|
||||
for _, g := range groups {
|
||||
for _, m := range g.AllowedModels {
|
||||
allowed[m] = true
|
||||
}
|
||||
}
|
||||
// No groups at all → fall through to Everyone group check.
|
||||
if len(groups) == 0 {
|
||||
if everyone, err := stores.Groups.GetByID(ctx, EveryoneGroupID); err == nil {
|
||||
if everyone.AllowedModels == nil {
|
||||
return nil, nil // Everyone has no restriction
|
||||
}
|
||||
for _, m := range everyone.AllowedModels {
|
||||
allowed[m] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(allowed) == 0 {
|
||||
return nil, nil // empty allowlists across all groups = unrestricted
|
||||
}
|
||||
return allowed, nil
|
||||
}
|
||||
Reference in New Issue
Block a user