Alerting: Fix folder permissions for Editor role in Prometheus import (#109977)
Alerting: Fix folder permisisons for Editor role in Prometheus import
This commit is contained in:
@@ -461,7 +461,7 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroups(c *context
|
||||
func (srv *ConvertPrometheusSrv) getOrCreateNamespace(c *contextmodel.ReqContext, title string, logger log.Logger, workingFolderUID string) (*folder.FolderReference, response.Response) {
|
||||
logger.Debug("Getting or creating a new folder")
|
||||
|
||||
ns, err := srv.ruleStore.GetOrCreateNamespaceByTitle(
|
||||
ns, created, err := srv.ruleStore.GetOrCreateNamespaceByTitle(
|
||||
c.Req.Context(),
|
||||
title,
|
||||
c.GetOrgID(),
|
||||
@@ -473,6 +473,26 @@ func (srv *ConvertPrometheusSrv) getOrCreateNamespace(c *contextmodel.ReqContext
|
||||
return nil, namespaceErrorResponse(err)
|
||||
}
|
||||
|
||||
// Not all users have global-scoped permissions, even if they can create folders.
|
||||
// For example, Editor users can create folders, but they have UID-scoped folder permissions.
|
||||
// Permissions are populated in a middleware before this handler, and the folder we just created
|
||||
// is not included in the permissions yet. We add it manually.
|
||||
if created {
|
||||
orgID := c.GetOrgID()
|
||||
if c.Permissions == nil {
|
||||
c.Permissions = make(map[int64]map[string][]string)
|
||||
}
|
||||
if c.Permissions[orgID] == nil {
|
||||
c.Permissions[orgID] = make(map[string][]string)
|
||||
}
|
||||
|
||||
folderScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(ns.UID)
|
||||
if c.Permissions[orgID][dashboards.ActionFoldersRead] == nil {
|
||||
c.Permissions[orgID][dashboards.ActionFoldersRead] = []string{}
|
||||
}
|
||||
c.Permissions[orgID][dashboards.ActionFoldersRead] = append(c.Permissions[orgID][dashboards.ActionFoldersRead], folderScope)
|
||||
}
|
||||
|
||||
logger.Debug("Using folder for the converted rules", "folder_uid", ns.UID)
|
||||
|
||||
return ns, nil
|
||||
|
||||
@@ -16,7 +16,7 @@ type RuleStore interface {
|
||||
GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error)
|
||||
GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error)
|
||||
GetNamespaceByTitle(ctx context.Context, fullpath string, orgID int64, user identity.Requester, parentUID string) (*folder.FolderReference, error)
|
||||
GetOrCreateNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.FolderReference, error)
|
||||
GetOrCreateNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.FolderReference, bool, error)
|
||||
// GetNamespaceChildren returns all children (first level) of the namespace with the given id.
|
||||
GetNamespaceChildren(ctx context.Context, uid string, orgID int64, user identity.Requester) ([]*folder.FolderReference, error)
|
||||
|
||||
|
||||
@@ -95,24 +95,25 @@ func (st DBstore) GetNamespaceByTitle(ctx context.Context, title string, orgID i
|
||||
//
|
||||
// To avoid race conditions when two concurrent requests try to create the same folder,
|
||||
// we create folders with a deterministic UID based on the parent UID, title, and organization ID.
|
||||
func (st DBstore) GetOrCreateNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.FolderReference, error) {
|
||||
func (st DBstore) GetOrCreateNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.FolderReference, bool, error) {
|
||||
if len(title) == 0 {
|
||||
return nil, fmt.Errorf("title is empty")
|
||||
return nil, false, fmt.Errorf("title is empty")
|
||||
}
|
||||
|
||||
var f *folder.FolderReference
|
||||
var err error
|
||||
var created bool
|
||||
|
||||
f, err = st.GetNamespaceByTitle(ctx, title, orgID, user, parentUID)
|
||||
if err != nil && !errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
// Generate a deterministic UID with an alerting prefix
|
||||
uid, err := generateAlertingFolderUID(title, parentUID, orgID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating a new folder: %w", err)
|
||||
return nil, false, fmt.Errorf("error creating a new folder: %w", err)
|
||||
}
|
||||
|
||||
cmd := &folder.CreateFolderCommand{
|
||||
@@ -129,20 +130,21 @@ func (st DBstore) GetOrCreateNamespaceByTitle(ctx context.Context, title string,
|
||||
// the folder between our check and creation attempt
|
||||
existingFolder, lookupErr := st.GetNamespaceByTitle(ctx, title, orgID, user, parentUID)
|
||||
if lookupErr == nil {
|
||||
return existingFolder, nil
|
||||
return existingFolder, false, nil
|
||||
}
|
||||
|
||||
// If we couldn't find it, return errors
|
||||
return nil, fmt.Errorf("failed to get or create folder: %w", errors.Join(
|
||||
return nil, false, fmt.Errorf("failed to get or create folder: %w", errors.Join(
|
||||
fmt.Errorf("create folder: %w", err),
|
||||
fmt.Errorf("lookup folder: %w", lookupErr),
|
||||
))
|
||||
}
|
||||
|
||||
f = newFolder.ToFolderReference()
|
||||
created = true
|
||||
}
|
||||
|
||||
return f, nil
|
||||
return f, created, nil
|
||||
}
|
||||
|
||||
// generateAlertingFolderUID creates a deterministic UID for folders
|
||||
|
||||
@@ -78,7 +78,8 @@ func TestGetNamespaceByTitle(t *testing.T) {
|
||||
|
||||
func TestGetOrCreateNamespaceByTitle(t *testing.T) {
|
||||
store := DBstore{}
|
||||
_, err := store.GetOrCreateNamespaceByTitle(context.Background(), "", 1, nil, folder.RootFolderUID)
|
||||
_, created, err := store.GetOrCreateNamespaceByTitle(context.Background(), "", 1, nil, folder.RootFolderUID)
|
||||
require.False(t, created)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "title is empty")
|
||||
|
||||
|
||||
@@ -363,13 +363,13 @@ func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64
|
||||
return nil, dashboards.ErrFolderNotFound
|
||||
}
|
||||
|
||||
func (f *RuleStore) GetOrCreateNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.FolderReference, error) {
|
||||
func (f *RuleStore) GetOrCreateNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.FolderReference, bool, error) {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
|
||||
for _, folder := range f.Folders[orgID] {
|
||||
if folder.Title == title && folder.ParentUID == parentUID {
|
||||
return folder.ToFolderReference(), nil
|
||||
return folder.ToFolderReference(), false, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ func (f *RuleStore) GetOrCreateNamespaceByTitle(ctx context.Context, title strin
|
||||
}
|
||||
|
||||
f.Folders[orgID] = append(f.Folders[orgID], newFolder)
|
||||
return newFolder.ToFolderReference(), nil
|
||||
return newFolder.ToFolderReference(), true, nil
|
||||
}
|
||||
|
||||
func (f *RuleStore) GetNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.FolderReference, error) {
|
||||
|
||||
Reference in New Issue
Block a user