The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/accesscontrol/acimpl/service_test.go

965 lines
31 KiB

package acimpl
import (
"context"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
func TestMain(m *testing.M) {
testsuite.Run(m)
}
func setupTestEnv(t testing.TB) *Service {
t.Helper()
cfg := setting.NewCfg()
ac := &Service{
cache: localcache.ProvideService(),
cfg: cfg,
features: featuremgmt.WithFeatures(),
log: log.New("accesscontrol"),
registrations: accesscontrol.RegistrationList{},
roles: accesscontrol.BuildBasicRoleDefinitions(),
store: database.ProvideService(db.InitTestDB(t)),
}
require.NoError(t, ac.RegisterFixedRoles(context.Background()))
return ac
}
func TestUsageMetrics(t *testing.T) {
tests := []struct {
name string
expectedValue int
}{
{
name: "Expecting metric with value 1",
expectedValue: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := setting.NewCfg()
s := ProvideOSSService(
cfg,
database.ProvideService(db.InitTestDB(t)),
&resourcepermissions.FakeActionSetSvc{},
localcache.ProvideService(),
featuremgmt.WithFeatures(),
tracing.InitializeTracerForTest(),
)
assert.Equal(t, tt.expectedValue, s.GetUsageStats(context.Background())["stats.oss.accesscontrol.enabled.count"])
})
}
}
func TestService_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 basic role assignment",
registrations: []accesscontrol.RoleRegistration{
{
Role: accesscontrol.RoleDTO{Name: "fixed:test:test"},
Grants: []string{"WrongAdmin"},
},
},
wantErr: true,
err: accesscontrol.ErrInvalidBuiltinRole.Build(accesscontrol.ErrInvalidBuiltinRoleData("WrongAdmin")),
},
{
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 TestService_DeclarePluginRoles(t *testing.T) {
tests := []struct {
name string
pluginID string
registrations []plugins.RoleRegistration
wantErr bool
err error
}{
{
name: "should work with empty list",
wantErr: false,
},
{
name: "should add registration",
pluginID: "test-app",
registrations: []plugins.RoleRegistration{
{
Role: plugins.Role{Name: "Tester"},
Grants: []string{"Admin"},
},
},
wantErr: false,
},
{
name: "should add registration with valid permissions",
pluginID: "test-app",
registrations: []plugins.RoleRegistration{
{
Role: plugins.Role{
Name: "Tester",
Permissions: []plugins.Permission{
{Action: "plugins.app:access", Scope: "plugins:id:test-app"},
{Action: "test-app:read"},
{Action: "test-app.resource:read"},
},
},
Grants: []string{"Admin"},
},
},
wantErr: false,
},
{
name: "should fail registration invalid permission action",
pluginID: "test-app",
registrations: []plugins.RoleRegistration{
{
Role: plugins.Role{
Name: "Tester",
Permissions: []plugins.Permission{
{Action: "invalid.test-app.resource:read"},
},
},
Grants: []string{"Admin"},
},
},
wantErr: true,
err: &accesscontrol.ErrorInvalidRole{},
},
{
name: "should fail registration invalid basic role assignment",
pluginID: "test-app",
registrations: []plugins.RoleRegistration{
{
Role: plugins.Role{Name: "Tester"},
Grants: []string{"WrongAdmin"},
},
},
wantErr: true,
err: accesscontrol.ErrInvalidBuiltinRole.Build(accesscontrol.ErrInvalidBuiltinRoleData("WrongAdmin")),
},
{
name: "should add multiple registrations at once",
pluginID: "test-app",
registrations: []plugins.RoleRegistration{
{
Role: plugins.Role{Name: "Tester"},
Grants: []string{"Admin"},
},
{
Role: plugins.Role{Name: "Tester2"},
Grants: []string{"Admin"},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ac := setupTestEnv(t)
ac.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)
// Reset the registations
ac.registrations = accesscontrol.RegistrationList{}
// Test
err := ac.DeclarePluginRoles(context.Background(), tt.pluginID, tt.pluginID, 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 TestService_RegisterFixedRoles(t *testing.T) {
tests := []struct {
name string
token licensing.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,
},
{
name: "should register and assign fixed and plugins roles",
registrations: []accesscontrol.RoleRegistration{
{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.PluginRolePrefix + "test-app:tester",
DisplayName: "Tester",
Permissions: []accesscontrol.Permission{{Action: "test-app: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 TestService_SearchUsersPermissions(t *testing.T) {
searchOption := accesscontrol.SearchOptions{ActionPrefix: "teams"}
ctx := context.Background()
listAllPerms := map[string][]string{accesscontrol.ActionUsersPermissionsRead: {"users:*"}}
listSomePerms := map[string][]string{accesscontrol.ActionUsersPermissionsRead: {"users:id:2"}}
tests := []struct {
name string
siuPermissions map[string][]string
searchOption accesscontrol.SearchOptions
ramRoles map[string]*accesscontrol.RoleDTO // BasicRole => RBAC BasicRole
storedPerms map[int64][]accesscontrol.Permission // UserID => Permissions
storedRoles map[int64][]string // UserID => Roles
want map[int64][]accesscontrol.Permission
wantErr bool
}{
{
name: "ram only",
siuPermissions: listAllPerms,
searchOption: searchOption,
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
}},
accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {string(identity.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: map[int64][]accesscontrol.Permission{
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
},
},
{
name: "stored only",
siuPermissions: listAllPerms,
searchOption: searchOption,
storedPerms: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {string(identity.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
},
},
{
name: "ram and stored",
siuPermissions: listAllPerms,
searchOption: searchOption,
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
}},
accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
}},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {string(identity.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
},
},
{
name: "ram only search on scope",
siuPermissions: listAllPerms,
searchOption: accesscontrol.SearchOptions{Scope: "teams:id:2"},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {string(identity.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: map[int64][]accesscontrol.Permission{
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}},
},
},
{
name: "view permission on subset of users only",
siuPermissions: listSomePerms,
searchOption: searchOption,
ramRoles: map[string]*accesscontrol.RoleDTO{
accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
}},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {accesscontrol.RoleGrafanaAdmin},
},
want: map[int64][]accesscontrol.Permission{
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
},
},
{
name: "check action filter on RAM permissions works correctly",
siuPermissions: listAllPerms,
searchOption: searchOption,
ramRoles: map[string]*accesscontrol.RoleDTO{
accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersCreate},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
}},
},
storedRoles: map[int64][]string{1: {accesscontrol.RoleGrafanaAdmin}},
want: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
},
},
{
name: "check empty action filter on RAM permissions works correctly",
siuPermissions: listAllPerms,
searchOption: accesscontrol.SearchOptions{},
ramRoles: map[string]*accesscontrol.RoleDTO{
accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionUsersCreate},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionAnnotationsRead, Scope: "annotations:*"},
}},
},
storedRoles: map[int64][]string{1: {accesscontrol.RoleGrafanaAdmin}},
want: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionUsersCreate},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionAnnotationsRead, Scope: "annotations:*"},
},
},
},
{
// This test is not exactly representative as normally the store would return
// only the user's basic roles and the user's stored permissions
name: "check namespacedId filter works correctly",
siuPermissions: listAllPerms,
searchOption: accesscontrol.SearchOptions{NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceServiceAccount)},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
}},
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:*"},
}},
accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
}},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {string(identity.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}, {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ac := setupTestEnv(t)
ac.roles = tt.ramRoles
ac.store = actest.FakeStore{
ExpectedUsersPermissions: tt.storedPerms,
ExpectedUsersRoles: tt.storedRoles,
}
siu := &user.SignedInUser{OrgID: 2, Permissions: map[int64]map[string][]string{2: tt.siuPermissions}}
got, err := ac.SearchUsersPermissions(ctx, siu, tt.searchOption)
if tt.wantErr {
require.NotNil(t, err)
return
}
require.Nil(t, err)
require.Len(t, got, len(tt.want), "expected more users permissions")
for userID, wantPerm := range tt.want {
gotPerm, ok := got[userID]
require.True(t, ok, "expected permissions for user", userID)
require.ElementsMatch(t, gotPerm, wantPerm)
}
})
}
}
func TestService_SearchUserPermissions(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
searchOption accesscontrol.SearchOptions
withActionSets bool
actionSets map[string][]string
ramRoles map[string]*accesscontrol.RoleDTO // BasicRole => RBAC BasicRole
storedPerms map[int64][]accesscontrol.Permission // UserID => Permissions
storedRoles map[int64][]string // UserID => Roles
want []accesscontrol.Permission
wantErr bool
}{
{
name: "ram only",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsCreate},
}},
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
}},
accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {string(identity.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
},
{
name: "stored only",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {string(identity.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
},
},
{
name: "ram and stored",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
}},
accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
}},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
2: {string(identity.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
},
},
{
name: "check action prefix filter works correctly",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionUsersCreate},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionAnnotationsRead, Scope: "annotations:*"},
}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
},
want: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"},
},
},
{
name: "check action filter works correctly",
searchOption: accesscontrol.SearchOptions{
Action: accesscontrol.ActionTeamsRead,
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
{Action: accesscontrol.ActionUsersCreate},
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionAnnotationsRead, Scope: "annotations:*"},
}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
},
want: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
},
},
{
name: "check action sets are correctly included if an action is specified",
searchOption: accesscontrol.SearchOptions{
Action: "dashboards:read",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
},
withActionSets: true,
actionSets: map[string][]string{
"dashboards:view": {"dashboards:read"},
"dashboards:edit": {"dashboards:read", "dashboards:write", "dashboards:read-advanced"},
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: "dashboards:read", Scope: "dashboards:uid:ram"},
}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {
{Action: "dashboards:read", Scope: "dashboards:uid:stored"},
{Action: "dashboards:edit", Scope: "dashboards:uid:stored2"},
{Action: "dashboards:view", Scope: "dashboards:uid:stored3"},
},
},
want: []accesscontrol.Permission{
{Action: "dashboards:read", Scope: "dashboards:uid:ram"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored2"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored3"},
},
},
{
name: "check action sets are correctly included if an action prefix is specified",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "dashboards",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
},
withActionSets: true,
actionSets: map[string][]string{
"dashboards:view": {"dashboards:read"},
"folders:view": {"dashboards:read", "folders:read"},
"dashboards:edit": {"dashboards:read", "dashboards:write"},
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: "dashboards:read", Scope: "dashboards:uid:ram"},
}},
},
storedRoles: map[int64][]string{
1: {string(identity.RoleEditor)},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {
{Action: "dashboards:read", Scope: "dashboards:uid:stored"},
{Action: "folders:view", Scope: "folders:uid:stored2"},
{Action: "dashboards:edit", Scope: "dashboards:uid:stored3"},
},
},
want: []accesscontrol.Permission{
{Action: "dashboards:read", Scope: "dashboards:uid:ram"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored"},
{Action: "dashboards:read", Scope: "folders:uid:stored2"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored3"},
{Action: "dashboards:write", Scope: "dashboards:uid:stored3"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ac := setupTestEnv(t)
if tt.withActionSets {
ac.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)
actionSetSvc := resourcepermissions.NewActionSetService()
for set, actions := range tt.actionSets {
actionSetSvc.StoreActionSet(strings.Split(set, ":")[0], strings.Split(set, ":")[1], actions)
}
ac.actionResolver = actionSetSvc
}
ac.roles = tt.ramRoles
ac.store = actest.FakeStore{
ExpectedUsersPermissions: tt.storedPerms,
ExpectedUsersRoles: tt.storedRoles,
}
got, err := ac.searchUserPermissions(ctx, 1, tt.searchOption)
if tt.wantErr {
require.NotNil(t, err)
return
}
require.Nil(t, err)
assert.ElementsMatch(t, got, tt.want)
})
}
}
func TestService_SaveExternalServiceRole(t *testing.T) {
type run struct {
cmd accesscontrol.SaveExternalServiceRoleCommand
wantErr bool
}
tests := []struct {
name string
runs []run
}{
{
name: "can create a role",
runs: []run{
{
cmd: accesscontrol.SaveExternalServiceRoleCommand{
AssignmentOrgID: 2,
ServiceAccountID: 2,
ExternalServiceID: "App 1",
Permissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:id:1"}},
},
wantErr: false,
},
},
},
{
name: "can update a role",
runs: []run{
{
cmd: accesscontrol.SaveExternalServiceRoleCommand{
AssignmentOrgID: 1,
ServiceAccountID: 2,
ExternalServiceID: "App 1",
Permissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:id:1"}},
},
wantErr: false,
},
{
cmd: accesscontrol.SaveExternalServiceRoleCommand{
AssignmentOrgID: 1,
ServiceAccountID: 2,
ExternalServiceID: "App 1",
Permissions: []accesscontrol.Permission{
{Action: "users:write", Scope: "users:id:1"},
{Action: "users:write", Scope: "users:id:2"},
},
},
wantErr: false,
},
},
},
{
name: "test command validity - no service account ID",
runs: []run{
{
cmd: accesscontrol.SaveExternalServiceRoleCommand{
AssignmentOrgID: 2,
ExternalServiceID: "App 1",
Permissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:id:1"}},
},
wantErr: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
ac := setupTestEnv(t)
ac.features = featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAccounts)
for _, r := range tt.runs {
err := ac.SaveExternalServiceRole(ctx, r.cmd)
if r.wantErr {
require.Error(t, err)
continue
}
require.NoError(t, err)
// Check that the permissions and assignment are stored correctly
perms, errGetPerms := ac.getUserPermissions(ctx, &user.SignedInUser{OrgID: r.cmd.AssignmentOrgID, UserID: 2}, accesscontrol.Options{})
require.NoError(t, errGetPerms)
assert.ElementsMatch(t, r.cmd.Permissions, perms)
}
})
}
}
func TestService_DeleteExternalServiceRole(t *testing.T) {
tests := []struct {
name string
initCmd *accesscontrol.SaveExternalServiceRoleCommand
externalServiceID string
wantErr bool
}{
{
name: "handles deleting role that doesn't exist",
externalServiceID: "App 1",
wantErr: false,
},
{
name: "handles deleting role that exists",
initCmd: &accesscontrol.SaveExternalServiceRoleCommand{
AssignmentOrgID: 1,
ServiceAccountID: 2,
ExternalServiceID: "App 1",
Permissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:id:1"}},
},
externalServiceID: "App 1",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
ac := setupTestEnv(t)
ac.features = featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAccounts)
if tt.initCmd != nil {
err := ac.SaveExternalServiceRole(ctx, *tt.initCmd)
require.NoError(t, err)
}
err := ac.DeleteExternalServiceRole(ctx, tt.externalServiceID)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
if tt.initCmd != nil {
// Check that the permissions and assignment are removed correctly
perms, errGetPerms := ac.getUserPermissions(ctx, &user.SignedInUser{OrgID: tt.initCmd.AssignmentOrgID, UserID: 2}, accesscontrol.Options{})
require.NoError(t, errGetPerms)
assert.Empty(t, perms)
}
})
}
}