mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-14 05:18:54 +00:00
feat(auth): let a user join several groups and hold a personal grant
A user entry can now list several groups in "groups", next to the existing "group" field. A user can also hold a personal grant with its own "providers" and optional "models" lists. Users with one group and no personal grant behave exactly as before. A request is allowed only when one grant allows the provider, the model and, for passthrough, the path. Grants never merge, so one grant cannot supply the provider while another supplies the model or the path. The personal grant uses the path list of the first group. A personal "models" list without "providers" is a configuration error. Each member group applies its own limits. Caching stays on unless a member group turns it off, and the shortest group TTL wins. The /v1/models cache uses a stable, length-prefixed key, so reloads do not grow it. Tests cover the grant union, cross-grant denials, passthrough path and model coupling, reloads and the cache key. The yaegi-check harness asserts the same cases under the interpreter. Claude-Session: https://claude.ai/code/session_019uJHQKcNLMACkXssb3L7Q3
This commit is contained in:
@@ -656,6 +656,60 @@ func TestEffectiveTTL_NilInheritsGlobal(t *testing.T) {
|
||||
|
||||
// TestEffectiveTTL_GroupOverrideHonored proves a group's own resolved
|
||||
// cacheTTL wins over the cache's global TTL.
|
||||
// --- effectiveGroup: multi-group cache merge rule ---
|
||||
|
||||
// TestEffectiveGroup_CacheMerge_FalseWinsOverTrue proves the multi-group
|
||||
// cache-merge rule (UserConfig.Groups' own doc comment, llmgateway.go):
|
||||
// if ANY member group sets cache false, the synthetic principal's own
|
||||
// effective cache setting is false, even when another member sets it
|
||||
// true.
|
||||
func TestEffectiveGroup_CacheMerge_FalseWinsOverTrue(t *testing.T) {
|
||||
no, yes := false, true
|
||||
a := &group{name: "a", cache: &yes}
|
||||
b := &group{name: "b", cache: &no}
|
||||
grp := effectiveGroup([]*group{a, b}, nil)
|
||||
if groupCacheEnabled(grp) {
|
||||
t.Error("groupCacheEnabled = true, want false (one member group opts out)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestEffectiveGroup_CacheMerge_TrueWinsOverNil proves true wins when no
|
||||
// member group sets false but at least one sets true.
|
||||
func TestEffectiveGroup_CacheMerge_TrueWinsOverNil(t *testing.T) {
|
||||
yes := true
|
||||
a := &group{name: "a", cache: &yes}
|
||||
b := &group{name: "b"} // nil: inherit
|
||||
grp := effectiveGroup([]*group{a, b}, nil)
|
||||
if !groupCacheEnabled(grp) {
|
||||
t.Error("groupCacheEnabled = false, want true (one member group opts in, none opts out)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestEffectiveGroup_CacheMerge_AllNilInherits proves nil-inherit stays
|
||||
// nil-inherit when no member group sets Cache at all.
|
||||
func TestEffectiveGroup_CacheMerge_AllNilInherits(t *testing.T) {
|
||||
a := &group{name: "a"}
|
||||
b := &group{name: "b"}
|
||||
grp := effectiveGroup([]*group{a, b}, nil)
|
||||
if grp.cache != nil {
|
||||
t.Errorf("grp.cache = %v, want nil (no member group sets it)", grp.cache)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEffectiveGroup_CacheTTLMerge_SmallestPositiveWins proves the
|
||||
// multi-group cacheTTL-merge rule: the SMALLEST positive cacheTTL among
|
||||
// member groups wins; a member that leaves cacheTTL unset (0) must never
|
||||
// win as "smallest".
|
||||
func TestEffectiveGroup_CacheTTLMerge_SmallestPositiveWins(t *testing.T) {
|
||||
a := &group{name: "a", cacheTTL: 30 * time.Second}
|
||||
b := &group{name: "b", cacheTTL: 5 * time.Minute}
|
||||
c := &group{name: "c"} // 0: unset
|
||||
grp := effectiveGroup([]*group{a, b, c}, nil)
|
||||
if grp.cacheTTL != 30*time.Second {
|
||||
t.Errorf("grp.cacheTTL = %v, want 30s (the smallest positive among member groups)", grp.cacheTTL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveTTL_GroupOverrideHonored(t *testing.T) {
|
||||
c := &responseCache{ttl: 5 * time.Minute}
|
||||
grp := &group{name: "g", cacheTTL: 30 * time.Second}
|
||||
|
||||
Reference in New Issue
Block a user