RBAC: Move resource permissions store to service package (#53815)

* Rename file to store

* Move resource permission specific database functions to
resourcepermissions package

* Wire: Remove interface bind

* RBAC: Remove injection of resourcepermission Store

* RBAC: Export store constructor

* Tests: Use resource permission package to initiate store used in tests

* RBAC: Remove internal types package and move to resourcepermissions
package

* RBAC: Run database tests as itegration tests
This commit is contained in:
Karl Persson
2022-08-18 09:43:45 +02:00
committed by GitHub
parent 83f8da2e02
commit 1b933ff3ed
18 changed files with 177 additions and 185 deletions

View File

@@ -111,26 +111,6 @@ func userRolesFilter(orgID, userID int64, teamIDs []int64, roles []string) (stri
return "INNER JOIN (" + builder.String() + ") as all_role ON role.id = all_role.role_id", params
}
func deletePermissions(sess *sqlstore.DBSession, ids []int64) error {
if len(ids) == 0 {
return nil
}
rawSQL := "DELETE FROM permission WHERE id IN(?" + strings.Repeat(",?", len(ids)-1) + ")"
args := make([]interface{}, 0, len(ids)+1)
args = append(args, rawSQL)
for _, id := range ids {
args = append(args, id)
}
_, err := sess.Exec(args...)
if err != nil {
return err
}
return nil
}
func (s *AccessControlStore) DeleteUserPermissions(ctx context.Context, orgID, userID int64) error {
err := s.sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
roleDeleteQuery := "DELETE FROM user_role WHERE user_id = ?"

View File

@@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types"
rs "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
@@ -79,12 +79,12 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
store, sql := setupTestEnv(t)
store, permissionStore, sql := setupTestEnv(t)
user, team := createUserAndTeam(t, sql, tt.orgID)
for _, id := range tt.userPermissions {
_, err := store.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.ID}, types.SetResourcePermissionCommand{
_, err := permissionStore.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{
Actions: []string{"dashboards:write"},
Resource: "dashboards",
ResourceID: id,
@@ -93,7 +93,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
}
for _, id := range tt.teamPermissions {
_, err := store.SetTeamResourcePermission(context.Background(), tt.orgID, team.Id, types.SetResourcePermissionCommand{
_, err := permissionStore.SetTeamResourcePermission(context.Background(), tt.orgID, team.Id, rs.SetResourcePermissionCommand{
Actions: []string{"dashboards:read"},
Resource: "dashboards",
ResourceID: id,
@@ -102,7 +102,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
}
for _, id := range tt.builtinPermissions {
_, err := store.SetBuiltInResourcePermission(context.Background(), tt.orgID, "Admin", types.SetResourcePermissionCommand{
_, err := permissionStore.SetBuiltInResourcePermission(context.Background(), tt.orgID, "Admin", rs.SetResourcePermissionCommand{
Actions: []string{"dashboards:read"},
Resource: "dashboards",
ResourceID: id,
@@ -142,11 +142,11 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
t.Run("expect permissions in all orgs to be deleted", func(t *testing.T) {
store, sql := setupTestEnv(t)
store, permissionsStore, sql := setupTestEnv(t)
user, _ := createUserAndTeam(t, sql, 1)
// generate permissions in org 1
_, err := store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, types.SetResourcePermissionCommand{
_, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{
Actions: []string{"dashboards:write"},
Resource: "dashboards",
ResourceID: "1",
@@ -154,7 +154,7 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
require.NoError(t, err)
// generate permissions in org 2
_, err = store.SetUserResourcePermission(context.Background(), 2, accesscontrol.User{ID: user.ID}, types.SetResourcePermissionCommand{
_, err = permissionsStore.SetUserResourcePermission(context.Background(), 2, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{
Actions: []string{"dashboards:write"},
Resource: "dashboards",
ResourceID: "1",
@@ -182,11 +182,11 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
})
t.Run("expect permissions in org 1 to be deleted", func(t *testing.T) {
store, sql := setupTestEnv(t)
store, permissionsStore, sql := setupTestEnv(t)
user, _ := createUserAndTeam(t, sql, 1)
// generate permissions in org 1
_, err := store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, types.SetResourcePermissionCommand{
_, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{
Actions: []string{"dashboards:write"},
Resource: "dashboards",
ResourceID: "1",
@@ -194,7 +194,7 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
require.NoError(t, err)
// generate permissions in org 2
_, err = store.SetUserResourcePermission(context.Background(), 2, accesscontrol.User{ID: user.ID}, types.SetResourcePermissionCommand{
_, err = permissionsStore.SetUserResourcePermission(context.Background(), 2, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{
Actions: []string{"dashboards:write"},
Resource: "dashboards",
ResourceID: "1",
@@ -240,7 +240,9 @@ func createUserAndTeam(t *testing.T, sql *sqlstore.SQLStore, orgID int64) (*user
return user, team
}
func setupTestEnv(t testing.TB) (*AccessControlStore, *sqlstore.SQLStore) {
store := sqlstore.InitTestDB(t)
return ProvideService(store), store
func setupTestEnv(t testing.TB) (*AccessControlStore, rs.Store, *sqlstore.SQLStore) {
sql := sqlstore.InitTestDB(t)
acstore := ProvideService(sql)
permissionStore := rs.NewStore(sql)
return acstore, permissionStore, sql
}

View File

@@ -1,618 +0,0 @@
package database
import (
"context"
"fmt"
"strings"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
)
type flatResourcePermission struct {
ID int64 `xorm:"id"`
RoleName string
Action string
Scope string
UserId int64
UserLogin string
UserEmail string
TeamId int64
TeamEmail string
Team string
BuiltInRole string
Created time.Time
Updated time.Time
}
func (p *flatResourcePermission) IsManaged(scope string) bool {
return strings.HasPrefix(p.RoleName, accesscontrol.ManagedRolePrefix) && !p.IsInherited(scope)
}
func (p *flatResourcePermission) IsInherited(scope string) bool {
return p.Scope != scope
}
func (s *AccessControlStore) SetUserResourcePermission(
ctx context.Context, orgID int64, usr accesscontrol.User,
cmd types.SetResourcePermissionCommand,
hook types.UserResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
if usr.ID == 0 {
return nil, user.ErrUserNotFound
}
var err error
var permission *accesscontrol.ResourcePermission
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
permission, err = s.setUserResourcePermission(sess, orgID, usr, cmd, hook)
return err
})
return permission, err
}
func (s *AccessControlStore) setUserResourcePermission(
sess *sqlstore.DBSession, orgID int64, user accesscontrol.User,
cmd types.SetResourcePermissionCommand,
hook types.UserResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
permission, err := s.setResourcePermission(sess, orgID, accesscontrol.ManagedUserRoleName(user.ID), s.userAdder(sess, orgID, user.ID), cmd)
if err != nil {
return nil, err
}
if hook != nil {
if err := hook(sess, orgID, user, cmd.ResourceID, cmd.Permission); err != nil {
return nil, err
}
}
return permission, nil
}
func (s *AccessControlStore) SetTeamResourcePermission(
ctx context.Context, orgID, teamID int64,
cmd types.SetResourcePermissionCommand,
hook types.TeamResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
if teamID == 0 {
return nil, models.ErrTeamNotFound
}
var err error
var permission *accesscontrol.ResourcePermission
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
permission, err = s.setTeamResourcePermission(sess, orgID, teamID, cmd, hook)
return err
})
return permission, err
}
func (s *AccessControlStore) setTeamResourcePermission(
sess *sqlstore.DBSession, orgID, teamID int64,
cmd types.SetResourcePermissionCommand,
hook types.TeamResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
permission, err := s.setResourcePermission(sess, orgID, accesscontrol.ManagedTeamRoleName(teamID), s.teamAdder(sess, orgID, teamID), cmd)
if err != nil {
return nil, err
}
if hook != nil {
if err := hook(sess, orgID, teamID, cmd.ResourceID, cmd.Permission); err != nil {
return nil, err
}
}
return permission, nil
}
func (s *AccessControlStore) SetBuiltInResourcePermission(
ctx context.Context, orgID int64, builtInRole string,
cmd types.SetResourcePermissionCommand,
hook types.BuiltinResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
if !org.RoleType(builtInRole).IsValid() || builtInRole == accesscontrol.RoleGrafanaAdmin {
return nil, fmt.Errorf("invalid role: %s", builtInRole)
}
var err error
var permission *accesscontrol.ResourcePermission
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
permission, err = s.setBuiltInResourcePermission(sess, orgID, builtInRole, cmd, hook)
return err
})
if err != nil {
return nil, err
}
return permission, nil
}
func (s *AccessControlStore) setBuiltInResourcePermission(
sess *sqlstore.DBSession, orgID int64, builtInRole string,
cmd types.SetResourcePermissionCommand,
hook types.BuiltinResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
permission, err := s.setResourcePermission(sess, orgID, accesscontrol.ManagedBuiltInRoleName(builtInRole), s.builtInRoleAdder(sess, orgID, builtInRole), cmd)
if err != nil {
return nil, err
}
if hook != nil {
if err := hook(sess, orgID, builtInRole, cmd.ResourceID, cmd.Permission); err != nil {
return nil, err
}
}
return permission, nil
}
func (s *AccessControlStore) SetResourcePermissions(
ctx context.Context, orgID int64,
commands []types.SetResourcePermissionsCommand,
hooks types.ResourceHooks,
) ([]accesscontrol.ResourcePermission, error) {
var err error
var permissions []accesscontrol.ResourcePermission
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
for _, cmd := range commands {
var p *accesscontrol.ResourcePermission
if cmd.User.ID != 0 {
p, err = s.setUserResourcePermission(sess, orgID, cmd.User, cmd.SetResourcePermissionCommand, hooks.User)
} else if cmd.TeamID != 0 {
p, err = s.setTeamResourcePermission(sess, orgID, cmd.TeamID, cmd.SetResourcePermissionCommand, hooks.Team)
} else if org.RoleType(cmd.BuiltinRole).IsValid() || cmd.BuiltinRole == accesscontrol.RoleGrafanaAdmin {
p, err = s.setBuiltInResourcePermission(sess, orgID, cmd.BuiltinRole, cmd.SetResourcePermissionCommand, hooks.BuiltInRole)
}
if err != nil {
return err
}
if p != nil {
permissions = append(permissions, *p)
}
}
return nil
})
return permissions, err
}
type roleAdder func(roleID int64) error
func (s *AccessControlStore) setResourcePermission(
sess *sqlstore.DBSession, orgID int64, roleName string, adder roleAdder, cmd types.SetResourcePermissionCommand,
) (*accesscontrol.ResourcePermission, error) {
role, err := s.getOrCreateManagedRole(sess, orgID, roleName, adder)
if err != nil {
return nil, err
}
rawSQL := `
SELECT
p.*
FROM permission as p
INNER JOIN role r on r.id = p.role_id
WHERE r.id = ?
AND p.scope = ?
`
var current []accesscontrol.Permission
scope := accesscontrol.Scope(cmd.Resource, cmd.ResourceAttribute, cmd.ResourceID)
if err := sess.SQL(rawSQL, role.ID, scope).Find(&current); err != nil {
return nil, err
}
missing := make(map[string]struct{}, len(cmd.Actions))
for _, a := range cmd.Actions {
missing[a] = struct{}{}
}
var keep []int64
var remove []int64
for _, p := range current {
if _, ok := missing[p.Action]; ok {
keep = append(keep, p.ID)
delete(missing, p.Action)
} else if !ok {
remove = append(remove, p.ID)
}
}
if err := deletePermissions(sess, remove); err != nil {
return nil, err
}
for action := range missing {
id, err := s.createResourcePermission(sess, role.ID, action, cmd.Resource, cmd.ResourceID, cmd.ResourceAttribute)
if err != nil {
return nil, err
}
keep = append(keep, id)
}
permissions, err := s.getResourcePermissionsByIds(sess, cmd.Resource, cmd.ResourceID, cmd.ResourceAttribute, keep)
if err != nil {
return nil, err
}
permission := flatPermissionsToResourcePermission(scope, permissions)
if permission == nil {
return &accesscontrol.ResourcePermission{}, nil
}
return permission, nil
}
func (s *AccessControlStore) GetResourcePermissions(ctx context.Context, orgID int64, query types.GetResourcePermissionsQuery) ([]accesscontrol.ResourcePermission, error) {
var result []accesscontrol.ResourcePermission
err := s.sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
var err error
result, err = s.getResourcePermissions(sess, orgID, query)
return err
})
return result, err
}
func (s *AccessControlStore) createResourcePermission(sess *sqlstore.DBSession, roleID int64, action, resource, resourceID, resourceAttribute string) (int64, error) {
permission := managedPermission(action, resource, resourceID, resourceAttribute)
permission.RoleID = roleID
permission.Created = time.Now()
permission.Updated = time.Now()
if _, err := sess.Insert(&permission); err != nil {
return 0, err
}
return permission.ID, nil
}
func (s *AccessControlStore) getResourcePermissions(sess *sqlstore.DBSession, orgID int64, query types.GetResourcePermissionsQuery) ([]accesscontrol.ResourcePermission, error) {
if len(query.Actions) == 0 {
return nil, nil
}
rawSelect := `
SELECT
p.*,
r.name as role_name,
`
userSelect := rawSelect + `
ur.user_id AS user_id,
u.login AS user_login,
u.email AS user_email,
0 AS team_id,
'' AS team,
'' AS team_email,
'' AS built_in_role
`
teamSelect := rawSelect + `
0 AS user_id,
'' AS user_login,
'' AS user_email,
tr.team_id AS team_id,
t.name AS team,
t.email AS team_email,
'' AS built_in_role
`
builtinSelect := rawSelect + `
0 AS user_id,
'' AS user_login,
'' AS user_email,
0 as team_id,
'' AS team,
'' AS team_email,
br.role AS built_in_role
`
rawFrom := `
FROM permission p
INNER JOIN role r ON p.role_id = r.id
`
userFrom := rawFrom + `
INNER JOIN user_role ur ON r.id = ur.role_id AND (ur.org_id = 0 OR ur.org_id = ?)
INNER JOIN ` + s.sql.Dialect.Quote("user") + ` u ON ur.user_id = u.id
`
teamFrom := rawFrom + `
INNER JOIN team_role tr ON r.id = tr.role_id AND (tr.org_id = 0 OR tr.org_id = ?)
INNER JOIN team t ON tr.team_id = t.id
`
builtinFrom := rawFrom + `
INNER JOIN builtin_role br ON r.id = br.role_id AND (br.org_id = 0 OR br.org_id = ?)
`
where := `WHERE (r.org_id = ? OR r.org_id = 0) AND (p.scope = '*' OR p.scope = ? OR p.scope = ? OR p.scope = ?`
scope := accesscontrol.Scope(query.Resource, query.ResourceAttribute, query.ResourceID)
args := []interface{}{
orgID,
orgID,
accesscontrol.Scope(query.Resource, "*"),
accesscontrol.Scope(query.Resource, query.ResourceAttribute, "*"),
scope,
}
if len(query.InheritedScopes) > 0 {
where += ` OR p.scope IN(?` + strings.Repeat(",?", len(query.InheritedScopes)-1) + `)`
for _, scope := range query.InheritedScopes {
args = append(args, scope)
}
}
where += `) AND p.action IN (?` + strings.Repeat(",?", len(query.Actions)-1) + `)`
if query.OnlyManaged {
where += `AND r.name LIKE 'managed:%'`
}
for _, a := range query.Actions {
args = append(args, a)
}
initialLength := len(args)
userFilter, err := accesscontrol.Filter(query.User, "u.id", "users:id:", accesscontrol.ActionOrgUsersRead)
if err != nil {
return nil, err
}
user := userSelect + userFrom + where + " AND " + userFilter.Where
args = append(args, userFilter.Args...)
teamFilter, err := accesscontrol.Filter(query.User, "t.id", "teams:id:", accesscontrol.ActionTeamsRead)
if err != nil {
return nil, err
}
team := teamSelect + teamFrom + where + " AND " + teamFilter.Where
args = append(args, args[:initialLength]...)
args = append(args, teamFilter.Args...)
builtin := builtinSelect + builtinFrom + where
args = append(args, args[:initialLength]...)
sql := user + " UNION " + team + " UNION " + builtin
queryResults := make([]flatResourcePermission, 0)
if err := sess.SQL(sql, args...).Find(&queryResults); err != nil {
return nil, err
}
var result []accesscontrol.ResourcePermission
users, teams, builtins := groupPermissionsByAssignment(queryResults)
for _, p := range users {
result = append(result, flatPermissionsToResourcePermissions(scope, p)...)
}
for _, p := range teams {
result = append(result, flatPermissionsToResourcePermissions(scope, p)...)
}
for _, p := range builtins {
result = append(result, flatPermissionsToResourcePermissions(scope, p)...)
}
return result, nil
}
func groupPermissionsByAssignment(permissions []flatResourcePermission) (map[int64][]flatResourcePermission, map[int64][]flatResourcePermission, map[string][]flatResourcePermission) {
users := make(map[int64][]flatResourcePermission)
teams := make(map[int64][]flatResourcePermission)
builtins := make(map[string][]flatResourcePermission)
for _, p := range permissions {
if p.UserId != 0 {
users[p.UserId] = append(users[p.UserId], p)
} else if p.TeamId != 0 {
teams[p.TeamId] = append(teams[p.TeamId], p)
} else if p.BuiltInRole != "" {
builtins[p.BuiltInRole] = append(builtins[p.BuiltInRole], p)
}
}
return users, teams, builtins
}
func flatPermissionsToResourcePermissions(scope string, permissions []flatResourcePermission) []accesscontrol.ResourcePermission {
var managed, provisioned []flatResourcePermission
for _, p := range permissions {
if p.IsManaged(scope) {
managed = append(managed, p)
} else {
provisioned = append(provisioned, p)
}
}
var result []accesscontrol.ResourcePermission
if g := flatPermissionsToResourcePermission(scope, managed); g != nil {
result = append(result, *g)
}
if g := flatPermissionsToResourcePermission(scope, provisioned); g != nil {
result = append(result, *g)
}
return result
}
func flatPermissionsToResourcePermission(scope string, permissions []flatResourcePermission) *accesscontrol.ResourcePermission {
if len(permissions) == 0 {
return nil
}
actions := make([]string, 0, len(permissions))
for _, p := range permissions {
actions = append(actions, p.Action)
}
first := permissions[0]
return &accesscontrol.ResourcePermission{
ID: first.ID,
RoleName: first.RoleName,
Actions: actions,
Scope: first.Scope,
UserId: first.UserId,
UserLogin: first.UserLogin,
UserEmail: first.UserEmail,
TeamId: first.TeamId,
TeamEmail: first.TeamEmail,
Team: first.Team,
BuiltInRole: first.BuiltInRole,
Created: first.Created,
Updated: first.Updated,
IsManaged: first.IsManaged(scope),
}
}
func (s *AccessControlStore) userAdder(sess *sqlstore.DBSession, orgID, userID int64) roleAdder {
return func(roleID int64) error {
if res, err := sess.Query("SELECT 1 FROM user_role WHERE org_id=? AND user_id=? AND role_id=?", orgID, userID, roleID); err != nil {
return err
} else if len(res) == 1 {
return fmt.Errorf("role is already added to this user")
}
userRole := &accesscontrol.UserRole{
OrgID: orgID,
UserID: userID,
RoleID: roleID,
Created: time.Now(),
}
_, err := sess.Insert(userRole)
return err
}
}
func (s *AccessControlStore) teamAdder(sess *sqlstore.DBSession, orgID, teamID int64) roleAdder {
return func(roleID int64) error {
if res, err := sess.Query("SELECT 1 FROM team_role WHERE org_id=? AND team_id=? AND role_id=?", orgID, teamID, roleID); err != nil {
return err
} else if len(res) == 1 {
return fmt.Errorf("role is already added to this team")
}
teamRole := &accesscontrol.TeamRole{
OrgID: orgID,
TeamID: teamID,
RoleID: roleID,
Created: time.Now(),
}
_, err := sess.Insert(teamRole)
return err
}
}
func (s *AccessControlStore) builtInRoleAdder(sess *sqlstore.DBSession, orgID int64, builtinRole string) roleAdder {
return func(roleID int64) error {
if res, err := sess.Query("SELECT 1 FROM builtin_role WHERE role_id=? AND role=? AND org_id=?", roleID, builtinRole, orgID); err != nil {
return err
} else if len(res) == 1 {
return fmt.Errorf("built-in role already has the role granted")
}
_, err := sess.Table("builtin_role").Insert(accesscontrol.BuiltinRole{
RoleID: roleID,
OrgID: orgID,
Role: builtinRole,
Updated: time.Now(),
Created: time.Now(),
})
return err
}
}
func (s *AccessControlStore) getOrCreateManagedRole(sess *sqlstore.DBSession, orgID int64, name string, add roleAdder) (*accesscontrol.Role, error) {
role := accesscontrol.Role{OrgID: orgID, Name: name}
has, err := sess.Where("org_id = ? AND name = ?", orgID, name).Get(&role)
// If managed role does not exist, create it and add it to user/team/builtin
if !has {
uid, err := generateNewRoleUID(sess, orgID)
if err != nil {
return nil, err
}
role = accesscontrol.Role{
OrgID: orgID,
Name: name,
UID: uid,
Created: time.Now(),
Updated: time.Now(),
}
if _, err := sess.Insert(&role); err != nil {
return nil, err
}
if err := add(role.ID); err != nil {
return nil, err
}
}
if err != nil {
return nil, err
}
return &role, nil
}
func (s *AccessControlStore) getResourcePermissionsByIds(sess *sqlstore.DBSession, resource, resourceID, resourceAttribute string, ids []int64) ([]flatResourcePermission, error) {
var result []flatResourcePermission
if len(ids) == 0 {
return result, nil
}
rawSql := `
SELECT
p.*,
ur.user_id AS user_id,
u.login AS user_login,
u.email AS user_email,
tr.team_id AS team_id,
t.name AS team,
t.email AS team_email,
r.name as role_name,
br.role AS built_in_role
FROM permission p
INNER JOIN role r ON p.role_id = r.id
LEFT JOIN team_role tr ON r.id = tr.role_id
LEFT JOIN team t ON tr.team_id = t.id
LEFT JOIN user_role ur ON r.id = ur.role_id
LEFT JOIN ` + s.sql.Dialect.Quote("user") + ` u ON ur.user_id = u.id
LEFT JOIN builtin_role br ON r.id = br.role_id
WHERE p.id IN (?` + strings.Repeat(",?", len(ids)-1) + `)
`
args := make([]interface{}, 0, len(ids)+1)
for _, id := range ids {
args = append(args, id)
}
if err := sess.SQL(rawSql, args...).Find(&result); err != nil {
return nil, err
}
return result, nil
}
func managedPermission(action, resource string, resourceID, resourceAttribute string) accesscontrol.Permission {
return accesscontrol.Permission{
Action: action,
Scope: accesscontrol.Scope(resource, resourceAttribute, resourceID),
}
}

View File

@@ -1,167 +0,0 @@
package database
import (
"context"
"fmt"
"math"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
)
const (
dsAction = "datasources:query"
dsResource = "datasources"
PermissionsPerRole = 10
UsersPerTeam = 10
permissionsPerDs = 100
)
func BenchmarkDSPermissions10_10(b *testing.B) { benchmarkDSPermissions(b, 10, 10) }
func BenchmarkDSPermissions10_100(b *testing.B) { benchmarkDSPermissions(b, 10, 100) }
func BenchmarkDSPermissions10_1000(b *testing.B) { benchmarkDSPermissions(b, 10, 1000) }
func BenchmarkDSPermissions100_10(b *testing.B) { benchmarkDSPermissions(b, 100, 10) }
func BenchmarkDSPermissions100_100(b *testing.B) { benchmarkDSPermissions(b, 100, 100) }
func BenchmarkDSPermissions100_1000(b *testing.B) { benchmarkDSPermissions(b, 100, 1000) }
func BenchmarkDSPermissions1000_10(b *testing.B) { benchmarkDSPermissions(b, 1000, 10) }
func BenchmarkDSPermissions1000_100(b *testing.B) { benchmarkDSPermissions(b, 1000, 100) }
func BenchmarkDSPermissions1000_1000(b *testing.B) { benchmarkDSPermissions(b, 1000, 1000) }
func benchmarkDSPermissions(b *testing.B, dsNum, usersNum int) {
ac, dataSources := setupResourceBenchmark(b, dsNum, usersNum)
// We don't want to measure DB initialization
b.ResetTimer()
for i := 0; i < b.N; i++ {
getDSPermissions(b, ac, dataSources)
}
}
func getDSPermissions(b *testing.B, store *AccessControlStore, dataSources []int64) {
dsId := dataSources[0]
permissions, err := store.GetResourcePermissions(context.Background(), accesscontrol.GlobalOrgID, types.GetResourcePermissionsQuery{
User: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: {"org.users:read": {"users:*"}, "teams:read": {"teams:*"}}}},
Actions: []string{dsAction},
Resource: dsResource,
ResourceID: strconv.Itoa(int(dsId)),
ResourceAttribute: "id",
})
require.NoError(b, err)
assert.GreaterOrEqual(b, len(permissions), 2)
}
func setupResourceBenchmark(b *testing.B, dsNum, usersNum int) (*AccessControlStore, []int64) {
ac, sql := setupTestEnv(b)
dataSources := GenerateDatasourcePermissions(b, sql, ac, dsNum, usersNum, permissionsPerDs)
return ac, dataSources
}
func GenerateDatasourcePermissions(b *testing.B, db *sqlstore.SQLStore, ac *AccessControlStore, dsNum, usersNum, permissionsPerDs int) []int64 {
dataSources := make([]int64, 0)
for i := 0; i < dsNum; i++ {
addDSCommand := &datasources.AddDataSourceCommand{
OrgId: 0,
Name: fmt.Sprintf("ds_%d", i),
Type: datasources.DS_GRAPHITE,
Access: datasources.DS_ACCESS_DIRECT,
Url: "http://test",
}
_ = db.AddDataSource(context.Background(), addDSCommand)
dataSources = append(dataSources, addDSCommand.Result.Id)
}
userIds, teamIds := generateTeamsAndUsers(b, db, usersNum)
for _, dsID := range dataSources {
// Add DS permissions for the users
maxPermissions := int(math.Min(float64(permissionsPerDs), float64(len(userIds))))
for i := 0; i < maxPermissions; i++ {
_, err := ac.SetUserResourcePermission(
context.Background(),
accesscontrol.GlobalOrgID,
accesscontrol.User{ID: userIds[i]},
types.SetResourcePermissionCommand{
Actions: []string{dsAction},
Resource: dsResource,
ResourceID: strconv.Itoa(int(dsID)),
ResourceAttribute: "id",
},
nil,
)
require.NoError(b, err)
}
// Add DS permissions for the teams
maxPermissions = int(math.Min(float64(permissionsPerDs), float64(len(teamIds))))
for i := 0; i < maxPermissions; i++ {
_, err := ac.SetTeamResourcePermission(
context.Background(),
accesscontrol.GlobalOrgID,
teamIds[i],
types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: strconv.Itoa(int(dsID)),
ResourceAttribute: "id",
},
nil,
)
require.NoError(b, err)
}
}
return dataSources
}
func generateTeamsAndUsers(b *testing.B, db *sqlstore.SQLStore, users int) ([]int64, []int64) {
numberOfTeams := int(math.Ceil(float64(users) / UsersPerTeam))
globalUserId := 0
userIds := make([]int64, 0)
teamIds := make([]int64, 0)
for i := 0; i < numberOfTeams; i++ {
// Create team
teamName := fmt.Sprintf("%s%v", "team", i)
teamEmail := fmt.Sprintf("%s@example.org", teamName)
team, err := db.CreateTeam(teamName, teamEmail, 1)
require.NoError(b, err)
teamId := team.Id
teamIds = append(teamIds, teamId)
// Create team users
for u := 0; u < UsersPerTeam; u++ {
userName := fmt.Sprintf("%s%v", "user", globalUserId)
userEmail := fmt.Sprintf("%s@example.org", userName)
createUserCmd := user.CreateUserCommand{Email: userEmail, Name: userName, Login: userName, OrgID: 1}
user, err := db.CreateUser(context.Background(), createUserCmd)
require.NoError(b, err)
userId := user.ID
globalUserId++
userIds = append(userIds, userId)
err = db.AddTeamMember(userId, 1, teamId, false, 1)
require.NoError(b, err)
}
}
return userIds, teamIds
}

View File

@@ -1,465 +0,0 @@
package database
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
)
type setUserResourcePermissionTest struct {
desc string
orgID int64
userID int64
actions []string
resource string
resourceID string
resourceAttribute string
seeds []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
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: []types.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: []types.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}, types.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 []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetTeamResourcePermission(t *testing.T) {
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: []types.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: []types.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, types.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 []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
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: []types.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: []types.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, types.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 []types.SetResourcePermissionsCommand
}
func TestAccessControlStore_SetResourcePermissions(t *testing.T) {
tests := []setResourcePermissionsTest{
{
desc: "should set all permissions provided",
orgID: 1,
resourceAttribute: "uid",
commands: []types.SetResourcePermissionsCommand{
{
User: accesscontrol.User{ID: 1},
SetResourcePermissionCommand: types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
{
TeamID: 3,
SetResourcePermissionCommand: types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
{
BuiltinRole: "Admin",
SetResourcePermissionCommand: types.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, types.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
actions []string
resource string
resourceID string
resourceAttribute string
onlyManaged bool
}
func TestAccessControlStore_GetResourcePermissions(t *testing.T) {
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,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
},
{
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,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
onlyManaged: true,
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
store, sql := setupTestEnv(t)
err := sql.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
role := &accesscontrol.Role{
OrgID: test.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, test.actions, test.resource, test.resourceID, test.resourceAttribute, test.numUsers)
permissions, err := store.GetResourcePermissions(context.Background(), test.user.OrgID, types.GetResourcePermissionsQuery{
User: test.user,
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
ResourceAttribute: test.resourceAttribute,
OnlyManaged: test.onlyManaged,
})
require.NoError(t, err)
expectedLen := test.numUsers
if !test.onlyManaged {
expectedLen += 1
}
assert.Len(t, permissions, expectedLen)
})
}
}
func seedResourcePermissions(t *testing.T, store *AccessControlStore, sql *sqlstore.SQLStore, actions []string, resource, resourceID, resourceAttribute 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(), user.CreateUserCommand{
Login: fmt.Sprintf("user:%s%d", resourceID, i),
OrgID: org.Id,
})
require.NoError(t, err)
_, err = store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: u.ID}, types.SetResourcePermissionCommand{
Actions: actions,
Resource: resource,
ResourceID: resourceID,
ResourceAttribute: resourceAttribute,
}, nil)
require.NoError(t, err)
}
}

View File

@@ -1,26 +0,0 @@
package database
import (
"fmt"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/util"
)
func generateNewRoleUID(sess *sqlstore.DBSession, orgID int64) (string, error) {
for i := 0; i < 3; i++ {
uid := util.GenerateShortUID()
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&accesscontrol.Role{})
if err != nil {
return "", err
}
if !exists {
return uid, nil
}
}
return "", fmt.Errorf("failed to generate uid")
}