mirror of
https://github.com/grafana/grafana.git
synced 2025-01-18 12:33:37 -06:00
Alerting: handle folder permissions when fine-grained access enabled (#47035)
* Use alert:create action for folder search with edit permissions. This matches the action that is used to query dashboards (the update will be addressed later) * Update rule store to use FindDashboards instead of folder service to list folders the user has access to view alerts. Folder service does not support query type and additional filters. * Do not check whether the user can save to folder if FGAC is enabled because it is checked on API level.
This commit is contained in:
parent
5c3308c6f3
commit
51114527dc
@ -85,7 +85,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
|
||||
labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels())
|
||||
}
|
||||
|
||||
namespaceMap, err := srv.store.GetNamespaces(c.Req.Context(), c.OrgId, c.SignedInUser)
|
||||
namespaceMap, err := srv.store.GetUserVisibleNamespaces(c.Req.Context(), c.OrgId, c.SignedInUser)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to get namespaces visible to the user")
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Resp
|
||||
}
|
||||
|
||||
func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response {
|
||||
namespaceMap, err := srv.store.GetNamespaces(c.Req.Context(), c.OrgId, c.SignedInUser)
|
||||
namespaceMap, err := srv.store.GetUserVisibleNamespaces(c.Req.Context(), c.OrgId, c.SignedInUser)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to get namespaces visible to the user")
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ func (ng *AlertNG) init() error {
|
||||
SQLStore: ng.SQLStore,
|
||||
Logger: ng.Log,
|
||||
FolderService: ng.folderService,
|
||||
AccessControl: ng.accesscontrol,
|
||||
}
|
||||
|
||||
decryptFn := ng.SecretsService.GetDecryptedValue
|
||||
|
@ -6,13 +6,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@ -42,7 +41,7 @@ type RuleStore interface {
|
||||
GetOrgAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error
|
||||
GetNamespaceAlertRules(ctx context.Context, query *ngmodels.ListNamespaceAlertRulesQuery) error
|
||||
GetAlertRules(ctx context.Context, query *ngmodels.GetAlertRulesQuery) error
|
||||
GetNamespaces(context.Context, int64, *models.SignedInUser) (map[string]*models.Folder, error)
|
||||
GetUserVisibleNamespaces(context.Context, int64, *models.SignedInUser) (map[string]*models.Folder, error)
|
||||
GetNamespaceByTitle(context.Context, string, int64, *models.SignedInUser, bool) (*models.Folder, error)
|
||||
GetOrgRuleGroups(ctx context.Context, query *ngmodels.ListOrgRuleGroupsQuery) error
|
||||
UpsertAlertRules(ctx context.Context, rule []UpsertRule) error
|
||||
@ -270,23 +269,44 @@ func (st DBstore) GetAlertRules(ctx context.Context, query *ngmodels.GetAlertRul
|
||||
})
|
||||
}
|
||||
|
||||
// GetNamespaces returns the folders that are visible to the user
|
||||
func (st DBstore) GetNamespaces(ctx context.Context, orgID int64, user *models.SignedInUser) (map[string]*models.Folder, error) {
|
||||
// GetNamespaces returns the folders that are visible to the user and have at least one alert in it
|
||||
func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user *models.SignedInUser) (map[string]*models.Folder, error) {
|
||||
namespaceMap := make(map[string]*models.Folder)
|
||||
|
||||
searchQuery := models.FindPersistedDashboardsQuery{
|
||||
OrgId: orgID,
|
||||
SignedInUser: user,
|
||||
Type: searchstore.TypeAlertFolder,
|
||||
Limit: -1,
|
||||
Permission: models.PERMISSION_VIEW,
|
||||
Sort: models.SortOption{},
|
||||
Filters: []interface{}{
|
||||
searchstore.FolderWithAlertsFilter{},
|
||||
},
|
||||
}
|
||||
|
||||
var page int64 = 1
|
||||
for {
|
||||
// if limit is negative; it fetches at most 1000
|
||||
folders, err := st.FolderService.GetFolders(ctx, user, orgID, -1, page)
|
||||
query := searchQuery
|
||||
query.Page = page
|
||||
proj, err := st.SQLStore.FindDashboards(ctx, &query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(folders) == 0 {
|
||||
if len(proj) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, f := range folders {
|
||||
namespaceMap[f.Uid] = f
|
||||
for _, hit := range proj {
|
||||
if !hit.IsFolder {
|
||||
continue
|
||||
}
|
||||
namespaceMap[hit.UID] = &models.Folder{
|
||||
Id: hit.ID,
|
||||
Uid: hit.UID,
|
||||
Title: hit.Title,
|
||||
}
|
||||
}
|
||||
page += 1
|
||||
}
|
||||
@ -300,7 +320,8 @@ func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, org
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if withCanSave {
|
||||
// if access control is disabled, check that the user is allowed to save in the folder.
|
||||
if withCanSave && st.AccessControl.IsDisabled() {
|
||||
g := guardian.New(ctx, folder.Id, orgID, user)
|
||||
if canSave, err := g.CanSave(); err != nil || !canSave {
|
||||
if err != nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
@ -34,4 +35,5 @@ type DBstore struct {
|
||||
SQLStore *sqlstore.SQLStore
|
||||
Logger log.Logger
|
||||
FolderService dashboards.FolderService
|
||||
AccessControl accesscontrol.AccessControl
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ func (f *FakeRuleStore) GetAlertRules(_ context.Context, q *models.GetAlertRules
|
||||
q.Result = result
|
||||
return nil
|
||||
}
|
||||
func (f *FakeRuleStore) GetNamespaces(_ context.Context, orgID int64, _ *models2.SignedInUser) (map[string]*models2.Folder, error) {
|
||||
func (f *FakeRuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ *models2.SignedInUser) (map[string]*models2.Folder, error) {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
|
||||
|
@ -91,7 +91,7 @@ func NewAccessControlDashboardPermissionFilter(user *models.SignedInUser, permis
|
||||
if queryType == searchstore.TypeAlertFolder {
|
||||
folderActions = append(folderActions, accesscontrol.ActionAlertingRuleRead)
|
||||
if needEdit {
|
||||
folderActions = append(folderActions, accesscontrol.ActionAlertingRuleUpdate)
|
||||
folderActions = append(folderActions, accesscontrol.ActionAlertingRuleCreate)
|
||||
}
|
||||
} else {
|
||||
dashboardActions = append(dashboardActions, accesscontrol.ActionDashboardsRead)
|
||||
|
@ -29,7 +29,7 @@ func TestNewAccessControlDashboardPermissionFilter(t *testing.T) {
|
||||
expectedFolderActions: []string{
|
||||
dashboards.ActionFoldersRead,
|
||||
accesscontrol.ActionAlertingRuleRead,
|
||||
accesscontrol.ActionAlertingRuleUpdate,
|
||||
accesscontrol.ActionAlertingRuleCreate,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -39,7 +39,7 @@ func TestNewAccessControlDashboardPermissionFilter(t *testing.T) {
|
||||
expectedFolderActions: []string{
|
||||
dashboards.ActionFoldersRead,
|
||||
accesscontrol.ActionAlertingRuleRead,
|
||||
accesscontrol.ActionAlertingRuleUpdate,
|
||||
accesscontrol.ActionAlertingRuleCreate,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -149,3 +149,13 @@ func sqlIDin(column string, ids []int64) (string, []interface{}) {
|
||||
}
|
||||
return fmt.Sprintf("%s IN %s", column, sqlArray), params
|
||||
}
|
||||
|
||||
// FolderWithAlertsFilter applies a filter that makes the result contain only folders that contain alert rules
|
||||
type FolderWithAlertsFilter struct {
|
||||
}
|
||||
|
||||
var _ FilterWhere = &FolderWithAlertsFilter{}
|
||||
|
||||
func (f FolderWithAlertsFilter) Where() (string, []interface{}) {
|
||||
return "EXISTS (SELECT 1 FROM alert_rule WHERE alert_rule.namespace_uid = dashboard.uid)", nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user