156 lines
6.5 KiB
Go
156 lines
6.5 KiB
Go
package access
|
|
|
|
import "testing"
|
|
|
|
func TestDefaultDeny(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
req Request
|
|
code DenialCode
|
|
}{
|
|
{name: "invalid role", req: Request{ActorRole: Role("root"), Capability: CapabilityConfigRead}, code: CodeInvalidRole},
|
|
{name: "unknown capability", req: Request{ActorRole: RoleSuperAdmin, Capability: Capability("config.delete")}, code: CodeUnknownCapability},
|
|
{name: "missing grant", req: Request{ActorRole: RoleUser, Capability: CapabilityConfigRollback}, code: CodeAccessDenied},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := Authorize(test.req); !IsDenied(err, test.code) {
|
|
t.Fatalf("expected %s, got %v", test.code, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRoleBoundaries(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
req Request
|
|
code DenialCode
|
|
}{
|
|
{name: "super admin resets admin", req: Request{ActorRole: RoleSuperAdmin, Capability: CapabilityPasswordReset, TargetRole: RoleAdmin, RecentAuth: true, RecoveryCodeConfigured: true}},
|
|
{name: "super admin cannot be web reset target", req: Request{ActorRole: RoleSuperAdmin, Capability: CapabilityPasswordReset, TargetRole: RoleSuperAdmin, RecentAuth: true, RecoveryCodeConfigured: true}, code: CodeTargetRoleDenied},
|
|
{name: "admin resets user", req: Request{ActorRole: RoleAdmin, Capability: CapabilityPasswordReset, TargetRole: RoleUser, RecentAuth: true, RecoveryCodeConfigured: true}},
|
|
{name: "admin cannot reset admin", req: Request{ActorRole: RoleAdmin, Capability: CapabilityPasswordReset, TargetRole: RoleAdmin, RecentAuth: true, RecoveryCodeConfigured: true}, code: CodeTargetRoleDenied},
|
|
{name: "user cannot reset another user", req: Request{ActorRole: RoleUser, Capability: CapabilityPasswordReset, TargetRole: RoleUser, RecentAuth: true, RecoveryCodeConfigured: true}, code: CodeAccessDenied},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := Authorize(test.req)
|
|
if test.code == "" && err != nil {
|
|
t.Fatalf("expected allow, got %v", err)
|
|
}
|
|
if test.code != "" && !IsDenied(err, test.code) {
|
|
t.Fatalf("expected %s, got %v", test.code, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRecoveryAndRecentAuthentication(t *testing.T) {
|
|
admin := Request{ActorRole: RoleAdmin, Capability: CapabilityPluginRestart, RecentAuth: true, PluginInstanceAuthorized: true}
|
|
if err := Authorize(admin); !IsDenied(err, CodeRecoveryCodeRequired) {
|
|
t.Fatalf("expected recovery code requirement, got %v", err)
|
|
}
|
|
admin.RecoveryCodeConfigured = true
|
|
admin.RecentAuth = false
|
|
if err := Authorize(admin); !IsDenied(err, CodeRecentAuthRequired) {
|
|
t.Fatalf("expected recent authentication requirement, got %v", err)
|
|
}
|
|
superAdmin := Request{ActorRole: RoleSuperAdmin, Capability: CapabilityConfigRollback, RecentAuth: true}
|
|
if err := Authorize(superAdmin); err != nil {
|
|
t.Fatalf("super admin should be exempt from recovery code precondition: %v", err)
|
|
}
|
|
adminRollback := Request{ActorRole: RoleAdmin, Capability: CapabilityConfigRollback, RecentAuth: true}
|
|
if err := Authorize(adminRollback); !IsDenied(err, CodeAccessDenied) {
|
|
t.Fatalf("admin must not receive a recovery-code bypass for a super-admin capability: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRoleChangeRequiresTargetRecovery(t *testing.T) {
|
|
req := Request{ActorRole: RoleSuperAdmin, Capability: CapabilityRoleManage, TargetRole: RoleUser, NewRole: RoleAdmin, RecentAuth: true, RecoveryCodeConfigured: true}
|
|
if err := Authorize(req); !IsDenied(err, CodeRecoveryCodeRequired) {
|
|
t.Fatalf("expected target recovery requirement, got %v", err)
|
|
}
|
|
req.TargetRecoveryConfigured = true
|
|
if err := Authorize(req); err != nil {
|
|
t.Fatalf("expected role change to be allowed, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAdminPluginRestartScope(t *testing.T) {
|
|
req := Request{ActorRole: RoleAdmin, Capability: CapabilityPluginRestart, RecentAuth: true, RecoveryCodeConfigured: true}
|
|
if err := Authorize(req); !IsDenied(err, CodePluginScopeDenied) {
|
|
t.Fatalf("expected plugin scope denial, got %v", err)
|
|
}
|
|
req.PluginInstanceAuthorized = true
|
|
if err := Authorize(req); err != nil {
|
|
t.Fatalf("expected plugin restart to be allowed, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestManagementEndpointCatalog(t *testing.T) {
|
|
endpoints := ManagementEndpoints()
|
|
if len(endpoints) != 32 {
|
|
t.Fatalf("unexpected endpoint count: %d", len(endpoints))
|
|
}
|
|
seen := map[string]struct{}{}
|
|
for _, endpoint := range endpoints {
|
|
key := endpoint.Method + " " + endpoint.Path
|
|
if _, ok := seen[key]; ok {
|
|
t.Fatalf("duplicate endpoint: %s", key)
|
|
}
|
|
seen[key] = struct{}{}
|
|
if _, ok := GetPolicy(endpoint.Capability); !ok {
|
|
t.Fatalf("endpoint %s references unknown capability", key)
|
|
}
|
|
}
|
|
endpoint, ok := LookupEndpoint("POST", "/api/plugin/settings/example")
|
|
if !ok || endpoint.Capability != CapabilityPluginWriteOwn {
|
|
t.Fatal("plugin settings endpoint was not matched")
|
|
}
|
|
endpoint, ok = LookupEndpoint("POST", "/api/v1/config/smtp/validate")
|
|
if !ok || endpoint.Capability != CapabilityConfigWriteGlobal {
|
|
t.Fatal("configuration validation endpoint was not matched")
|
|
}
|
|
endpoint, ok = LookupEndpoint("POST", "/api/v1/system/restart")
|
|
if !ok || endpoint.Capability != CapabilityServiceRestart {
|
|
t.Fatal("system restart endpoint was not matched")
|
|
}
|
|
endpoint, ok = LookupEndpoint("GET", "/api/v1/config/email")
|
|
if !ok || endpoint.Capability != CapabilityConfigRead {
|
|
t.Fatal("configuration section endpoint was not matched")
|
|
}
|
|
endpoint, ok = LookupEndpoint("PUT", "/api/v1/config/email")
|
|
if !ok || endpoint.Capability != CapabilityConfigWriteGlobal {
|
|
t.Fatal("configuration update endpoint was not matched")
|
|
}
|
|
endpoint, ok = LookupEndpoint("GET", "/api/v1/config/backups")
|
|
if !ok || endpoint.Capability != CapabilityConfigRollback {
|
|
t.Fatal("configuration backup endpoint did not require rollback capability")
|
|
}
|
|
}
|
|
|
|
func TestEveryManagementEndpointHasAllowAndDeny(t *testing.T) {
|
|
for _, endpoint := range ManagementEndpoints() {
|
|
actorRole := RoleSuperAdmin
|
|
allowed := Request{
|
|
ActorRole: actorRole,
|
|
Capability: endpoint.Capability,
|
|
TargetRole: RoleUser,
|
|
OwnResource: true,
|
|
RecentAuth: true,
|
|
RecoveryCodeConfigured: true,
|
|
TargetRecoveryConfigured: true,
|
|
PluginInstanceAuthorized: true,
|
|
}
|
|
if err := Authorize(allowed); err != nil {
|
|
t.Fatalf("expected allow for %s %s: %v", endpoint.Method, endpoint.Path, err)
|
|
}
|
|
denied := allowed
|
|
denied.ActorRole = Role("unknown")
|
|
if err := Authorize(denied); !IsDenied(err, CodeInvalidRole) {
|
|
t.Fatalf("expected deny for %s %s, got %v", endpoint.Method, endpoint.Path, err)
|
|
}
|
|
}
|
|
}
|