diff --git a/pkg/registry/apis/dashboard/authorizer.go b/pkg/registry/apis/dashboard/authorizer.go deleted file mode 100644 index 07ad6bfda19..00000000000 --- a/pkg/registry/apis/dashboard/authorizer.go +++ /dev/null @@ -1,129 +0,0 @@ -package dashboard - -import ( - "context" - - "k8s.io/apiserver/pkg/authorization/authorizer" - - "github.com/grafana/authlib/types" - dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/libraryelements" -) - -func newLegacyAuthorizer(ac accesscontrol.AccessControl) authorizer.Authorizer { - return authorizer.AuthorizerFunc( - func(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - // Note that we will return Allow more than expected. - // This is because we do NOT want to hit the RoleAuthorizer that would be evaluated afterwards. - - if !attr.IsResourceRequest() { - return authorizer.DecisionDeny, "unexpected non-resource request", nil - } - - user, err := identity.GetRequester(ctx) - if err != nil { - return authorizer.DecisionDeny, "error getting requester", err - } - - ns := attr.GetNamespace() - if ns == "" { - return authorizer.DecisionDeny, "expected namespace", nil - } - - info, err := types.ParseNamespace(attr.GetNamespace()) - if err != nil { - return authorizer.DecisionDeny, "error reading org from namespace", err - } - - // Validate organization access before we possibly step out here. - if user.GetOrgID() != info.OrgID { - return authorizer.DecisionDeny, "org mismatch", dashboards.ErrUserIsNotSignedInToOrg - } - - // Determine if this is a library panel or dashboard resource - resource := attr.GetResource() - isLibraryPanel := resource == dashv0.LIBRARY_PANEL_RESOURCE - - if isLibraryPanel { - return authorizeLibraryPanel(ctx, ac, user, attr) - } else { - return authorizeDashboard(ctx, ac, user, attr) - } - }) -} - -func authorizeLibraryPanel(ctx context.Context, ac accesscontrol.AccessControl, user identity.Requester, attr authorizer.Attributes) (authorizer.Decision, string, error) { - switch attr.GetVerb() { - case "list", "search": - // Detailed read permissions are handled by authz, this just checks whether the user can ready *any* library panel - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsRead)) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not read any library panels", err - } - case "create": - // TODO: uncomment this when we implement create :) - // - // Detailed create permissions are handled by authz, this just checks whether the user can create *any* library panel - // ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsCreate)) - // if !ok || err != nil { - // return authorizer.DecisionDeny, "can not create any library panels", err - // } - return authorizer.DecisionDeny, "can not create any library panels", nil - case "get": - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsRead, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName()))) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not view library panel", err - } - case "update", "patch": - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsWrite, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName()))) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not edit library panel", err - } - case "delete": - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsDelete, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName()))) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not delete library panel", err - } - default: - return authorizer.DecisionDeny, "unsupported verb for library panels", nil - } - return authorizer.DecisionAllow, "", nil -} - -func authorizeDashboard(ctx context.Context, ac accesscontrol.AccessControl, user identity.Requester, attr authorizer.Attributes) (authorizer.Decision, string, error) { - switch attr.GetVerb() { - case "list", "search": - // Detailed read permissions are handled by authz, this just checks whether the user can ready *any* dashboard - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead)) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not read any dashboards", err - } - case "create": - // Detailed create permissions are handled by authz, this just checks whether the user can create *any* dashboard - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate)) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not create any dashboards", err - } - case "get": - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName()))) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not view dashboard", err - } - case "update", "patch": - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName()))) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not edit dashboard", err - } - case "delete": - ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName()))) - if !ok || err != nil { - return authorizer.DecisionDeny, "can not delete dashboard", err - } - default: - return authorizer.DecisionDeny, "unsupported verb for dashboards", nil - } - return authorizer.DecisionAllow, "", nil -} diff --git a/pkg/registry/apis/dashboard/legacy_storage.go b/pkg/registry/apis/dashboard/legacy_storage.go index 97bcf1ce4f3..d51bea21bf9 100644 --- a/pkg/registry/apis/dashboard/legacy_storage.go +++ b/pkg/registry/apis/dashboard/legacy_storage.go @@ -11,7 +11,6 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "github.com/grafana/authlib/types" - "github.com/grafana/grafana/pkg/apimachinery/utils" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index 309c46b4363..aeb6348b6bd 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -42,7 +42,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver" - authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" + grafanaauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/client" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" @@ -92,7 +92,6 @@ type DashboardsAPIBuilder struct { dashboardService dashboards.DashboardService features featuremgmt.FeatureToggles - authorizer authorizer.Authorizer accessControl accesscontrol.AccessControl accessClient authlib.AccessClient legacy *DashboardStorage @@ -143,7 +142,6 @@ func RegisterAPIService( folderClient := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), folders.FolderResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashStore, userService, unified, sorter, features) builder := &DashboardsAPIBuilder{ - authorizer: newLegacyAuthorizer(accessControl), dashboardService: dashboardService, dashboardPermissions: dashboardPermissions, dashboardPermissionsSvc: dashboardPermissionsSvc, @@ -179,7 +177,6 @@ func NewAPIService(ac authlib.AccessClient, features featuremgmt.FeatureToggles, return &DashboardsAPIBuilder{ minRefreshInterval: "10s", accessClient: ac, - authorizer: authsvc.NewResourceAuthorizer(ac), features: features, dashboardService: &dashsvc.DashboardServiceImpl{}, // for validation helpers only folderClientProvider: folderClientProvider, @@ -764,8 +761,9 @@ func (b *DashboardsAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.API return b.search.GetAPIRoutes(defs) } +// The default authorizer is fine because authorization happens in storage where we know the parent folder func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return b.authorizer + return grafanaauthorizer.NewServiceAuthorizer() } func (b *DashboardsAPIBuilder) verifyFolderAccessPermissions(ctx context.Context, user identity.Requester, folderIds ...string) error { diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index 966dc214044..5ef0a252ed8 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -181,7 +181,8 @@ func newFolderTranslation() translation { func NewMapperRegistry() MapperRegistry { mapper := mapper(map[string]map[string]translation{ "dashboard.grafana.app": { - "dashboards": newDashboardTranslation(), + "dashboards": newDashboardTranslation(), + "librarypanels": newResourceTranslation("library.panels", "uid", true, false), }, "folder.grafana.app": { "folders": newFolderTranslation(),