3
0
mirror of https://github.com/grafana/grafana.git synced 2025-02-25 18:55:37 -06:00
grafana/pkg/services/accesscontrol/resourcepermissions/store_test.go
Ieva cfa1a2c55f
RBAC: Split non-empty scopes into kind, attribute and identifier fields for better search performance ()
* add a feature toggle

* add the fields for attribute, kind and identifier to permission

Co-authored-by: Kalle Persson <kalle.persson@grafana.com>

* set the new fields when new permissions are stored

* add migrations

Co-authored-by: Kalle Persson <kalle.persson@grafana.com>

* remove comments

* Update pkg/services/accesscontrol/migrator/migrator.go

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* feedback: put column migrations behind the feature toggle, added an index, changed how wildcard scopes are split

* PR feedback: add a comment and revert an accidentally changed file

* PR feedback: handle the case with : in resource identifier

* switch from checking feature toggle through cfg to checking it through featuremgmt

* don't put the column migrations behind a feature toggle after all - this breaks permission queries from db

---------

Co-authored-by: Kalle Persson <kalle.persson@grafana.com>
Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
2023-07-21 15:23:01 +01:00

718 lines
21 KiB
Go

package resourcepermissions
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/userimpl"
)
type setUserResourcePermissionTest struct {
desc string
orgID int64
userID int64
actions []string
resource string
resourceID string
resourceAttribute string
seeds []SetResourcePermissionCommand
}
func TestIntegrationStore_SetUserResourcePermission(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
tests := []setUserResourcePermissionTest{
{
desc: "should set resource permission for user",
userID: 1,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
},
{
desc: "should remove resource permission for user",
orgID: 1,
userID: 1,
actions: []string{},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
},
},
},
{
desc: "should add new resource permission for user",
orgID: 1,
userID: 1,
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []SetResourcePermissionCommand{
{
Actions: []string{"datasources:write"},
Resource: "datasources",
ResourceID: "1",
},
},
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
store, _ := setupTestEnv(t)
for _, s := range test.seeds {
_, err := store.SetUserResourcePermission(context.Background(), test.orgID, accesscontrol.User{ID: test.userID}, s, nil)
require.NoError(t, err)
}
added, err := store.SetUserResourcePermission(context.Background(), test.userID, accesscontrol.User{ID: test.userID}, SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
ResourceAttribute: test.resourceAttribute,
}, nil)
require.NoError(t, err)
if len(test.actions) == 0 {
assert.Equal(t, accesscontrol.ResourcePermission{}, *added)
} else {
assert.Len(t, added.Actions, len(test.actions))
assert.Equal(t, accesscontrol.Scope(test.resource, test.resourceAttribute, test.resourceID), added.Scope)
}
})
}
}
type setTeamResourcePermissionTest struct {
desc string
orgID int64
teamID int64
actions []string
resource string
resourceID string
resourceAttribute string
seeds []SetResourcePermissionCommand
}
func TestIntegrationStore_SetTeamResourcePermission(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
tests := []setTeamResourcePermissionTest{
{
desc: "should add new resource permission for team",
orgID: 1,
teamID: 1,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
},
{
desc: "should add new resource permission when others exist",
orgID: 1,
teamID: 1,
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
{
desc: "should remove permissions for team",
orgID: 1,
teamID: 1,
actions: []string{},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
store, _ := setupTestEnv(t)
for _, s := range test.seeds {
_, err := store.SetTeamResourcePermission(context.Background(), test.orgID, test.teamID, s, nil)
require.NoError(t, err)
}
added, err := store.SetTeamResourcePermission(context.Background(), test.orgID, test.teamID, SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
ResourceAttribute: test.resourceAttribute,
}, nil)
require.NoError(t, err)
if len(test.actions) == 0 {
assert.Equal(t, accesscontrol.ResourcePermission{}, *added)
} else {
assert.Len(t, added.Actions, len(test.actions))
assert.Equal(t, accesscontrol.Scope(test.resource, test.resourceAttribute, test.resourceID), added.Scope)
}
})
}
}
type setBuiltInResourcePermissionTest struct {
desc string
orgID int64
builtInRole string
actions []string
resource string
resourceID string
resourceAttribute string
seeds []SetResourcePermissionCommand
}
func TestIntegrationStore_SetBuiltInResourcePermission(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
tests := []setBuiltInResourcePermissionTest{
{
desc: "should add new resource permission for builtin role",
orgID: 1,
builtInRole: "Viewer",
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
},
{
desc: "should add new resource permission when others exist",
orgID: 1,
builtInRole: "Viewer",
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
{
desc: "should remove permissions for builtin role",
orgID: 1,
builtInRole: "Viewer",
actions: []string{},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
store, _ := setupTestEnv(t)
for _, s := range test.seeds {
_, err := store.SetBuiltInResourcePermission(context.Background(), test.orgID, test.builtInRole, s, nil)
require.NoError(t, err)
}
added, err := store.SetBuiltInResourcePermission(context.Background(), test.orgID, test.builtInRole, SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
ResourceAttribute: test.resourceAttribute,
}, nil)
require.NoError(t, err)
if len(test.actions) == 0 {
assert.Equal(t, accesscontrol.ResourcePermission{}, *added)
} else {
assert.Len(t, added.Actions, len(test.actions))
assert.Equal(t, accesscontrol.Scope(test.resource, test.resourceAttribute, test.resourceID), added.Scope)
}
})
}
}
type setResourcePermissionsTest struct {
desc string
orgID int64
resourceAttribute string
commands []SetResourcePermissionsCommand
}
func TestIntegrationStore_SetResourcePermissions(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
tests := []setResourcePermissionsTest{
{
desc: "should set all permissions provided",
orgID: 1,
resourceAttribute: "uid",
commands: []SetResourcePermissionsCommand{
{
User: accesscontrol.User{ID: 1},
SetResourcePermissionCommand: SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
{
TeamID: 3,
SetResourcePermissionCommand: SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
{
BuiltinRole: "Admin",
SetResourcePermissionCommand: SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
store, _ := setupTestEnv(t)
permissions, err := store.SetResourcePermissions(context.Background(), tt.orgID, tt.commands, ResourceHooks{})
require.NoError(t, err)
require.Len(t, permissions, len(tt.commands))
for i, c := range tt.commands {
if len(c.Actions) == 0 {
assert.Equal(t, accesscontrol.ResourcePermission{}, permissions[i])
} else {
assert.Len(t, permissions[i].Actions, len(c.Actions))
assert.Equal(t, c.TeamID, permissions[i].TeamId)
assert.Equal(t, c.User.ID, permissions[i].UserId)
assert.Equal(t, c.BuiltinRole, permissions[i].BuiltInRole)
assert.Equal(t, accesscontrol.Scope(c.Resource, tt.resourceAttribute, c.ResourceID), permissions[i].Scope)
}
}
})
}
}
type getResourcePermissionsTest struct {
desc string
user *user.SignedInUser
numUsers int
query GetResourcePermissionsQuery
expectedLen int
}
func TestIntegrationStore_GetResourcePermissions(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
tests := []getResourcePermissionsTest{
{
desc: "should return permissions for resource id",
user: &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}},
}},
numUsers: 3,
query: GetResourcePermissionsQuery{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
EnforceAccessControl: true,
},
expectedLen: 4,
},
{
desc: "should return manage permissions for all resource ids",
user: &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}},
}},
numUsers: 3,
query: GetResourcePermissionsQuery{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
OnlyManaged: true,
EnforceAccessControl: true,
},
expectedLen: 3,
},
{
desc: "should return users caller can read",
user: &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionOrgUsersRead: {"users:id:1", "users:id:3"}},
}},
numUsers: 3,
query: GetResourcePermissionsQuery{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
OnlyManaged: true,
EnforceAccessControl: true,
},
expectedLen: 2,
},
{
desc: "should return permissions for all users when access control is not enforces",
user: &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{1: {}}},
numUsers: 3,
query: GetResourcePermissionsQuery{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
OnlyManaged: true,
EnforceAccessControl: false,
},
expectedLen: 3,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
store, sql := setupTestEnv(t)
orgService, err := orgimpl.ProvideService(sql, sql.Cfg, quotatest.New(false, nil))
require.NoError(t, err)
err = sql.WithDbSession(context.Background(), func(sess *db.Session) error {
role := &accesscontrol.Role{
OrgID: tt.user.OrgID,
UID: "seeded",
Name: "seeded",
Updated: time.Now(),
Created: time.Now(),
}
_, err := sess.Insert(role)
require.NoError(t, err)
permission := &accesscontrol.Permission{
RoleID: role.ID,
Action: "datasources:query",
Scope: "datasources:*",
Updated: time.Now(),
Created: time.Now(),
}
_, err = sess.Insert(permission)
require.NoError(t, err)
builtInRole := &accesscontrol.BuiltinRole{
RoleID: role.ID,
OrgID: 1,
Role: "Viewer",
Updated: time.Now(),
Created: time.Now(),
}
_, err = sess.Insert(builtInRole)
require.NoError(t, err)
return nil
})
require.NoError(t, err)
seedResourcePermissions(t, store, sql, orgService, tt.query.Actions, tt.query.Resource, tt.query.ResourceID, tt.query.ResourceAttribute, tt.numUsers)
tt.query.User = tt.user
permissions, err := store.GetResourcePermissions(context.Background(), tt.user.OrgID, tt.query)
require.NoError(t, err)
assert.Len(t, permissions, tt.expectedLen)
})
}
}
func seedResourcePermissions(t *testing.T, store *store, sql *sqlstore.SQLStore, orgService org.Service, actions []string, resource, resourceID, resourceAttribute string, numUsers int) {
t.Helper()
var orgModel *org.Org
usrSvc, err := userimpl.ProvideService(sql, orgService, sql.Cfg, nil, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService())
require.NoError(t, err)
for i := 0; i < numUsers; i++ {
if orgModel == nil {
cmd := &org.CreateOrgCommand{Name: "test", UserID: int64(i)}
addedOrg, err := orgService.CreateWithMember(context.Background(), cmd)
require.NoError(t, err)
orgModel = addedOrg
}
u, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{
Login: fmt.Sprintf("user:%s%d", resourceID, i),
OrgID: orgModel.ID,
})
require.NoError(t, err)
_, err = store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: u.ID}, SetResourcePermissionCommand{
Actions: actions,
Resource: resource,
ResourceID: resourceID,
ResourceAttribute: resourceAttribute,
}, nil)
require.NoError(t, err)
}
}
func setupTestEnv(t testing.TB) (*store, *sqlstore.SQLStore) {
sql := db.InitTestDB(t)
return NewStore(sql, featuremgmt.WithFeatures()), sql
}
func TestStore_IsInherited(t *testing.T) {
type testCase struct {
description string
permission *flatResourcePermission
requiredScope string
expected bool
}
testCases := []testCase{
{
description: "same scope is not inherited",
permission: &flatResourcePermission{
Scope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID("some_uid"),
RoleName: fmt.Sprintf("%stest_role", accesscontrol.ManagedRolePrefix),
},
requiredScope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID("some_uid"),
expected: false,
},
{
description: "specific folder scope for dashboards is inherited",
permission: &flatResourcePermission{
Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("parent"),
RoleName: fmt.Sprintf("%stest_role", accesscontrol.ManagedRolePrefix),
},
requiredScope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID("some_uid"),
expected: true,
},
{
description: "wildcard scope from a fixed role is not inherited",
permission: &flatResourcePermission{
Scope: dashboards.ScopeDashboardsAll,
RoleName: fmt.Sprintf("%sfixed_role", accesscontrol.FixedRolePrefix),
},
requiredScope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID("some_uid"),
expected: false,
},
{
description: "parent folder scope for nested folders is inherited",
permission: &flatResourcePermission{
Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("parent"),
RoleName: fmt.Sprintf("%stest_role", accesscontrol.ManagedRolePrefix),
},
requiredScope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("some_folder"),
expected: true,
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
isInherited := tc.permission.IsInherited(tc.requiredScope)
assert.Equal(t, tc.expected, isInherited)
})
}
}
type orgPermission struct {
OrgID int64 `xorm:"org_id"`
Action string `json:"action"`
Scope string `json:"scope"`
}
func TestIntegrationStore_DeleteResourcePermissions(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
type deleteResourcePermissionsTest struct {
desc string
orgID int64
resourceAttribute string
command DeleteResourcePermissionsCmd
shouldExist []orgPermission
shouldNotExist []orgPermission
}
tests := []deleteResourcePermissionsTest{
{
desc: "should delete all permissions for resource id in org 1",
orgID: 1,
resourceAttribute: "uid",
command: DeleteResourcePermissionsCmd{
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
shouldExist: []orgPermission{
{
OrgID: 2,
Action: "datasources:query",
Scope: "datasources:uid:1",
}, {
OrgID: 2,
Action: "datasources:write",
Scope: "datasources:uid:1",
},
{
OrgID: 1,
Action: "datasources:query",
Scope: "datasources:uid:2",
}, {
OrgID: 1,
Action: "datasources:write",
Scope: "datasources:uid:2",
}},
shouldNotExist: []orgPermission{
{
OrgID: 1,
Action: "datasources:query",
Scope: "datasources:uid:1",
}, {
OrgID: 1,
Action: "datasources:write",
Scope: "datasources:uid:1",
}},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
store, _ := setupTestEnv(t)
_, err := store.SetResourcePermissions(context.Background(), 1, []SetResourcePermissionsCommand{
{
User: accesscontrol.User{ID: 1},
SetResourcePermissionCommand: SetResourcePermissionCommand{
Actions: []string{"datasources:query", "datasources:write"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
}, ResourceHooks{})
require.NoError(t, err)
_, err = store.SetResourcePermissions(context.Background(), 1, []SetResourcePermissionsCommand{
{
User: accesscontrol.User{ID: 1},
SetResourcePermissionCommand: SetResourcePermissionCommand{
Actions: []string{"datasources:query", "datasources:write"},
Resource: "datasources",
ResourceID: "2",
ResourceAttribute: "uid",
},
},
}, ResourceHooks{})
require.NoError(t, err)
_, err = store.SetResourcePermissions(context.Background(), 2, []SetResourcePermissionsCommand{
{
User: accesscontrol.User{ID: 1},
SetResourcePermissionCommand: SetResourcePermissionCommand{
Actions: []string{"datasources:query", "datasources:write"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
}, ResourceHooks{})
require.NoError(t, err)
err = store.DeleteResourcePermissions(context.Background(), tt.orgID, &tt.command)
require.NoError(t, err)
permissions := retrievePermissionsHelper(store, t)
for _, p := range tt.shouldExist {
assert.Contains(t, permissions, p)
}
for _, p := range tt.shouldNotExist {
assert.NotContains(t, permissions, p)
}
})
}
}
func retrievePermissionsHelper(store *store, t *testing.T) []orgPermission {
permissions := []orgPermission{}
err := store.sql.WithDbSession(context.Background(), func(sess *db.Session) error {
err := sess.SQL(`
SELECT permission.*, role.org_id
FROM permission
INNER JOIN role ON permission.role_id = role.id
`).Find(&permissions)
require.NoError(t, err)
return nil
})
require.NoError(t, err)
return permissions
}