mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AccessControl: Enable RBAC by default (#48813)
* Add RBAC section to settings * Default to RBAC enabled settings to true * Update tests to respect RBAC Co-authored-by: Karl Persson <kalle.persson@grafana.com>
This commit is contained in:
parent
3106af9eec
commit
f256f625d8
@ -643,6 +643,10 @@ managed_identity_enabled = false
|
|||||||
# Should be set for user-assigned identity and should be empty for system-assigned identity
|
# Should be set for user-assigned identity and should be empty for system-assigned identity
|
||||||
managed_identity_client_id =
|
managed_identity_client_id =
|
||||||
|
|
||||||
|
#################################### Role-based Access Control ###########
|
||||||
|
[rbac]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
#################################### SMTP / Emailing #####################
|
#################################### SMTP / Emailing #####################
|
||||||
[smtp]
|
[smtp]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -623,6 +623,10 @@
|
|||||||
# Should be set for user-assigned identity and should be empty for system-assigned identity
|
# Should be set for user-assigned identity and should be empty for system-assigned identity
|
||||||
;managed_identity_client_id =
|
;managed_identity_client_id =
|
||||||
|
|
||||||
|
#################################### Role-based Access Control ###########
|
||||||
|
[rbac]
|
||||||
|
;enabled = true
|
||||||
|
|
||||||
#################################### SMTP / Emailing ##########################
|
#################################### SMTP / Emailing ##########################
|
||||||
[smtp]
|
[smtp]
|
||||||
;enabled = false
|
;enabled = false
|
||||||
|
@ -162,6 +162,7 @@ export interface GrafanaConfig {
|
|||||||
autoAssignOrg: boolean;
|
autoAssignOrg: boolean;
|
||||||
verifyEmailEnabled: boolean;
|
verifyEmailEnabled: boolean;
|
||||||
oauth: OAuthSettings;
|
oauth: OAuthSettings;
|
||||||
|
rbacEnabled: boolean;
|
||||||
disableUserSignUp: boolean;
|
disableUserSignUp: boolean;
|
||||||
loginHint: string;
|
loginHint: string;
|
||||||
passwordHint: string;
|
passwordHint: string;
|
||||||
|
@ -32,7 +32,6 @@ export interface FeatureToggles {
|
|||||||
tempoBackendSearch?: boolean;
|
tempoBackendSearch?: boolean;
|
||||||
tempoServiceGraph?: boolean;
|
tempoServiceGraph?: boolean;
|
||||||
lokiBackendMode?: boolean;
|
lokiBackendMode?: boolean;
|
||||||
accesscontrol?: boolean;
|
|
||||||
['accesscontrol-builtins']?: boolean;
|
['accesscontrol-builtins']?: boolean;
|
||||||
prometheus_azure_auth?: boolean;
|
prometheus_azure_auth?: boolean;
|
||||||
influxdbBackendMigration?: boolean;
|
influxdbBackendMigration?: boolean;
|
||||||
|
@ -57,6 +57,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
|||||||
autoAssignOrg = true;
|
autoAssignOrg = true;
|
||||||
verifyEmailEnabled = false;
|
verifyEmailEnabled = false;
|
||||||
oauth: OAuthSettings = {};
|
oauth: OAuthSettings = {};
|
||||||
|
rbacEnabled = true;
|
||||||
disableUserSignUp = false;
|
disableUserSignUp = false;
|
||||||
loginHint = '';
|
loginHint = '';
|
||||||
passwordHint = '';
|
passwordHint = '';
|
||||||
|
@ -340,20 +340,21 @@ func setupHTTPServerWithMockDb(t *testing.T, useFakeAccessControl bool, enableAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessControl bool, cfg *setting.Cfg) accessControlScenarioContext {
|
func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessControl bool, cfg *setting.Cfg) accessControlScenarioContext {
|
||||||
var db *sqlstore.SQLStore
|
db := sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{})
|
||||||
if useFakeAccessControl && enableAccessControl {
|
|
||||||
db = sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
|
||||||
} else {
|
|
||||||
db = sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{})
|
|
||||||
}
|
|
||||||
return setupHTTPServerWithCfgDb(t, useFakeAccessControl, enableAccessControl, cfg, db, db)
|
return setupHTTPServerWithCfgDb(t, useFakeAccessControl, enableAccessControl, cfg, db, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessControl bool, cfg *setting.Cfg, db *sqlstore.SQLStore, store sqlstore.Store) accessControlScenarioContext {
|
func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessControl bool, cfg *setting.Cfg, db *sqlstore.SQLStore, store sqlstore.Store) accessControlScenarioContext {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
features := featuremgmt.WithFeatures(featuremgmt.FlagAccesscontrol, enableAccessControl)
|
if enableAccessControl {
|
||||||
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
cfg.RBACEnabled = true
|
||||||
|
db.Cfg.RBACEnabled = true
|
||||||
|
} else {
|
||||||
|
cfg.RBACEnabled = false
|
||||||
|
db.Cfg.RBACEnabled = false
|
||||||
|
}
|
||||||
|
features := featuremgmt.WithFeatures()
|
||||||
|
|
||||||
var acmock *accesscontrolmock.Mock
|
var acmock *accesscontrolmock.Mock
|
||||||
|
|
||||||
@ -388,7 +389,7 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
hs.teamPermissionsService = teamPermissionService
|
hs.teamPermissionsService = teamPermissionService
|
||||||
} else {
|
} else {
|
||||||
ac, errInitAc := ossaccesscontrol.ProvideService(hs.Features, database.ProvideService(db), routing.NewRouteRegister())
|
ac, errInitAc := ossaccesscontrol.ProvideService(hs.Features, hs.Cfg, database.ProvideService(db), routing.NewRouteRegister())
|
||||||
require.NoError(t, errInitAc)
|
require.NoError(t, errInitAc)
|
||||||
hs.AccessControl = ac
|
hs.AccessControl = ac
|
||||||
// Perform role registration
|
// Perform role registration
|
||||||
|
@ -105,6 +105,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
|||||||
"autoAssignOrg": setting.AutoAssignOrg,
|
"autoAssignOrg": setting.AutoAssignOrg,
|
||||||
"verifyEmailEnabled": setting.VerifyEmailEnabled,
|
"verifyEmailEnabled": setting.VerifyEmailEnabled,
|
||||||
"sigV4AuthEnabled": setting.SigV4AuthEnabled,
|
"sigV4AuthEnabled": setting.SigV4AuthEnabled,
|
||||||
|
"rbacEnabled": hs.Cfg.RBACEnabled,
|
||||||
"exploreEnabled": setting.ExploreEnabled,
|
"exploreEnabled": setting.ExploreEnabled,
|
||||||
"helpEnabled": setting.HelpEnabled,
|
"helpEnabled": setting.HelpEnabled,
|
||||||
"profileEnabled": setting.ProfileEnabled,
|
"profileEnabled": setting.ProfileEnabled,
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -237,5 +236,5 @@ func extractPrefixes(prefix string) (string, string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsDisabled(cfg *setting.Cfg) bool {
|
func IsDisabled(cfg *setting.Cfg) bool {
|
||||||
return !cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol)
|
return !cfg.RBACEnabled
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,14 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/api"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/api"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProvideService(features featuremgmt.FeatureToggles,
|
func ProvideService(features featuremgmt.FeatureToggles, cfg *setting.Cfg,
|
||||||
provider accesscontrol.PermissionsProvider, routeRegister routing.RouteRegister) (*OSSAccessControlService, error) {
|
provider accesscontrol.PermissionsProvider, routeRegister routing.RouteRegister) (*OSSAccessControlService, error) {
|
||||||
var errDeclareRoles error
|
var errDeclareRoles error
|
||||||
s := ProvideOSSAccessControl(features, provider)
|
s := ProvideOSSAccessControl(features, cfg, provider)
|
||||||
if !s.IsDisabled() {
|
if !s.IsDisabled() {
|
||||||
api := api.AccessControlAPI{
|
api := api.AccessControlAPI{
|
||||||
RouteRegister: routeRegister,
|
RouteRegister: routeRegister,
|
||||||
@ -31,9 +32,10 @@ func ProvideService(features featuremgmt.FeatureToggles,
|
|||||||
return s, errDeclareRoles
|
return s, errDeclareRoles
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideOSSAccessControl(features featuremgmt.FeatureToggles, provider accesscontrol.PermissionsProvider) *OSSAccessControlService {
|
func ProvideOSSAccessControl(features featuremgmt.FeatureToggles, cfg *setting.Cfg, provider accesscontrol.PermissionsProvider) *OSSAccessControlService {
|
||||||
s := &OSSAccessControlService{
|
s := &OSSAccessControlService{
|
||||||
features: features,
|
features: features,
|
||||||
|
cfg: cfg,
|
||||||
provider: provider,
|
provider: provider,
|
||||||
log: log.New("accesscontrol"),
|
log: log.New("accesscontrol"),
|
||||||
scopeResolvers: accesscontrol.NewScopeResolvers(),
|
scopeResolvers: accesscontrol.NewScopeResolvers(),
|
||||||
@ -47,6 +49,7 @@ func ProvideOSSAccessControl(features featuremgmt.FeatureToggles, provider acces
|
|||||||
type OSSAccessControlService struct {
|
type OSSAccessControlService struct {
|
||||||
log log.Logger
|
log log.Logger
|
||||||
features featuremgmt.FeatureToggles
|
features featuremgmt.FeatureToggles
|
||||||
|
cfg *setting.Cfg
|
||||||
scopeResolvers accesscontrol.ScopeResolvers
|
scopeResolvers accesscontrol.ScopeResolvers
|
||||||
provider accesscontrol.PermissionsProvider
|
provider accesscontrol.PermissionsProvider
|
||||||
registrations accesscontrol.RegistrationList
|
registrations accesscontrol.RegistrationList
|
||||||
@ -54,10 +57,10 @@ type OSSAccessControlService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ac *OSSAccessControlService) IsDisabled() bool {
|
func (ac *OSSAccessControlService) IsDisabled() bool {
|
||||||
if ac.features == nil {
|
if ac.cfg == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return !ac.features.IsEnabled(featuremgmt.FlagAccesscontrol)
|
return !ac.cfg.RBACEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *OSSAccessControlService) GetUsageStats(_ context.Context) map[string]interface{} {
|
func (ac *OSSAccessControlService) GetUsageStats(_ context.Context) map[string]interface{} {
|
||||||
|
@ -14,13 +14,17 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestEnv(t testing.TB) *OSSAccessControlService {
|
func setupTestEnv(t testing.TB) *OSSAccessControlService {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.RBACEnabled = true
|
||||||
|
|
||||||
ac := &OSSAccessControlService{
|
ac := &OSSAccessControlService{
|
||||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAccesscontrol),
|
cfg: cfg,
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
log: log.New("accesscontrol"),
|
log: log.New("accesscontrol"),
|
||||||
registrations: accesscontrol.RegistrationList{},
|
registrations: accesscontrol.RegistrationList{},
|
||||||
scopeResolvers: accesscontrol.NewScopeResolvers(),
|
scopeResolvers: accesscontrol.NewScopeResolvers(),
|
||||||
@ -133,8 +137,13 @@ func TestUsageMetrics(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
if tt.enabled {
|
||||||
|
cfg.RBACEnabled = true
|
||||||
|
}
|
||||||
s, errInitAc := ProvideService(
|
s, errInitAc := ProvideService(
|
||||||
featuremgmt.WithFeatures("accesscontrol", tt.enabled),
|
featuremgmt.WithFeatures(),
|
||||||
|
cfg,
|
||||||
database.ProvideService(sqlstore.InitTestDB(t)),
|
database.ProvideService(sqlstore.InitTestDB(t)),
|
||||||
routing.NewRouteRegister(),
|
routing.NewRouteRegister(),
|
||||||
)
|
)
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
@ -278,9 +279,15 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
t.Run("Should be able to search for dashboard folder", func(t *testing.T) {
|
t.Run("Should be able to search for dashboard folder", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
query := models.FindPersistedDashboardsQuery{
|
query := models.FindPersistedDashboardsQuery{
|
||||||
Title: "1 test dash folder",
|
Title: "1 test dash folder",
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, OrgRole: models.ROLE_EDITOR},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
OrgRole: models.ROLE_EDITOR,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionFoldersRead: []string{dashboards.ScopeFoldersAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := sqlStore.SearchDashboards(context.Background(), &query)
|
err := sqlStore.SearchDashboards(context.Background(), &query)
|
||||||
@ -296,9 +303,15 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
t.Run("Should be able to limit search", func(t *testing.T) {
|
t.Run("Should be able to limit search", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
query := models.FindPersistedDashboardsQuery{
|
query := models.FindPersistedDashboardsQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, OrgRole: models.ROLE_EDITOR},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
OrgRole: models.ROLE_EDITOR,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionFoldersRead: []string{dashboards.ScopeFoldersAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := sqlStore.SearchDashboards(context.Background(), &query)
|
err := sqlStore.SearchDashboards(context.Background(), &query)
|
||||||
@ -311,10 +324,19 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
t.Run("Should be able to search beyond limit using paging", func(t *testing.T) {
|
t.Run("Should be able to search beyond limit using paging", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
query := models.FindPersistedDashboardsQuery{
|
query := models.FindPersistedDashboardsQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
Page: 2,
|
Page: 2,
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, OrgRole: models.ROLE_EDITOR},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
OrgRole: models.ROLE_EDITOR,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {
|
||||||
|
dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll},
|
||||||
|
dashboards.ActionFoldersRead: []string{dashboards.ScopeFoldersAll},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := sqlStore.SearchDashboards(context.Background(), &query)
|
err := sqlStore.SearchDashboards(context.Background(), &query)
|
||||||
@ -327,10 +349,16 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
t.Run("Should be able to filter by tag and type", func(t *testing.T) {
|
t.Run("Should be able to filter by tag and type", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
query := models.FindPersistedDashboardsQuery{
|
query := models.FindPersistedDashboardsQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
Type: "dash-db",
|
Type: "dash-db",
|
||||||
Tags: []string{"prod"},
|
Tags: []string{"prod"},
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, OrgRole: models.ROLE_EDITOR},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
OrgRole: models.ROLE_EDITOR,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := sqlStore.SearchDashboards(context.Background(), &query)
|
err := sqlStore.SearchDashboards(context.Background(), &query)
|
||||||
@ -343,9 +371,15 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
t.Run("Should be able to search for a dashboard folder's children", func(t *testing.T) {
|
t.Run("Should be able to search for a dashboard folder's children", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
query := models.FindPersistedDashboardsQuery{
|
query := models.FindPersistedDashboardsQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
FolderIds: []int64{savedFolder.Id},
|
FolderIds: []int64{savedFolder.Id},
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, OrgRole: models.ROLE_EDITOR},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
OrgRole: models.ROLE_EDITOR,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := sqlStore.SearchDashboards(context.Background(), &query)
|
err := sqlStore.SearchDashboards(context.Background(), &query)
|
||||||
@ -365,7 +399,13 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
setup()
|
setup()
|
||||||
query := models.FindPersistedDashboardsQuery{
|
query := models.FindPersistedDashboardsQuery{
|
||||||
DashboardIds: []int64{savedDash.Id, savedDash2.Id},
|
DashboardIds: []int64{savedDash.Id, savedDash2.Id},
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, OrgRole: models.ROLE_EDITOR},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
OrgRole: models.ROLE_EDITOR,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := sqlStore.SearchDashboards(context.Background(), &query)
|
err := sqlStore.SearchDashboards(context.Background(), &query)
|
||||||
@ -396,8 +436,15 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
query := models.FindPersistedDashboardsQuery{
|
query := models.FindPersistedDashboardsQuery{
|
||||||
SignedInUser: &models.SignedInUser{UserId: 10, OrgId: 1, OrgRole: models.ROLE_EDITOR},
|
SignedInUser: &models.SignedInUser{
|
||||||
IsStarred: true,
|
UserId: 10,
|
||||||
|
OrgId: 1,
|
||||||
|
OrgRole: models.ROLE_EDITOR,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IsStarred: true,
|
||||||
}
|
}
|
||||||
err = sqlStore.SearchDashboards(context.Background(), &query)
|
err = sqlStore.SearchDashboards(context.Background(), &query)
|
||||||
|
|
||||||
@ -435,27 +482,41 @@ func TestDashboard_SortingOptions(t *testing.T) {
|
|||||||
assert.NotZero(t, dashA.Id)
|
assert.NotZero(t, dashA.Id)
|
||||||
assert.Less(t, dashB.Id, dashA.Id)
|
assert.Less(t, dashB.Id, dashA.Id)
|
||||||
qNoSort := &models.FindPersistedDashboardsQuery{
|
qNoSort := &models.FindPersistedDashboardsQuery{
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, UserId: 1, OrgRole: models.ROLE_ADMIN},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
UserId: 1,
|
||||||
|
OrgRole: models.ROLE_ADMIN,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
dashboards, err := sqlStore.FindDashboards(context.Background(), qNoSort)
|
results, err := sqlStore.FindDashboards(context.Background(), qNoSort)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, dashboards, 2)
|
require.Len(t, results, 2)
|
||||||
assert.Equal(t, dashA.Id, dashboards[0].ID)
|
assert.Equal(t, dashA.Id, results[0].ID)
|
||||||
assert.Equal(t, dashB.Id, dashboards[1].ID)
|
assert.Equal(t, dashB.Id, results[1].ID)
|
||||||
|
|
||||||
qSort := &models.FindPersistedDashboardsQuery{
|
qSort := &models.FindPersistedDashboardsQuery{
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, UserId: 1, OrgRole: models.ROLE_ADMIN},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
UserId: 1,
|
||||||
|
OrgRole: models.ROLE_ADMIN,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
Sort: models.SortOption{
|
Sort: models.SortOption{
|
||||||
Filter: []models.SortOptionFilter{
|
Filter: []models.SortOptionFilter{
|
||||||
searchstore.TitleSorter{Descending: true},
|
searchstore.TitleSorter{Descending: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
dashboards, err = sqlStore.FindDashboards(context.Background(), qSort)
|
results, err = sqlStore.FindDashboards(context.Background(), qSort)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, dashboards, 2)
|
require.Len(t, results, 2)
|
||||||
assert.Equal(t, dashB.Id, dashboards[0].ID)
|
assert.Equal(t, dashB.Id, results[0].ID)
|
||||||
assert.Equal(t, dashA.Id, dashboards[1].ID)
|
assert.Equal(t, dashA.Id, results[1].ID)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,14 +526,28 @@ func TestDashboard_Filter(t *testing.T) {
|
|||||||
insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false)
|
insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false)
|
||||||
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false)
|
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false)
|
||||||
qNoFilter := &models.FindPersistedDashboardsQuery{
|
qNoFilter := &models.FindPersistedDashboardsQuery{
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, UserId: 1, OrgRole: models.ROLE_ADMIN},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
UserId: 1,
|
||||||
|
OrgRole: models.ROLE_ADMIN,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
dashboards, err := sqlStore.FindDashboards(context.Background(), qNoFilter)
|
results, err := sqlStore.FindDashboards(context.Background(), qNoFilter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, dashboards, 2)
|
require.Len(t, results, 2)
|
||||||
|
|
||||||
qFilter := &models.FindPersistedDashboardsQuery{
|
qFilter := &models.FindPersistedDashboardsQuery{
|
||||||
SignedInUser: &models.SignedInUser{OrgId: 1, UserId: 1, OrgRole: models.ROLE_ADMIN},
|
SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
UserId: 1,
|
||||||
|
OrgRole: models.ROLE_ADMIN,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
Filters: []interface{}{
|
Filters: []interface{}{
|
||||||
searchstore.TitleFilter{
|
searchstore.TitleFilter{
|
||||||
Dialect: sqlStore.Dialect,
|
Dialect: sqlStore.Dialect,
|
||||||
@ -480,10 +555,10 @@ func TestDashboard_Filter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
dashboards, err = sqlStore.FindDashboards(context.Background(), qFilter)
|
results, err = sqlStore.FindDashboards(context.Background(), qFilter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, dashboards, 1)
|
require.Len(t, results, 1)
|
||||||
assert.Equal(t, dashB.Id, dashboards[0].ID)
|
assert.Equal(t, dashB.Id, results[0].ID)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
|||||||
|
|
||||||
setup := func() {
|
setup := func() {
|
||||||
sqlStore = sqlstore.InitTestDB(t)
|
sqlStore = sqlstore.InitTestDB(t)
|
||||||
|
sqlStore.Cfg.RBACEnabled = false
|
||||||
dashboardStore = ProvideDashboardStore(sqlStore)
|
dashboardStore = ProvideDashboardStore(sqlStore)
|
||||||
folder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
|
folder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||||
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod", "webapp")
|
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod", "webapp")
|
||||||
|
@ -94,11 +94,6 @@ var (
|
|||||||
State: FeatureStateAlpha,
|
State: FeatureStateAlpha,
|
||||||
FrontendOnly: true,
|
FrontendOnly: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "accesscontrol",
|
|
||||||
Description: "Support robust access control",
|
|
||||||
State: FeatureStateBeta,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "accesscontrol-builtins",
|
Name: "accesscontrol-builtins",
|
||||||
Description: "Simplify access control builtin roles",
|
Description: "Simplify access control builtin roles",
|
||||||
|
@ -71,10 +71,6 @@ const (
|
|||||||
// Loki datasource works as backend datasource
|
// Loki datasource works as backend datasource
|
||||||
FlagLokiBackendMode = "lokiBackendMode"
|
FlagLokiBackendMode = "lokiBackendMode"
|
||||||
|
|
||||||
// FlagAccesscontrol
|
|
||||||
// Support robust access control
|
|
||||||
FlagAccesscontrol = "accesscontrol"
|
|
||||||
|
|
||||||
// FlagAccesscontrolBuiltins
|
// FlagAccesscontrolBuiltins
|
||||||
// Simplify access control builtin roles
|
// Simplify access control builtin roles
|
||||||
FlagAccesscontrolBuiltins = "accesscontrol-builtins"
|
FlagAccesscontrolBuiltins = "accesscontrol-builtins"
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/annotations"
|
"github.com/grafana/grafana/pkg/services/annotations"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,6 +24,16 @@ func TestAnnotations(t *testing.T) {
|
|||||||
sql := sqlstore.InitTestDB(t)
|
sql := sqlstore.InitTestDB(t)
|
||||||
repo := sqlstore.NewSQLAnnotationRepo(sql)
|
repo := sqlstore.NewSQLAnnotationRepo(sql)
|
||||||
|
|
||||||
|
testUser := &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {
|
||||||
|
accesscontrol.ActionAnnotationsRead: []string{accesscontrol.ScopeAnnotationsAll},
|
||||||
|
dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Testing annotation create, read, update and delete", func(t *testing.T) {
|
t.Run("Testing annotation create, read, update and delete", func(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err := sql.WithDbSession(context.Background(), func(dbSession *sqlstore.DBSession) error {
|
err := sql.WithDbSession(context.Background(), func(dbSession *sqlstore.DBSession) error {
|
||||||
@ -38,16 +47,38 @@ func TestAnnotations(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
dashboardStore := dashboardstore.ProvideDashboardStore(sql)
|
||||||
|
|
||||||
|
testDashboard1 := models.SaveDashboardCommand{
|
||||||
|
UserId: 1,
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"title": "Dashboard 1",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
dashboard, err := dashboardStore.SaveDashboard(testDashboard1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testDashboard2 := models.SaveDashboardCommand{
|
||||||
|
UserId: 1,
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"title": "Dashboard 2",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
dashboard2, err := dashboardStore.SaveDashboard(testDashboard2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
annotation := &annotations.Item{
|
annotation := &annotations.Item{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: dashboard.Id,
|
||||||
Text: "hello",
|
Text: "hello",
|
||||||
Type: "alert",
|
Type: "alert",
|
||||||
Epoch: 10,
|
Epoch: 10,
|
||||||
Tags: []string{"outage", "error", "type:outage", "server:server-1"},
|
Tags: []string{"outage", "error", "type:outage", "server:server-1"},
|
||||||
}
|
}
|
||||||
err := repo.Save(annotation)
|
err = repo.Save(annotation)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Greater(t, annotation.Id, int64(0))
|
assert.Greater(t, annotation.Id, int64(0))
|
||||||
assert.Equal(t, annotation.Epoch, annotation.EpochEnd)
|
assert.Equal(t, annotation.Epoch, annotation.EpochEnd)
|
||||||
@ -55,7 +86,7 @@ func TestAnnotations(t *testing.T) {
|
|||||||
annotation2 := &annotations.Item{
|
annotation2 := &annotations.Item{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
DashboardId: 2,
|
DashboardId: dashboard2.Id,
|
||||||
Text: "hello",
|
Text: "hello",
|
||||||
Type: "alert",
|
Type: "alert",
|
||||||
Epoch: 21, // Should swap epoch & epochEnd
|
Epoch: 21, // Should swap epoch & epochEnd
|
||||||
@ -93,10 +124,11 @@ func TestAnnotations(t *testing.T) {
|
|||||||
assert.Greater(t, globalAnnotation2.Id, int64(0))
|
assert.Greater(t, globalAnnotation2.Id, int64(0))
|
||||||
t.Run("Can query for annotation by dashboard id", func(t *testing.T) {
|
t.Run("Can query for annotation by dashboard id", func(t *testing.T) {
|
||||||
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: dashboard.Id,
|
||||||
From: 0,
|
From: 0,
|
||||||
To: 15,
|
To: 15,
|
||||||
|
SignedInUser: testUser,
|
||||||
})
|
})
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -113,6 +145,7 @@ func TestAnnotations(t *testing.T) {
|
|||||||
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
AnnotationId: annotation2.Id,
|
AnnotationId: annotation2.Id,
|
||||||
|
SignedInUser: testUser,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, items, 1)
|
assert.Len(t, items, 1)
|
||||||
@ -121,10 +154,11 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Should not find any when item is outside time range", func(t *testing.T) {
|
t.Run("Should not find any when item is outside time range", func(t *testing.T) {
|
||||||
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
From: 12,
|
From: 12,
|
||||||
To: 15,
|
To: 15,
|
||||||
|
SignedInUser: testUser,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, items)
|
assert.Empty(t, items)
|
||||||
@ -132,11 +166,12 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Should not find one when tag filter does not match", func(t *testing.T) {
|
t.Run("Should not find one when tag filter does not match", func(t *testing.T) {
|
||||||
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
From: 1,
|
From: 1,
|
||||||
To: 15,
|
To: 15,
|
||||||
Tags: []string{"asd"},
|
Tags: []string{"asd"},
|
||||||
|
SignedInUser: testUser,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, items)
|
assert.Empty(t, items)
|
||||||
@ -144,11 +179,12 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Should not find one when type filter does not match", func(t *testing.T) {
|
t.Run("Should not find one when type filter does not match", func(t *testing.T) {
|
||||||
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
From: 1,
|
From: 1,
|
||||||
To: 15,
|
To: 15,
|
||||||
Type: "alert",
|
Type: "alert",
|
||||||
|
SignedInUser: testUser,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, items)
|
assert.Empty(t, items)
|
||||||
@ -156,11 +192,12 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Should find one when all tag filters does match", func(t *testing.T) {
|
t.Run("Should find one when all tag filters does match", func(t *testing.T) {
|
||||||
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
From: 1,
|
From: 1,
|
||||||
To: 15, // this will exclude the second test annotation
|
To: 15, // this will exclude the second test annotation
|
||||||
Tags: []string{"outage", "error"},
|
Tags: []string{"outage", "error"},
|
||||||
|
SignedInUser: testUser,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, items, 1)
|
assert.Len(t, items, 1)
|
||||||
@ -168,11 +205,12 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Should find two annotations using partial match", func(t *testing.T) {
|
t.Run("Should find two annotations using partial match", func(t *testing.T) {
|
||||||
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
From: 1,
|
From: 1,
|
||||||
To: 25,
|
To: 25,
|
||||||
MatchAny: true,
|
MatchAny: true,
|
||||||
Tags: []string{"rollback", "deploy"},
|
Tags: []string{"rollback", "deploy"},
|
||||||
|
SignedInUser: testUser,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, items, 2)
|
assert.Len(t, items, 2)
|
||||||
@ -180,11 +218,12 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Should find one when all key value tag filters does match", func(t *testing.T) {
|
t.Run("Should find one when all key value tag filters does match", func(t *testing.T) {
|
||||||
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
items, err := repo.Find(context.Background(), &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
From: 1,
|
From: 1,
|
||||||
To: 15,
|
To: 15,
|
||||||
Tags: []string{"type:outage", "server:server-1"},
|
Tags: []string{"type:outage", "server:server-1"},
|
||||||
|
SignedInUser: testUser,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, items, 1)
|
assert.Len(t, items, 1)
|
||||||
@ -192,10 +231,11 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Can update annotation and remove all tags", func(t *testing.T) {
|
t.Run("Can update annotation and remove all tags", func(t *testing.T) {
|
||||||
query := &annotations.ItemQuery{
|
query := &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
From: 0,
|
From: 0,
|
||||||
To: 15,
|
To: 15,
|
||||||
|
SignedInUser: testUser,
|
||||||
}
|
}
|
||||||
items, err := repo.Find(context.Background(), query)
|
items, err := repo.Find(context.Background(), query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -219,10 +259,11 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Can update annotation with new tags", func(t *testing.T) {
|
t.Run("Can update annotation with new tags", func(t *testing.T) {
|
||||||
query := &annotations.ItemQuery{
|
query := &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
From: 0,
|
From: 0,
|
||||||
To: 15,
|
To: 15,
|
||||||
|
SignedInUser: testUser,
|
||||||
}
|
}
|
||||||
items, err := repo.Find(context.Background(), query)
|
items, err := repo.Find(context.Background(), query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -247,10 +288,11 @@ func TestAnnotations(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Can delete annotation", func(t *testing.T) {
|
t.Run("Can delete annotation", func(t *testing.T) {
|
||||||
query := &annotations.ItemQuery{
|
query := &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
From: 0,
|
From: 0,
|
||||||
To: 15,
|
To: 15,
|
||||||
|
SignedInUser: testUser,
|
||||||
}
|
}
|
||||||
items, err := repo.Find(context.Background(), query)
|
items, err := repo.Find(context.Background(), query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -268,11 +310,12 @@ func TestAnnotations(t *testing.T) {
|
|||||||
annotation3 := &annotations.Item{
|
annotation3 := &annotations.Item{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
DashboardId: 3,
|
DashboardId: dashboard2.Id,
|
||||||
Text: "toBeDeletedWithPanelId",
|
Text: "toBeDeletedWithPanelId",
|
||||||
Type: "alert",
|
Type: "alert",
|
||||||
Epoch: 11,
|
Epoch: 11,
|
||||||
Tags: []string{"test"},
|
Tags: []string{"test"},
|
||||||
|
PanelId: 20,
|
||||||
}
|
}
|
||||||
err = repo.Save(annotation3)
|
err = repo.Save(annotation3)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -280,6 +323,7 @@ func TestAnnotations(t *testing.T) {
|
|||||||
query := &annotations.ItemQuery{
|
query := &annotations.ItemQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
AnnotationId: annotation3.Id,
|
AnnotationId: annotation3.Id,
|
||||||
|
SignedInUser: testUser,
|
||||||
}
|
}
|
||||||
items, err := repo.Find(context.Background(), query)
|
items, err := repo.Find(context.Background(), query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -339,7 +383,7 @@ func TestAnnotations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAnnotationListingWithRBAC(t *testing.T) {
|
func TestAnnotationListingWithRBAC(t *testing.T) {
|
||||||
sql := sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
sql := sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{})
|
||||||
repo := sqlstore.NewSQLAnnotationRepo(sql)
|
repo := sqlstore.NewSQLAnnotationRepo(sql)
|
||||||
dashboardStore := dashboardstore.ProvideDashboardStore(sql)
|
dashboardStore := dashboardstore.ProvideDashboardStore(sql)
|
||||||
|
|
||||||
|
@ -9,11 +9,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestApiKeyDataAccess(t *testing.T) {
|
func TestApiKeyDataAccess(t *testing.T) {
|
||||||
@ -99,7 +99,13 @@ func TestApiKeyDataAccess(t *testing.T) {
|
|||||||
// advance mocked getTime by 1s
|
// advance mocked getTime by 1s
|
||||||
timeNow()
|
timeNow()
|
||||||
|
|
||||||
query := models.GetApiKeysQuery{OrgId: 1, IncludeExpired: false}
|
testUser := &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {accesscontrol.ActionAPIKeyRead: []string{accesscontrol.ScopeAPIKeysAll}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
query := models.GetApiKeysQuery{OrgId: 1, IncludeExpired: false, User: testUser}
|
||||||
err = ss.GetAPIKeys(context.Background(), &query)
|
err = ss.GetAPIKeys(context.Background(), &query)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@ -109,7 +115,7 @@ func TestApiKeyDataAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query = models.GetApiKeysQuery{OrgId: 1, IncludeExpired: true}
|
query = models.GetApiKeysQuery{OrgId: 1, IncludeExpired: true, User: testUser}
|
||||||
err = ss.GetAPIKeys(context.Background(), &query)
|
err = ss.GetAPIKeys(context.Background(), &query)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@ -187,7 +193,7 @@ func TestSQLStore_GetAPIKeys(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
store := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
store := InitTestDB(t, InitTestDBOpt{})
|
||||||
seedApiKeys(t, store, 10)
|
seedApiKeys(t, store, 10)
|
||||||
|
|
||||||
query := &models.GetApiKeysQuery{OrgId: 1, User: tt.user}
|
query := &models.GetApiKeysQuery{OrgId: 1, User: tt.user}
|
||||||
|
@ -77,7 +77,7 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
|
|||||||
addQueryHistoryMigrations(mg)
|
addQueryHistoryMigrations(mg)
|
||||||
|
|
||||||
if mg.Cfg != nil && mg.Cfg.IsFeatureToggleEnabled != nil {
|
if mg.Cfg != nil && mg.Cfg.IsFeatureToggleEnabled != nil {
|
||||||
if mg.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) {
|
if mg.Cfg.RBACEnabled {
|
||||||
accesscontrol.AddTeamMembershipMigrations(mg)
|
accesscontrol.AddTeamMembershipMigrations(mg)
|
||||||
accesscontrol.AddDashboardPermissionsMigrator(mg)
|
accesscontrol.AddDashboardPermissionsMigrator(mg)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -19,6 +20,11 @@ import (
|
|||||||
func TestAccountDataAccess(t *testing.T) {
|
func TestAccountDataAccess(t *testing.T) {
|
||||||
t.Run("Testing Account DB Access", func(t *testing.T) {
|
t.Run("Testing Account DB Access", func(t *testing.T) {
|
||||||
sqlStore := InitTestDB(t)
|
sqlStore := InitTestDB(t)
|
||||||
|
testUser := &models.SignedInUser{
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {accesscontrol.ActionOrgUsersRead: []string{accesscontrol.ScopeUsersAll}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Given we have organizations, we can query them by IDs", func(t *testing.T) {
|
t.Run("Given we have organizations, we can query them by IDs", func(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
@ -109,6 +115,7 @@ func TestAccountDataAccess(t *testing.T) {
|
|||||||
ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
|
ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
|
||||||
|
|
||||||
ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
|
ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
|
||||||
|
testUser.OrgId = ac1.OrgId
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = sqlStore.CreateUser(context.Background(), ac2cmd)
|
_, err = sqlStore.CreateUser(context.Background(), ac2cmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -117,6 +124,7 @@ func TestAccountDataAccess(t *testing.T) {
|
|||||||
query := models.SearchOrgUsersQuery{
|
query := models.SearchOrgUsersQuery{
|
||||||
OrgID: ac1.OrgId,
|
OrgID: ac1.OrgId,
|
||||||
Page: 1,
|
Page: 1,
|
||||||
|
User: testUser,
|
||||||
}
|
}
|
||||||
err = sqlStore.SearchOrgUsers(context.Background(), &query)
|
err = sqlStore.SearchOrgUsers(context.Background(), &query)
|
||||||
|
|
||||||
@ -129,6 +137,7 @@ func TestAccountDataAccess(t *testing.T) {
|
|||||||
OrgID: ac1.OrgId,
|
OrgID: ac1.OrgId,
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
Page: 1,
|
Page: 1,
|
||||||
|
User: testUser,
|
||||||
}
|
}
|
||||||
err = sqlStore.SearchOrgUsers(context.Background(), &query)
|
err = sqlStore.SearchOrgUsers(context.Background(), &query)
|
||||||
|
|
||||||
@ -163,7 +172,12 @@ func TestAccountDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Can search users", func(t *testing.T) {
|
t.Run("Can search users", func(t *testing.T) {
|
||||||
query := models.SearchUsersQuery{Query: ""}
|
query := models.SearchUsersQuery{Query: "", SignedInUser: &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {accesscontrol.ActionUsersRead: {accesscontrol.ScopeGlobalUsersAll}},
|
||||||
|
},
|
||||||
|
}}
|
||||||
err := sqlStore.SearchUsers(context.Background(), &query)
|
err := sqlStore.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type getOrgUsersTestCase struct {
|
type getOrgUsersTestCase struct {
|
||||||
@ -61,7 +60,7 @@ func TestSQLStore_GetOrgUsers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
store := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
store := InitTestDB(t, InitTestDBOpt{})
|
||||||
store.Cfg.IsEnterprise = true
|
store.Cfg.IsEnterprise = true
|
||||||
defer func() {
|
defer func() {
|
||||||
store.Cfg.IsEnterprise = false
|
store.Cfg.IsEnterprise = false
|
||||||
@ -130,7 +129,7 @@ func TestSQLStore_SearchOrgUsers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
store := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
store := InitTestDB(t, InitTestDBOpt{})
|
||||||
seedOrgUsers(t, store, 10)
|
seedOrgUsers(t, store, 10)
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -487,6 +487,7 @@ func initTestDB(migration registry.DatabaseMigrator, opts ...InitTestDBOpt) (*SQ
|
|||||||
|
|
||||||
// set test db config
|
// set test db config
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
|
cfg.RBACEnabled = true
|
||||||
cfg.IsFeatureToggleEnabled = func(key string) bool {
|
cfg.IsFeatureToggleEnabled = func(key string) bool {
|
||||||
for _, enabledFeature := range features {
|
for _, enabledFeature := range features {
|
||||||
if enabledFeature == key {
|
if enabledFeature == key {
|
||||||
|
@ -13,12 +13,20 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTeamCommandsAndQueries(t *testing.T) {
|
func TestTeamCommandsAndQueries(t *testing.T) {
|
||||||
t.Run("Testing Team commands & queries", func(t *testing.T) {
|
t.Run("Testing Team commands & queries", func(t *testing.T) {
|
||||||
sqlStore := InitTestDB(t)
|
sqlStore := InitTestDB(t)
|
||||||
|
testUser := &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {
|
||||||
|
ac.ActionTeamsRead: []string{ac.ScopeTeamsAll},
|
||||||
|
ac.ActionOrgUsersRead: []string{ac.ScopeUsersAll},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Given saved users and two teams", func(t *testing.T) {
|
t.Run("Given saved users and two teams", func(t *testing.T) {
|
||||||
var userIds []int64
|
var userIds []int64
|
||||||
@ -47,7 +55,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
setup()
|
setup()
|
||||||
|
|
||||||
t.Run("Should be able to create teams and add users", func(t *testing.T) {
|
t.Run("Should be able to create teams and add users", func(t *testing.T) {
|
||||||
query := &models.SearchTeamsQuery{OrgId: testOrgID, Name: "group1 name", Page: 1, Limit: 10}
|
query := &models.SearchTeamsQuery{OrgId: testOrgID, Name: "group1 name", Page: 1, Limit: 10, SignedInUser: testUser}
|
||||||
err = sqlStore.SearchTeams(context.Background(), query)
|
err = sqlStore.SearchTeams(context.Background(), query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, query.Page, 1)
|
require.Equal(t, query.Page, 1)
|
||||||
@ -63,7 +71,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
err = sqlStore.AddTeamMember(userIds[1], testOrgID, team1.Id, true, 0)
|
err = sqlStore.AddTeamMember(userIds[1], testOrgID, team1.Id, true, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
q1 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id}
|
q1 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamMembers(context.Background(), q1)
|
err = sqlStore.GetTeamMembers(context.Background(), q1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, len(q1.Result), 2)
|
require.Equal(t, len(q1.Result), 2)
|
||||||
@ -75,7 +83,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
require.Equal(t, q1.Result[1].OrgId, testOrgID)
|
require.Equal(t, q1.Result[1].OrgId, testOrgID)
|
||||||
require.Equal(t, q1.Result[1].External, true)
|
require.Equal(t, q1.Result[1].External, true)
|
||||||
|
|
||||||
q2 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, External: true}
|
q2 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, External: true, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamMembers(context.Background(), q2)
|
err = sqlStore.GetTeamMembers(context.Background(), q2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, len(q2.Result), 1)
|
require.Equal(t, len(q2.Result), 1)
|
||||||
@ -89,7 +97,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
team1 = query.Result.Teams[0]
|
team1 = query.Result.Teams[0]
|
||||||
require.EqualValues(t, team1.MemberCount, 2)
|
require.EqualValues(t, team1.MemberCount, 2)
|
||||||
|
|
||||||
getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgID, Id: team1.Id}
|
getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgID, Id: team1.Id, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamById(context.Background(), getTeamQuery)
|
err = sqlStore.GetTeamById(context.Background(), getTeamQuery)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
team1 = getTeamQuery.Result
|
team1 = getTeamQuery.Result
|
||||||
@ -104,7 +112,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
setup()
|
setup()
|
||||||
userId := userIds[1]
|
userId := userIds[1]
|
||||||
|
|
||||||
teamQuery := &models.SearchTeamsQuery{OrgId: testOrgID, Name: "group1 name", Page: 1, Limit: 10}
|
teamQuery := &models.SearchTeamsQuery{OrgId: testOrgID, Name: "group1 name", Page: 1, Limit: 10, SignedInUser: testUser}
|
||||||
err = sqlStore.SearchTeams(context.Background(), teamQuery)
|
err = sqlStore.SearchTeams(context.Background(), teamQuery)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, teamQuery.Page, 1)
|
require.Equal(t, teamQuery.Page, 1)
|
||||||
@ -114,7 +122,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
err = sqlStore.AddTeamMember(userId, testOrgID, team1.Id, true, 0)
|
err = sqlStore.AddTeamMember(userId, testOrgID, team1.Id, true, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
memberQuery := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, External: true}
|
memberQuery := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, External: true, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamMembers(context.Background(), memberQuery)
|
err = sqlStore.GetTeamMembers(context.Background(), memberQuery)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, len(memberQuery.Result), 1)
|
require.Equal(t, len(memberQuery.Result), 1)
|
||||||
@ -130,7 +138,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
err = sqlStore.AddTeamMember(userId, testOrgID, team.Id, false, 0)
|
err = sqlStore.AddTeamMember(userId, testOrgID, team.Id, false, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
|
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamMembers(context.Background(), qBeforeUpdate)
|
err = sqlStore.GetTeamMembers(context.Background(), qBeforeUpdate)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, qBeforeUpdate.Result[0].Permission, 0)
|
require.EqualValues(t, qBeforeUpdate.Result[0].Permission, 0)
|
||||||
@ -144,7 +152,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
|
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamMembers(context.Background(), qAfterUpdate)
|
err = sqlStore.GetTeamMembers(context.Background(), qAfterUpdate)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, qAfterUpdate.Result[0].Permission, models.PERMISSION_ADMIN)
|
require.Equal(t, qAfterUpdate.Result[0].Permission, models.PERMISSION_ADMIN)
|
||||||
@ -158,7 +166,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
err = sqlStore.AddTeamMember(userID, testOrgID, team.Id, false, 0)
|
err = sqlStore.AddTeamMember(userID, testOrgID, team.Id, false, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
|
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamMembers(context.Background(), qBeforeUpdate)
|
err = sqlStore.GetTeamMembers(context.Background(), qBeforeUpdate)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, qBeforeUpdate.Result[0].Permission, 0)
|
require.EqualValues(t, qBeforeUpdate.Result[0].Permission, 0)
|
||||||
@ -173,7 +181,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
|
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamMembers(context.Background(), qAfterUpdate)
|
err = sqlStore.GetTeamMembers(context.Background(), qAfterUpdate)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, qAfterUpdate.Result[0].Permission, 0)
|
require.EqualValues(t, qAfterUpdate.Result[0].Permission, 0)
|
||||||
@ -193,13 +201,13 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should be able to search for teams", func(t *testing.T) {
|
t.Run("Should be able to search for teams", func(t *testing.T) {
|
||||||
query := &models.SearchTeamsQuery{OrgId: testOrgID, Query: "group", Page: 1}
|
query := &models.SearchTeamsQuery{OrgId: testOrgID, Query: "group", Page: 1, SignedInUser: testUser}
|
||||||
err = sqlStore.SearchTeams(context.Background(), query)
|
err = sqlStore.SearchTeams(context.Background(), query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, len(query.Result.Teams), 2)
|
require.Equal(t, len(query.Result.Teams), 2)
|
||||||
require.EqualValues(t, query.Result.TotalCount, 2)
|
require.EqualValues(t, query.Result.TotalCount, 2)
|
||||||
|
|
||||||
query2 := &models.SearchTeamsQuery{OrgId: testOrgID, Query: ""}
|
query2 := &models.SearchTeamsQuery{OrgId: testOrgID, Query: "", SignedInUser: testUser}
|
||||||
err = sqlStore.SearchTeams(context.Background(), query2)
|
err = sqlStore.SearchTeams(context.Background(), query2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, len(query2.Result.Teams), 2)
|
require.Equal(t, len(query2.Result.Teams), 2)
|
||||||
@ -227,7 +235,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
err = sqlStore.RemoveTeamMember(context.Background(), &models.RemoveTeamMemberCommand{OrgId: testOrgID, TeamId: team1.Id, UserId: userIds[0]})
|
err = sqlStore.RemoveTeamMember(context.Background(), &models.RemoveTeamMemberCommand{OrgId: testOrgID, TeamId: team1.Id, UserId: userIds[0]})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
q2 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id}
|
q2 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, SignedInUser: testUser}
|
||||||
err = sqlStore.GetTeamMembers(context.Background(), q2)
|
err = sqlStore.GetTeamMembers(context.Background(), q2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, len(q2.Result), 0)
|
require.Equal(t, len(q2.Result), 0)
|
||||||
@ -315,7 +323,16 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
|||||||
t.Run("Should not return hidden users in team member count", func(t *testing.T) {
|
t.Run("Should not return hidden users in team member count", func(t *testing.T) {
|
||||||
sqlStore = InitTestDB(t)
|
sqlStore = InitTestDB(t)
|
||||||
setup()
|
setup()
|
||||||
signedInUser := &models.SignedInUser{Login: "loginuser0"}
|
signedInUser := &models.SignedInUser{
|
||||||
|
Login: "loginuser0",
|
||||||
|
OrgId: testOrgID,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
testOrgID: {
|
||||||
|
ac.ActionTeamsRead: []string{ac.ScopeTeamsAll},
|
||||||
|
ac.ActionOrgUsersRead: []string{ac.ScopeUsersAll},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
hiddenUsers := map[string]struct{}{"loginuser0": {}, "loginuser1": {}}
|
hiddenUsers := map[string]struct{}{"loginuser0": {}, "loginuser1": {}}
|
||||||
|
|
||||||
teamId := team1.Id
|
teamId := team1.Id
|
||||||
@ -396,7 +413,7 @@ func TestSQLStore_SearchTeams(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
store := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
store := InitTestDB(t, InitTestDBOpt{})
|
||||||
|
|
||||||
// Seed 10 teams
|
// Seed 10 teams
|
||||||
for i := 1; i <= 10; i++ {
|
for i := 1; i <= 10; i++ {
|
||||||
@ -455,7 +472,7 @@ func TestSQLStore_GetTeamMembers_ACFilter(t *testing.T) {
|
|||||||
require.NoError(t, errAddMember)
|
require.NoError(t, errAddMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
store := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
store := InitTestDB(t, InitTestDBOpt{})
|
||||||
setup(store)
|
setup(store)
|
||||||
|
|
||||||
type getTeamMembersTestCase struct {
|
type getTeamMembersTestCase struct {
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -18,6 +17,10 @@ import (
|
|||||||
func TestUserDataAccess(t *testing.T) {
|
func TestUserDataAccess(t *testing.T) {
|
||||||
|
|
||||||
ss := InitTestDB(t)
|
ss := InitTestDB(t)
|
||||||
|
user := &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
Permissions: map[int64]map[string][]string{1: {"users:read": {"global.users:*"}}},
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Testing DB - creates and loads user", func(t *testing.T) {
|
t.Run("Testing DB - creates and loads user", func(t *testing.T) {
|
||||||
cmd := models.CreateUserCommand{
|
cmd := models.CreateUserCommand{
|
||||||
@ -131,7 +134,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Return the first page of users and a total count
|
// Return the first page of users and a total count
|
||||||
query := models.SearchUsersQuery{Query: "", Page: 1, Limit: 3}
|
query := models.SearchUsersQuery{Query: "", Page: 1, Limit: 3, SignedInUser: user}
|
||||||
err := ss.SearchUsers(context.Background(), &query)
|
err := ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -139,7 +142,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
require.EqualValues(t, query.Result.TotalCount, 5)
|
require.EqualValues(t, query.Result.TotalCount, 5)
|
||||||
|
|
||||||
// Return the second page of users and a total count
|
// Return the second page of users and a total count
|
||||||
query = models.SearchUsersQuery{Query: "", Page: 2, Limit: 3}
|
query = models.SearchUsersQuery{Query: "", Page: 2, Limit: 3, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), &query)
|
err = ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -147,28 +150,28 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
require.EqualValues(t, query.Result.TotalCount, 5)
|
require.EqualValues(t, query.Result.TotalCount, 5)
|
||||||
|
|
||||||
// Return list of users matching query on user name
|
// Return list of users matching query on user name
|
||||||
query = models.SearchUsersQuery{Query: "use", Page: 1, Limit: 3}
|
query = models.SearchUsersQuery{Query: "use", Page: 1, Limit: 3, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), &query)
|
err = ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Len(t, query.Result.Users, 3)
|
require.Len(t, query.Result.Users, 3)
|
||||||
require.EqualValues(t, query.Result.TotalCount, 5)
|
require.EqualValues(t, query.Result.TotalCount, 5)
|
||||||
|
|
||||||
query = models.SearchUsersQuery{Query: "ser1", Page: 1, Limit: 3}
|
query = models.SearchUsersQuery{Query: "ser1", Page: 1, Limit: 3, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), &query)
|
err = ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Len(t, query.Result.Users, 1)
|
require.Len(t, query.Result.Users, 1)
|
||||||
require.EqualValues(t, query.Result.TotalCount, 1)
|
require.EqualValues(t, query.Result.TotalCount, 1)
|
||||||
|
|
||||||
query = models.SearchUsersQuery{Query: "USER1", Page: 1, Limit: 3}
|
query = models.SearchUsersQuery{Query: "USER1", Page: 1, Limit: 3, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), &query)
|
err = ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Len(t, query.Result.Users, 1)
|
require.Len(t, query.Result.Users, 1)
|
||||||
require.EqualValues(t, query.Result.TotalCount, 1)
|
require.EqualValues(t, query.Result.TotalCount, 1)
|
||||||
|
|
||||||
query = models.SearchUsersQuery{Query: "idontexist", Page: 1, Limit: 3}
|
query = models.SearchUsersQuery{Query: "idontexist", Page: 1, Limit: 3, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), &query)
|
err = ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -176,7 +179,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
require.EqualValues(t, query.Result.TotalCount, 0)
|
require.EqualValues(t, query.Result.TotalCount, 0)
|
||||||
|
|
||||||
// Return list of users matching query on email
|
// Return list of users matching query on email
|
||||||
query = models.SearchUsersQuery{Query: "ser1@test.com", Page: 1, Limit: 3}
|
query = models.SearchUsersQuery{Query: "ser1@test.com", Page: 1, Limit: 3, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), &query)
|
err = ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -184,7 +187,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
require.EqualValues(t, query.Result.TotalCount, 1)
|
require.EqualValues(t, query.Result.TotalCount, 1)
|
||||||
|
|
||||||
// Return list of users matching query on login name
|
// Return list of users matching query on login name
|
||||||
query = models.SearchUsersQuery{Query: "loginuser1", Page: 1, Limit: 3}
|
query = models.SearchUsersQuery{Query: "loginuser1", Page: 1, Limit: 3, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), &query)
|
err = ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -204,7 +207,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
isDisabled := false
|
isDisabled := false
|
||||||
query := models.SearchUsersQuery{IsDisabled: &isDisabled}
|
query := models.SearchUsersQuery{IsDisabled: &isDisabled, SignedInUser: user}
|
||||||
err := ss.SearchUsers(context.Background(), &query)
|
err := ss.SearchUsers(context.Background(), &query)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
@ -251,7 +254,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
err = ss.DeleteUser(context.Background(), &models.DeleteUserCommand{UserId: users[1].Id})
|
err = ss.DeleteUser(context.Background(), &models.DeleteUserCommand{UserId: users[1].Id})
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
query1 := &models.GetOrgUsersQuery{OrgId: users[0].OrgId}
|
query1 := &models.GetOrgUsersQuery{OrgId: users[0].OrgId, User: user}
|
||||||
err = ss.GetOrgUsersForTest(context.Background(), query1)
|
err = ss.GetOrgUsersForTest(context.Background(), query1)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
@ -314,7 +317,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
isDisabled = true
|
isDisabled = true
|
||||||
query5 := &models.SearchUsersQuery{IsDisabled: &isDisabled}
|
query5 := &models.SearchUsersQuery{IsDisabled: &isDisabled, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), query5)
|
err = ss.SearchUsers(context.Background(), query5)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -339,7 +342,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Testing DB - return list of users that the SignedInUser has permission to read", func(t *testing.T) {
|
t.Run("Testing DB - return list of users that the SignedInUser has permission to read", func(t *testing.T) {
|
||||||
ss := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
ss := InitTestDB(t)
|
||||||
createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
|
createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
|
||||||
return &models.CreateUserCommand{
|
return &models.CreateUserCommand{
|
||||||
Email: fmt.Sprint("user", i, "@test.com"),
|
Email: fmt.Sprint("user", i, "@test.com"),
|
||||||
@ -380,7 +383,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
isDisabled := false
|
isDisabled := false
|
||||||
query := &models.SearchUsersQuery{IsDisabled: &isDisabled}
|
query := &models.SearchUsersQuery{IsDisabled: &isDisabled, SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), query)
|
err = ss.SearchUsers(context.Background(), query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -411,7 +414,7 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
err := ss.BatchDisableUsers(context.Background(), &disableCmd)
|
err := ss.BatchDisableUsers(context.Background(), &disableCmd)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
query := models.SearchUsersQuery{}
|
query := models.SearchUsersQuery{SignedInUser: user}
|
||||||
err = ss.SearchUsers(context.Background(), &query)
|
err = ss.SearchUsers(context.Background(), &query)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -443,6 +443,9 @@ type Cfg struct {
|
|||||||
QueryHistoryEnabled bool
|
QueryHistoryEnabled bool
|
||||||
|
|
||||||
DashboardPreviews DashboardPreviewsSettings
|
DashboardPreviews DashboardPreviewsSettings
|
||||||
|
|
||||||
|
// Access Control
|
||||||
|
RBACEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandLineArgs struct {
|
type CommandLineArgs struct {
|
||||||
@ -925,6 +928,7 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
|
|||||||
if err := readAuthSettings(iniFile, cfg); err != nil {
|
if err := readAuthSettings(iniFile, cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
readAccessControlSettings(iniFile, cfg)
|
||||||
if err := cfg.readRenderingSettings(iniFile); err != nil {
|
if err := cfg.readRenderingSettings(iniFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1351,6 +1355,11 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readAccessControlSettings(iniFile *ini.File, cfg *Cfg) {
|
||||||
|
rbac := iniFile.Section("rbac")
|
||||||
|
cfg.RBACEnabled = rbac.Key("enabled").MustBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
func readUserSettings(iniFile *ini.File, cfg *Cfg) error {
|
func readUserSettings(iniFile *ini.File, cfg *Cfg) error {
|
||||||
users := iniFile.Section("users")
|
users := iniFile.Section("users")
|
||||||
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
|
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
|
||||||
|
@ -152,8 +152,8 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
|
|||||||
|
|
||||||
// Now, let's set an alert that should fire as quickly as possible.
|
// Now, let's set an alert that should fire as quickly as possible.
|
||||||
{
|
{
|
||||||
// create the namespace we'll save our alerts to
|
// Create the namespace we'll save our alerts to
|
||||||
_, err := createFolder(t, s, 0, "default")
|
err := createFolder(t, "default", grafanaListedAddr, "grafana", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
interval, err := model.ParseDuration("10s")
|
interval, err := model.ParseDuration("10s")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -16,10 +16,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
@ -97,13 +95,13 @@ func TestAMConfigAccess(t *testing.T) {
|
|||||||
desc: "un-authenticated request should fail",
|
desc: "un-authenticated request should fail",
|
||||||
url: "http://%s/api/alertmanager/grafana/config/api/v1/alerts",
|
url: "http://%s/api/alertmanager/grafana/config/api/v1/alerts",
|
||||||
expStatus: http.StatusUnauthorized,
|
expStatus: http.StatusUnauthorized,
|
||||||
expBody: `{"message": "Unauthorized"}`,
|
expBody: `{"message":"Unauthorized"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "viewer request should fail",
|
desc: "viewer request should fail",
|
||||||
url: "http://viewer:viewer@%s/api/alertmanager/grafana/config/api/v1/alerts",
|
url: "http://viewer:viewer@%s/api/alertmanager/grafana/config/api/v1/alerts",
|
||||||
expStatus: http.StatusForbidden,
|
expStatus: http.StatusForbidden,
|
||||||
expBody: `{"message": "Permission denied"}`,
|
expBody: `"title":"Access denied"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "editor request should succeed",
|
desc: "editor request should succeed",
|
||||||
@ -132,7 +130,7 @@ func TestAMConfigAccess(t *testing.T) {
|
|||||||
require.Equal(t, tc.expStatus, resp.StatusCode)
|
require.Equal(t, tc.expStatus, resp.StatusCode)
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.JSONEq(t, tc.expBody, string(b))
|
require.Contains(t, string(b), tc.expBody)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -170,10 +168,10 @@ func TestAMConfigAccess(t *testing.T) {
|
|||||||
expBody: `{"message": "Unauthorized"}`,
|
expBody: `{"message": "Unauthorized"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "viewer request should fail",
|
desc: "viewer request should succeed",
|
||||||
url: "http://viewer:viewer@%s/api/alertmanager/grafana/config/api/v1/alerts",
|
url: "http://viewer:viewer@%s/api/alertmanager/grafana/config/api/v1/alerts",
|
||||||
expStatus: http.StatusForbidden,
|
expStatus: http.StatusOK,
|
||||||
expBody: `{"message": "Permission denied"}`,
|
expBody: cfgBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "editor request should succeed",
|
desc: "editor request should succeed",
|
||||||
@ -230,25 +228,25 @@ func TestAMConfigAccess(t *testing.T) {
|
|||||||
desc: "un-authenticated request should fail",
|
desc: "un-authenticated request should fail",
|
||||||
url: "http://%s/api/alertmanager/grafana/config/api/v2/silences",
|
url: "http://%s/api/alertmanager/grafana/config/api/v2/silences",
|
||||||
expStatus: http.StatusUnauthorized,
|
expStatus: http.StatusUnauthorized,
|
||||||
expBody: `{"message": "Unauthorized"}`,
|
expBody: `{"message":"Unauthorized"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "viewer request should fail",
|
desc: "viewer request should fail",
|
||||||
url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silences",
|
url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silences",
|
||||||
expStatus: http.StatusForbidden,
|
expStatus: http.StatusForbidden,
|
||||||
expBody: `{"message": "Permission denied"}`,
|
expBody: `"title":"Access denied"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "editor request should succeed",
|
desc: "editor request should succeed",
|
||||||
url: "http://editor:editor@%s/api/alertmanager/grafana/api/v2/silences",
|
url: "http://editor:editor@%s/api/alertmanager/grafana/api/v2/silences",
|
||||||
expStatus: http.StatusAccepted,
|
expStatus: http.StatusAccepted,
|
||||||
expBody: `{"id": "0", "message":"silence created"}`,
|
expBody: `{"id":"0","message":"silence created"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "admin request should succeed",
|
desc: "admin request should succeed",
|
||||||
url: "http://admin:admin@%s/api/alertmanager/grafana/api/v2/silences",
|
url: "http://admin:admin@%s/api/alertmanager/grafana/api/v2/silences",
|
||||||
expStatus: http.StatusAccepted,
|
expStatus: http.StatusAccepted,
|
||||||
expBody: `{"id": "0", "message":"silence created"}`,
|
expBody: `{"id":"0","message":"silence created"}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +267,7 @@ func TestAMConfigAccess(t *testing.T) {
|
|||||||
re := regexp.MustCompile(`"id":"([\w|-]+)"`)
|
re := regexp.MustCompile(`"id":"([\w|-]+)"`)
|
||||||
b = re.ReplaceAll(b, []byte(`"id":"0"`))
|
b = re.ReplaceAll(b, []byte(`"id":"0"`))
|
||||||
}
|
}
|
||||||
require.JSONEq(t, tc.expBody, string(b))
|
require.Contains(t, string(b), tc.expBody)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -336,25 +334,25 @@ func TestAMConfigAccess(t *testing.T) {
|
|||||||
desc: "un-authenticated request should fail",
|
desc: "un-authenticated request should fail",
|
||||||
url: "http://%s/api/alertmanager/grafana/api/v2/silence/%s",
|
url: "http://%s/api/alertmanager/grafana/api/v2/silence/%s",
|
||||||
expStatus: http.StatusUnauthorized,
|
expStatus: http.StatusUnauthorized,
|
||||||
expBody: `{"message": "Unauthorized"}`,
|
expBody: `{"message":"Unauthorized"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "viewer request should fail",
|
desc: "viewer request should fail",
|
||||||
url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silence/%s",
|
url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silence/%s",
|
||||||
expStatus: http.StatusForbidden,
|
expStatus: http.StatusForbidden,
|
||||||
expBody: `{"message": "Permission denied"}`,
|
expBody: `"title":"Access denied"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "editor request should succeed",
|
desc: "editor request should succeed",
|
||||||
url: "http://editor:editor@%s/api/alertmanager/grafana/api/v2/silence/%s",
|
url: "http://editor:editor@%s/api/alertmanager/grafana/api/v2/silence/%s",
|
||||||
expStatus: http.StatusOK,
|
expStatus: http.StatusOK,
|
||||||
expBody: `{"message": "silence deleted"}`,
|
expBody: `{"message":"silence deleted"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "admin request should succeed",
|
desc: "admin request should succeed",
|
||||||
url: "http://admin:admin@%s/api/alertmanager/grafana/api/v2/silence/%s",
|
url: "http://admin:admin@%s/api/alertmanager/grafana/api/v2/silence/%s",
|
||||||
expStatus: http.StatusOK,
|
expStatus: http.StatusOK,
|
||||||
expBody: `{"message": "silence deleted"}`,
|
expBody: `{"message":"silence deleted"}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +385,7 @@ func TestAMConfigAccess(t *testing.T) {
|
|||||||
if tc.expStatus == http.StatusOK {
|
if tc.expStatus == http.StatusOK {
|
||||||
unconsumedSilenceIdx++
|
unconsumedSilenceIdx++
|
||||||
}
|
}
|
||||||
require.JSONEq(t, tc.expBody, string(b))
|
require.Contains(t, string(b), tc.expBody)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -484,7 +482,8 @@ func TestAlertAndGroupsQuery(t *testing.T) {
|
|||||||
// Now, let's test the endpoint with some alerts.
|
// Now, let's test the endpoint with some alerts.
|
||||||
{
|
{
|
||||||
// Create the namespace we'll save our alerts to.
|
// Create the namespace we'll save our alerts to.
|
||||||
_, err := createFolder(t, store, 0, "default")
|
err := createFolder(t, "default", grafanaListedAddr, "grafana", "password")
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,10 +577,6 @@ func TestRulerAccess(t *testing.T) {
|
|||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
|
|
||||||
// Create the namespace we'll save our alerts to.
|
|
||||||
_, err = createFolder(t, store, 0, "default")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a users to make authenticated requests
|
// Create a users to make authenticated requests
|
||||||
createUser(t, store, models.CreateUserCommand{
|
createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_VIEWER),
|
DefaultOrgRole: string(models.ROLE_VIEWER),
|
||||||
@ -599,36 +594,41 @@ func TestRulerAccess(t *testing.T) {
|
|||||||
Login: "admin",
|
Login: "admin",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create the namespace we'll save our alerts to.
|
||||||
|
err = createFolder(t, "default", grafanaListedAddr, "editor", "editor")
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "editor", "editor")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Now, let's test the access policies.
|
// Now, let's test the access policies.
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
url string
|
url string
|
||||||
expStatus int
|
expStatus int
|
||||||
expectedResponse string
|
expectedMessage string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "un-authenticated request should fail",
|
desc: "un-authenticated request should fail",
|
||||||
url: "http://%s/api/ruler/grafana/api/v1/rules/default",
|
url: "http://%s/api/ruler/grafana/api/v1/rules/default",
|
||||||
expStatus: http.StatusUnauthorized,
|
expStatus: http.StatusUnauthorized,
|
||||||
expectedResponse: `{"message": "Unauthorized"}`,
|
expectedMessage: `Unauthorized`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "viewer request should fail",
|
desc: "viewer request should fail",
|
||||||
url: "http://viewer:viewer@%s/api/ruler/grafana/api/v1/rules/default",
|
url: "http://viewer:viewer@%s/api/ruler/grafana/api/v1/rules/default",
|
||||||
expStatus: http.StatusForbidden,
|
expStatus: http.StatusForbidden,
|
||||||
expectedResponse: `{"message": "Permission denied"}`,
|
expectedMessage: `You'll need additional permissions to perform this action. Permissions needed: any of alert.rules:update, alert.rules:create, alert.rules:delete`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "editor request should succeed",
|
desc: "editor request should succeed",
|
||||||
url: "http://editor:editor@%s/api/ruler/grafana/api/v1/rules/default",
|
url: "http://editor:editor@%s/api/ruler/grafana/api/v1/rules/default",
|
||||||
expStatus: http.StatusAccepted,
|
expStatus: http.StatusAccepted,
|
||||||
expectedResponse: `{"message":"rule group updated successfully"}`,
|
expectedMessage: `rule group updated successfully`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "admin request should succeed",
|
desc: "admin request should succeed",
|
||||||
url: "http://admin:admin@%s/api/ruler/grafana/api/v1/rules/default",
|
url: "http://admin:admin@%s/api/ruler/grafana/api/v1/rules/default",
|
||||||
expStatus: http.StatusAccepted,
|
expStatus: http.StatusAccepted,
|
||||||
expectedResponse: `{"message":"rule group updated successfully"}`,
|
expectedMessage: `rule group updated successfully`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -686,7 +686,10 @@ func TestRulerAccess(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, tc.expStatus, resp.StatusCode)
|
assert.Equal(t, tc.expStatus, resp.StatusCode)
|
||||||
require.JSONEq(t, tc.expectedResponse, string(b))
|
res := &Response{}
|
||||||
|
err = json.Unmarshal(b, &res)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.expectedMessage, res.Message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -706,10 +709,6 @@ func TestDeleteFolderWithRules(t *testing.T) {
|
|||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
|
|
||||||
// Create the namespace we'll save our alerts to.
|
|
||||||
namespaceUID, err := createFolder(t, store, 0, "default")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
createUser(t, store, models.CreateUserCommand{
|
createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_VIEWER),
|
DefaultOrgRole: string(models.ROLE_VIEWER),
|
||||||
Password: "viewer",
|
Password: "viewer",
|
||||||
@ -721,6 +720,12 @@ func TestDeleteFolderWithRules(t *testing.T) {
|
|||||||
Login: "editor",
|
Login: "editor",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create the namespace we'll save our alerts to.
|
||||||
|
namespaceUID := "default"
|
||||||
|
err = createFolder(t, namespaceUID, grafanaListedAddr, "editor", "editor")
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "editor", "editor")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
createRule(t, grafanaListedAddr, "default", "editor", "editor")
|
createRule(t, grafanaListedAddr, "default", "editor", "editor")
|
||||||
|
|
||||||
// First, let's have an editor create a rule within the folder/namespace.
|
// First, let's have an editor create a rule within the folder/namespace.
|
||||||
@ -873,8 +878,9 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create the namespace we'll save our alerts to.
|
// Create the namespace we'll save our alerts to.
|
||||||
_, err = createFolder(t, store, 0, "default")
|
err = createFolder(t, "default", grafanaListedAddr, "grafana", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
interval, err := model.ParseDuration("1m")
|
interval, err := model.ParseDuration("1m")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -2011,10 +2017,6 @@ func TestQuota(t *testing.T) {
|
|||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
|
|
||||||
// Create the namespace we'll save our alerts to.
|
|
||||||
_, err = createFolder(t, store, 0, "default")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a user to make authenticated requests
|
// Create a user to make authenticated requests
|
||||||
createUser(t, store, models.CreateUserCommand{
|
createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||||
@ -2022,6 +2024,11 @@ func TestQuota(t *testing.T) {
|
|||||||
Login: "grafana",
|
Login: "grafana",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create the namespace we'll save our alerts to.
|
||||||
|
err = createFolder(t, "default", grafanaListedAddr, "grafana", "password")
|
||||||
|
require.NoError(t, err)
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
interval, err := model.ParseDuration("1m")
|
interval, err := model.ParseDuration("1m")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -2265,7 +2272,7 @@ func TestEval(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create the namespace we'll save our alerts to.
|
// Create the namespace we'll save our alerts to.
|
||||||
_, err = createFolder(t, store, 0, "default")
|
err = createFolder(t, "default", grafanaListedAddr, "grafana", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// test eval conditions
|
// test eval conditions
|
||||||
@ -2447,8 +2454,8 @@ func TestEval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
expectedStatusCode: http.StatusBadRequest,
|
expectedStatusCode: http.StatusUnauthorized,
|
||||||
expectedMessage: "invalid condition: invalid query A: data source not found: unknown",
|
expectedMessage: "user is not authorized to query one or many data sources used by the rule",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2613,8 +2620,8 @@ func TestEval(t *testing.T) {
|
|||||||
"now": "2021-04-11T14:38:14Z"
|
"now": "2021-04-11T14:38:14Z"
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
expectedStatusCode: http.StatusBadRequest,
|
expectedStatusCode: http.StatusUnauthorized,
|
||||||
expectedMessage: "invalid queries or expressions: invalid query A: data source not found: unknown",
|
expectedMessage: "user is not authorized to query one or many data sources used by the rule",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2650,25 +2657,20 @@ func TestEval(t *testing.T) {
|
|||||||
|
|
||||||
// createFolder creates a folder for storing our alerts under. Grafana uses folders as a replacement for alert namespaces to match its permission model.
|
// createFolder creates a folder for storing our alerts under. Grafana uses folders as a replacement for alert namespaces to match its permission model.
|
||||||
// We use the dashboard command using IsFolder = true to tell it's a folder, it takes the dashboard as the name of the folder.
|
// We use the dashboard command using IsFolder = true to tell it's a folder, it takes the dashboard as the name of the folder.
|
||||||
func createFolder(t *testing.T, store *sqlstore.SQLStore, folderID int64, folderName string) (string, error) {
|
func createFolder(t *testing.T, folderUID, grafanaListedAddr, login, password string) error {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
cmd := models.SaveDashboardCommand{
|
payload := fmt.Sprintf(`{"uid": "%s","title": "%s"}`, folderUID, folderUID)
|
||||||
OrgId: 1, // default organisation
|
u := fmt.Sprintf("http://%s:%s@%s/api/folders", login, password, grafanaListedAddr)
|
||||||
FolderId: folderID,
|
r := strings.NewReader(payload)
|
||||||
IsFolder: true,
|
// nolint:gosec
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
resp, err := http.Post(u, "application/json", r)
|
||||||
"title": folderName,
|
t.Cleanup(func() {
|
||||||
}),
|
require.NoError(t, resp.Body.Close())
|
||||||
}
|
})
|
||||||
dashboardsStore := database.ProvideDashboardStore(store)
|
require.NoError(t, err)
|
||||||
f, err := dashboardsStore.SaveDashboard(cmd)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
return err
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.Uid, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rulesNamespaceWithoutVariableValues takes a apimodels.NamespaceConfigResponse JSON-based input and makes the dynamic fields static e.g. uid, dates, etc.
|
// rulesNamespaceWithoutVariableValues takes a apimodels.NamespaceConfigResponse JSON-based input and makes the dynamic fields static e.g. uid, dates, etc.
|
||||||
|
@ -775,8 +775,9 @@ func TestNotificationChannels(t *testing.T) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// Create the namespace we'll save our alerts to.
|
// Create the namespace we'll save our alerts to.
|
||||||
_, err := createFolder(t, env.SQLStore, 0, "default")
|
err = createFolder(t, "default", grafanaListedAddr, "grafana", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
// Post the alertmanager config.
|
// Post the alertmanager config.
|
||||||
u := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
|
u := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
|
||||||
|
@ -16,7 +16,9 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
dashboardsstore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
acdb "github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types"
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||||
@ -35,10 +37,6 @@ func TestPrometheusRules(t *testing.T) {
|
|||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
|
|
||||||
// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
|
|
||||||
_, err = createFolder(t, store, 0, "default")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a user to make authenticated requests
|
// Create a user to make authenticated requests
|
||||||
createUser(t, store, models.CreateUserCommand{
|
createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||||
@ -46,6 +44,11 @@ func TestPrometheusRules(t *testing.T) {
|
|||||||
Login: "grafana",
|
Login: "grafana",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create the namespace we'll save our alerts to.
|
||||||
|
err = createFolder(t, "default", grafanaListedAddr, "grafana", "password")
|
||||||
|
require.NoError(t, err)
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
interval, err := model.ParseDuration("10s")
|
interval, err := model.ParseDuration("10s")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -330,10 +333,6 @@ func TestPrometheusRulesFilterByDashboard(t *testing.T) {
|
|||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
|
|
||||||
// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
|
|
||||||
dashboardUID, err := createFolder(t, store, 0, "default")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a user to make authenticated requests
|
// Create a user to make authenticated requests
|
||||||
createUser(t, store, models.CreateUserCommand{
|
createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||||
@ -341,6 +340,12 @@ func TestPrometheusRulesFilterByDashboard(t *testing.T) {
|
|||||||
Login: "grafana",
|
Login: "grafana",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create the namespace we'll save our alerts to.
|
||||||
|
dashboardUID := "default"
|
||||||
|
err = createFolder(t, dashboardUID, grafanaListedAddr, "grafana", "password")
|
||||||
|
require.NoError(t, err)
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
interval, err := model.ParseDuration("10s")
|
interval, err := model.ParseDuration("10s")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -625,23 +630,27 @@ func TestPrometheusRulesPermissions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
dashboardsStore := dashboardsstore.ProvideDashboardStore(store)
|
|
||||||
|
|
||||||
// Create a user to make authenticated requests
|
// Create a user to make authenticated requests
|
||||||
createUser(t, store, models.CreateUserCommand{
|
userID := createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||||
Password: "password",
|
Password: "password",
|
||||||
Login: "grafana",
|
Login: "grafana",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create a namespace under default organisation (orgID = 1) where we'll save some alerts.
|
// access control permissions store
|
||||||
_, err = createFolder(t, store, 0, "folder1")
|
permissionsStore := acdb.ProvideService(store)
|
||||||
|
|
||||||
|
// Create the namespace we'll save our alerts to.
|
||||||
|
err = createFolder(t, "folder1", grafanaListedAddr, "grafana", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create another namespace under default organisation (orgID = 1) where we'll save some alerts.
|
// Create the namespace we'll save our alerts to.
|
||||||
_, err = createFolder(t, store, 0, "folder2")
|
err = createFolder(t, "folder2", grafanaListedAddr, "grafana", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
// Create rule under folder1
|
// Create rule under folder1
|
||||||
createRule(t, grafanaListedAddr, "folder1", "grafana", "password")
|
createRule(t, grafanaListedAddr, "folder1", "grafana", "password")
|
||||||
|
|
||||||
@ -717,7 +726,8 @@ func TestPrometheusRulesPermissions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove permissions from folder2
|
// remove permissions from folder2
|
||||||
require.NoError(t, dashboardsStore.UpdateDashboardACL(context.Background(), 2, nil))
|
removeFolderPermission(t, permissionsStore, 1, userID, models.ROLE_EDITOR, "folder2")
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
// make sure that folder2 is not included in the response
|
// make sure that folder2 is not included in the response
|
||||||
{
|
{
|
||||||
@ -764,8 +774,9 @@ func TestPrometheusRulesPermissions(t *testing.T) {
|
|||||||
}`, string(b))
|
}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove permissions from _ALL_ folders
|
// remove permissions from folder1
|
||||||
require.NoError(t, dashboardsStore.UpdateDashboardACL(context.Background(), 1, nil))
|
removeFolderPermission(t, permissionsStore, 1, userID, models.ROLE_EDITOR, "folder1")
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
// make sure that no folders are included in the response
|
// make sure that no folders are included in the response
|
||||||
{
|
{
|
||||||
@ -790,3 +801,42 @@ func TestPrometheusRulesPermissions(t *testing.T) {
|
|||||||
}`, string(b))
|
}`, string(b))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reloadCachedPermissions(t *testing.T, addr, login, password string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://%s:%s@%s/api/access-control/user/permissions?reloadcache=true", login, password, addr)
|
||||||
|
// nolint:gosec
|
||||||
|
resp, err := http.Get(u)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, resp.Body.Close())
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFolderPermission(t *testing.T, store *acdb.AccessControlStore, orgID, userID int64, role models.RoleType, uid string) {
|
||||||
|
t.Helper()
|
||||||
|
// remove user permissions on folder
|
||||||
|
_, _ = store.SetUserResourcePermission(context.Background(), orgID, accesscontrol.User{ID: userID}, types.SetResourcePermissionCommand{
|
||||||
|
Resource: "folders",
|
||||||
|
ResourceID: uid,
|
||||||
|
ResourceAttribute: "uid",
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
// remove org role permissions from folder
|
||||||
|
_, _ = store.SetBuiltInResourcePermission(context.Background(), orgID, string(role), types.SetResourcePermissionCommand{
|
||||||
|
Resource: "folders",
|
||||||
|
ResourceID: uid,
|
||||||
|
ResourceAttribute: "uid",
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
// remove org role children permissions from folder
|
||||||
|
for _, c := range role.Children() {
|
||||||
|
_, _ = store.SetBuiltInResourcePermission(context.Background(), orgID, string(c), types.SetResourcePermissionCommand{
|
||||||
|
Resource: "folders",
|
||||||
|
ResourceID: uid,
|
||||||
|
ResourceAttribute: "uid",
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@ package alerting
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -16,7 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
dashboardsstore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
acdb "github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||||
@ -35,23 +34,25 @@ func TestAlertRulePermissions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
dashboardsStore := dashboardsstore.ProvideDashboardStore(store)
|
permissionsStore := acdb.ProvideService(store)
|
||||||
|
|
||||||
// Create a user to make authenticated requests
|
// Create a user to make authenticated requests
|
||||||
createUser(t, store, models.CreateUserCommand{
|
userID := createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||||
Password: "password",
|
Password: "password",
|
||||||
Login: "grafana",
|
Login: "grafana",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create the namespace we'll save our alerts to.
|
// Create the namespace we'll save our alerts to.
|
||||||
_, err = createFolder(t, store, 0, "folder1")
|
err = createFolder(t, "folder1", grafanaListedAddr, "grafana", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = createFolder(t, store, 0, "folder2")
|
err = createFolder(t, "folder2", grafanaListedAddr, "grafana", "password")
|
||||||
// Create the namespace we'll save our alerts to.
|
// Create the namespace we'll save our alerts to.
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
// Create rule under folder1
|
// Create rule under folder1
|
||||||
createRule(t, grafanaListedAddr, "folder1", "grafana", "password")
|
createRule(t, grafanaListedAddr, "folder1", "grafana", "password")
|
||||||
|
|
||||||
@ -180,7 +181,8 @@ func TestAlertRulePermissions(t *testing.T) {
|
|||||||
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
||||||
|
|
||||||
// remove permissions from folder2
|
// remove permissions from folder2
|
||||||
require.NoError(t, dashboardsStore.UpdateDashboardACL(context.Background(), 2, nil))
|
removeFolderPermission(t, permissionsStore, 1, userID, models.ROLE_EDITOR, "folder2")
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
// make sure that folder2 is not included in the response
|
// make sure that folder2 is not included in the response
|
||||||
// nolint:gosec
|
// nolint:gosec
|
||||||
@ -252,8 +254,9 @@ func TestAlertRulePermissions(t *testing.T) {
|
|||||||
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove permissions from ALL folders.
|
// Remove permissions from folder1.
|
||||||
require.NoError(t, dashboardsStore.UpdateDashboardACL(context.Background(), 1, nil))
|
removeFolderPermission(t, permissionsStore, 1, userID, models.ROLE_EDITOR, "folder1")
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
{
|
{
|
||||||
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
|
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
|
||||||
// nolint:gosec
|
// nolint:gosec
|
||||||
@ -343,12 +346,6 @@ func TestAlertRuleConflictingTitle(t *testing.T) {
|
|||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
|
|
||||||
// Create the namespace we'll save our alerts to.
|
|
||||||
_, err = createFolder(t, store, 0, "folder1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = createFolder(t, store, 0, "folder2")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
createUser(t, store, models.CreateUserCommand{
|
createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_ADMIN),
|
DefaultOrgRole: string(models.ROLE_ADMIN),
|
||||||
@ -356,6 +353,13 @@ func TestAlertRuleConflictingTitle(t *testing.T) {
|
|||||||
Login: "admin",
|
Login: "admin",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create the namespace we'll save our alerts to.
|
||||||
|
err = createFolder(t, "folder1", grafanaListedAddr, "admin", "admin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// Create the namespace we'll save our alerts to.
|
||||||
|
err = createFolder(t, "folder2", grafanaListedAddr, "admin", "admin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
rules := newTestingRuleConfig(t)
|
rules := newTestingRuleConfig(t)
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
@ -479,10 +483,6 @@ func TestRulerRulesFilterByDashboard(t *testing.T) {
|
|||||||
|
|
||||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||||
|
|
||||||
// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
|
|
||||||
dashboardUID, err := createFolder(t, store, 0, "default")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a user to make authenticated requests
|
// Create a user to make authenticated requests
|
||||||
createUser(t, store, models.CreateUserCommand{
|
createUser(t, store, models.CreateUserCommand{
|
||||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||||
@ -490,6 +490,12 @@ func TestRulerRulesFilterByDashboard(t *testing.T) {
|
|||||||
Login: "grafana",
|
Login: "grafana",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
dashboardUID := "default"
|
||||||
|
// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
|
||||||
|
err = createFolder(t, "default", grafanaListedAddr, "grafana", "password")
|
||||||
|
require.NoError(t, err)
|
||||||
|
reloadCachedPermissions(t, grafanaListedAddr, "grafana", "password")
|
||||||
|
|
||||||
interval, err := model.ParseDuration("10s")
|
interval, err := model.ParseDuration("10s")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ export class ContextSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
accessControlEnabled(): boolean {
|
accessControlEnabled(): boolean {
|
||||||
return Boolean(config.featureToggles['accesscontrol']);
|
return config.rbacEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
accessControlBuiltinRefactorEnabled(): boolean {
|
accessControlBuiltinRefactorEnabled(): boolean {
|
||||||
@ -117,7 +117,7 @@ export class ContextSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
licensedAccessControlEnabled(): boolean {
|
licensedAccessControlEnabled(): boolean {
|
||||||
return featureEnabled('accesscontrol') && Boolean(config.featureToggles['accesscontrol']);
|
return featureEnabled('accesscontrol') && config.rbacEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether user has required permission
|
// Checks whether user has required permission
|
||||||
@ -174,7 +174,7 @@ export class ContextSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasAccessInMetadata(action: string, object: WithAccessControlMetadata, fallBack: boolean) {
|
hasAccessInMetadata(action: string, object: WithAccessControlMetadata, fallBack: boolean) {
|
||||||
if (!config.featureToggles['accesscontrol']) {
|
if (!config.rbacEnabled) {
|
||||||
return fallBack;
|
return fallBack;
|
||||||
}
|
}
|
||||||
return this.hasPermissionInMetadata(action, object);
|
return this.hasPermissionInMetadata(action, object);
|
||||||
|
@ -2,7 +2,7 @@ import config from '../../core/config';
|
|||||||
|
|
||||||
// accessControlQueryParam adds an additional accesscontrol=true param to params when accesscontrol is enabled
|
// accessControlQueryParam adds an additional accesscontrol=true param to params when accesscontrol is enabled
|
||||||
export function accessControlQueryParam(params = {}) {
|
export function accessControlQueryParam(params = {}) {
|
||||||
if (!config.featureToggles.accesscontrol) {
|
if (!config.rbacEnabled) {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
return { ...params, accesscontrol: true };
|
return { ...params, accesscontrol: true };
|
||||||
|
@ -11,7 +11,7 @@ import { configureStore } from 'app/store/configureStore';
|
|||||||
|
|
||||||
import MuteTimings from './MuteTimings';
|
import MuteTimings from './MuteTimings';
|
||||||
import { fetchAlertManagerConfig, updateAlertManagerConfig } from './api/alertmanager';
|
import { fetchAlertManagerConfig, updateAlertManagerConfig } from './api/alertmanager';
|
||||||
import { mockDataSource, MockDataSourceSrv } from './mocks';
|
import { disableRBAC, mockDataSource, MockDataSourceSrv } from './mocks';
|
||||||
import { DataSourceType } from './utils/datasource';
|
import { DataSourceType } from './utils/datasource';
|
||||||
|
|
||||||
jest.mock('./api/alertmanager');
|
jest.mock('./api/alertmanager');
|
||||||
@ -113,6 +113,7 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('creates a new mute timing', async () => {
|
it('creates a new mute timing', async () => {
|
||||||
|
disableRBAC();
|
||||||
await renderMuteTimings();
|
await renderMuteTimings();
|
||||||
|
|
||||||
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled());
|
||||||
|
@ -17,6 +17,7 @@ import { PanelAlertTabContent } from './PanelAlertTabContent';
|
|||||||
import { fetchRules } from './api/prometheus';
|
import { fetchRules } from './api/prometheus';
|
||||||
import { fetchRulerRules } from './api/ruler';
|
import { fetchRulerRules } from './api/ruler';
|
||||||
import {
|
import {
|
||||||
|
disableRBAC,
|
||||||
mockDataSource,
|
mockDataSource,
|
||||||
MockDataSourceSrv,
|
MockDataSourceSrv,
|
||||||
mockPromAlertingRule,
|
mockPromAlertingRule,
|
||||||
@ -184,6 +185,7 @@ describe('PanelAlertTabContent', () => {
|
|||||||
any
|
any
|
||||||
>;
|
>;
|
||||||
setDataSourceSrv(dsService);
|
setDataSourceSrv(dsService);
|
||||||
|
disableRBAC();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Will take into account panel maxDataPoints', async () => {
|
it('Will take into account panel maxDataPoints', async () => {
|
||||||
|
@ -19,7 +19,7 @@ import RuleEditor from './RuleEditor';
|
|||||||
import { fetchBuildInfo } from './api/buildInfo';
|
import { fetchBuildInfo } from './api/buildInfo';
|
||||||
import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler';
|
import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler';
|
||||||
import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
|
import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
|
||||||
import { mockDataSource, MockDataSourceSrv } from './mocks';
|
import { disableRBAC, mockDataSource, MockDataSourceSrv } from './mocks';
|
||||||
import { getAllDataSources } from './utils/config';
|
import { getAllDataSources } from './utils/config';
|
||||||
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||||
import { getDefaultQueries } from './utils/rule-form';
|
import { getDefaultQueries } from './utils/rule-form';
|
||||||
@ -100,6 +100,8 @@ describe('RuleEditor', () => {
|
|||||||
contextSrv.isEditor = true;
|
contextSrv.isEditor = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
disableRBAC();
|
||||||
|
|
||||||
it('can create a new cloud alert', async () => {
|
it('can create a new cloud alert', async () => {
|
||||||
const dataSources = {
|
const dataSources = {
|
||||||
default: mockDataSource(
|
default: mockDataSource(
|
||||||
|
@ -16,6 +16,7 @@ import { fetchBuildInfo } from './api/buildInfo';
|
|||||||
import { fetchRules } from './api/prometheus';
|
import { fetchRules } from './api/prometheus';
|
||||||
import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
|
import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
|
||||||
import {
|
import {
|
||||||
|
disableRBAC,
|
||||||
enableRBAC,
|
enableRBAC,
|
||||||
grantUserPermissions,
|
grantUserPermissions,
|
||||||
mockDataSource,
|
mockDataSource,
|
||||||
@ -121,6 +122,7 @@ describe('RuleList', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('load & show rule groups from multiple cloud data sources', async () => {
|
it('load & show rule groups from multiple cloud data sources', async () => {
|
||||||
|
disableRBAC();
|
||||||
mocks.getAllDataSourcesMock.mockReturnValue(Object.values(dataSources));
|
mocks.getAllDataSourcesMock.mockReturnValue(Object.values(dataSources));
|
||||||
|
|
||||||
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
fetchStatus,
|
fetchStatus,
|
||||||
} from '../../api/alertmanager';
|
} from '../../api/alertmanager';
|
||||||
import {
|
import {
|
||||||
|
disableRBAC,
|
||||||
mockDataSource,
|
mockDataSource,
|
||||||
MockDataSourceSrv,
|
MockDataSourceSrv,
|
||||||
someCloudAlertManagerConfig,
|
someCloudAlertManagerConfig,
|
||||||
@ -94,6 +95,7 @@ describe('Admin config', () => {
|
|||||||
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
||||||
contextSrv.isGrafanaAdmin = true;
|
contextSrv.isGrafanaAdmin = true;
|
||||||
store.delete(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
|
store.delete(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
|
||||||
|
disableRBAC();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Reset alertmanager config', async () => {
|
it('Reset alertmanager config', async () => {
|
||||||
|
@ -7,7 +7,7 @@ import { byTestId, byText } from 'testing-library-selector';
|
|||||||
import { configureStore } from 'app/store/configureStore';
|
import { configureStore } from 'app/store/configureStore';
|
||||||
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||||
|
|
||||||
import { mockCombinedRule, mockDataSource } from '../../mocks';
|
import { disableRBAC, mockCombinedRule, mockDataSource } from '../../mocks';
|
||||||
|
|
||||||
import { RulesGroup } from './RulesGroup';
|
import { RulesGroup } from './RulesGroup';
|
||||||
|
|
||||||
@ -72,6 +72,8 @@ describe('Rules group tests', () => {
|
|||||||
groups: [group],
|
groups: [group],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
disableRBAC();
|
||||||
|
|
||||||
it('When ruler enabled should display delete and edit group buttons', () => {
|
it('When ruler enabled should display delete and edit group buttons', () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
hasRulerMock.mockReturnValue(true);
|
hasRulerMock.mockReturnValue(true);
|
||||||
|
@ -471,6 +471,10 @@ export const enableRBAC = () => {
|
|||||||
jest.spyOn(contextSrv, 'accessControlEnabled').mockReturnValue(true);
|
jest.spyOn(contextSrv, 'accessControlEnabled').mockReturnValue(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const disableRBAC = () => {
|
||||||
|
jest.spyOn(contextSrv, 'accessControlEnabled').mockReturnValue(false);
|
||||||
|
};
|
||||||
|
|
||||||
export const grantUserPermissions = (permissions: AccessControlAction[]) => {
|
export const grantUserPermissions = (permissions: AccessControlAction[]) => {
|
||||||
jest
|
jest
|
||||||
.spyOn(contextSrv, 'hasPermission')
|
.spyOn(contextSrv, 'hasPermission')
|
||||||
|
@ -117,7 +117,7 @@ export function DashboardSettings({ dashboard, editview }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dashboard.id && dashboard.meta.canAdmin) {
|
if (dashboard.id && dashboard.meta.canAdmin) {
|
||||||
if (!config.featureToggles['accesscontrol']) {
|
if (!config.rbacEnabled) {
|
||||||
pages.push({
|
pages.push({
|
||||||
title: 'Permissions',
|
title: 'Permissions',
|
||||||
id: 'permissions',
|
id: 'permissions',
|
||||||
|
@ -1297,6 +1297,7 @@ describe('grafanaGraph', () => {
|
|||||||
describe('when called and user can edit the dashboard', () => {
|
describe('when called and user can edit the dashboard', () => {
|
||||||
it('then the correct menu items should be returned', () => {
|
it('then the correct menu items should be returned', () => {
|
||||||
const element = getGraphElement({ canEdit: true, canMakeEditable: false });
|
const element = getGraphElement({ canEdit: true, canMakeEditable: false });
|
||||||
|
jest.spyOn(element.dashboard, 'canAddAnnotations').mockReturnValue(true);
|
||||||
|
|
||||||
const result = element.getContextMenuItemsSupplier({ x: 1, y: 1 })();
|
const result = element.getContextMenuItemsSupplier({ x: 1, y: 1 })();
|
||||||
|
|
||||||
@ -1311,6 +1312,7 @@ describe('grafanaGraph', () => {
|
|||||||
describe('when called and user can make the dashboard editable', () => {
|
describe('when called and user can make the dashboard editable', () => {
|
||||||
it('then the correct menu items should be returned', () => {
|
it('then the correct menu items should be returned', () => {
|
||||||
const element = getGraphElement({ canEdit: false, canMakeEditable: true });
|
const element = getGraphElement({ canEdit: false, canMakeEditable: true });
|
||||||
|
jest.spyOn(element.dashboard, 'canAddAnnotations').mockReturnValue(true);
|
||||||
|
|
||||||
const result = element.getContextMenuItemsSupplier({ x: 1, y: 1 })();
|
const result = element.getContextMenuItemsSupplier({ x: 1, y: 1 })();
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ export function getAppRoutes(): RouteDescriptor[] {
|
|||||||
{
|
{
|
||||||
path: '/dashboards/f/:uid/:slug/permissions',
|
path: '/dashboards/f/:uid/:slug/permissions',
|
||||||
component:
|
component:
|
||||||
config.featureToggles['accesscontrol'] && contextSrv.hasPermission(AccessControlAction.FoldersPermissionsRead)
|
config.rbacEnabled && contextSrv.hasPermission(AccessControlAction.FoldersPermissionsRead)
|
||||||
? SafeDynamicImport(
|
? SafeDynamicImport(
|
||||||
() =>
|
() =>
|
||||||
import(/* webpackChunkName: "FolderPermissions"*/ 'app/features/folders/AccessControlFolderPermissions')
|
import(/* webpackChunkName: "FolderPermissions"*/ 'app/features/folders/AccessControlFolderPermissions')
|
||||||
|
Loading…
Reference in New Issue
Block a user