mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Access Control: move features to Enterprise (#32640)
* Move db package WIP * Implement OSS access control * Register OSS access control * Fix linter error in tests * Fix linter error in evaluator * Simplify OSS tests * Optimize builtin roles * Chore: add comments to the exported functions * Remove init from ossaccesscontrol package (moved to ext) * Add access control as a dependency for http server * Modify middleware to receive fallback function * Middleware: refactor fallback function call * Move unused models to enterprise * Simplify AccessControl type * Chore: use bool IsDisabled() method instead of CanBeDisabled interface
This commit is contained in:
parent
664268a498
commit
823f0bc460
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/plugins/plugincontext"
|
"github.com/grafana/grafana/pkg/plugins/plugincontext"
|
||||||
"github.com/grafana/grafana/pkg/plugins/plugindashboards"
|
"github.com/grafana/grafana/pkg/plugins/plugindashboards"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/alerting"
|
"github.com/grafana/grafana/pkg/services/alerting"
|
||||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||||
@ -81,6 +82,7 @@ type HTTPServer struct {
|
|||||||
ProvisioningService provisioning.ProvisioningService `inject:""`
|
ProvisioningService provisioning.ProvisioningService `inject:""`
|
||||||
Login login.Service `inject:""`
|
Login login.Service `inject:""`
|
||||||
License models.Licensing `inject:""`
|
License models.Licensing `inject:""`
|
||||||
|
AccessControl accesscontrol.AccessControl `inject:""`
|
||||||
BackendPluginManager backendplugin.Manager `inject:""`
|
BackendPluginManager backendplugin.Manager `inject:""`
|
||||||
DataProxy *datasourceproxy.DatasourceProxyService `inject:""`
|
DataProxy *datasourceproxy.DatasourceProxyService `inject:""`
|
||||||
PluginRequestValidator models.PluginRequestValidator `inject:""`
|
PluginRequestValidator models.PluginRequestValidator `inject:""`
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
_ "github.com/crewjam/saml"
|
_ "github.com/crewjam/saml"
|
||||||
_ "github.com/gobwas/glob"
|
_ "github.com/gobwas/glob"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/licensing"
|
"github.com/grafana/grafana/pkg/services/licensing"
|
||||||
"github.com/grafana/grafana/pkg/services/validations"
|
"github.com/grafana/grafana/pkg/services/validations"
|
||||||
_ "github.com/grafana/loki/pkg/logproto"
|
_ "github.com/grafana/loki/pkg/logproto"
|
||||||
@ -29,6 +30,7 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
registry.RegisterService(&licensing.OSSLicensingService{})
|
registry.RegisterService(&licensing.OSSLicensingService{})
|
||||||
registry.RegisterService(&validations.OSSPluginRequestValidator{})
|
registry.RegisterService(&validations.OSSPluginRequestValidator{})
|
||||||
|
registry.RegisterService(&ossaccesscontrol.OSSAccessControlService{})
|
||||||
}
|
}
|
||||||
|
|
||||||
var IsEnterprise bool = false
|
var IsEnterprise bool = false
|
||||||
|
@ -31,7 +31,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
_ "github.com/grafana/grafana/pkg/plugins/manager"
|
_ "github.com/grafana/grafana/pkg/plugins/manager"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
_ "github.com/grafana/grafana/pkg/services/accesscontrol/manager"
|
|
||||||
_ "github.com/grafana/grafana/pkg/services/alerting"
|
_ "github.com/grafana/grafana/pkg/services/alerting"
|
||||||
_ "github.com/grafana/grafana/pkg/services/auth"
|
_ "github.com/grafana/grafana/pkg/services/auth"
|
||||||
_ "github.com/grafana/grafana/pkg/services/auth/jwt"
|
_ "github.com/grafana/grafana/pkg/services/auth/jwt"
|
||||||
|
@ -7,38 +7,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AccessControl interface {
|
type AccessControl interface {
|
||||||
Evaluator
|
// Evaluate evaluates access to the given resource.
|
||||||
Store
|
|
||||||
}
|
|
||||||
|
|
||||||
type Evaluator interface {
|
|
||||||
// Evaluate evaluates access to the given resource
|
|
||||||
Evaluate(ctx context.Context, user *models.SignedInUser, permission string, scope ...string) (bool, error)
|
Evaluate(ctx context.Context, user *models.SignedInUser, permission string, scope ...string) (bool, error)
|
||||||
}
|
|
||||||
|
|
||||||
type Store interface {
|
// GetUserPermissions returns user permissions.
|
||||||
// Database access methods
|
GetUserPermissions(ctx context.Context, user *models.SignedInUser, roles []string) ([]*Permission, error)
|
||||||
GetRoles(ctx context.Context, orgID int64) ([]*Role, error)
|
|
||||||
GetRole(ctx context.Context, orgID, roleID int64) (*RoleDTO, error)
|
|
||||||
GetRoleByUID(ctx context.Context, orgId int64, uid string) (*RoleDTO, error)
|
|
||||||
CreateRole(ctx context.Context, cmd CreateRoleCommand) (*Role, error)
|
|
||||||
CreateRoleWithPermissions(ctx context.Context, cmd CreateRoleWithPermissionsCommand) (*RoleDTO, error)
|
|
||||||
UpdateRole(ctx context.Context, cmd UpdateRoleCommand) (*RoleDTO, error)
|
|
||||||
DeleteRole(cmd *DeleteRoleCommand) error
|
|
||||||
GetRolePermissions(ctx context.Context, roleID int64) ([]Permission, error)
|
|
||||||
CreatePermission(ctx context.Context, cmd CreatePermissionCommand) (*Permission, error)
|
|
||||||
UpdatePermission(cmd *UpdatePermissionCommand) (*Permission, error)
|
|
||||||
DeletePermission(ctx context.Context, cmd *DeletePermissionCommand) error
|
|
||||||
GetTeamRoles(query *GetTeamRolesQuery) ([]*RoleDTO, error)
|
|
||||||
GetUserRoles(ctx context.Context, query GetUserRolesQuery) ([]*RoleDTO, error)
|
|
||||||
GetUserPermissions(ctx context.Context, query GetUserPermissionsQuery) ([]*Permission, error)
|
|
||||||
AddTeamRole(cmd *AddTeamRoleCommand) error
|
|
||||||
RemoveTeamRole(cmd *RemoveTeamRoleCommand) error
|
|
||||||
AddUserRole(cmd *AddUserRoleCommand) error
|
|
||||||
RemoveUserRole(cmd *RemoveUserRoleCommand) error
|
|
||||||
AddBuiltinRole(ctx context.Context, orgID, roleID int64, roleName string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Seeder interface {
|
// Middleware checks if service disabled or not to switch to fallback authorization.
|
||||||
Seed(ctx context.Context, orgID int64) error
|
IsDisabled() bool
|
||||||
}
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
// accessControlStoreTestImpl is a test store implementation which additionally executes a database migrations
|
|
||||||
type accessControlStoreTestImpl struct {
|
|
||||||
AccessControlStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *accessControlStoreTestImpl) AddMigration(mg *migrator.Migrator) {
|
|
||||||
AddAccessControlMigrations(mg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTestEnv(t testing.TB) *accessControlStoreTestImpl {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
store := overrideDatabaseInRegistry(cfg)
|
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
|
||||||
store.SQLStore = sqlStore
|
|
||||||
|
|
||||||
err := store.Init()
|
|
||||||
require.NoError(t, err)
|
|
||||||
return &store
|
|
||||||
}
|
|
||||||
|
|
||||||
func overrideDatabaseInRegistry(cfg *setting.Cfg) accessControlStoreTestImpl {
|
|
||||||
store := accessControlStoreTestImpl{
|
|
||||||
AccessControlStore: AccessControlStore{
|
|
||||||
SQLStore: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) {
|
|
||||||
if _, ok := descriptor.Instance.(*AccessControlStore); ok {
|
|
||||||
return ®istry.Descriptor{
|
|
||||||
Name: "Database",
|
|
||||||
Instance: &store,
|
|
||||||
InitPriority: descriptor.InitPriority,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
registry.RegisterOverride(overrideServiceFunc)
|
|
||||||
|
|
||||||
return store
|
|
||||||
}
|
|
@ -1,700 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
||||||
"github.com/grafana/grafana/pkg/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TimeNow makes it possible to test usage of time
|
|
||||||
var TimeNow = time.Now
|
|
||||||
|
|
||||||
type AccessControlStore struct {
|
|
||||||
SQLStore *sqlstore.SQLStore `inject:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterService(&AccessControlStore{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) Init() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) GetRoles(ctx context.Context, orgID int64) ([]*accesscontrol.Role, error) {
|
|
||||||
var result []*accesscontrol.Role
|
|
||||||
err := ac.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
roles := make([]*accesscontrol.Role, 0)
|
|
||||||
q := "SELECT id, uid, org_id, name, description, updated FROM role WHERE org_id = ?"
|
|
||||||
if err := sess.SQL(q, orgID).Find(&roles); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = roles
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) GetRole(ctx context.Context, orgID, roleID int64) (*accesscontrol.RoleDTO, error) {
|
|
||||||
var result *accesscontrol.RoleDTO
|
|
||||||
|
|
||||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
role, err := getRoleById(sess, roleID, orgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
permissions, err := getRolePermissions(sess, roleID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
role.Permissions = permissions
|
|
||||||
result = role
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) GetRoleByUID(ctx context.Context, orgId int64, uid string) (*accesscontrol.RoleDTO, error) {
|
|
||||||
var result *accesscontrol.RoleDTO
|
|
||||||
|
|
||||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
role, err := getRoleByUID(sess, uid, orgId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
permissions, err := getRolePermissions(sess, role.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
role.Permissions = permissions
|
|
||||||
result = role
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) CreateRole(ctx context.Context, cmd accesscontrol.CreateRoleCommand) (*accesscontrol.Role, error) {
|
|
||||||
var result *accesscontrol.Role
|
|
||||||
|
|
||||||
err := ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
role, err := ac.createRole(sess, cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = role
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) createRole(sess *sqlstore.DBSession, cmd accesscontrol.CreateRoleCommand) (*accesscontrol.Role, error) {
|
|
||||||
if cmd.UID == "" {
|
|
||||||
uid, err := generateNewRoleUID(sess, cmd.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate UID for role %q: %w", cmd.Name, err)
|
|
||||||
}
|
|
||||||
cmd.UID = uid
|
|
||||||
}
|
|
||||||
|
|
||||||
role := &accesscontrol.Role{
|
|
||||||
OrgID: cmd.OrgID,
|
|
||||||
UID: cmd.UID,
|
|
||||||
Name: cmd.Name,
|
|
||||||
Description: cmd.Description,
|
|
||||||
Created: TimeNow(),
|
|
||||||
Updated: TimeNow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Insert(role); err != nil {
|
|
||||||
if ac.SQLStore.Dialect.IsUniqueConstraintViolation(err) && strings.Contains(err.Error(), "name") {
|
|
||||||
return nil, fmt.Errorf("role with the name '%s' already exists: %w", cmd.Name, err)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return role, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) CreateRoleWithPermissions(ctx context.Context, cmd accesscontrol.CreateRoleWithPermissionsCommand) (*accesscontrol.RoleDTO, error) {
|
|
||||||
var result *accesscontrol.RoleDTO
|
|
||||||
|
|
||||||
err := ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
createRoleCmd := accesscontrol.CreateRoleCommand{
|
|
||||||
OrgID: cmd.OrgID,
|
|
||||||
UID: cmd.UID,
|
|
||||||
Name: cmd.Name,
|
|
||||||
Description: cmd.Description,
|
|
||||||
}
|
|
||||||
|
|
||||||
role, err := ac.createRole(sess, createRoleCmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = &accesscontrol.RoleDTO{
|
|
||||||
ID: role.ID,
|
|
||||||
UID: role.UID,
|
|
||||||
OrgID: role.OrgID,
|
|
||||||
Name: role.Name,
|
|
||||||
Description: role.Description,
|
|
||||||
Created: role.Created,
|
|
||||||
Updated: role.Updated,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add permissions
|
|
||||||
for _, p := range cmd.Permissions {
|
|
||||||
createPermissionCmd := accesscontrol.CreatePermissionCommand{
|
|
||||||
RoleID: role.ID,
|
|
||||||
Permission: p.Permission,
|
|
||||||
Scope: p.Scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
permission, err := createPermission(sess, createPermissionCmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
result.Permissions = append(result.Permissions, *permission)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRole updates role with permissions
|
|
||||||
func (ac *AccessControlStore) UpdateRole(ctx context.Context, cmd accesscontrol.UpdateRoleCommand) (*accesscontrol.RoleDTO, error) {
|
|
||||||
var result *accesscontrol.RoleDTO
|
|
||||||
err := ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
// TODO: work with both ID and UID
|
|
||||||
existingRole, err := getRoleByUID(sess, cmd.UID, cmd.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
version := existingRole.Version + 1
|
|
||||||
if cmd.Version != 0 {
|
|
||||||
if existingRole.Version >= cmd.Version {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"could not update '%s' (UID %s) from version %d to %d: %w",
|
|
||||||
cmd.Name,
|
|
||||||
existingRole.UID,
|
|
||||||
existingRole.Version,
|
|
||||||
cmd.Version,
|
|
||||||
accesscontrol.ErrVersionLE,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
version = cmd.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
role := &accesscontrol.Role{
|
|
||||||
ID: existingRole.ID,
|
|
||||||
UID: existingRole.UID,
|
|
||||||
Version: version,
|
|
||||||
OrgID: existingRole.OrgID,
|
|
||||||
Name: cmd.Name,
|
|
||||||
Description: cmd.Description,
|
|
||||||
Updated: TimeNow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
affectedRows, err := sess.ID(existingRole.ID).Update(role)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if affectedRows == 0 {
|
|
||||||
return accesscontrol.ErrRoleNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
result = &accesscontrol.RoleDTO{
|
|
||||||
ID: role.ID,
|
|
||||||
Version: version,
|
|
||||||
UID: role.UID,
|
|
||||||
OrgID: role.OrgID,
|
|
||||||
Name: role.Name,
|
|
||||||
Description: role.Description,
|
|
||||||
Created: role.Created,
|
|
||||||
Updated: role.Updated,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete role's permissions
|
|
||||||
_, err = sess.Exec("DELETE FROM permission WHERE role_id = ?", existingRole.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add permissions
|
|
||||||
for _, p := range cmd.Permissions {
|
|
||||||
createPermissionCmd := accesscontrol.CreatePermissionCommand{
|
|
||||||
RoleID: role.ID,
|
|
||||||
Permission: p.Permission,
|
|
||||||
Scope: p.Scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
permission, err := createPermission(sess, createPermissionCmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
result.Permissions = append(result.Permissions, *permission)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) DeleteRole(cmd *accesscontrol.DeleteRoleCommand) error {
|
|
||||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
roleId := cmd.ID
|
|
||||||
if roleId == 0 {
|
|
||||||
role, err := getRoleByUID(sess, cmd.UID, cmd.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
roleId = role.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete role's permissions
|
|
||||||
_, err := sess.Exec("DELETE FROM permission WHERE role_id = ?", roleId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sess.Exec("DELETE FROM role WHERE id = ? AND org_id = ?", roleId, cmd.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) GetRolePermissions(ctx context.Context, roleID int64) ([]accesscontrol.Permission, error) {
|
|
||||||
var result []accesscontrol.Permission
|
|
||||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
permissions, err := getRolePermissions(sess, roleID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = permissions
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) CreatePermission(ctx context.Context, cmd accesscontrol.CreatePermissionCommand) (*accesscontrol.Permission, error) {
|
|
||||||
var result *accesscontrol.Permission
|
|
||||||
err := ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
permission, err := createPermission(sess, cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = permission
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) UpdatePermission(cmd *accesscontrol.UpdatePermissionCommand) (*accesscontrol.Permission, error) {
|
|
||||||
var result *accesscontrol.Permission
|
|
||||||
err := ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
permission := &accesscontrol.Permission{
|
|
||||||
Permission: cmd.Permission,
|
|
||||||
Scope: cmd.Scope,
|
|
||||||
Updated: TimeNow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
affectedRows, err := sess.ID(cmd.ID).Update(permission)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if affectedRows == 0 {
|
|
||||||
return accesscontrol.ErrPermissionNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
result = permission
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) DeletePermission(ctx context.Context, cmd *accesscontrol.DeletePermissionCommand) error {
|
|
||||||
return ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
_, err := sess.Exec("DELETE FROM permission WHERE id = ?", cmd.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) GetTeamRoles(query *accesscontrol.GetTeamRolesQuery) ([]*accesscontrol.RoleDTO, error) {
|
|
||||||
var result []*accesscontrol.RoleDTO
|
|
||||||
err := ac.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
q := `SELECT
|
|
||||||
role.id,
|
|
||||||
role.name AS name,
|
|
||||||
role.description AS description,
|
|
||||||
role.updated FROM role AS role
|
|
||||||
INNER JOIN team_role ON role.id = team_role.role_id AND team_role.team_id = ?
|
|
||||||
WHERE role.org_id = ? `
|
|
||||||
|
|
||||||
if err := sess.SQL(q, query.TeamID, query.OrgID).Find(&result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) GetUserRoles(ctx context.Context, query accesscontrol.GetUserRolesQuery) ([]*accesscontrol.RoleDTO, error) {
|
|
||||||
var result []*accesscontrol.RoleDTO
|
|
||||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
// TODO: optimize this
|
|
||||||
filter, params := ac.userRolesFilter(query.OrgID, query.UserID, query.Roles)
|
|
||||||
|
|
||||||
q := `SELECT
|
|
||||||
role.id,
|
|
||||||
role.org_id,
|
|
||||||
role.name,
|
|
||||||
role.description,
|
|
||||||
role.created,
|
|
||||||
role.updated
|
|
||||||
FROM role
|
|
||||||
` + filter
|
|
||||||
|
|
||||||
err := sess.SQL(q, params...).Find(&result)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) GetUserPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) ([]*accesscontrol.Permission, error) {
|
|
||||||
var result []*accesscontrol.Permission
|
|
||||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
filter, params := ac.userRolesFilter(query.OrgID, query.UserID, query.Roles)
|
|
||||||
|
|
||||||
// TODO: optimize this
|
|
||||||
q := `SELECT
|
|
||||||
permission.id,
|
|
||||||
permission.role_id,
|
|
||||||
permission.permission,
|
|
||||||
permission.scope,
|
|
||||||
permission.updated,
|
|
||||||
permission.created
|
|
||||||
FROM permission
|
|
||||||
INNER JOIN role ON role.id = permission.role_id
|
|
||||||
` + filter
|
|
||||||
|
|
||||||
if err := sess.SQL(q, params...).Find(&result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*AccessControlStore) userRolesFilter(orgID, userID int64, roles []string) (string, []interface{}) {
|
|
||||||
q := `WHERE role.id IN (
|
|
||||||
SELECT up.role_id FROM user_role AS up WHERE up.user_id = ?
|
|
||||||
UNION
|
|
||||||
SELECT tp.role_id FROM team_role as tp
|
|
||||||
INNER JOIN team_member as tm ON tm.team_id = tp.team_id
|
|
||||||
WHERE tm.user_id = ?`
|
|
||||||
params := []interface{}{userID, userID}
|
|
||||||
|
|
||||||
if len(roles) != 0 {
|
|
||||||
q += `
|
|
||||||
UNION
|
|
||||||
SELECT br.role_id FROM builtin_role AS br
|
|
||||||
WHERE role IN (? ` + strings.Repeat(", ?", len(roles)-1) + `)`
|
|
||||||
for _, role := range roles {
|
|
||||||
params = append(params, role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
q += `) and role.org_id = ?`
|
|
||||||
params = append(params, orgID)
|
|
||||||
|
|
||||||
return q, params
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) AddTeamRole(cmd *accesscontrol.AddTeamRoleCommand) error {
|
|
||||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
role, err := getRoleByUID(sess, cmd.RoleUID, cmd.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := teamExists(cmd.OrgID, cmd.TeamID, sess); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if res, err := sess.Query("SELECT 1 from team_role WHERE org_id=? and team_id=? and role_id=?", cmd.OrgID, cmd.TeamID, role.ID); err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(res) == 1 {
|
|
||||||
return accesscontrol.ErrTeamRoleAlreadyAdded
|
|
||||||
}
|
|
||||||
|
|
||||||
teamRole := &accesscontrol.TeamRole{
|
|
||||||
OrgID: cmd.OrgID,
|
|
||||||
TeamID: cmd.TeamID,
|
|
||||||
RoleID: role.ID,
|
|
||||||
Created: TimeNow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sess.Insert(teamRole)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) RemoveTeamRole(cmd *accesscontrol.RemoveTeamRoleCommand) error {
|
|
||||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
role, err := getRoleByUID(sess, cmd.RoleUID, cmd.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := teamExists(cmd.OrgID, cmd.TeamID, sess); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
q := "DELETE FROM team_role WHERE org_id=? and team_id=? and role_id=?"
|
|
||||||
res, err := sess.Exec(q, cmd.OrgID, cmd.TeamID, role.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rows, err := res.RowsAffected()
|
|
||||||
if rows == 0 {
|
|
||||||
return accesscontrol.ErrTeamRoleNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) AddUserRole(cmd *accesscontrol.AddUserRoleCommand) error {
|
|
||||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
role, err := getRoleByUID(sess, cmd.RoleUID, cmd.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if res, err := sess.Query("SELECT 1 from user_role WHERE org_id=? and user_id=? and role_id=?", cmd.OrgID, cmd.UserID, role.ID); err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(res) == 1 {
|
|
||||||
return accesscontrol.ErrUserRoleAlreadyAdded
|
|
||||||
}
|
|
||||||
|
|
||||||
userRole := &accesscontrol.UserRole{
|
|
||||||
OrgID: cmd.OrgID,
|
|
||||||
UserID: cmd.UserID,
|
|
||||||
RoleID: role.ID,
|
|
||||||
Created: TimeNow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sess.Insert(userRole)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) RemoveUserRole(cmd *accesscontrol.RemoveUserRoleCommand) error {
|
|
||||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
role, err := getRoleByUID(sess, cmd.RoleUID, cmd.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
q := "DELETE FROM user_role WHERE org_id=? and user_id=? and role_id=?"
|
|
||||||
res, err := sess.Exec(q, cmd.OrgID, cmd.UserID, role.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rows, err := res.RowsAffected()
|
|
||||||
if rows == 0 {
|
|
||||||
return accesscontrol.ErrUserRoleNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AccessControlStore) AddBuiltinRole(ctx context.Context, orgID, roleID int64, roleName string) error {
|
|
||||||
if !models.RoleType(roleName).IsValid() && roleName != "Grafana Admin" {
|
|
||||||
return fmt.Errorf("role '%s' is not a valid role", roleName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
if res, err := sess.Query("SELECT 1 from builtin_role WHERE role_id=? and role=?", roleID, roleName); err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(res) == 1 {
|
|
||||||
return accesscontrol.ErrUserRoleAlreadyAdded
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := roleExists(orgID, roleID, sess); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
role := accesscontrol.BuiltinRole{
|
|
||||||
RoleID: roleID,
|
|
||||||
Role: roleName,
|
|
||||||
Updated: TimeNow(),
|
|
||||||
Created: TimeNow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := sess.Table("builtin_role").Insert(role)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRoleById(sess *sqlstore.DBSession, roleId int64, orgId int64) (*accesscontrol.RoleDTO, error) {
|
|
||||||
role := accesscontrol.Role{OrgID: orgId, ID: roleId}
|
|
||||||
has, err := sess.Get(&role)
|
|
||||||
if !has {
|
|
||||||
return nil, accesscontrol.ErrRoleNotFound
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
roleDTO := accesscontrol.RoleDTO{
|
|
||||||
ID: roleId,
|
|
||||||
OrgID: role.OrgID,
|
|
||||||
Name: role.Name,
|
|
||||||
Description: role.Description,
|
|
||||||
Permissions: nil,
|
|
||||||
Created: role.Created,
|
|
||||||
Updated: role.Updated,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &roleDTO, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRoleByUID(sess *sqlstore.DBSession, uid string, orgId int64) (*accesscontrol.RoleDTO, error) {
|
|
||||||
role := accesscontrol.Role{OrgID: orgId, UID: uid}
|
|
||||||
has, err := sess.Get(&role)
|
|
||||||
if !has {
|
|
||||||
return nil, accesscontrol.ErrRoleNotFound
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
roleDTO := accesscontrol.RoleDTO{
|
|
||||||
ID: role.ID,
|
|
||||||
UID: role.UID,
|
|
||||||
Version: role.Version,
|
|
||||||
OrgID: role.OrgID,
|
|
||||||
Name: role.Name,
|
|
||||||
Description: role.Description,
|
|
||||||
Permissions: nil,
|
|
||||||
Created: role.Created,
|
|
||||||
Updated: role.Updated,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &roleDTO, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRolePermissions(sess *sqlstore.DBSession, roleId int64) ([]accesscontrol.Permission, error) {
|
|
||||||
permissions := make([]accesscontrol.Permission, 0)
|
|
||||||
q := "SELECT id, role_id, permission, scope, updated, created FROM permission WHERE role_id = ?"
|
|
||||||
if err := sess.SQL(q, roleId).Find(&permissions); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPermission(sess *sqlstore.DBSession, cmd accesscontrol.CreatePermissionCommand) (*accesscontrol.Permission, error) {
|
|
||||||
permission := &accesscontrol.Permission{
|
|
||||||
RoleID: cmd.RoleID,
|
|
||||||
Permission: cmd.Permission,
|
|
||||||
Scope: cmd.Scope,
|
|
||||||
Created: TimeNow(),
|
|
||||||
Updated: TimeNow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Insert(permission); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return permission, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func teamExists(orgId int64, teamId int64, sess *sqlstore.DBSession) (bool, error) {
|
|
||||||
if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgId, teamId); err != nil {
|
|
||||||
return false, err
|
|
||||||
} else if len(res) != 1 {
|
|
||||||
return false, accesscontrol.ErrTeamNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func roleExists(orgId int64, roleId int64, sess *sqlstore.DBSession) (bool, error) {
|
|
||||||
if res, err := sess.Query("SELECT 1 from role WHERE org_id=? and id=?", orgId, roleId); err != nil {
|
|
||||||
return false, err
|
|
||||||
} else if len(res) != 1 {
|
|
||||||
return false, accesscontrol.ErrRoleNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "", accesscontrol.ErrRoleFailedGenerateUniqueUID
|
|
||||||
}
|
|
||||||
|
|
||||||
func MockTimeNow() {
|
|
||||||
var timeSeed int64
|
|
||||||
TimeNow = func() time.Time {
|
|
||||||
fakeNow := time.Unix(timeSeed, 0).UTC()
|
|
||||||
timeSeed++
|
|
||||||
return fakeNow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResetTimeNow() {
|
|
||||||
TimeNow = time.Now
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
actesting "github.com/grafana/grafana/pkg/services/accesscontrol/testing"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setup(b *testing.B, rolesPerUser, users int) *accessControlStoreTestImpl {
|
|
||||||
ac := setupTestEnv(b)
|
|
||||||
b.Cleanup(registry.ClearOverrides)
|
|
||||||
actesting.GenerateRoles(b, ac.SQLStore, ac, rolesPerUser, users)
|
|
||||||
return ac
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRoles(b *testing.B, ac accesscontrol.Store, rolesPerUser, users int) {
|
|
||||||
userQuery := models.GetUserByLoginQuery{
|
|
||||||
LoginOrEmail: "user1@test.com",
|
|
||||||
}
|
|
||||||
err := sqlstore.GetUserByLogin(&userQuery)
|
|
||||||
require.NoError(b, err)
|
|
||||||
userId := userQuery.Result.Id
|
|
||||||
|
|
||||||
userPermissionsQuery := accesscontrol.GetUserPermissionsQuery{OrgID: 1, UserID: userId}
|
|
||||||
res, err := ac.GetUserPermissions(context.Background(), userPermissionsQuery)
|
|
||||||
require.NoError(b, err)
|
|
||||||
expectedPermissions := actesting.PermissionsPerRole * rolesPerUser
|
|
||||||
assert.Greater(b, len(res), expectedPermissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkRoles(b *testing.B, rolesPerUser, users int) {
|
|
||||||
ac := setup(b, rolesPerUser, users)
|
|
||||||
// We don't wanna measure DB initialization
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
getRoles(b, ac, rolesPerUser, users)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkRolesUsers10_10(b *testing.B) { benchmarkRoles(b, 10, 10) }
|
|
||||||
|
|
||||||
func BenchmarkRolesUsers10_100(b *testing.B) { benchmarkRoles(b, 10, 100) }
|
|
||||||
func BenchmarkRolesUsers10_500(b *testing.B) { benchmarkRoles(b, 10, 500) }
|
|
||||||
func BenchmarkRolesUsers10_1000(b *testing.B) { benchmarkRoles(b, 10, 1000) }
|
|
||||||
func BenchmarkRolesUsers10_5000(b *testing.B) { benchmarkRoles(b, 10, 5000) }
|
|
||||||
func BenchmarkRolesUsers10_10000(b *testing.B) {
|
|
||||||
if testing.Short() {
|
|
||||||
b.Skip("Skipping benchmark in short mode")
|
|
||||||
}
|
|
||||||
benchmarkRoles(b, 10, 10000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkRolesPerUser10_10(b *testing.B) { benchmarkRoles(b, 10, 10) }
|
|
||||||
func BenchmarkRolesPerUser100_10(b *testing.B) { benchmarkRoles(b, 100, 10) }
|
|
||||||
func BenchmarkRolesPerUser500_10(b *testing.B) { benchmarkRoles(b, 500, 10) }
|
|
||||||
func BenchmarkRolesPerUser1000_10(b *testing.B) { benchmarkRoles(b, 1000, 10) }
|
|
@ -1,118 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|
||||||
|
|
||||||
func AddAccessControlMigrations(mg *migrator.Migrator) {
|
|
||||||
permissionV1 := migrator.Table{
|
|
||||||
Name: "permission",
|
|
||||||
Columns: []*migrator.Column{
|
|
||||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
|
||||||
{Name: "role_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "permission", Type: migrator.DB_Varchar, Length: 190, Nullable: false},
|
|
||||||
{Name: "scope", Type: migrator.DB_Varchar, Length: 190, Nullable: false},
|
|
||||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
|
||||||
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
|
|
||||||
},
|
|
||||||
Indices: []*migrator.Index{
|
|
||||||
{Cols: []string{"role_id"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.AddMigration("create permission table", migrator.NewAddTableMigration(permissionV1))
|
|
||||||
|
|
||||||
//------- indexes ------------------
|
|
||||||
mg.AddMigration("add unique index permission.role_id", migrator.NewAddIndexMigration(permissionV1, permissionV1.Indices[0]))
|
|
||||||
|
|
||||||
roleV1 := migrator.Table{
|
|
||||||
Name: "role",
|
|
||||||
Columns: []*migrator.Column{
|
|
||||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
|
||||||
{Name: "name", Type: migrator.DB_NVarchar, Length: 190, Nullable: false},
|
|
||||||
{Name: "description", Type: migrator.DB_Text, Nullable: true},
|
|
||||||
{Name: "version", Type: migrator.DB_BigInt, Nullable: false},
|
|
||||||
{Name: "org_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "uid", Type: migrator.DB_NVarchar, Length: 40, Nullable: false},
|
|
||||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
|
||||||
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
|
|
||||||
},
|
|
||||||
Indices: []*migrator.Index{
|
|
||||||
{Cols: []string{"org_id"}},
|
|
||||||
{Cols: []string{"org_id", "name"}, Type: migrator.UniqueIndex},
|
|
||||||
{Cols: []string{"org_id", "uid"}, Type: migrator.UniqueIndex},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.AddMigration("create role table", migrator.NewAddTableMigration(roleV1))
|
|
||||||
|
|
||||||
//------- indexes ------------------
|
|
||||||
mg.AddMigration("add index role.org_id", migrator.NewAddIndexMigration(roleV1, roleV1.Indices[0]))
|
|
||||||
mg.AddMigration("add unique index role_org_id_name", migrator.NewAddIndexMigration(roleV1, roleV1.Indices[1]))
|
|
||||||
mg.AddMigration("add index role_org_id_uid", migrator.NewAddIndexMigration(roleV1, roleV1.Indices[2]))
|
|
||||||
|
|
||||||
teamRoleV1 := migrator.Table{
|
|
||||||
Name: "team_role",
|
|
||||||
Columns: []*migrator.Column{
|
|
||||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
|
||||||
{Name: "org_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "team_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "role_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
|
||||||
},
|
|
||||||
Indices: []*migrator.Index{
|
|
||||||
{Cols: []string{"org_id"}},
|
|
||||||
{Cols: []string{"org_id", "team_id", "role_id"}, Type: migrator.UniqueIndex},
|
|
||||||
{Cols: []string{"team_id"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.AddMigration("create team role table", migrator.NewAddTableMigration(teamRoleV1))
|
|
||||||
|
|
||||||
//------- indexes ------------------
|
|
||||||
mg.AddMigration("add index team_role.org_id", migrator.NewAddIndexMigration(teamRoleV1, teamRoleV1.Indices[0]))
|
|
||||||
mg.AddMigration("add unique index team_role_org_id_team_id_role_id", migrator.NewAddIndexMigration(teamRoleV1, teamRoleV1.Indices[1]))
|
|
||||||
mg.AddMigration("add index team_role.team_id", migrator.NewAddIndexMigration(teamRoleV1, teamRoleV1.Indices[2]))
|
|
||||||
|
|
||||||
userRoleV1 := migrator.Table{
|
|
||||||
Name: "user_role",
|
|
||||||
Columns: []*migrator.Column{
|
|
||||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
|
||||||
{Name: "org_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "user_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "role_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
|
||||||
},
|
|
||||||
Indices: []*migrator.Index{
|
|
||||||
{Cols: []string{"org_id"}},
|
|
||||||
{Cols: []string{"org_id", "user_id", "role_id"}, Type: migrator.UniqueIndex},
|
|
||||||
{Cols: []string{"user_id"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.AddMigration("create user role table", migrator.NewAddTableMigration(userRoleV1))
|
|
||||||
|
|
||||||
//------- indexes ------------------
|
|
||||||
mg.AddMigration("add index user_role.org_id", migrator.NewAddIndexMigration(userRoleV1, userRoleV1.Indices[0]))
|
|
||||||
mg.AddMigration("add unique index user_role_org_id_user_id_role_id", migrator.NewAddIndexMigration(userRoleV1, userRoleV1.Indices[1]))
|
|
||||||
mg.AddMigration("add index user_role.user_id", migrator.NewAddIndexMigration(userRoleV1, userRoleV1.Indices[2]))
|
|
||||||
|
|
||||||
builtinRoleV1 := migrator.Table{
|
|
||||||
Name: "builtin_role",
|
|
||||||
Columns: []*migrator.Column{
|
|
||||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
|
||||||
{Name: "role", Type: migrator.DB_NVarchar, Length: 190, Nullable: false},
|
|
||||||
{Name: "role_id", Type: migrator.DB_BigInt},
|
|
||||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
|
||||||
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
|
|
||||||
},
|
|
||||||
Indices: []*migrator.Index{
|
|
||||||
{Cols: []string{"role_id"}},
|
|
||||||
{Cols: []string{"role"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.AddMigration("create builtin role table", migrator.NewAddTableMigration(builtinRoleV1))
|
|
||||||
|
|
||||||
//------- indexes ------------------
|
|
||||||
mg.AddMigration("add index builtin_role.role_id", migrator.NewAddIndexMigration(builtinRoleV1, builtinRoleV1.Indices[0]))
|
|
||||||
mg.AddMigration("add index builtin_role.name", migrator.NewAddIndexMigration(builtinRoleV1, builtinRoleV1.Indices[1]))
|
|
||||||
}
|
|
@ -1,387 +0,0 @@
|
|||||||
// +build integration
|
|
||||||
|
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
actesting "github.com/grafana/grafana/pkg/services/accesscontrol/testing"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreatingRole(t *testing.T) {
|
|
||||||
MockTimeNow()
|
|
||||||
t.Cleanup(ResetTimeNow)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
role actesting.RoleTestCase
|
|
||||||
permissions []actesting.PermissionTestCase
|
|
||||||
|
|
||||||
expectedError error
|
|
||||||
expectedUpdated time.Time
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "should successfully create simple role",
|
|
||||||
role: actesting.RoleTestCase{
|
|
||||||
Name: "a name",
|
|
||||||
Permissions: nil,
|
|
||||||
},
|
|
||||||
expectedUpdated: time.Unix(1, 0).UTC(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "should successfully create role with UID",
|
|
||||||
role: actesting.RoleTestCase{
|
|
||||||
Name: "a name",
|
|
||||||
UID: "testUID",
|
|
||||||
Permissions: nil,
|
|
||||||
},
|
|
||||||
expectedUpdated: time.Unix(3, 0).UTC(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "should successfully create role with permissions",
|
|
||||||
role: actesting.RoleTestCase{
|
|
||||||
Name: "a name",
|
|
||||||
Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "users", Permission: "admin.users:create"},
|
|
||||||
{Scope: "reports", Permission: "reports:read"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedUpdated: time.Unix(5, 0).UTC(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
|
||||||
store := setupTestEnv(t)
|
|
||||||
t.Cleanup(registry.ClearOverrides)
|
|
||||||
|
|
||||||
createRoleRes := actesting.CreateRole(t, store, tc.role)
|
|
||||||
|
|
||||||
res, err := store.GetRoleByUID(context.Background(), 1, createRoleRes.UID)
|
|
||||||
role := res
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, tc.expectedUpdated, role.Updated)
|
|
||||||
|
|
||||||
if tc.role.UID != "" {
|
|
||||||
assert.Equal(t, tc.role.UID, role.UID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.role.Permissions == nil {
|
|
||||||
assert.Empty(t, role.Permissions)
|
|
||||||
} else {
|
|
||||||
assert.Len(t, tc.role.Permissions, len(role.Permissions))
|
|
||||||
for i, p := range role.Permissions {
|
|
||||||
assert.Equal(t, tc.role.Permissions[i].Permission, p.Permission)
|
|
||||||
assert.Equal(t, tc.role.Permissions[i].Scope, p.Scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdatingRole(t *testing.T) {
|
|
||||||
MockTimeNow()
|
|
||||||
t.Cleanup(ResetTimeNow)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
role actesting.RoleTestCase
|
|
||||||
newRole actesting.RoleTestCase
|
|
||||||
|
|
||||||
expectedError error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "should successfully update role name",
|
|
||||||
role: actesting.RoleTestCase{
|
|
||||||
Name: "a name",
|
|
||||||
Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "reports", Permission: "reports:read"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
newRole: actesting.RoleTestCase{
|
|
||||||
Name: "a different name",
|
|
||||||
Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "reports", Permission: "reports:create"},
|
|
||||||
{Scope: "reports", Permission: "reports:read"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "should successfully create role with permissions",
|
|
||||||
role: actesting.RoleTestCase{
|
|
||||||
Name: "a name",
|
|
||||||
Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "users", Permission: "admin.users:create"},
|
|
||||||
{Scope: "reports", Permission: "reports:read"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
newRole: actesting.RoleTestCase{
|
|
||||||
Name: "a different name",
|
|
||||||
Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "users", Permission: "admin.users:read"},
|
|
||||||
{Scope: "reports", Permission: "reports:create"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
|
||||||
store := setupTestEnv(t)
|
|
||||||
t.Cleanup(registry.ClearOverrides)
|
|
||||||
|
|
||||||
role := actesting.CreateRole(t, store, tc.role)
|
|
||||||
updated := role.Updated
|
|
||||||
|
|
||||||
updateRoleCmd := accesscontrol.UpdateRoleCommand{
|
|
||||||
UID: role.UID,
|
|
||||||
Name: tc.newRole.Name,
|
|
||||||
}
|
|
||||||
for _, perm := range tc.newRole.Permissions {
|
|
||||||
updateRoleCmd.Permissions = append(updateRoleCmd.Permissions, accesscontrol.Permission{
|
|
||||||
Permission: perm.Permission,
|
|
||||||
Scope: perm.Scope,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := store.UpdateRole(context.Background(), updateRoleCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
updatedRole, err := store.GetRoleByUID(context.Background(), 1, role.UID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, tc.newRole.Name, updatedRole.Name)
|
|
||||||
assert.True(t, updatedRole.Updated.After(updated))
|
|
||||||
assert.Equal(t, len(tc.newRole.Permissions), len(updatedRole.Permissions))
|
|
||||||
|
|
||||||
// Check permissions
|
|
||||||
require.NoError(t, err)
|
|
||||||
for i, updatedPermission := range updatedRole.Permissions {
|
|
||||||
assert.Equal(t, tc.newRole.Permissions[i].Permission, updatedPermission.Permission)
|
|
||||||
assert.Equal(t, tc.newRole.Permissions[i].Scope, updatedPermission.Scope)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type userRoleTestCase struct {
|
|
||||||
desc string
|
|
||||||
userName string
|
|
||||||
teamName string
|
|
||||||
userRoles []actesting.RoleTestCase
|
|
||||||
teamRoles []actesting.RoleTestCase
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserRole(t *testing.T) {
|
|
||||||
MockTimeNow()
|
|
||||||
t.Cleanup(ResetTimeNow)
|
|
||||||
|
|
||||||
testCases := []userRoleTestCase{
|
|
||||||
{
|
|
||||||
desc: "should successfully get user roles",
|
|
||||||
userName: "testuser",
|
|
||||||
teamName: "team1",
|
|
||||||
userRoles: []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: "CreateUser", Permissions: []actesting.PermissionTestCase{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
teamRoles: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "should successfully get user and team roles",
|
|
||||||
userName: "testuser",
|
|
||||||
teamName: "team1",
|
|
||||||
userRoles: []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: "CreateUser", Permissions: []actesting.PermissionTestCase{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
teamRoles: []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: "CreateDataSource", Permissions: []actesting.PermissionTestCase{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "EditDataSource", Permissions: []actesting.PermissionTestCase{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "should successfully get user and team roles if user has no roles",
|
|
||||||
userName: "testuser",
|
|
||||||
teamName: "team1",
|
|
||||||
userRoles: nil,
|
|
||||||
teamRoles: []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: "CreateDataSource", Permissions: []actesting.PermissionTestCase{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "EditDataSource", Permissions: []actesting.PermissionTestCase{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
|
||||||
store := setupTestEnv(t)
|
|
||||||
t.Cleanup(registry.ClearOverrides)
|
|
||||||
|
|
||||||
actesting.CreateUserWithRole(t, store.SQLStore, store, tc.userName, tc.userRoles)
|
|
||||||
actesting.CreateTeamWithRole(t, store.SQLStore, store, tc.teamName, tc.teamRoles)
|
|
||||||
|
|
||||||
// Create more teams
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
teamName := fmt.Sprintf("faketeam%v", i)
|
|
||||||
roles := []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: fmt.Sprintf("fakerole%v", i),
|
|
||||||
Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "datasources", Permission: "datasources:create"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actesting.CreateTeamWithRole(t, store.SQLStore, store, teamName, roles)
|
|
||||||
}
|
|
||||||
|
|
||||||
userQuery := models.GetUserByLoginQuery{
|
|
||||||
LoginOrEmail: tc.userName,
|
|
||||||
}
|
|
||||||
err := sqlstore.GetUserByLogin(&userQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
userId := userQuery.Result.Id
|
|
||||||
|
|
||||||
teamQuery := models.SearchTeamsQuery{
|
|
||||||
OrgId: 1,
|
|
||||||
Name: tc.teamName,
|
|
||||||
}
|
|
||||||
err = sqlstore.SearchTeams(&teamQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, teamQuery.Result.Teams, 1)
|
|
||||||
teamId := teamQuery.Result.Teams[0].Id
|
|
||||||
|
|
||||||
err = store.SQLStore.AddTeamMember(userId, 1, teamId, false, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
userRolesQuery := accesscontrol.GetUserRolesQuery{
|
|
||||||
OrgID: 1,
|
|
||||||
UserID: userQuery.Result.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := store.GetUserRoles(context.Background(), userRolesQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, len(tc.userRoles)+len(tc.teamRoles), len(res))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type userTeamRoleTestCase struct {
|
|
||||||
desc string
|
|
||||||
userName string
|
|
||||||
teamName string
|
|
||||||
userRoles []actesting.RoleTestCase
|
|
||||||
teamRoles []actesting.RoleTestCase
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserPermissions(t *testing.T) {
|
|
||||||
MockTimeNow()
|
|
||||||
t.Cleanup(ResetTimeNow)
|
|
||||||
|
|
||||||
testCases := []userTeamRoleTestCase{
|
|
||||||
{
|
|
||||||
desc: "should successfully get user and team permissions",
|
|
||||||
userName: "testuser",
|
|
||||||
teamName: "team1",
|
|
||||||
userRoles: []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: "CreateUser", Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "users", Permission: "admin.users:create"},
|
|
||||||
{Scope: "reports", Permission: "reports:read"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
teamRoles: []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: "CreateDataSource", Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "datasources", Permission: "datasources:create"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
|
||||||
store := setupTestEnv(t)
|
|
||||||
t.Cleanup(registry.ClearOverrides)
|
|
||||||
|
|
||||||
actesting.CreateUserWithRole(t, store.SQLStore, store, tc.userName, tc.userRoles)
|
|
||||||
actesting.CreateTeamWithRole(t, store.SQLStore, store, tc.teamName, tc.teamRoles)
|
|
||||||
|
|
||||||
// Create more teams
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
teamName := fmt.Sprintf("faketeam%v", i)
|
|
||||||
roles := []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: fmt.Sprintf("fakerole%v", i),
|
|
||||||
Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "datasources", Permission: "datasources:create"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actesting.CreateTeamWithRole(t, store.SQLStore, store, teamName, roles)
|
|
||||||
}
|
|
||||||
|
|
||||||
userQuery := models.GetUserByLoginQuery{
|
|
||||||
LoginOrEmail: tc.userName,
|
|
||||||
}
|
|
||||||
err := sqlstore.GetUserByLogin(&userQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
userId := userQuery.Result.Id
|
|
||||||
|
|
||||||
teamQuery := models.SearchTeamsQuery{
|
|
||||||
OrgId: 1,
|
|
||||||
Name: tc.teamName,
|
|
||||||
}
|
|
||||||
err = sqlstore.SearchTeams(&teamQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, teamQuery.Result.Teams, 1)
|
|
||||||
teamId := teamQuery.Result.Teams[0].Id
|
|
||||||
|
|
||||||
err = store.SQLStore.AddTeamMember(userId, 1, teamId, false, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
userPermissionsQuery := accesscontrol.GetUserPermissionsQuery{
|
|
||||||
OrgID: 1,
|
|
||||||
UserID: userId,
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserTeamsQuery := models.GetTeamsByUserQuery{
|
|
||||||
OrgId: 1,
|
|
||||||
UserId: userId,
|
|
||||||
}
|
|
||||||
err = sqlstore.GetTeamsByUser(&getUserTeamsQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, getUserTeamsQuery.Result, 1)
|
|
||||||
|
|
||||||
expectedPermissions := []actesting.PermissionTestCase{}
|
|
||||||
for _, p := range tc.userRoles {
|
|
||||||
expectedPermissions = append(expectedPermissions, p.Permissions...)
|
|
||||||
}
|
|
||||||
for _, p := range tc.teamRoles {
|
|
||||||
expectedPermissions = append(expectedPermissions, p.Permissions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := store.GetUserPermissions(context.Background(), userPermissionsQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, res, len(expectedPermissions))
|
|
||||||
assert.Contains(t, expectedPermissions, actesting.PermissionTestCase{Scope: "datasources", Permission: "datasources:create"})
|
|
||||||
assert.NotContains(t, expectedPermissions, actesting.PermissionTestCase{Scope: "/api/restricted", Permission: "restricted:read"})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package manager
|
package evaluator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -11,7 +11,8 @@ import (
|
|||||||
|
|
||||||
const roleGrafanaAdmin = "Grafana Admin"
|
const roleGrafanaAdmin = "Grafana Admin"
|
||||||
|
|
||||||
func (m *Manager) Evaluate(ctx context.Context, user *models.SignedInUser, permission string, scope ...string) (bool, error) {
|
// Evaluate evaluates access to the given resource, using provided AccessControl instance
|
||||||
|
func Evaluate(ctx context.Context, ac accesscontrol.AccessControl, user *models.SignedInUser, permission string, scope ...string) (bool, error) {
|
||||||
roles := []string{string(user.OrgRole)}
|
roles := []string{string(user.OrgRole)}
|
||||||
for _, role := range user.OrgRole.Children() {
|
for _, role := range user.OrgRole.Children() {
|
||||||
roles = append(roles, string(role))
|
roles = append(roles, string(role))
|
||||||
@ -20,11 +21,7 @@ func (m *Manager) Evaluate(ctx context.Context, user *models.SignedInUser, permi
|
|||||||
roles = append(roles, roleGrafanaAdmin)
|
roles = append(roles, roleGrafanaAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := m.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{
|
res, err := ac.GetUserPermissions(ctx, user, roles)
|
||||||
OrgID: user.OrgId,
|
|
||||||
UserID: user.UserId,
|
|
||||||
Roles: roles,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
@ -1,57 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/routing"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/seeder"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Manager is the service implementing role based access control.
|
|
||||||
type Manager struct {
|
|
||||||
Cfg *setting.Cfg `inject:""`
|
|
||||||
RouteRegister routing.RouteRegister `inject:""`
|
|
||||||
Log log.Logger
|
|
||||||
*database.AccessControlStore `inject:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterService(&Manager{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes the Manager.
|
|
||||||
func (m *Manager) Init() error {
|
|
||||||
m.Log = log.New("accesscontrol")
|
|
||||||
|
|
||||||
seeder := seeder.NewSeeder(m, m.Log)
|
|
||||||
|
|
||||||
// TODO: Seed all orgs
|
|
||||||
err := seeder.Seed(context.TODO(), 1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) IsDisabled() bool {
|
|
||||||
if m.Cfg == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
_, exists := m.Cfg.FeatureToggles["accesscontrol"]
|
|
||||||
return !exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) AddMigration(mg *migrator.Migrator) {
|
|
||||||
if m.IsDisabled() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
database.AddAccessControlMigrations(mg)
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/routing"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
|
||||||
actesting "github.com/grafana/grafana/pkg/services/accesscontrol/testing"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupTestEnv(t testing.TB) *Manager {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
|
|
||||||
|
|
||||||
ac := overrideAccessControlInRegistry(t, cfg)
|
|
||||||
|
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
|
||||||
ac.AccessControlStore.SQLStore = sqlStore
|
|
||||||
|
|
||||||
err := ac.Init()
|
|
||||||
require.NoError(t, err)
|
|
||||||
return &ac
|
|
||||||
}
|
|
||||||
|
|
||||||
func overrideAccessControlInRegistry(t testing.TB, cfg *setting.Cfg) Manager {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
ac := Manager{
|
|
||||||
Cfg: cfg,
|
|
||||||
RouteRegister: routing.NewRouteRegister(),
|
|
||||||
Log: log.New("accesscontrol-test"),
|
|
||||||
AccessControlStore: &database.AccessControlStore{
|
|
||||||
SQLStore: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) {
|
|
||||||
if _, ok := descriptor.Instance.(*Manager); ok {
|
|
||||||
return ®istry.Descriptor{
|
|
||||||
Name: "AccessControl",
|
|
||||||
Instance: &ac,
|
|
||||||
InitPriority: descriptor.InitPriority,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
registry.RegisterOverride(overrideServiceFunc)
|
|
||||||
|
|
||||||
return ac
|
|
||||||
}
|
|
||||||
|
|
||||||
type evaluatingPermissionsTestCase struct {
|
|
||||||
desc string
|
|
||||||
userName string
|
|
||||||
roles []actesting.RoleTestCase
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluatingPermissions(t *testing.T) {
|
|
||||||
testCases := []evaluatingPermissionsTestCase{
|
|
||||||
{
|
|
||||||
desc: "should successfully evaluate access to the endpoint",
|
|
||||||
userName: "testuser",
|
|
||||||
roles: []actesting.RoleTestCase{
|
|
||||||
{
|
|
||||||
Name: "CreateUser", Permissions: []actesting.PermissionTestCase{
|
|
||||||
{Scope: "/api/admin/users", Permission: "post"},
|
|
||||||
{Scope: "/api/report", Permission: "get"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
|
||||||
ac := setupTestEnv(t)
|
|
||||||
t.Cleanup(registry.ClearOverrides)
|
|
||||||
|
|
||||||
actesting.CreateUserWithRole(t, ac.SQLStore, ac, tc.userName, tc.roles)
|
|
||||||
|
|
||||||
userQuery := models.GetUserByLoginQuery{
|
|
||||||
LoginOrEmail: tc.userName,
|
|
||||||
}
|
|
||||||
err := sqlstore.GetUserByLogin(&userQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
userRolesQuery := accesscontrol.GetUserRolesQuery{
|
|
||||||
OrgID: 1,
|
|
||||||
UserID: userQuery.Result.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := ac.GetUserRoles(context.Background(), userRolesQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, res, len(tc.roles))
|
|
||||||
|
|
||||||
userPermissionsQuery := accesscontrol.GetUserPermissionsQuery{
|
|
||||||
OrgID: 1,
|
|
||||||
UserID: userQuery.Result.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
permissions, err := ac.GetUserPermissions(context.Background(), userPermissionsQuery)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, permissions, len(tc.roles[0].Permissions))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,8 +11,12 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Middleware(ac accesscontrol.AccessControl) func(string, ...string) macaron.Handler {
|
func Middleware(ac accesscontrol.AccessControl) func(macaron.Handler, string, ...string) macaron.Handler {
|
||||||
return func(permission string, scopes ...string) macaron.Handler {
|
return func(fallback macaron.Handler, permission string, scopes ...string) macaron.Handler {
|
||||||
|
if ac.IsDisabled() {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
return func(c *models.ReqContext) {
|
return func(c *models.ReqContext) {
|
||||||
for i, scope := range scopes {
|
for i, scope := range scopes {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -37,7 +41,7 @@ func Middleware(ac accesscontrol.AccessControl) func(string, ...string) macaron.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !hasAccess {
|
if !hasAccess {
|
||||||
c.Logger.Info("Access denied", "error", err, "userID", c.UserId, "permission", permission, "scopes", scopes)
|
c.Logger.Info("Access denied", "userID", c.UserId, "permission", permission, "scopes", scopes)
|
||||||
c.JsonApiErr(http.StatusForbidden, "Forbidden", nil)
|
c.JsonApiErr(http.StatusForbidden, "Forbidden", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -54,123 +54,12 @@ type Permission struct {
|
|||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TeamRole struct {
|
|
||||||
ID int64 `json:"id" xorm:"pk autoincr 'id'"`
|
|
||||||
OrgID int64 `json:"orgId" xorm:"org_id"`
|
|
||||||
RoleID int64 `json:"roleId" xorm:"role_id"`
|
|
||||||
TeamID int64 `json:"teamId" xorm:"team_id"`
|
|
||||||
|
|
||||||
Created time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRole struct {
|
|
||||||
ID int64 `json:"id" xorm:"pk autoincr 'id'"`
|
|
||||||
OrgID int64 `json:"orgId" xorm:"org_id"`
|
|
||||||
RoleID int64 `json:"roleId" xorm:"role_id"`
|
|
||||||
UserID int64 `json:"userId" xorm:"user_id"`
|
|
||||||
|
|
||||||
Created time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type BuiltinRole struct {
|
|
||||||
ID *int64 `json:"id" xorm:"pk autoincr 'id'"`
|
|
||||||
RoleID int64 `json:"roleId" xorm:"role_id"`
|
|
||||||
Role string
|
|
||||||
|
|
||||||
Updated time.Time
|
|
||||||
Created time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetTeamRolesQuery struct {
|
|
||||||
OrgID int64 `json:"-"`
|
|
||||||
TeamID int64 `json:"teamId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetUserRolesQuery struct {
|
|
||||||
OrgID int64 `json:"-"`
|
|
||||||
UserID int64 `json:"userId"`
|
|
||||||
Roles []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetUserPermissionsQuery struct {
|
type GetUserPermissionsQuery struct {
|
||||||
OrgID int64 `json:"-"`
|
OrgID int64 `json:"-"`
|
||||||
UserID int64 `json:"userId"`
|
UserID int64 `json:"userId"`
|
||||||
Roles []string
|
Roles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreatePermissionCommand struct {
|
|
||||||
RoleID int64 `json:"roleId"`
|
|
||||||
Permission string
|
|
||||||
Scope string
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdatePermissionCommand struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Permission string
|
|
||||||
Scope string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeletePermissionCommand struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateRoleCommand struct {
|
|
||||||
OrgID int64 `json:"-"`
|
|
||||||
UID string `json:"uid"`
|
|
||||||
Version int64 `json:"version"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateRoleWithPermissionsCommand struct {
|
|
||||||
OrgID int64 `json:"orgId"`
|
|
||||||
UID string `json:"uid"`
|
|
||||||
Version int64 `json:"version"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Permissions []Permission `json:"permissions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateRoleCommand struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
OrgID int64 `json:"orgId"`
|
|
||||||
Version int64 `json:"version"`
|
|
||||||
UID string `json:"uid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Permissions []Permission `json:"permissions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteRoleCommand struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
UID string `json:"uid"`
|
|
||||||
OrgID int64 `json:"org_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddTeamRoleCommand struct {
|
|
||||||
OrgID int64 `json:"org_id"`
|
|
||||||
RoleUID string `json:"role_uid"`
|
|
||||||
TeamID int64 `json:"team_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoveTeamRoleCommand struct {
|
|
||||||
OrgID int64 `json:"org_id"`
|
|
||||||
RoleUID string `json:"role_uid"`
|
|
||||||
TeamID int64 `json:"team_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddUserRoleCommand struct {
|
|
||||||
OrgID int64 `json:"org_id"`
|
|
||||||
RoleUID string `json:"role_uid"`
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoveUserRoleCommand struct {
|
|
||||||
OrgID int64 `json:"org_id"`
|
|
||||||
RoleUID string `json:"role_uid"`
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EvaluationResult struct {
|
type EvaluationResult struct {
|
||||||
HasAccess bool
|
HasAccess bool
|
||||||
Meta interface{}
|
Meta interface{}
|
||||||
|
42
pkg/services/accesscontrol/ossaccesscontrol/builtin_roles.go
Normal file
42
pkg/services/accesscontrol/ossaccesscontrol/builtin_roles.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package ossaccesscontrol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
)
|
||||||
|
|
||||||
|
var builtInRolesMap = map[string]accesscontrol.RoleDTO{
|
||||||
|
"grafana:builtin:users:read:self": {
|
||||||
|
Name: "grafana:builtin:users:read:self",
|
||||||
|
Version: 1,
|
||||||
|
Permissions: []accesscontrol.Permission{
|
||||||
|
{
|
||||||
|
Permission: "users:read",
|
||||||
|
Scope: "users:self",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Permission: "users.tokens:list",
|
||||||
|
Scope: "users:self",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Permission: "users.teams:read",
|
||||||
|
Scope: "users:self",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtInRoleGrants = map[string][]string{
|
||||||
|
"Viewer": {
|
||||||
|
"grafana:builtin:users:read:self",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBuiltInRole(role string) *accesscontrol.RoleDTO {
|
||||||
|
var builtInRole accesscontrol.RoleDTO
|
||||||
|
if r, ok := builtInRolesMap[role]; ok {
|
||||||
|
// Do not modify builtInRoles
|
||||||
|
builtInRole = r
|
||||||
|
return &builtInRole
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package ossaccesscontrol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol/evaluator"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OSSAccessControlService is the service implementing role based access control.
|
||||||
|
type OSSAccessControlService struct {
|
||||||
|
Cfg *setting.Cfg `inject:""`
|
||||||
|
Log log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes the OSSAccessControlService.
|
||||||
|
func (ac *OSSAccessControlService) Init() error {
|
||||||
|
ac.Log = log.New("accesscontrol")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *OSSAccessControlService) IsDisabled() bool {
|
||||||
|
if ac.Cfg == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := ac.Cfg.FeatureToggles["accesscontrol"]
|
||||||
|
return !exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate evaluates access to the given resource
|
||||||
|
func (ac *OSSAccessControlService) Evaluate(ctx context.Context, user *models.SignedInUser, permission string, scope ...string) (bool, error) {
|
||||||
|
return evaluator.Evaluate(ctx, ac, user, permission, scope...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPermissions returns user permissions based on built-in roles
|
||||||
|
func (ac *OSSAccessControlService) GetUserPermissions(ctx context.Context, user *models.SignedInUser, roles []string) ([]*accesscontrol.Permission, error) {
|
||||||
|
permissions := make([]*accesscontrol.Permission, 0)
|
||||||
|
for _, legacyRole := range roles {
|
||||||
|
if builtInRoleNames, ok := builtInRoleGrants[legacyRole]; ok {
|
||||||
|
for _, builtInRoleName := range builtInRoleNames {
|
||||||
|
builtInRole := getBuiltInRole(builtInRoleName)
|
||||||
|
if builtInRole == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, p := range builtInRole.Permissions {
|
||||||
|
permission := p
|
||||||
|
permissions = append(permissions, &permission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions, nil
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package ossaccesscontrol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestEnv(t testing.TB) *OSSAccessControlService {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
|
||||||
|
|
||||||
|
ac := OSSAccessControlService{
|
||||||
|
Cfg: cfg,
|
||||||
|
Log: log.New("accesscontrol-test"),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ac.Init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
return &ac
|
||||||
|
}
|
||||||
|
|
||||||
|
type evaluatingPermissionsTestCase struct {
|
||||||
|
desc string
|
||||||
|
user userTestCase
|
||||||
|
endpoints []endpointTestCase
|
||||||
|
evalResult bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type userTestCase struct {
|
||||||
|
name string
|
||||||
|
orgRole models.RoleType
|
||||||
|
isGrafanaAdmin bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type endpointTestCase struct {
|
||||||
|
permission string
|
||||||
|
scope []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvaluatingPermissions(t *testing.T) {
|
||||||
|
testCases := []evaluatingPermissionsTestCase{
|
||||||
|
{
|
||||||
|
desc: "should successfully evaluate access to the endpoint",
|
||||||
|
user: userTestCase{
|
||||||
|
name: "testuser",
|
||||||
|
orgRole: models.ROLE_EDITOR,
|
||||||
|
isGrafanaAdmin: false,
|
||||||
|
},
|
||||||
|
endpoints: []endpointTestCase{
|
||||||
|
{permission: "users.teams:read", scope: []string{"users:self"}},
|
||||||
|
{permission: "users:read", scope: []string{"users:self"}},
|
||||||
|
},
|
||||||
|
evalResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should restrict access to the unauthorized endpoints",
|
||||||
|
user: userTestCase{
|
||||||
|
name: "testuser",
|
||||||
|
orgRole: models.ROLE_VIEWER,
|
||||||
|
isGrafanaAdmin: false,
|
||||||
|
},
|
||||||
|
endpoints: []endpointTestCase{
|
||||||
|
{permission: "users:create", scope: []string{"users"}},
|
||||||
|
},
|
||||||
|
evalResult: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
ac := setupTestEnv(t)
|
||||||
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
|
user := &models.SignedInUser{
|
||||||
|
UserId: 1,
|
||||||
|
OrgId: 1,
|
||||||
|
Name: tc.user.name,
|
||||||
|
OrgRole: tc.user.orgRole,
|
||||||
|
IsGrafanaAdmin: tc.user.isGrafanaAdmin,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, endpoint := range tc.endpoints {
|
||||||
|
result, err := ac.Evaluate(context.Background(), user, endpoint.permission, endpoint.scope...)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.evalResult, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,219 +0,0 @@
|
|||||||
package seeder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
)
|
|
||||||
|
|
||||||
type seeder struct {
|
|
||||||
Store accesscontrol.Store
|
|
||||||
log log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
var builtInRoles = []accesscontrol.RoleDTO{
|
|
||||||
{
|
|
||||||
Name: "grafana:builtin:users:read:self",
|
|
||||||
Version: 1,
|
|
||||||
Permissions: []accesscontrol.Permission{
|
|
||||||
{
|
|
||||||
Permission: "users:read",
|
|
||||||
Scope: "users:self",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Permission: "users.tokens:list",
|
|
||||||
Scope: "users:self",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Permission: "users.teams:read",
|
|
||||||
Scope: "users:self",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Make sure builtin grants can be removed without being recreated
|
|
||||||
var builtInRoleGrants = map[string][]string{
|
|
||||||
"grafana:builtin:users:read:self": {
|
|
||||||
"Viewer",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSeeder(s accesscontrol.AccessControl, log log.Logger) *seeder {
|
|
||||||
return &seeder{Store: s, log: log}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seeder) Seed(ctx context.Context, orgID int64) error {
|
|
||||||
err := s.seed(ctx, orgID, builtInRoles, builtInRoleGrants)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seeder) seed(ctx context.Context, orgID int64, roles []accesscontrol.RoleDTO, roleGrants map[string][]string) error {
|
|
||||||
// FIXME: As this will run on startup, we want to optimize running this
|
|
||||||
existingRoles, err := s.Store.GetRoles(ctx, orgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
roleSet := map[string]*accesscontrol.Role{}
|
|
||||||
for _, role := range existingRoles {
|
|
||||||
if role == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
roleSet[role.Name] = role
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, role := range roles {
|
|
||||||
role.OrgID = orgID
|
|
||||||
|
|
||||||
current, exists := roleSet[role.Name]
|
|
||||||
if exists {
|
|
||||||
if role.Version <= current.Version {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
roleID, err := s.createOrUpdateRole(ctx, role, current)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("failed to create/update role", "name", role.Name, "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if builtinRoles, exists := roleGrants[role.Name]; exists {
|
|
||||||
for _, builtinRole := range builtinRoles {
|
|
||||||
err := s.Store.AddBuiltinRole(ctx, orgID, roleID, builtinRole)
|
|
||||||
if err != nil && !errors.Is(err, accesscontrol.ErrUserRoleAlreadyAdded) {
|
|
||||||
s.log.Error("failed to assign role to role",
|
|
||||||
"name", role.Name,
|
|
||||||
"role", builtinRole,
|
|
||||||
"err", err,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seeder) createOrUpdateRole(ctx context.Context, role accesscontrol.RoleDTO, old *accesscontrol.Role) (int64, error) {
|
|
||||||
if role.Version == 0 {
|
|
||||||
return 0, fmt.Errorf("error when seeding '%s': all seeder roles must have a version", role.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if old == nil {
|
|
||||||
p, err := s.Store.CreateRoleWithPermissions(ctx, accesscontrol.CreateRoleWithPermissionsCommand{
|
|
||||||
OrgID: role.OrgID,
|
|
||||||
Version: role.Version,
|
|
||||||
Name: role.Name,
|
|
||||||
Description: role.Description,
|
|
||||||
Permissions: role.Permissions,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return p.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := s.Store.UpdateRole(ctx, accesscontrol.UpdateRoleCommand{
|
|
||||||
UID: old.UID,
|
|
||||||
Name: role.Name,
|
|
||||||
Description: role.Description,
|
|
||||||
Version: role.Version,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, accesscontrol.ErrVersionLE) {
|
|
||||||
return old.ID, nil
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
existingPermissions, err := s.Store.GetRolePermissions(ctx, old.ID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to get current permissions for role '%s': %w", role.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.idempotentUpdatePermissions(ctx, old.ID, role.Permissions, existingPermissions)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to update role permissions for role '%s': %w", role.Name, err)
|
|
||||||
}
|
|
||||||
return old.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seeder) idempotentUpdatePermissions(ctx context.Context, roleID int64, new []accesscontrol.Permission, old []accesscontrol.Permission) error {
|
|
||||||
if roleID == 0 {
|
|
||||||
return fmt.Errorf("refusing to add permissions to role with ID 0 (it should not exist)")
|
|
||||||
}
|
|
||||||
|
|
||||||
added, removed := diffPermissionList(new, old)
|
|
||||||
|
|
||||||
for _, p := range added {
|
|
||||||
_, err := s.Store.CreatePermission(ctx, accesscontrol.CreatePermissionCommand{
|
|
||||||
RoleID: roleID,
|
|
||||||
Permission: p.Permission,
|
|
||||||
Scope: p.Scope,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create permission %s (%s): %w", p.Permission, p.Scope, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range removed {
|
|
||||||
err := s.Store.DeletePermission(ctx, &accesscontrol.DeletePermissionCommand{
|
|
||||||
ID: p.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not delete permission %s (%s): %w", p.Permission, p.Scope, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func diffPermissionList(new, old []accesscontrol.Permission) (added, removed []accesscontrol.Permission) {
|
|
||||||
newMap, oldMap := permissionMap(new), permissionMap(old)
|
|
||||||
|
|
||||||
added = []accesscontrol.Permission{}
|
|
||||||
removed = []accesscontrol.Permission{}
|
|
||||||
|
|
||||||
for _, p := range newMap {
|
|
||||||
if _, exists := oldMap[permissionTuple{
|
|
||||||
Permission: p.Permission,
|
|
||||||
Scope: p.Scope,
|
|
||||||
}]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
added = append(added, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range oldMap {
|
|
||||||
if _, exists := newMap[permissionTuple{
|
|
||||||
Permission: p.Permission,
|
|
||||||
Scope: p.Scope,
|
|
||||||
}]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
removed = append(removed, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return added, removed
|
|
||||||
}
|
|
||||||
|
|
||||||
type permissionTuple struct {
|
|
||||||
Permission string
|
|
||||||
Scope string
|
|
||||||
}
|
|
||||||
|
|
||||||
func permissionMap(l []accesscontrol.Permission) map[permissionTuple]accesscontrol.Permission {
|
|
||||||
m := make(map[permissionTuple]accesscontrol.Permission, len(l))
|
|
||||||
for _, p := range l {
|
|
||||||
m[permissionTuple{
|
|
||||||
Permission: p.Permission,
|
|
||||||
Scope: p.Scope,
|
|
||||||
}] = p
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
package seeder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
// accessControlStoreTestImpl is a test store implementation which additionally executes a database migrations
|
|
||||||
type accessControlStoreTestImpl struct {
|
|
||||||
database.AccessControlStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *accessControlStoreTestImpl) AddMigration(mg *migrator.Migrator) {
|
|
||||||
database.AddAccessControlMigrations(mg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTestEnv(t testing.TB) *accessControlStoreTestImpl {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
store := overrideDatabaseInRegistry(t, cfg)
|
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
|
||||||
store.SQLStore = sqlStore
|
|
||||||
|
|
||||||
err := store.Init()
|
|
||||||
require.NoError(t, err)
|
|
||||||
return &store
|
|
||||||
}
|
|
||||||
|
|
||||||
func overrideDatabaseInRegistry(t testing.TB, cfg *setting.Cfg) accessControlStoreTestImpl {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
store := accessControlStoreTestImpl{
|
|
||||||
AccessControlStore: database.AccessControlStore{
|
|
||||||
SQLStore: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) {
|
|
||||||
if _, ok := descriptor.Instance.(*database.AccessControlStore); ok {
|
|
||||||
return ®istry.Descriptor{
|
|
||||||
Name: "Database",
|
|
||||||
Instance: &store,
|
|
||||||
InitPriority: descriptor.InitPriority,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
registry.RegisterOverride(overrideServiceFunc)
|
|
||||||
|
|
||||||
return store
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSeeder(t *testing.T) {
|
|
||||||
ac := setupTestEnv(t)
|
|
||||||
|
|
||||||
s := &seeder{
|
|
||||||
Store: ac,
|
|
||||||
log: log.New("accesscontrol-test"),
|
|
||||||
}
|
|
||||||
|
|
||||||
v1 := accesscontrol.RoleDTO{
|
|
||||||
OrgID: 1,
|
|
||||||
Name: "grafana:tests:fake",
|
|
||||||
Version: 1,
|
|
||||||
Permissions: []accesscontrol.Permission{
|
|
||||||
{
|
|
||||||
Permission: "ice_cream:eat",
|
|
||||||
Scope: "flavor:vanilla",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Permission: "ice_cream:eat",
|
|
||||||
Scope: "flavor:chocolate",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
v2 := accesscontrol.RoleDTO{
|
|
||||||
OrgID: 1,
|
|
||||||
Name: "grafana:tests:fake",
|
|
||||||
Version: 2,
|
|
||||||
Permissions: []accesscontrol.Permission{
|
|
||||||
{
|
|
||||||
Permission: "ice_cream:eat",
|
|
||||||
Scope: "flavor:vanilla",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Permission: "ice_cream:serve",
|
|
||||||
Scope: "flavor:mint",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Permission: "candy.liquorice:eat",
|
|
||||||
Scope: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("create role", func(t *testing.T) {
|
|
||||||
id, err := s.createOrUpdateRole(
|
|
||||||
context.Background(),
|
|
||||||
v1,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotZero(t, id)
|
|
||||||
|
|
||||||
p, err := s.Store.GetRole(context.Background(), 1, id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
lookup := permissionMap(p.Permissions)
|
|
||||||
assert.Contains(t, lookup, permissionTuple{
|
|
||||||
Permission: "ice_cream:eat",
|
|
||||||
Scope: "flavor:vanilla",
|
|
||||||
})
|
|
||||||
assert.Contains(t, lookup, permissionTuple{
|
|
||||||
Permission: "ice_cream:eat",
|
|
||||||
Scope: "flavor:chocolate",
|
|
||||||
})
|
|
||||||
|
|
||||||
role := p.Role()
|
|
||||||
|
|
||||||
t.Run("update to same version", func(t *testing.T) {
|
|
||||||
err := s.seed(context.Background(), 1, []accesscontrol.RoleDTO{v1}, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("update to new role version", func(t *testing.T) {
|
|
||||||
err := s.seed(context.Background(), 1, []accesscontrol.RoleDTO{v2}, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
p, err := s.Store.GetRole(context.Background(), 1, role.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, p.Permissions, len(v2.Permissions))
|
|
||||||
|
|
||||||
lookup := permissionMap(p.Permissions)
|
|
||||||
assert.Contains(t, lookup, permissionTuple{
|
|
||||||
Permission: "candy.liquorice:eat",
|
|
||||||
Scope: "",
|
|
||||||
})
|
|
||||||
assert.NotContains(t, lookup, permissionTuple{
|
|
||||||
Permission: "ice_cream:eat",
|
|
||||||
Scope: "flavor:chocolate",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
package testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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 RoleTestCase struct {
|
|
||||||
Name string
|
|
||||||
UID string
|
|
||||||
Permissions []PermissionTestCase
|
|
||||||
}
|
|
||||||
|
|
||||||
type PermissionTestCase struct {
|
|
||||||
Permission string
|
|
||||||
Scope string
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateRole(t *testing.T, ac accesscontrol.Store, p RoleTestCase) *accesscontrol.RoleDTO {
|
|
||||||
createRoleCmd := accesscontrol.CreateRoleWithPermissionsCommand{
|
|
||||||
OrgID: 1,
|
|
||||||
UID: p.UID,
|
|
||||||
Name: p.Name,
|
|
||||||
Permissions: []accesscontrol.Permission{},
|
|
||||||
}
|
|
||||||
for _, perm := range p.Permissions {
|
|
||||||
createRoleCmd.Permissions = append(createRoleCmd.Permissions, accesscontrol.Permission{
|
|
||||||
Permission: perm.Permission,
|
|
||||||
Scope: perm.Scope,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := ac.CreateRoleWithPermissions(context.Background(), createRoleCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateUserWithRole(t *testing.T, db *sqlstore.SQLStore, ac accesscontrol.Store, user string, roles []RoleTestCase) {
|
|
||||||
createUserCmd := models.CreateUserCommand{
|
|
||||||
Email: user + "@test.com",
|
|
||||||
Name: user,
|
|
||||||
Login: user,
|
|
||||||
OrgId: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := db.CreateUser(context.Background(), createUserCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
userId := u.Id
|
|
||||||
|
|
||||||
for _, p := range roles {
|
|
||||||
createRoleCmd := accesscontrol.CreateRoleCommand{
|
|
||||||
OrgID: 1,
|
|
||||||
Name: p.Name,
|
|
||||||
}
|
|
||||||
role, err := ac.CreateRole(context.Background(), createRoleCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, perm := range p.Permissions {
|
|
||||||
permCmd := accesscontrol.CreatePermissionCommand{
|
|
||||||
RoleID: role.ID,
|
|
||||||
Permission: perm.Permission,
|
|
||||||
Scope: perm.Scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := ac.CreatePermission(context.Background(), permCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addUserRoleCmd := accesscontrol.AddUserRoleCommand{
|
|
||||||
OrgID: 1,
|
|
||||||
RoleUID: role.UID,
|
|
||||||
UserID: userId,
|
|
||||||
}
|
|
||||||
err = ac.AddUserRole(&addUserRoleCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTeamWithRole(t *testing.T, db *sqlstore.SQLStore, ac accesscontrol.Store, teamname string, roles []RoleTestCase) {
|
|
||||||
email, orgID := teamname+"@test.com", int64(1)
|
|
||||||
team, err := db.CreateTeam(teamname, email, orgID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
teamId := team.Id
|
|
||||||
|
|
||||||
for _, p := range roles {
|
|
||||||
createRoleCmd := accesscontrol.CreateRoleCommand{
|
|
||||||
OrgID: orgID,
|
|
||||||
Name: p.Name,
|
|
||||||
}
|
|
||||||
role, err := ac.CreateRole(context.Background(), createRoleCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, perm := range p.Permissions {
|
|
||||||
permCmd := accesscontrol.CreatePermissionCommand{
|
|
||||||
RoleID: role.ID,
|
|
||||||
Permission: perm.Permission,
|
|
||||||
Scope: perm.Scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := ac.CreatePermission(context.Background(), permCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addTeamRoleCmd := accesscontrol.AddTeamRoleCommand{
|
|
||||||
OrgID: 1,
|
|
||||||
RoleUID: role.UID,
|
|
||||||
TeamID: teamId,
|
|
||||||
}
|
|
||||||
err = ac.AddTeamRole(&addTeamRoleCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
package testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
usernamePrefix = "user"
|
|
||||||
teamPrefix = "team"
|
|
||||||
PermissionsPerRole = 10
|
|
||||||
UsersPerTeam = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
func GenerateRoles(b *testing.B, db *sqlstore.SQLStore, ac accesscontrol.Store, rolesPerUser, users int) {
|
|
||||||
numberOfTeams := int(math.Ceil(float64(users) / UsersPerTeam))
|
|
||||||
globalUserId := 0
|
|
||||||
for i := 0; i < numberOfTeams; i++ {
|
|
||||||
// Create team
|
|
||||||
teamName := fmt.Sprintf("%s%v", teamPrefix, i)
|
|
||||||
teamEmail := fmt.Sprintf("%s@test.com", teamName)
|
|
||||||
team, err := db.CreateTeam(teamName, teamEmail, 1)
|
|
||||||
require.NoError(b, err)
|
|
||||||
teamId := team.Id
|
|
||||||
|
|
||||||
// Create team roles
|
|
||||||
for j := 0; j < rolesPerUser; j++ {
|
|
||||||
roleName := fmt.Sprintf("role_%s_%v", teamName, j)
|
|
||||||
createRoleCmd := accesscontrol.CreateRoleCommand{OrgID: 1, Name: roleName}
|
|
||||||
role, err := ac.CreateRole(context.Background(), createRoleCmd)
|
|
||||||
require.NoError(b, err)
|
|
||||||
|
|
||||||
for k := 0; k < PermissionsPerRole; k++ {
|
|
||||||
permission := fmt.Sprintf("permission_%v", k)
|
|
||||||
scope := fmt.Sprintf("scope_%v", k)
|
|
||||||
permCmd := accesscontrol.CreatePermissionCommand{
|
|
||||||
RoleID: role.ID,
|
|
||||||
Permission: permission,
|
|
||||||
Scope: scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := ac.CreatePermission(context.Background(), permCmd)
|
|
||||||
require.NoError(b, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addTeamRoleCmd := accesscontrol.AddTeamRoleCommand{
|
|
||||||
OrgID: 1,
|
|
||||||
RoleUID: role.UID,
|
|
||||||
TeamID: teamId,
|
|
||||||
}
|
|
||||||
err = ac.AddTeamRole(&addTeamRoleCmd)
|
|
||||||
require.NoError(b, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create team users
|
|
||||||
for u := 0; u < UsersPerTeam; u++ {
|
|
||||||
userName := fmt.Sprintf("%s%v", usernamePrefix, globalUserId)
|
|
||||||
userEmail := fmt.Sprintf("%s@test.com", userName)
|
|
||||||
createUserCmd := models.CreateUserCommand{Email: userEmail, Name: userName, Login: userName, OrgId: 1}
|
|
||||||
|
|
||||||
user, err := db.CreateUser(context.Background(), createUserCmd)
|
|
||||||
require.NoError(b, err)
|
|
||||||
userId := user.Id
|
|
||||||
globalUserId++
|
|
||||||
|
|
||||||
// Create user roles
|
|
||||||
for j := 0; j < rolesPerUser; j++ {
|
|
||||||
roleName := fmt.Sprintf("role_%s_%v", userName, j)
|
|
||||||
createRoleCmd := accesscontrol.CreateRoleCommand{OrgID: 1, Name: roleName}
|
|
||||||
role, err := ac.CreateRole(context.Background(), createRoleCmd)
|
|
||||||
require.NoError(b, err)
|
|
||||||
|
|
||||||
for k := 0; k < PermissionsPerRole; k++ {
|
|
||||||
permission := fmt.Sprintf("permission_%v", k)
|
|
||||||
scope := fmt.Sprintf("scope_%v", k)
|
|
||||||
permCmd := accesscontrol.CreatePermissionCommand{
|
|
||||||
RoleID: role.ID,
|
|
||||||
Permission: permission,
|
|
||||||
Scope: scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := ac.CreatePermission(context.Background(), permCmd)
|
|
||||||
require.NoError(b, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addUserRoleCmd := accesscontrol.AddUserRoleCommand{
|
|
||||||
OrgID: 1,
|
|
||||||
RoleUID: role.UID,
|
|
||||||
UserID: userId,
|
|
||||||
}
|
|
||||||
err = ac.AddUserRole(&addUserRoleCmd)
|
|
||||||
require.NoError(b, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddTeamMember(userId, 1, teamId, false, 1)
|
|
||||||
require.NoError(b, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user