mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-07-26 07:48:13 +00:00
0d804fbd39
* feat(access-control): fine grain tokens * fix(docs): clean wording * test(access-control): add tests for team extraction from access tokens and bucket info permissions * test(vocabulary): add test for ExpandGlob function to reject non-glob patterns * feat(helm): add multi-user access control documentation and schema support
118 lines
3.8 KiB
Go
118 lines
3.8 KiB
Go
package authz
|
|
|
|
import (
|
|
"sort"
|
|
"testing"
|
|
)
|
|
|
|
func testSubject() Subject {
|
|
return Subject{
|
|
ID: "alice@example.com",
|
|
Bindings: []Binding{
|
|
{BucketPrefixes: []string{"backend-"}, Permissions: PermSet{
|
|
"bucket.list": {}, "bucket.read": {}, "bucket.create": {}, "object.read": {}, "object.write": {},
|
|
}},
|
|
{BucketPrefixes: []string{"shared-"}, Permissions: PermSet{
|
|
"bucket.list": {}, "bucket.read": {}, "object.read": {},
|
|
}},
|
|
},
|
|
ClusterPerms: PermSet{"cluster.status": {}},
|
|
}
|
|
}
|
|
|
|
func TestDecidePrefixScoped(t *testing.T) {
|
|
s := testSubject()
|
|
cases := []struct {
|
|
action, bucket string
|
|
allow bool
|
|
}{
|
|
{"bucket.read", "backend-api", true},
|
|
{"bucket.read", "shared-docs", true},
|
|
{"bucket.read", "data-warehouse", false}, // no binding matches
|
|
{"object.write", "backend-api", true},
|
|
{"object.write", "shared-docs", false}, // readonly binding
|
|
{"bucket.create", "backend-new", true}, // prefix guard: new name matches
|
|
{"bucket.create", "frontend-new", false}, // prefix guard: no match
|
|
{"bucket.delete", "backend-api", false}, // permission not granted at all
|
|
}
|
|
for _, tc := range cases {
|
|
d := Decide(s, tc.action, Resource{Bucket: tc.bucket})
|
|
if d.Allow != tc.allow {
|
|
t.Errorf("Decide(%s, %s) = %v (%s), want %v", tc.action, tc.bucket, d.Allow, d.Reason, tc.allow)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecideUnscopedListEndpoint(t *testing.T) {
|
|
// GET /buckets carries no bucket name: allowed if ANY binding grants
|
|
// bucket.list (the response is filtered per bucket afterwards).
|
|
s := testSubject()
|
|
if d := Decide(s, "bucket.list", Resource{}); !d.Allow {
|
|
t.Errorf("bucket.list with empty resource should be allowed for a subject holding it in any binding: %s", d.Reason)
|
|
}
|
|
noList := Subject{ID: "bob", Bindings: []Binding{{BucketPrefixes: []string{"x-"}, Permissions: PermSet{"object.read": {}}}}}
|
|
if d := Decide(noList, "bucket.list", Resource{}); d.Allow {
|
|
t.Error("bucket.list should be denied when no binding grants it")
|
|
}
|
|
}
|
|
|
|
func TestDecideGlobal(t *testing.T) {
|
|
s := testSubject()
|
|
if d := Decide(s, "cluster.status", Resource{}); !d.Allow {
|
|
t.Errorf("cluster.status should be allowed: %s", d.Reason)
|
|
}
|
|
if d := Decide(s, "cluster.statistics", Resource{}); d.Allow {
|
|
t.Error("cluster.statistics should be denied")
|
|
}
|
|
if d := Decide(s, "key.create", Resource{}); d.Allow {
|
|
t.Error("admin-only key.create should be denied for a team subject")
|
|
}
|
|
}
|
|
|
|
func TestDecideUnknownPermission(t *testing.T) {
|
|
if d := Decide(testSubject(), "bucket.explode", Resource{}); d.Allow {
|
|
t.Error("unknown permission must be denied")
|
|
}
|
|
}
|
|
|
|
func TestAdminSubject(t *testing.T) {
|
|
a := AdminSubject("root")
|
|
if !a.IsAdmin {
|
|
t.Error("AdminSubject must set IsAdmin")
|
|
}
|
|
// Admin goes through the exact same Decide path, no shortcut.
|
|
for _, action := range []string{"bucket.delete", "object.write", "key.create", "key.read_secret", "cluster.layout.apply", "node.repair"} {
|
|
if d := Decide(a, action, Resource{Bucket: "any-bucket-at-all"}); !d.Allow {
|
|
t.Errorf("admin denied %s: %s", action, d.Reason)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEffectivePermissions(t *testing.T) {
|
|
s := testSubject()
|
|
got := EffectivePermissions(s, "backend-api")
|
|
want := []string{"bucket.create", "bucket.list", "bucket.read", "object.read", "object.write"}
|
|
if !equalStrings(got, want) {
|
|
t.Errorf("EffectivePermissions(backend-api) = %v, want %v", got, want)
|
|
}
|
|
if got := EffectivePermissions(s, "data-x"); got != nil {
|
|
t.Errorf("EffectivePermissions(data-x) = %v, want nil", got)
|
|
}
|
|
admin := EffectivePermissions(AdminSubject("root"), "anything")
|
|
if !sort.StringsAreSorted(admin) || len(admin) == 0 {
|
|
t.Errorf("admin effective permissions should be all prefix-scoped perms, sorted; got %v", admin)
|
|
}
|
|
}
|
|
|
|
func equalStrings(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|