279 lines
7.3 KiB
Go
279 lines
7.3 KiB
Go
package permission
|
|
|
|
import "testing"
|
|
|
|
func TestHasRole(t *testing.T) {
|
|
tests := []struct {
|
|
groups []string
|
|
role Role
|
|
want bool
|
|
}{
|
|
{[]string{"role:admin"}, RoleAdmin, true},
|
|
{[]string{"admin"}, RoleAdmin, true},
|
|
{[]string{"role:user"}, RoleAdmin, false},
|
|
{[]string{" role:service "}, RoleService, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := HasRole(tt.groups, tt.role); got != tt.want {
|
|
t.Fatalf("HasRole(%v, %q) = %v, want %v", tt.groups, tt.role, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHasPermissionHierarchy(t *testing.T) {
|
|
groups := []string{"drive:write"}
|
|
|
|
if !HasPermission(groups, ResourceDrive, LevelRead) {
|
|
t.Fatal("write should satisfy read")
|
|
}
|
|
if !HasPermission(groups, ResourceDrive, LevelWrite) {
|
|
t.Fatal("write should satisfy write")
|
|
}
|
|
if HasPermission(groups, ResourceDrive, LevelAdmin) {
|
|
t.Fatal("write should not satisfy admin")
|
|
}
|
|
}
|
|
|
|
func TestHasPermissionAdminBypass(t *testing.T) {
|
|
groups := []string{"role:admin"}
|
|
|
|
if !HasPermission(groups, ResourcePhotos, LevelAdmin) {
|
|
t.Fatal("platform admin should bypass resource checks")
|
|
}
|
|
}
|
|
|
|
func TestHasPermissionResourceAdmin(t *testing.T) {
|
|
groups := []string{"calendar:admin"}
|
|
|
|
if !HasPermission(groups, ResourceCalendar, LevelRead) {
|
|
t.Fatal("resource admin should satisfy read")
|
|
}
|
|
if !HasPermission(groups, ResourceCalendar, LevelWrite) {
|
|
t.Fatal("resource admin should satisfy write")
|
|
}
|
|
if !HasPermission(groups, ResourceCalendar, LevelAdmin) {
|
|
t.Fatal("resource admin should satisfy admin")
|
|
}
|
|
}
|
|
|
|
func TestWithSuiteDefaultsEmptyGroups(t *testing.T) {
|
|
groups := WithSuiteDefaults(nil)
|
|
|
|
if !HasRole(groups, RoleUser) {
|
|
t.Fatal("expected role:user")
|
|
}
|
|
if !HasPermission(groups, ResourceContacts, LevelWrite) {
|
|
t.Fatal("expected contacts write")
|
|
}
|
|
if !HasPermission(groups, ResourceCalendar, LevelWrite) {
|
|
t.Fatal("expected calendar write")
|
|
}
|
|
}
|
|
|
|
func TestWithSuiteDefaultsPreservesExplicitResource(t *testing.T) {
|
|
groups := WithSuiteDefaults([]string{"contacts:read"})
|
|
|
|
if !HasPermission(groups, ResourceContacts, LevelRead) {
|
|
t.Fatal("expected contacts read")
|
|
}
|
|
if HasPermission(groups, ResourceContacts, LevelWrite) {
|
|
t.Fatal("contacts:read must not be upgraded to write")
|
|
}
|
|
if HasPermission(groups, ResourceDrive, LevelRead) {
|
|
t.Fatal("must not grant drive when contacts-only group is present")
|
|
}
|
|
}
|
|
|
|
func TestDeriveAccountRole(t *testing.T) {
|
|
if got := DeriveAccountRole(true, "active"); got != AccountRoleAdmin {
|
|
t.Fatalf("admin = %q", got)
|
|
}
|
|
if got := DeriveAccountRole(false, "active"); got != AccountRoleUser {
|
|
t.Fatalf("user = %q", got)
|
|
}
|
|
if got := DeriveAccountRole(false, "invited"); got != AccountRoleGuest {
|
|
t.Fatalf("guest = %q", got)
|
|
}
|
|
if got := DeriveAccountRole(true, "disabled"); got != AccountRoleSuspended {
|
|
t.Fatalf("suspended = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestWithGuestAccess(t *testing.T) {
|
|
groups := WithGuestAccess(nil)
|
|
if !HasRole(groups, RoleGuest) {
|
|
t.Fatal("expected guest role")
|
|
}
|
|
if !HasPermission(groups, ResourceDrive, LevelWrite) {
|
|
t.Fatal("expected drive write for own uploads")
|
|
}
|
|
if HasPermission(groups, ResourceContacts, LevelRead) {
|
|
t.Fatal("guest must not get contacts")
|
|
}
|
|
if HasPermission(groups, ResourcePhotos, LevelRead) {
|
|
t.Fatal("guest must not get photos")
|
|
}
|
|
}
|
|
|
|
func TestWithSuiteDefaultsUserRoleOnly(t *testing.T) {
|
|
groups := WithSuiteDefaults([]string{"role:user"})
|
|
|
|
if !HasPermission(groups, ResourceContacts, LevelWrite) {
|
|
t.Fatal("role:user without resource groups should get suite defaults")
|
|
}
|
|
}
|
|
|
|
func TestHasPermissionIsolation(t *testing.T) {
|
|
groups := []string{"contacts:read"}
|
|
|
|
if !HasPermission(groups, ResourceContacts, LevelRead) {
|
|
t.Fatal("expected contacts read")
|
|
}
|
|
if HasPermission(groups, ResourceDrive, LevelRead) {
|
|
t.Fatal("contacts permission must not grant drive access")
|
|
}
|
|
}
|
|
|
|
func TestAdminScopeString(t *testing.T) {
|
|
tests := []struct {
|
|
scope AdminScope
|
|
want string
|
|
}{
|
|
{AdminScopeRead, "read"},
|
|
{AdminScopeWrite, "write"},
|
|
{AdminScope(0), "unknown"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := tt.scope.String(); got != tt.want {
|
|
t.Fatalf("AdminScope(%d).String() = %q, want %q", tt.scope, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseAdminScope(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
want AdminScope
|
|
ok bool
|
|
}{
|
|
{"read", AdminScopeRead, true},
|
|
{"write", AdminScopeWrite, true},
|
|
{" READ ", AdminScopeRead, true},
|
|
{"Write", AdminScopeWrite, true},
|
|
{"admin", 0, false},
|
|
{"", 0, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got, ok := ParseAdminScope(tt.in)
|
|
if ok != tt.ok || got != tt.want {
|
|
t.Fatalf("ParseAdminScope(%q) = (%v, %v), want (%v, %v)", tt.in, got, ok, tt.want, tt.ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHasAdminScopeReadOnly(t *testing.T) {
|
|
groups := []string{GroupAdminRead}
|
|
|
|
if !HasAdminScope(groups, AdminScopeRead) {
|
|
t.Fatal("admin:read should satisfy read scope")
|
|
}
|
|
if HasAdminScope(groups, AdminScopeWrite) {
|
|
t.Fatal("admin:read should not satisfy write scope")
|
|
}
|
|
}
|
|
|
|
func TestHasAdminScopeWriteImpliesRead(t *testing.T) {
|
|
groups := []string{GroupAdminWrite}
|
|
|
|
if !HasAdminScope(groups, AdminScopeRead) {
|
|
t.Fatal("admin:write should satisfy read scope")
|
|
}
|
|
if !HasAdminScope(groups, AdminScopeWrite) {
|
|
t.Fatal("admin:write should satisfy write scope")
|
|
}
|
|
}
|
|
|
|
func TestHasAdminScopePlatformAdminBypass(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
groups []string
|
|
}{
|
|
{"bare admin role", []string{"admin"}},
|
|
{"prefixed admin role", []string{"role:admin"}},
|
|
{"trimmed prefixed admin role", []string{" role:admin "}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if !HasAdminScope(tt.groups, AdminScopeRead) {
|
|
t.Fatal("platform admin should satisfy read scope")
|
|
}
|
|
if !HasAdminScope(tt.groups, AdminScopeWrite) {
|
|
t.Fatal("platform admin should satisfy write scope")
|
|
}
|
|
if !HasAdminScope(tt.groups, DefaultAdminScope) {
|
|
t.Fatal("platform admin should satisfy default admin scope")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWithPlatformAdmin(t *testing.T) {
|
|
groups := WithSuiteDefaults(nil)
|
|
out := WithPlatformAdmin(groups)
|
|
if !HasRole(out, RoleAdmin) {
|
|
t.Fatal("expected admin role")
|
|
}
|
|
if !HasAdminScope(out, AdminScopeWrite) {
|
|
t.Fatal("expected admin:write scope")
|
|
}
|
|
}
|
|
|
|
func TestHasAdminScopeNoAccess(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
groups []string
|
|
}{
|
|
{"empty groups", nil},
|
|
{"unrelated user role", []string{"role:user"}},
|
|
{"resource permission only", []string{"drive:admin"}},
|
|
{"unknown admin suffix", []string{"admin:unknown"}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if HasAdminScope(tt.groups, AdminScopeRead) {
|
|
t.Fatal("expected no admin read scope")
|
|
}
|
|
if HasAdminScope(tt.groups, AdminScopeWrite) {
|
|
t.Fatal("expected no admin write scope")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHasAdminScopeMaxWins(t *testing.T) {
|
|
groups := []string{GroupAdminRead, GroupAdminWrite}
|
|
|
|
if !HasAdminScope(groups, AdminScopeRead) {
|
|
t.Fatal("combined scopes should satisfy read")
|
|
}
|
|
if !HasAdminScope(groups, AdminScopeWrite) {
|
|
t.Fatal("combined scopes should satisfy write")
|
|
}
|
|
}
|
|
|
|
func TestHasAdminScopeNormalization(t *testing.T) {
|
|
groups := []string{" Admin:Write "}
|
|
|
|
if !HasAdminScope(groups, AdminScopeWrite) {
|
|
t.Fatal("admin scope groups should be case- and whitespace-normalized")
|
|
}
|
|
if !HasAdminScope(groups, AdminScopeRead) {
|
|
t.Fatal("normalized admin:write should still imply read")
|
|
}
|
|
}
|