Access Control: Move part of access control database (#40483)
* Add accesscontrol migrations * Add ResourceStore interface and related structs * Add team/user/builtin-role * Add accesscontrol database with functions to handle managed roles and permissions * Add ResourceManager * Add GetUserPermissions * Update pkg/services/accesscontrol/accesscontrol.go Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:
co-authored by
Emil Tullstedt
parent
8c98f24777
commit
3c659f1ea0
@@ -0,0 +1,391 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
type setUserResourcePermissionsTest struct {
|
||||
desc string
|
||||
orgID int64
|
||||
userID int64
|
||||
actions []string
|
||||
resource string
|
||||
resourceID string
|
||||
seeds []accesscontrol.SetResourcePermissionsCommand
|
||||
}
|
||||
|
||||
func TestAccessControlStore_SetUserResourcePermissions(t *testing.T) {
|
||||
tests := []setUserResourcePermissionsTest{
|
||||
{
|
||||
desc: "should set resource permission for user",
|
||||
userID: 1,
|
||||
actions: []string{"datasources:query"},
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
},
|
||||
{
|
||||
desc: "should remove resource permission for user",
|
||||
orgID: 1,
|
||||
userID: 1,
|
||||
actions: []string{},
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
seeds: []accesscontrol.SetResourcePermissionsCommand{
|
||||
{
|
||||
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",
|
||||
seeds: []accesscontrol.SetResourcePermissionsCommand{
|
||||
{
|
||||
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.SetUserResourcePermissions(context.Background(), test.orgID, test.userID, s)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
added, err := store.SetUserResourcePermissions(context.Background(), test.userID, test.userID, accesscontrol.SetResourcePermissionsCommand{
|
||||
Actions: test.actions,
|
||||
Resource: test.resource,
|
||||
ResourceID: test.resourceID,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, added, len(test.actions))
|
||||
for _, p := range added {
|
||||
assert.Equal(t, getResourceScope(test.resource, test.resourceID), p.Scope)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type setTeamResourcePermissionsTest struct {
|
||||
desc string
|
||||
orgID int64
|
||||
teamID int64
|
||||
actions []string
|
||||
resource string
|
||||
resourceID string
|
||||
seeds []accesscontrol.SetResourcePermissionsCommand
|
||||
}
|
||||
|
||||
func TestAccessControlStore_SetTeamResourcePermissions(t *testing.T) {
|
||||
tests := []setTeamResourcePermissionsTest{
|
||||
{
|
||||
desc: "should add new resource permission for team",
|
||||
orgID: 1,
|
||||
teamID: 1,
|
||||
actions: []string{"datasources:query"},
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
},
|
||||
{
|
||||
desc: "should add new resource permission when others exist",
|
||||
orgID: 1,
|
||||
teamID: 1,
|
||||
actions: []string{"datasources:query", "datasources:write"},
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
seeds: []accesscontrol.SetResourcePermissionsCommand{
|
||||
{
|
||||
Actions: []string{"datasources:query"},
|
||||
Resource: "datasources",
|
||||
ResourceID: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should remove permissions for team",
|
||||
orgID: 1,
|
||||
teamID: 1,
|
||||
actions: []string{},
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
seeds: []accesscontrol.SetResourcePermissionsCommand{
|
||||
{
|
||||
Actions: []string{"datasources:query"},
|
||||
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.SetTeamResourcePermissions(context.Background(), test.orgID, test.teamID, s)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
added, err := store.SetTeamResourcePermissions(context.Background(), test.orgID, test.teamID, accesscontrol.SetResourcePermissionsCommand{
|
||||
Actions: test.actions,
|
||||
Resource: test.resource,
|
||||
ResourceID: test.resourceID,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, added, len(test.actions))
|
||||
for _, p := range added {
|
||||
assert.Equal(t, getResourceScope(test.resource, test.resourceID), p.Scope)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type setBuiltinResourcePermissionsTest struct {
|
||||
desc string
|
||||
orgID int64
|
||||
builtinRole string
|
||||
actions []string
|
||||
resource string
|
||||
resourceID string
|
||||
seeds []accesscontrol.SetResourcePermissionsCommand
|
||||
}
|
||||
|
||||
func TestAccessControlStore_SetBuiltinResourcePermissions(t *testing.T) {
|
||||
tests := []setBuiltinResourcePermissionsTest{
|
||||
{
|
||||
desc: "should add new resource permission for builtin role",
|
||||
orgID: 1,
|
||||
builtinRole: "Viewer",
|
||||
actions: []string{"datasources:query"},
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
},
|
||||
{
|
||||
desc: "should add new resource permission when others exist",
|
||||
orgID: 1,
|
||||
builtinRole: "Viewer",
|
||||
actions: []string{"datasources:query", "datasources:write"},
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
seeds: []accesscontrol.SetResourcePermissionsCommand{
|
||||
{
|
||||
Actions: []string{"datasources:query"},
|
||||
Resource: "datasources",
|
||||
ResourceID: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should remove permissions for builtin role",
|
||||
orgID: 1,
|
||||
builtinRole: "Viewer",
|
||||
actions: []string{},
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
seeds: []accesscontrol.SetResourcePermissionsCommand{
|
||||
{
|
||||
Actions: []string{"datasources:query"},
|
||||
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.SetBuiltinResourcePermissions(context.Background(), test.orgID, test.builtinRole, s)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
added, err := store.SetBuiltinResourcePermissions(context.Background(), test.orgID, test.builtinRole, accesscontrol.SetResourcePermissionsCommand{
|
||||
Actions: test.actions,
|
||||
Resource: test.resource,
|
||||
ResourceID: test.resourceID,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, added, len(test.actions))
|
||||
for _, p := range added {
|
||||
assert.Equal(t, getResourceScope(test.resource, test.resourceID), p.Scope)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type resourcePermission struct {
|
||||
resource string
|
||||
resourceID string
|
||||
}
|
||||
|
||||
type removeResourcePermissionTest struct {
|
||||
desc string
|
||||
add resourcePermission
|
||||
remove resourcePermission
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
func TestAccessControlStore_RemoveResourcePermission(t *testing.T) {
|
||||
tests := []removeResourcePermissionTest{
|
||||
{
|
||||
desc: "should remove resource permission",
|
||||
add: resourcePermission{
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
},
|
||||
remove: resourcePermission{
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "should return nil when permission does not exist",
|
||||
add: resourcePermission{
|
||||
resource: "datasources",
|
||||
resourceID: "1",
|
||||
},
|
||||
remove: resourcePermission{
|
||||
resource: "datasources",
|
||||
resourceID: "2",
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
store, sql := setupTestEnv(t)
|
||||
|
||||
user, err := sql.CreateUser(context.Background(), models.CreateUserCommand{
|
||||
Login: "user",
|
||||
OrgId: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Seed with permission
|
||||
seeded, err := store.SetUserResourcePermissions(context.Background(), user.OrgId, user.Id, accesscontrol.SetResourcePermissionsCommand{
|
||||
Actions: []string{"datasources:query"},
|
||||
Resource: test.add.resource,
|
||||
ResourceID: test.add.resourceID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.RemoveResourcePermission(context.Background(), user.OrgId, accesscontrol.RemoveResourcePermissionCommand{
|
||||
Actions: []string{"datasources:query"},
|
||||
Resource: test.remove.resource,
|
||||
ResourceID: test.remove.resourceID,
|
||||
PermissionID: seeded[0].ID,
|
||||
})
|
||||
|
||||
if test.expectedErr != nil {
|
||||
assert.ErrorIs(t, err, test.expectedErr)
|
||||
} else {
|
||||
permissions, err := store.GetResourcesPermissions(context.Background(), user.OrgId, accesscontrol.GetResourcesPermissionsQuery{
|
||||
Actions: []string{"datasources:query"},
|
||||
Resource: test.add.resource,
|
||||
ResourceIDs: []string{test.add.resourceID},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
if test.add.resourceID != test.remove.resourceID {
|
||||
assert.Len(t, permissions, 1)
|
||||
} else {
|
||||
assert.Len(t, permissions, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type getResourcesPermissionsTest struct {
|
||||
desc string
|
||||
numUsers int
|
||||
actions []string
|
||||
resource string
|
||||
resourceIDs []string
|
||||
}
|
||||
|
||||
func TestAccessControlStore_GetResourcesPermissions(t *testing.T) {
|
||||
tests := []getResourcesPermissionsTest{
|
||||
{
|
||||
desc: "should return permissions for all resource ids",
|
||||
numUsers: 3,
|
||||
actions: []string{"datasources:query"},
|
||||
resource: "datasources",
|
||||
resourceIDs: []string{"1", "2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
store, sql := setupTestEnv(t)
|
||||
|
||||
for _, id := range test.resourceIDs {
|
||||
seedResourcePermissions(t, store, sql, test.actions, test.resource, id, test.numUsers)
|
||||
}
|
||||
|
||||
permissions, err := store.GetResourcesPermissions(context.Background(), 1, accesscontrol.GetResourcesPermissionsQuery{
|
||||
Actions: test.actions,
|
||||
Resource: test.resource,
|
||||
ResourceIDs: test.resourceIDs,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedLen := test.numUsers * len(test.resourceIDs)
|
||||
assert.Len(t, permissions, expectedLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func seedResourcePermissions(t *testing.T, store *AccessControlStore, sql *sqlstore.SQLStore, actions []string, resource, resourceID string, numUsers int) {
|
||||
t.Helper()
|
||||
for i := 0; i < numUsers; i++ {
|
||||
org, _ := sql.GetOrgByName("test")
|
||||
|
||||
if org == nil {
|
||||
addedOrg, err := sql.CreateOrgWithMember("test", int64(i))
|
||||
require.NoError(t, err)
|
||||
org = &addedOrg
|
||||
}
|
||||
|
||||
u, err := sql.CreateUser(context.Background(), models.CreateUserCommand{
|
||||
Login: fmt.Sprintf("user:%s%d", resourceID, i),
|
||||
OrgId: org.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.SetUserResourcePermissions(context.Background(), 1, u.Id, accesscontrol.SetResourcePermissionsCommand{
|
||||
Actions: actions,
|
||||
Resource: resource,
|
||||
ResourceID: resourceID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user