grafana/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go
2022-06-23 12:09:03 +02:00

497 lines
13 KiB
Go

package ossaccesscontrol
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
func setupTestEnv(t testing.TB) *OSSAccessControlService {
t.Helper()
cfg := setting.NewCfg()
cfg.RBACEnabled = true
ac := &OSSAccessControlService{
cfg: cfg,
log: log.New("accesscontrol"),
registrations: accesscontrol.RegistrationList{},
scopeResolvers: accesscontrol.NewScopeResolvers(),
store: database.ProvideService(sqlstore.InitTestDB(t)),
roles: accesscontrol.BuildBasicRoleDefinitions(),
}
require.NoError(t, ac.RegisterFixedRoles(context.Background()))
return ac
}
// extractRawPermissionsHelper extracts action and scope fields only from a permission slice
func extractRawPermissionsHelper(perms []accesscontrol.Permission) []accesscontrol.Permission {
res := make([]accesscontrol.Permission, len(perms))
for i, p := range perms {
res[i] = accesscontrol.Permission{Action: p.Action, Scope: p.Scope}
}
return res
}
type evaluatingPermissionsTestCase struct {
desc string
user userTestCase
endpoints []endpointTestCase
evalResult bool
}
type userTestCase struct {
name string
orgRole models.RoleType
isGrafanaAdmin bool
}
type endpointTestCase struct {
evaluator accesscontrol.Evaluator
}
func TestEvaluatingPermissions(t *testing.T) {
testCases := []evaluatingPermissionsTestCase{
{
desc: "should successfully evaluate access to the endpoint",
user: userTestCase{
name: "testuser",
orgRole: models.ROLE_VIEWER,
isGrafanaAdmin: true,
},
endpoints: []endpointTestCase{
{evaluator: accesscontrol.EvalPermission(accesscontrol.ActionUsersDisable, accesscontrol.ScopeGlobalUsersAll)},
{evaluator: accesscontrol.EvalPermission(accesscontrol.ActionUsersEnable, accesscontrol.ScopeGlobalUsersAll)},
},
evalResult: true,
},
{
desc: "should restrict access to the unauthorized endpoints",
user: userTestCase{
name: "testuser",
orgRole: models.ROLE_VIEWER,
isGrafanaAdmin: false,
},
endpoints: []endpointTestCase{
{evaluator: accesscontrol.EvalPermission(accesscontrol.ActionUsersCreate, accesscontrol.ScopeGlobalUsersAll)},
},
evalResult: false,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
ac := setupTestEnv(t)
// Use OSS roles for this test to pass
err := accesscontrol.DeclareFixedRoles(ac)
require.NoError(t, err)
errRegisterRoles := ac.RegisterFixedRoles(context.Background())
require.NoError(t, errRegisterRoles)
user := &models.SignedInUser{
UserId: 1,
OrgId: 1,
Name: tc.user.name,
OrgRole: tc.user.orgRole,
IsGrafanaAdmin: tc.user.isGrafanaAdmin,
}
for _, endpoint := range tc.endpoints {
result, err := ac.Evaluate(context.Background(), user, endpoint.evaluator)
require.NoError(t, err)
assert.Equal(t, tc.evalResult, result)
}
})
}
}
func TestUsageMetrics(t *testing.T) {
tests := []struct {
name string
enabled bool
expectedValue int
}{
{
name: "Expecting metric with value 0",
enabled: false,
expectedValue: 0,
},
{
name: "Expecting metric with value 1",
enabled: true,
expectedValue: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := setting.NewCfg()
if tt.enabled {
cfg.RBACEnabled = true
}
s, errInitAc := ProvideService(
featuremgmt.WithFeatures(),
cfg,
database.ProvideService(sqlstore.InitTestDB(t)),
routing.NewRouteRegister(),
)
require.NoError(t, errInitAc)
assert.Equal(t, tt.expectedValue, s.GetUsageStats(context.Background())["stats.oss.accesscontrol.enabled.count"])
})
}
}
func TestOSSAccessControlService_RegisterFixedRole(t *testing.T) {
perm := accesscontrol.Permission{Action: "test:test", Scope: "test:*"}
role := accesscontrol.RoleDTO{
Version: 1,
Name: "fixed:test:test",
Permissions: []accesscontrol.Permission{perm},
}
builtInRoles := []string{"Editor"}
// Admin is going to get the role as well
includedBuiltInRoles := []string{"Editor", "Admin"}
// Grafana Admin and Viewer won't get the role
excludedbuiltInRoles := []string{"Viewer", "Grafana Admin"}
ac := setupTestEnv(t)
ac.registerFixedRole(role, builtInRoles)
for _, br := range includedBuiltInRoles {
builtinRole, ok := ac.roles[br]
assert.True(t, ok)
assert.Contains(t, builtinRole.Permissions, perm)
}
for _, br := range excludedbuiltInRoles {
builtinRole, ok := ac.roles[br]
assert.True(t, ok)
assert.NotContains(t, builtinRole.Permissions, perm)
}
}
func TestOSSAccessControlService_DeclareFixedRoles(t *testing.T) {
tests := []struct {
name string
registrations []accesscontrol.RoleRegistration
wantErr bool
err error
}{
{
name: "should work with empty list",
wantErr: false,
},
{
name: "should add registration",
registrations: []accesscontrol.RoleRegistration{
{
Role: accesscontrol.RoleDTO{
Name: "fixed:test:test",
},
Grants: []string{"Admin"},
},
},
wantErr: false,
},
{
name: "should fail registration invalid role name",
registrations: []accesscontrol.RoleRegistration{
{
Role: accesscontrol.RoleDTO{
Name: "custom:test:test",
},
Grants: []string{"Admin"},
},
},
wantErr: true,
err: accesscontrol.ErrFixedRolePrefixMissing,
},
{
name: "should fail registration invalid builtin role assignment",
registrations: []accesscontrol.RoleRegistration{
{
Role: accesscontrol.RoleDTO{
Name: "fixed:test:test",
},
Grants: []string{"WrongAdmin"},
},
},
wantErr: true,
err: accesscontrol.ErrInvalidBuiltinRole,
},
{
name: "should add multiple registrations at once",
registrations: []accesscontrol.RoleRegistration{
{
Role: accesscontrol.RoleDTO{
Name: "fixed:test:test",
},
Grants: []string{"Admin"},
},
{
Role: accesscontrol.RoleDTO{
Name: "fixed:test2:test2",
},
Grants: []string{"Admin"},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ac := setupTestEnv(t)
// Reset the registations
ac.registrations = accesscontrol.RegistrationList{}
// Test
err := ac.DeclareFixedRoles(tt.registrations...)
if tt.wantErr {
require.Error(t, err)
assert.ErrorIs(t, err, tt.err)
return
}
require.NoError(t, err)
registrationCnt := 0
ac.registrations.Range(func(registration accesscontrol.RoleRegistration) bool {
registrationCnt++
return true
})
assert.Equal(t, len(tt.registrations), registrationCnt,
"expected service registration list to contain all test registrations")
})
}
}
func TestOSSAccessControlService_RegisterFixedRoles(t *testing.T) {
tests := []struct {
name string
token models.Licensing
registrations []accesscontrol.RoleRegistration
wantErr bool
}{
{
name: "should work with empty list",
},
{
name: "should register and assign role",
registrations: []accesscontrol.RoleRegistration{
{
Role: accesscontrol.RoleDTO{
Name: "fixed:test:test",
Permissions: []accesscontrol.Permission{{Action: "test:test"}},
},
Grants: []string{"Editor"},
},
},
wantErr: false,
},
{
name: "should register and assign multiple roles",
registrations: []accesscontrol.RoleRegistration{
{
Role: accesscontrol.RoleDTO{
Name: "fixed:test:test",
Permissions: []accesscontrol.Permission{{Action: "test:test"}},
},
Grants: []string{"Editor"},
},
{
Role: accesscontrol.RoleDTO{
Name: "fixed:test2:test2",
Permissions: []accesscontrol.Permission{
{Action: "test:test2"},
{Action: "test:test3", Scope: "test:*"},
},
},
Grants: []string{"Viewer"},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ac := setupTestEnv(t)
ac.registrations.Append(tt.registrations...)
// Test
err := ac.RegisterFixedRoles(context.Background())
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
// Check
for _, registration := range tt.registrations {
// Check builtin roles (parents included) have been granted with the permissions
for br := range accesscontrol.BuiltInRolesWithParents(registration.Grants) {
builtinRole, ok := ac.roles[br]
assert.True(t, ok)
for _, expectedPermission := range registration.Role.Permissions {
assert.Contains(t, builtinRole.Permissions, expectedPermission)
}
}
}
})
}
}
func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
testUser := models.SignedInUser{
UserId: 2,
OrgId: 3,
OrgName: "TestOrg",
OrgRole: models.ROLE_VIEWER,
Login: "testUser",
Name: "Test User",
Email: "testuser@example.org",
}
registration := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
UID: "fixed:test:test",
Name: "fixed:test:test",
Description: "Test role",
Permissions: []accesscontrol.Permission{},
},
Grants: []string{"Viewer"},
}
tests := []struct {
name string
user models.SignedInUser
rawPerm accesscontrol.Permission
wantPerm accesscontrol.Permission
wantErr bool
}{
{
name: "Translate users:self",
user: testUser,
rawPerm: accesscontrol.Permission{Action: "users:read", Scope: "users:self"},
wantPerm: accesscontrol.Permission{Action: "users:read", Scope: "users:id:2"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup
ac := setupTestEnv(t)
registration.Role.Permissions = []accesscontrol.Permission{tt.rawPerm}
err := ac.DeclareFixedRoles(registration)
require.NoError(t, err)
err = ac.RegisterFixedRoles(context.Background())
require.NoError(t, err)
// Test
userPerms, err := ac.GetUserPermissions(context.Background(), &tt.user, accesscontrol.Options{})
if tt.wantErr {
assert.Error(t, err, "Expected an error with GetUserPermissions.")
return
}
require.NoError(t, err, "Did not expect an error with GetUserPermissions.")
rawUserPerms := extractRawPermissionsHelper(userPerms)
assert.Contains(t, rawUserPerms, tt.wantPerm, "Expected resolution of raw permission")
assert.NotContains(t, rawUserPerms, tt.rawPerm, "Expected raw permission to have been resolved")
})
}
}
func TestOSSAccessControlService_Evaluate(t *testing.T) {
testUser := models.SignedInUser{
UserId: 2,
OrgId: 3,
OrgName: "TestOrg",
OrgRole: models.ROLE_VIEWER,
Login: "testUser",
Name: "Test User",
Email: "testuser@example.org",
}
registration := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
UID: "fixed:test:test",
Name: "fixed:test:test",
Description: "Test role",
Permissions: []accesscontrol.Permission{},
},
Grants: []string{"Viewer"},
}
userLoginScopeSolver := accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) {
if initialScope == "users:login:testUser" {
return []string{"users:id:2"}, nil
}
return []string{initialScope}, nil
})
tests := []struct {
name string
user models.SignedInUser
rawPerm accesscontrol.Permission
evaluator accesscontrol.Evaluator
wantAccess bool
wantErr bool
}{
{
name: "Should translate users:self",
user: testUser,
rawPerm: accesscontrol.Permission{Action: "users:read", Scope: "users:self"},
evaluator: accesscontrol.EvalPermission("users:read", "users:id:2"),
wantAccess: true,
wantErr: false,
},
{
name: "Should translate users:login:testUser",
user: testUser,
rawPerm: accesscontrol.Permission{Action: "users:read", Scope: "users:id:2"},
evaluator: accesscontrol.EvalPermission("users:read", "users:login:testUser"),
wantAccess: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup
ac := setupTestEnv(t)
ac.RegisterScopeAttributeResolver("users:login:", userLoginScopeSolver)
registration.Role.Permissions = []accesscontrol.Permission{tt.rawPerm}
err := ac.DeclareFixedRoles(registration)
require.NoError(t, err)
err = ac.RegisterFixedRoles(context.Background())
require.NoError(t, err)
// Test
hasAccess, err := ac.Evaluate(context.TODO(), &tt.user, tt.evaluator)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.wantAccess, hasAccess)
})
}
}