mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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 = ?"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(¤t); 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),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user