mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 17:32:12 +08:00
LibraryPanels: Add RBAC support (#73475)
This commit is contained in:
@ -136,6 +136,7 @@ Experimental features might be changed or removed without prior notice.
|
|||||||
| `dashgpt` | Enable AI powered features in dashboards |
|
| `dashgpt` | Enable AI powered features in dashboards |
|
||||||
| `sseGroupByDatasource` | Send query to the same datasource in a single request when using server side expressions |
|
| `sseGroupByDatasource` | Send query to the same datasource in a single request when using server side expressions |
|
||||||
| `requestInstrumentationStatusSource` | Include a status source label for request metrics and logs |
|
| `requestInstrumentationStatusSource` | Include a status source label for request metrics and logs |
|
||||||
|
| `libraryPanelRBAC` | Enables RBAC support for library panels |
|
||||||
| `wargamesTesting` | Placeholder feature flag for internal testing |
|
| `wargamesTesting` | Placeholder feature flag for internal testing |
|
||||||
| `alertingInsights` | Show the new alerting insights landing page |
|
| `alertingInsights` | Show the new alerting insights landing page |
|
||||||
| `externalCorePlugins` | Allow core plugins to be loaded as external |
|
| `externalCorePlugins` | Allow core plugins to be loaded as external |
|
||||||
|
@ -125,6 +125,7 @@ export interface FeatureToggles {
|
|||||||
newBrowseDashboards?: boolean;
|
newBrowseDashboards?: boolean;
|
||||||
sseGroupByDatasource?: boolean;
|
sseGroupByDatasource?: boolean;
|
||||||
requestInstrumentationStatusSource?: boolean;
|
requestInstrumentationStatusSource?: boolean;
|
||||||
|
libraryPanelRBAC?: boolean;
|
||||||
lokiRunQueriesInParallel?: boolean;
|
lokiRunQueriesInParallel?: boolean;
|
||||||
wargamesTesting?: boolean;
|
wargamesTesting?: boolean;
|
||||||
alertingInsights?: boolean;
|
alertingInsights?: boolean;
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||||
@ -408,6 +410,76 @@ func (hs *HTTPServer) declareFixedRoles() error {
|
|||||||
Grants: []string{"Admin"},
|
Grants: []string{"Admin"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
libraryPanelsCreatorRole := ac.RoleRegistration{
|
||||||
|
Role: ac.RoleDTO{
|
||||||
|
Name: "fixed:library.panels:creator",
|
||||||
|
DisplayName: "Library panel creator",
|
||||||
|
Description: "Create library panel in general folder.",
|
||||||
|
Group: "Library panels",
|
||||||
|
Permissions: []ac.Permission{
|
||||||
|
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)},
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsCreate, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Grants: []string{"Editor"},
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryPanelsReaderRole := ac.RoleRegistration{
|
||||||
|
Role: ac.RoleDTO{
|
||||||
|
Name: "fixed:library.panels:reader",
|
||||||
|
DisplayName: "Library panel reader",
|
||||||
|
Description: "Read all library panels.",
|
||||||
|
Group: "Library panels",
|
||||||
|
Permissions: []ac.Permission{
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsRead, Scope: libraryelements.ScopeLibraryPanelsAll},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Grants: []string{"Admin"},
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryPanelsGeneralReaderRole := ac.RoleRegistration{
|
||||||
|
Role: ac.RoleDTO{
|
||||||
|
Name: "fixed:library.panels:general.reader",
|
||||||
|
DisplayName: "Library panel general reader",
|
||||||
|
Description: "Read all library panels in general folder.",
|
||||||
|
Group: "Library panels",
|
||||||
|
Permissions: []ac.Permission{
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Grants: []string{"Viewer"},
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryPanelsWriterRole := ac.RoleRegistration{
|
||||||
|
Role: ac.RoleDTO{
|
||||||
|
Name: "fixed:library.panels:writer",
|
||||||
|
DisplayName: "Library panel writer",
|
||||||
|
Group: "Library panels",
|
||||||
|
Description: "Create, read, write or delete all library panels and their permissions.",
|
||||||
|
Permissions: ac.ConcatPermissions(libraryPanelsReaderRole.Role.Permissions, []ac.Permission{
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsWrite, Scope: libraryelements.ScopeLibraryPanelsAll},
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsDelete, Scope: libraryelements.ScopeLibraryPanelsAll},
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsCreate, Scope: libraryelements.ScopeLibraryPanelsAll},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Grants: []string{"Admin"},
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryPanelsGeneralWriterRole := ac.RoleRegistration{
|
||||||
|
Role: ac.RoleDTO{
|
||||||
|
Name: "fixed:library.panels:general.writer",
|
||||||
|
DisplayName: "Library panel general writer",
|
||||||
|
Group: "Library panels",
|
||||||
|
Description: "Create, read, write or delete all library panels and their permissions in the general folder.",
|
||||||
|
Permissions: ac.ConcatPermissions(libraryPanelsGeneralReaderRole.Role.Permissions, []ac.Permission{
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)},
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsDelete, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)},
|
||||||
|
{Action: libraryelements.ActionLibraryPanelsCreate, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Grants: []string{"Editor"},
|
||||||
|
}
|
||||||
|
|
||||||
publicDashboardsWriterRole := ac.RoleRegistration{
|
publicDashboardsWriterRole := ac.RoleRegistration{
|
||||||
Role: ac.RoleDTO{
|
Role: ac.RoleDTO{
|
||||||
Name: "fixed:dashboards.public:writer",
|
Name: "fixed:dashboards.public:writer",
|
||||||
@ -447,15 +519,18 @@ func (hs *HTTPServer) declareFixedRoles() error {
|
|||||||
Grants: []string{"Admin"},
|
Grants: []string{"Admin"},
|
||||||
}
|
}
|
||||||
|
|
||||||
return hs.accesscontrolService.DeclareFixedRoles(
|
roles := []ac.RoleRegistration{provisioningWriterRole, datasourcesReaderRole, builtInDatasourceReader, datasourcesWriterRole,
|
||||||
provisioningWriterRole, datasourcesReaderRole, builtInDatasourceReader, datasourcesWriterRole,
|
|
||||||
datasourcesIdReaderRole, orgReaderRole, orgWriterRole,
|
datasourcesIdReaderRole, orgReaderRole, orgWriterRole,
|
||||||
orgMaintainerRole, teamsCreatorRole, teamsWriterRole, datasourcesExplorerRole,
|
orgMaintainerRole, teamsCreatorRole, teamsWriterRole, datasourcesExplorerRole,
|
||||||
annotationsReaderRole, dashboardAnnotationsWriterRole, annotationsWriterRole,
|
annotationsReaderRole, dashboardAnnotationsWriterRole, annotationsWriterRole,
|
||||||
dashboardsCreatorRole, dashboardsReaderRole, dashboardsWriterRole,
|
dashboardsCreatorRole, dashboardsReaderRole, dashboardsWriterRole,
|
||||||
foldersCreatorRole, foldersReaderRole, foldersWriterRole, apikeyReaderRole, apikeyWriterRole,
|
foldersCreatorRole, foldersReaderRole, foldersWriterRole, apikeyReaderRole, apikeyWriterRole,
|
||||||
publicDashboardsWriterRole, featuremgmtReaderRole, featuremgmtWriterRole,
|
publicDashboardsWriterRole, featuremgmtReaderRole, featuremgmtWriterRole}
|
||||||
)
|
if hs.Features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) {
|
||||||
|
roles = append(roles, libraryPanelsCreatorRole, libraryPanelsReaderRole, libraryPanelsWriterRole, libraryPanelsGeneralReaderRole, libraryPanelsGeneralWriterRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hs.accesscontrolService.DeclareFixedRoles(roles...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata helpers
|
// Metadata helpers
|
||||||
|
@ -468,6 +468,12 @@ const (
|
|||||||
// Feature Management actions
|
// Feature Management actions
|
||||||
ActionFeatureManagementRead = "featuremgmt.read"
|
ActionFeatureManagementRead = "featuremgmt.read"
|
||||||
ActionFeatureManagementWrite = "featuremgmt.write"
|
ActionFeatureManagementWrite = "featuremgmt.write"
|
||||||
|
|
||||||
|
// Library Panel actions
|
||||||
|
ActionLibraryPanelsCreate = "library.panels:create"
|
||||||
|
ActionLibraryPanelsRead = "library.panels:read"
|
||||||
|
ActionLibraryPanelsWrite = "library.panels:write"
|
||||||
|
ActionLibraryPanelsDelete = "library.panels:delete"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
|
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||||
"github.com/grafana/grafana/pkg/services/licensing"
|
"github.com/grafana/grafana/pkg/services/licensing"
|
||||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
|
"github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
|
||||||
@ -191,7 +192,7 @@ type FolderPermissionsService struct {
|
|||||||
*resourcepermissions.Service
|
*resourcepermissions.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
var FolderViewActions = []string{dashboards.ActionFoldersRead, accesscontrol.ActionAlertingRuleRead}
|
var FolderViewActions = []string{dashboards.ActionFoldersRead, accesscontrol.ActionAlertingRuleRead, libraryelements.ActionLibraryPanelsRead}
|
||||||
var FolderEditActions = append(FolderViewActions, []string{
|
var FolderEditActions = append(FolderViewActions, []string{
|
||||||
dashboards.ActionFoldersWrite,
|
dashboards.ActionFoldersWrite,
|
||||||
dashboards.ActionFoldersDelete,
|
dashboards.ActionFoldersDelete,
|
||||||
@ -199,6 +200,9 @@ var FolderEditActions = append(FolderViewActions, []string{
|
|||||||
accesscontrol.ActionAlertingRuleCreate,
|
accesscontrol.ActionAlertingRuleCreate,
|
||||||
accesscontrol.ActionAlertingRuleUpdate,
|
accesscontrol.ActionAlertingRuleUpdate,
|
||||||
accesscontrol.ActionAlertingRuleDelete,
|
accesscontrol.ActionAlertingRuleDelete,
|
||||||
|
libraryelements.ActionLibraryPanelsCreate,
|
||||||
|
libraryelements.ActionLibraryPanelsWrite,
|
||||||
|
libraryelements.ActionLibraryPanelsDelete,
|
||||||
}...)
|
}...)
|
||||||
var FolderAdminActions = append(FolderEditActions, []string{dashboards.ActionFoldersPermissionsRead, dashboards.ActionFoldersPermissionsWrite}...)
|
var FolderAdminActions = append(FolderEditActions, []string{dashboards.ActionFoldersPermissionsRead, dashboards.ActionFoldersPermissionsWrite}...)
|
||||||
|
|
||||||
|
@ -746,6 +746,14 @@ var (
|
|||||||
FrontendOnly: false,
|
FrontendOnly: false,
|
||||||
Owner: grafanaPluginsPlatformSquad,
|
Owner: grafanaPluginsPlatformSquad,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "libraryPanelRBAC",
|
||||||
|
Description: "Enables RBAC support for library panels",
|
||||||
|
Stage: FeatureStageExperimental,
|
||||||
|
FrontendOnly: false,
|
||||||
|
Owner: grafanaDashboardsSquad,
|
||||||
|
RequiresRestart: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "lokiRunQueriesInParallel",
|
Name: "lokiRunQueriesInParallel",
|
||||||
Description: "Enables running Loki queries in parallel",
|
Description: "Enables running Loki queries in parallel",
|
||||||
|
@ -106,6 +106,7 @@ reportingRetries,preview,@grafana/sharing-squad,false,false,true,false
|
|||||||
newBrowseDashboards,GA,@grafana/grafana-frontend-platform,false,false,false,true
|
newBrowseDashboards,GA,@grafana/grafana-frontend-platform,false,false,false,true
|
||||||
sseGroupByDatasource,experimental,@grafana/observability-metrics,false,false,false,false
|
sseGroupByDatasource,experimental,@grafana/observability-metrics,false,false,false,false
|
||||||
requestInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,false,false,false,false
|
requestInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,false,false,false,false
|
||||||
|
libraryPanelRBAC,experimental,@grafana/dashboards-squad,false,false,true,false
|
||||||
lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,false,false,false,false
|
lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,false,false,false,false
|
||||||
wargamesTesting,experimental,@grafana/hosted-grafana-team,false,false,false,false
|
wargamesTesting,experimental,@grafana/hosted-grafana-team,false,false,false,false
|
||||||
alertingInsights,experimental,@grafana/alerting-squad,false,false,false,true
|
alertingInsights,experimental,@grafana/alerting-squad,false,false,false,true
|
||||||
|
|
@ -435,6 +435,10 @@ const (
|
|||||||
// Include a status source label for request metrics and logs
|
// Include a status source label for request metrics and logs
|
||||||
FlagRequestInstrumentationStatusSource = "requestInstrumentationStatusSource"
|
FlagRequestInstrumentationStatusSource = "requestInstrumentationStatusSource"
|
||||||
|
|
||||||
|
// FlagLibraryPanelRBAC
|
||||||
|
// Enables RBAC support for library panels
|
||||||
|
FlagLibraryPanelRBAC = "libraryPanelRBAC"
|
||||||
|
|
||||||
// FlagLokiRunQueriesInParallel
|
// FlagLokiRunQueriesInParallel
|
||||||
// Enables running Loki queries in parallel
|
// Enables running Loki queries in parallel
|
||||||
FlagLokiRunQueriesInParallel = "lokiRunQueriesInParallel"
|
FlagLokiRunQueriesInParallel = "lokiRunQueriesInParallel"
|
||||||
|
@ -406,7 +406,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv)
|
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOn, featuresFlagOn)
|
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOn, featuresFlagOn, ac)
|
||||||
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOn)
|
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOn)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -481,7 +481,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, ac, dashSrv)
|
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, ac, dashSrv)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOff, featuresFlagOff)
|
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOff, featuresFlagOff, ac)
|
||||||
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOff)
|
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOff)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -602,7 +602,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
CanEditValue: true,
|
CanEditValue: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
elementService := libraryelements.ProvideService(cfg, db, routeRegister, tc.service, tc.featuresFlag)
|
elementService := libraryelements.ProvideService(cfg, db, routeRegister, tc.service, tc.featuresFlag, ac)
|
||||||
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, tc.service)
|
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, tc.service)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
69
pkg/services/libraryelements/accesscontrol.go
Normal file
69
pkg/services/libraryelements/accesscontrol.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package libraryelements
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||||
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
|
"github.com/grafana/grafana/pkg/services/libraryelements/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScopeLibraryPanelsRoot = "library.panels"
|
||||||
|
ScopeLibraryPanelsPrefix = "library.panels:uid:"
|
||||||
|
|
||||||
|
ActionLibraryPanelsCreate = "library.panels:create"
|
||||||
|
ActionLibraryPanelsRead = "library.panels:read"
|
||||||
|
ActionLibraryPanelsWrite = "library.panels:write"
|
||||||
|
ActionLibraryPanelsDelete = "library.panels:delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ScopeLibraryPanelsProvider = ac.NewScopeProvider(ScopeLibraryPanelsRoot)
|
||||||
|
|
||||||
|
ScopeLibraryPanelsAll = ScopeLibraryPanelsProvider.GetResourceAllScope()
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoElementsFound = errors.New("library element not found")
|
||||||
|
ErrElementNameNotUnique = errors.New("several library elements with the same name were found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// LibraryPanelUIDScopeResolver provides a ScopeAttributeResolver that is able to convert a scope prefixed with "library.panels:uid:"
|
||||||
|
// into uid based scopes for a library panel and its associated folder hierarchy
|
||||||
|
func LibraryPanelUIDScopeResolver(l *LibraryElementService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
|
||||||
|
prefix := ScopeLibraryPanelsProvider.GetResourceScopeUID("")
|
||||||
|
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
|
||||||
|
if !strings.HasPrefix(scope, prefix) {
|
||||||
|
return nil, ac.ErrInvalidScope
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := ac.ParseScopeUID(scope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := appcontext.User(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
libElDTO, err := l.getLibraryElementByUid(ctx, user, model.GetLibraryElementCommand{
|
||||||
|
UID: uid,
|
||||||
|
FolderName: dashboards.RootFolderName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inheritedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, libElDTO.FolderUID, folderSvc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(inheritedScopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(libElDTO.FolderUID), ScopeLibraryPanelsProvider.GetResourceScopeUID(uid)), nil
|
||||||
|
})
|
||||||
|
}
|
@ -6,23 +6,38 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/api/routing"
|
"github.com/grafana/grafana/pkg/api/routing"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/libraryelements/model"
|
"github.com/grafana/grafana/pkg/services/libraryelements/model"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *LibraryElementService) registerAPIEndpoints() {
|
func (l *LibraryElementService) registerAPIEndpoints() {
|
||||||
|
authorize := ac.Middleware(l.AccessControl)
|
||||||
|
|
||||||
l.RouteRegister.Group("/api/library-elements", func(entities routing.RouteRegister) {
|
l.RouteRegister.Group("/api/library-elements", func(entities routing.RouteRegister) {
|
||||||
entities.Post("/", middleware.ReqSignedIn, routing.Wrap(l.createHandler))
|
uidScope := ScopeLibraryPanelsProvider.GetResourceScopeUID(ac.Parameter(":uid"))
|
||||||
entities.Delete("/:uid", middleware.ReqSignedIn, routing.Wrap(l.deleteHandler))
|
|
||||||
entities.Get("/", middleware.ReqSignedIn, routing.Wrap(l.getAllHandler))
|
if l.features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) {
|
||||||
entities.Get("/:uid", middleware.ReqSignedIn, routing.Wrap(l.getHandler))
|
entities.Post("/", authorize(ac.EvalPermission(ActionLibraryPanelsCreate)), routing.Wrap(l.createHandler))
|
||||||
entities.Get("/:uid/connections/", middleware.ReqSignedIn, routing.Wrap(l.getConnectionsHandler))
|
entities.Delete("/:uid", authorize(ac.EvalPermission(ActionLibraryPanelsDelete, uidScope)), routing.Wrap(l.deleteHandler))
|
||||||
entities.Get("/name/:name", middleware.ReqSignedIn, routing.Wrap(l.getByNameHandler))
|
entities.Get("/", authorize(ac.EvalPermission(ActionLibraryPanelsRead)), routing.Wrap(l.getAllHandler))
|
||||||
entities.Patch("/:uid", middleware.ReqSignedIn, routing.Wrap(l.patchHandler))
|
entities.Get("/:uid", authorize(ac.EvalPermission(ActionLibraryPanelsRead, uidScope)), routing.Wrap(l.getHandler))
|
||||||
|
entities.Get("/:uid/connections/", authorize(ac.EvalPermission(ActionLibraryPanelsRead, uidScope)), routing.Wrap(l.getConnectionsHandler))
|
||||||
|
entities.Get("/name/:name", routing.Wrap(l.getByNameHandler))
|
||||||
|
entities.Patch("/:uid", authorize(ac.EvalPermission(ActionLibraryPanelsWrite, uidScope)), routing.Wrap(l.patchHandler))
|
||||||
|
} else {
|
||||||
|
entities.Post("/", routing.Wrap(l.createHandler))
|
||||||
|
entities.Delete("/:uid", routing.Wrap(l.deleteHandler))
|
||||||
|
entities.Get("/", routing.Wrap(l.getAllHandler))
|
||||||
|
entities.Get("/:uid", routing.Wrap(l.getHandler))
|
||||||
|
entities.Get("/:uid/connections/", routing.Wrap(l.getConnectionsHandler))
|
||||||
|
entities.Get("/name/:name", routing.Wrap(l.getByNameHandler))
|
||||||
|
entities.Patch("/:uid", routing.Wrap(l.patchHandler))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +71,8 @@ func (l *LibraryElementService) createHandler(c *contextmodel.ReqContext) respon
|
|||||||
cmd.FolderID = folder.ID
|
cmd.FolderID = folder.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
element, err := l.CreateElement(c.Req.Context(), c.SignedInUser, cmd)
|
|
||||||
|
element, err := l.createLibraryElement(c.Req.Context(), c.SignedInUser, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toLibraryElementError(err, "Failed to create library element")
|
return toLibraryElementError(err, "Failed to create library element")
|
||||||
}
|
}
|
||||||
@ -109,6 +125,7 @@ func (l *LibraryElementService) deleteHandler(c *contextmodel.ReqContext) respon
|
|||||||
// Responses:
|
// Responses:
|
||||||
// 200: getLibraryElementResponse
|
// 200: getLibraryElementResponse
|
||||||
// 401: unauthorisedError
|
// 401: unauthorisedError
|
||||||
|
// 403: forbiddenError
|
||||||
// 404: notFoundError
|
// 404: notFoundError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (l *LibraryElementService) getHandler(c *contextmodel.ReqContext) response.Response {
|
func (l *LibraryElementService) getHandler(c *contextmodel.ReqContext) response.Response {
|
||||||
@ -154,6 +171,14 @@ func (l *LibraryElementService) getAllHandler(c *contextmodel.ReqContext) respon
|
|||||||
return toLibraryElementError(err, "Failed to get library elements")
|
return toLibraryElementError(err, "Failed to get library elements")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if l.features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) {
|
||||||
|
filteredPanels, err := l.filterLibraryPanelsByPermission(c, elementsResult.Elements)
|
||||||
|
if err != nil {
|
||||||
|
return toLibraryElementError(err, "Failed to evaluate permissions")
|
||||||
|
}
|
||||||
|
elementsResult.Elements = filteredPanels
|
||||||
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusOK, model.LibraryElementSearchResponse{Result: elementsResult})
|
return response.JSON(http.StatusOK, model.LibraryElementSearchResponse{Result: elementsResult})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +241,7 @@ func (l *LibraryElementService) patchHandler(c *contextmodel.ReqContext) respons
|
|||||||
// Responses:
|
// Responses:
|
||||||
// 200: getLibraryElementConnectionsResponse
|
// 200: getLibraryElementConnectionsResponse
|
||||||
// 401: unauthorisedError
|
// 401: unauthorisedError
|
||||||
|
// 403: forbiddenError
|
||||||
// 404: notFoundError
|
// 404: notFoundError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (l *LibraryElementService) getConnectionsHandler(c *contextmodel.ReqContext) response.Response {
|
func (l *LibraryElementService) getConnectionsHandler(c *contextmodel.ReqContext) response.Response {
|
||||||
@ -244,8 +270,32 @@ func (l *LibraryElementService) getByNameHandler(c *contextmodel.ReqContext) res
|
|||||||
return toLibraryElementError(err, "Failed to get library element")
|
return toLibraryElementError(err, "Failed to get library element")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if l.features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) {
|
||||||
|
filteredElements, err := l.filterLibraryPanelsByPermission(c, elements)
|
||||||
|
if err != nil {
|
||||||
|
return toLibraryElementError(err, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.JSON(http.StatusOK, model.LibraryElementArrayResponse{Result: filteredElements})
|
||||||
|
} else {
|
||||||
return response.JSON(http.StatusOK, model.LibraryElementArrayResponse{Result: elements})
|
return response.JSON(http.StatusOK, model.LibraryElementArrayResponse{Result: elements})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LibraryElementService) filterLibraryPanelsByPermission(c *contextmodel.ReqContext, elements []model.LibraryElementDTO) ([]model.LibraryElementDTO, error) {
|
||||||
|
filteredPanels := make([]model.LibraryElementDTO, 0)
|
||||||
|
for _, p := range elements {
|
||||||
|
allowed, err := l.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, ac.EvalPermission(ActionLibraryPanelsRead, ScopeLibraryPanelsProvider.GetResourceScopeUID(p.UID)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if allowed {
|
||||||
|
filteredPanels = append(filteredPanels, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredPanels, nil
|
||||||
|
}
|
||||||
|
|
||||||
func toLibraryElementError(err error, message string) response.Response {
|
func toLibraryElementError(err error, message string) response.Response {
|
||||||
if errors.Is(err, model.ErrLibraryElementAlreadyExists) {
|
if errors.Is(err, model.ErrLibraryElementAlreadyExists) {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/kinds/librarypanel"
|
"github.com/grafana/grafana/pkg/kinds/librarypanel"
|
||||||
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
@ -82,7 +83,7 @@ func syncFieldsWithModel(libraryElement *model.LibraryElement) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLibraryElement(dialect migrator.Dialect, session *db.Session, uid string, orgID int64) (model.LibraryElementWithMeta, error) {
|
func GetLibraryElement(dialect migrator.Dialect, session *db.Session, uid string, orgID int64) (model.LibraryElementWithMeta, error) {
|
||||||
elements := make([]model.LibraryElementWithMeta, 0)
|
elements := make([]model.LibraryElementWithMeta, 0)
|
||||||
sql := selectLibraryElementDTOWithMeta +
|
sql := selectLibraryElementDTOWithMeta +
|
||||||
", coalesce(dashboard.title, 'General') AS folder_name" +
|
", coalesce(dashboard.title, 'General') AS folder_name" +
|
||||||
@ -161,9 +162,19 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
|
err = l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
|
||||||
|
if l.features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) {
|
||||||
|
allowed, err := l.AccessControl.Evaluate(c, signedInUser, ac.EvalPermission(ActionLibraryPanelsCreate, dashboards.ScopeFoldersProvider.GetResourceScopeUID(*cmd.FolderUID)))
|
||||||
|
if !allowed {
|
||||||
|
return fmt.Errorf("insufficient permissions for creating library panel in folder with UID %s", *cmd.FolderUID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err := l.requireEditPermissionsOnFolder(c, signedInUser, cmd.FolderID); err != nil {
|
if err := l.requireEditPermissionsOnFolder(c, signedInUser, cmd.FolderID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if _, err := session.Insert(&element); err != nil {
|
if _, err := session.Insert(&element); err != nil {
|
||||||
if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) {
|
if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) {
|
||||||
return model.ErrLibraryElementAlreadyExists
|
return model.ErrLibraryElementAlreadyExists
|
||||||
@ -208,7 +219,7 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn
|
|||||||
func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedInUser identity.Requester, uid string) (int64, error) {
|
func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedInUser identity.Requester, uid string) (int64, error) {
|
||||||
var elementID int64
|
var elementID int64
|
||||||
err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
|
err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
|
||||||
element, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.GetOrgID())
|
element, err := GetLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.GetOrgID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -520,7 +531,7 @@ func (l *LibraryElementService) patchLibraryElement(c context.Context, signedInU
|
|||||||
return model.LibraryElementDTO{}, err
|
return model.LibraryElementDTO{}, err
|
||||||
}
|
}
|
||||||
err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
|
err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
|
||||||
elementInDB, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.GetOrgID())
|
elementInDB, err := GetLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.GetOrgID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -537,7 +548,7 @@ func (l *LibraryElementService) patchLibraryElement(c context.Context, signedInU
|
|||||||
return model.ErrLibraryElementUIDTooLong
|
return model.ErrLibraryElementUIDTooLong
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := getLibraryElement(l.SQLStore.GetDialect(), session, updateUID, signedInUser.GetOrgID())
|
_, err := GetLibraryElement(l.SQLStore.GetDialect(), session, updateUID, signedInUser.GetOrgID())
|
||||||
if !errors.Is(err, model.ErrLibraryElementNotFound) {
|
if !errors.Is(err, model.ErrLibraryElementNotFound) {
|
||||||
return model.ErrLibraryElementAlreadyExists
|
return model.ErrLibraryElementAlreadyExists
|
||||||
}
|
}
|
||||||
@ -634,7 +645,7 @@ func (l *LibraryElementService) getConnections(c context.Context, signedInUser i
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = l.SQLStore.WithDbSession(c, func(session *db.Session) error {
|
err = l.SQLStore.WithDbSession(c, func(session *db.Session) error {
|
||||||
element, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.GetOrgID())
|
element, err := GetLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.GetOrgID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -737,7 +748,7 @@ func (l *LibraryElementService) connectElementsToDashboardID(c context.Context,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, elementUID := range elementUIDs {
|
for _, elementUID := range elementUIDs {
|
||||||
element, err := getLibraryElement(l.SQLStore.GetDialect(), session, elementUID, signedInUser.GetOrgID())
|
element, err := GetLibraryElement(l.SQLStore.GetDialect(), session, elementUID, signedInUser.GetOrgID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/routing"
|
"github.com/grafana/grafana/pkg/api/routing"
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
@ -14,7 +15,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.RouteRegister, folderService folder.Service, features featuremgmt.FeatureToggles) *LibraryElementService {
|
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.RouteRegister, folderService folder.Service, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) *LibraryElementService {
|
||||||
l := &LibraryElementService{
|
l := &LibraryElementService{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
SQLStore: sqlStore,
|
SQLStore: sqlStore,
|
||||||
@ -22,8 +23,12 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.Rout
|
|||||||
folderService: folderService,
|
folderService: folderService,
|
||||||
log: log.New("library-elements"),
|
log: log.New("library-elements"),
|
||||||
features: features,
|
features: features,
|
||||||
|
AccessControl: ac,
|
||||||
}
|
}
|
||||||
|
|
||||||
l.registerAPIEndpoints()
|
l.registerAPIEndpoints()
|
||||||
|
ac.RegisterScopeAttributeResolver(LibraryPanelUIDScopeResolver(l, l.folderService))
|
||||||
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +50,7 @@ type LibraryElementService struct {
|
|||||||
folderService folder.Service
|
folderService folder.Service
|
||||||
log log.Logger
|
log log.Logger
|
||||||
features featuremgmt.FeatureToggles
|
features featuremgmt.FeatureToggles
|
||||||
|
AccessControl accesscontrol.AccessControl
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Service = (*LibraryElementService)(nil)
|
var _ Service = (*LibraryElementService)(nil)
|
||||||
|
@ -830,7 +830,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
features := featuremgmt.WithFeatures()
|
features := featuremgmt.WithFeatures()
|
||||||
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features)
|
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features)
|
||||||
|
|
||||||
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, featuremgmt.WithFeatures())
|
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, featuremgmt.WithFeatures(), ac)
|
||||||
service := LibraryPanelService{
|
service := LibraryPanelService{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
SQLStore: sqlStore,
|
SQLStore: sqlStore,
|
||||||
|
@ -555,6 +555,111 @@ func (m *managedFolderAlertActionsRepeatMigrator) Exec(sess *xorm.Session, mg *m
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const managedFolderLibraryPanelActionsMigratorID = "managed folder permissions library panel actions migration"
|
||||||
|
|
||||||
|
func AddManagedFolderLibraryPanelActionsMigration(mg *migrator.Migrator) {
|
||||||
|
mg.AddMigration(managedFolderLibraryPanelActionsMigratorID, &managedFolderLibraryPanelActionsMigrator{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type managedFolderLibraryPanelActionsMigrator struct {
|
||||||
|
migrator.MigrationBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *managedFolderLibraryPanelActionsMigrator) SQL(dialect migrator.Dialect) string {
|
||||||
|
return CodeMigrationSQL
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor with alerts migration
|
||||||
|
func (m *managedFolderLibraryPanelActionsMigrator) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||||
|
var ids []any
|
||||||
|
if err := sess.SQL("SELECT id FROM role WHERE name LIKE 'managed:%'").Find(&ids); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var permissions []ac.Permission
|
||||||
|
if err := sess.SQL("SELECT role_id, action, scope FROM permission WHERE role_id IN(?"+strings.Repeat(" ,?", len(ids)-1)+") AND scope LIKE 'folders:%'", ids...).Find(&permissions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped := make(map[int64]map[string][]ac.Permission, len(ids)-1)
|
||||||
|
for _, p := range permissions {
|
||||||
|
if mapped[p.RoleID] == nil {
|
||||||
|
mapped[p.RoleID] = make(map[string][]ac.Permission)
|
||||||
|
}
|
||||||
|
mapped[p.RoleID][p.Scope] = append(mapped[p.RoleID][p.Scope], p)
|
||||||
|
}
|
||||||
|
|
||||||
|
var toAdd []ac.Permission
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for id, a := range mapped {
|
||||||
|
for scope, p := range a {
|
||||||
|
if hasFolderView(p) {
|
||||||
|
if !hasAction(ac.ActionLibraryPanelsRead, p) {
|
||||||
|
toAdd = append(toAdd, ac.Permission{
|
||||||
|
RoleID: id,
|
||||||
|
Updated: now,
|
||||||
|
Created: now,
|
||||||
|
Scope: scope,
|
||||||
|
Action: ac.ActionLibraryPanelsRead,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasFolderAdmin(p) || hasFolderEdit(p) {
|
||||||
|
if !hasAction(ac.ActionLibraryPanelsCreate, p) {
|
||||||
|
toAdd = append(toAdd, ac.Permission{
|
||||||
|
RoleID: id,
|
||||||
|
Updated: now,
|
||||||
|
Created: now,
|
||||||
|
Scope: scope,
|
||||||
|
Action: ac.ActionLibraryPanelsCreate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !hasAction(ac.ActionLibraryPanelsDelete, p) {
|
||||||
|
toAdd = append(toAdd, ac.Permission{
|
||||||
|
RoleID: id,
|
||||||
|
Updated: now,
|
||||||
|
Created: now,
|
||||||
|
Scope: scope,
|
||||||
|
Action: ac.ActionLibraryPanelsDelete,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !hasAction(ac.ActionLibraryPanelsWrite, p) {
|
||||||
|
toAdd = append(toAdd, ac.Permission{
|
||||||
|
RoleID: id,
|
||||||
|
Updated: now,
|
||||||
|
Created: now,
|
||||||
|
Scope: scope,
|
||||||
|
Action: ac.ActionLibraryPanelsWrite,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(toAdd) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := batch(len(toAdd), batchSize, func(start, end int) error {
|
||||||
|
if _, err := sess.InsertMulti(toAdd[start:end]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func hasFolderAdmin(permissions []ac.Permission) bool {
|
func hasFolderAdmin(permissions []ac.Permission) bool {
|
||||||
return hasActions(folderPermissionTranslation[dashboards.PERMISSION_ADMIN], permissions)
|
return hasActions(folderPermissionTranslation[dashboards.PERMISSION_ADMIN], permissions)
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,7 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
|
|||||||
accesscontrol.AddAdminOnlyMigration(mg)
|
accesscontrol.AddAdminOnlyMigration(mg)
|
||||||
accesscontrol.AddSeedAssignmentMigrations(mg)
|
accesscontrol.AddSeedAssignmentMigrations(mg)
|
||||||
accesscontrol.AddManagedFolderAlertActionsRepeatFixedMigration(mg)
|
accesscontrol.AddManagedFolderAlertActionsRepeatFixedMigration(mg)
|
||||||
|
accesscontrol.AddManagedFolderLibraryPanelActionsMigration(mg)
|
||||||
|
|
||||||
AddExternalAlertmanagerToDatasourceMigration(mg)
|
AddExternalAlertmanagerToDatasourceMigration(mg)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user