Scope match/Allows tables, route classification for every phase09Routes entry, SPA plaintext lifetime backstops, and Postgres owner lifecycle. Co-authored-by: Cursor <cursoragent@cursor.com>
171 lines
5.2 KiB
Go
171 lines
5.2 KiB
Go
package cabana
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
)
|
|
|
|
func TestScopeMatch(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
pattern string
|
|
required string
|
|
want int
|
|
}{
|
|
{"exact full same", "a.b.c", "a.b.c", matchFull},
|
|
{"exact full via a.b.*", "a.b.*", "a.b.c", matchFull},
|
|
{"exact full via a.*", "a.*", "a.b.c", matchFull},
|
|
{"exact none a.x.*", "a.x.*", "a.b.c", matchNone},
|
|
{"exact none a.b.d", "a.b.d", "a.b.c", matchNone},
|
|
{"wild full same", "a.b.*", "a.b.*", matchFull},
|
|
{"wild full broader a.*", "a.*", "a.b.*", matchFull},
|
|
{"wild partial narrower pattern", "a.b.*", "a.*", matchPartial},
|
|
{"wild partial exact narrower", "a.b.c", "a.*", matchPartial},
|
|
{"wild none a.x.*", "a.x.*", "a.b.*", matchNone},
|
|
{"wild none x.*", "x.*", "a.b.*", matchNone},
|
|
{"leading partial exact", "*.manage", "a.manage", matchPartial},
|
|
{"leading none wild required", "*.manage", "a.manage.*", matchNone},
|
|
{"leading none wild pattern required", "*.manage", "*.manage", matchNone},
|
|
{"empty pattern", "", "a.b.c", matchNone},
|
|
{"empty required", "a.b.c", "", matchNone},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := scopeMatch(tc.pattern, tc.required); got != tc.want {
|
|
t.Fatalf("scopeMatch(%q, %q) = %d, want %d", tc.pattern, tc.required, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAllowsScope(t *testing.T) {
|
|
dev := &bouncer.Principal{
|
|
ID: 1,
|
|
Backend: true,
|
|
PermissionGrants: map[string]bool{
|
|
"acme.demo.access": true,
|
|
"acme.demo.reports": true,
|
|
},
|
|
}
|
|
super := &bouncer.Principal{ID: 2, Backend: true, IsSuperuser: true}
|
|
|
|
cases := []struct {
|
|
name string
|
|
p *bouncer.Principal
|
|
scope *bouncer.ScopeLimit
|
|
required []string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "JWT nil scope keeps empty-list shortcut",
|
|
p: dev, scope: nil, required: nil, want: true,
|
|
},
|
|
{
|
|
name: "JWT nil scope permission grant",
|
|
p: dev, scope: nil, required: []string{"acme.demo.access"}, want: true,
|
|
},
|
|
{
|
|
name: "JWT nil scope missing grant",
|
|
p: dev, scope: nil, required: []string{"acme.other.x"}, want: false,
|
|
},
|
|
{
|
|
name: "JWT superuser empty list",
|
|
p: super, scope: nil, required: nil, want: true,
|
|
},
|
|
{
|
|
name: "full * every method empty list",
|
|
p: dev, scope: &bouncer.ScopeLimit{Full: true}, required: nil, want: true,
|
|
},
|
|
{
|
|
name: "full * permission as owner",
|
|
p: dev, scope: &bouncer.ScopeLimit{Full: true}, required: []string{"acme.demo.access"}, want: true,
|
|
},
|
|
{
|
|
name: "full admin:read empty list",
|
|
p: dev, scope: &bouncer.ScopeLimit{Full: true}, required: nil, want: true,
|
|
},
|
|
{
|
|
name: "plugin scope with owner grant",
|
|
p: dev,
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.*"}}},
|
|
required: []string{"acme.demo.access"}, want: true,
|
|
},
|
|
{
|
|
name: "plugin scope without owner grant",
|
|
p: &bouncer.Principal{
|
|
ID: 3, Backend: true,
|
|
PermissionGrants: map[string]bool{"acme.other.x": true},
|
|
},
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.*"}}},
|
|
required: []string{"acme.demo.access"}, want: false,
|
|
},
|
|
{
|
|
name: "superuser with scoped token limited",
|
|
p: super,
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.reports"}}},
|
|
required: []string{"acme.demo.access"}, want: false,
|
|
},
|
|
{
|
|
name: "superuser scoped token matching scope code",
|
|
p: super,
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.reports"}}},
|
|
required: []string{"acme.demo.reports"}, want: true,
|
|
},
|
|
{
|
|
name: "empty requirement list refused unless full",
|
|
p: dev,
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.*"}}},
|
|
required: nil, want: false,
|
|
},
|
|
{
|
|
name: "multi-code literal: one scope must match every code",
|
|
p: dev,
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.*"}}},
|
|
required: []string{"acme.demo.access", "acme.demo.reports"}, want: true,
|
|
},
|
|
{
|
|
name: "multi-code literal: two scopes each matching part fails",
|
|
p: dev,
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{
|
|
{"acme.demo.access"},
|
|
{"acme.demo.reports"},
|
|
}},
|
|
required: []string{"acme.demo.access", "acme.demo.reports"}, want: false,
|
|
},
|
|
{
|
|
name: "wildcard requirement broader pattern partial via ownerHas(pattern)",
|
|
p: &bouncer.Principal{
|
|
ID: 4, Backend: true,
|
|
PermissionGrants: map[string]bool{"acme.demo.*": true},
|
|
},
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.access"}}},
|
|
required: []string{"acme.demo.*"}, want: true,
|
|
},
|
|
{
|
|
name: "wildcard requirement equal pattern full",
|
|
p: dev,
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.*"}}},
|
|
required: []string{"acme.demo.*"}, want: true,
|
|
},
|
|
{
|
|
name: "wildcard requirement narrower pattern none for Allows owner side",
|
|
p: &bouncer.Principal{
|
|
ID: 5, Backend: true,
|
|
PermissionGrants: map[string]bool{"acme.demo.access": true},
|
|
},
|
|
scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.demo.access.extra"}}},
|
|
required: []string{"acme.demo.*"}, want: false,
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
p := *tc.p
|
|
p.Scope = tc.scope
|
|
if got := Allows(&p, tc.required); got != tc.want {
|
|
t.Fatalf("Allows = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|