AccessControl: Resource permission translator (#95423)

* Standardize Resource Translator in options

* Add resource UID translator for resource permissions

* fix comments

* fix comments
This commit is contained in:
Jo
2024-10-29 10:21:39 +01:00
committed by GitHub
parent fb51a5e21f
commit 2b867d9850
11 changed files with 102 additions and 73 deletions
@@ -42,6 +42,9 @@ func ProvideReceiverPermissionsService(
options := resourcepermissions.Options{
Resource: "receivers",
ResourceAttribute: "uid",
ResourceTranslator: func(ctx context.Context, orgID int64, resourceID string) (string, error) {
return alertingac.ScopeReceiversProvider.GetResourceIDFromUID(resourceID), nil
},
Assignments: resourcepermissions.Assignments{
Users: true,
Teams: true,
@@ -41,8 +41,9 @@ func ProvideServiceAccountPermissions(
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
) (*ServiceAccountPermissionsService, error) {
options := resourcepermissions.Options{
Resource: "serviceaccounts",
ResourceAttribute: "id",
Resource: "serviceaccounts",
ResourceAttribute: "id",
ResourceTranslator: serviceaccounts.UIDToIDHandler(serviceAccountRetrieverService),
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideServiceAccountPermissions.ResourceValidator")
defer span.End()
@@ -41,9 +41,10 @@ func ProvideTeamPermissions(
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
) (*TeamPermissionsService, error) {
options := resourcepermissions.Options{
Resource: "teams",
ResourceAttribute: "id",
OnlyManaged: true,
Resource: "teams",
ResourceAttribute: "id",
OnlyManaged: true,
ResourceTranslator: team.UIDToIDHandler(teamService),
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideTeamerPermissions.ResourceValidator")
defer span.End()
@@ -12,7 +12,6 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
alertingac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/setting"
@@ -46,29 +45,40 @@ func (a *api) registerEndpoints() {
}
teamUIDResolver := team.MiddlewareTeamUIDResolver(a.service.teamService, ":teamID")
teamUIDResolverResource := func() web.Handler { return func(c *contextmodel.ReqContext) {} }() // no-op
if a.service.options.Resource == "teams" {
teamUIDResolverResource = team.MiddlewareTeamUIDResolver(a.service.teamService, ":resourceID")
}
if a.service.options.Resource == "receivers" {
teamUIDResolverResource = MiddlewareReceiverUIDResolver(":resourceID")
}
resourceResolver := func(resTranslator ResourceTranslator) web.Handler {
return func(c *contextmodel.ReqContext) {
// no-op
if resTranslator == nil {
return
}
gotParams := web.Params(c.Req)
resourceID := gotParams[":resourceID"]
resourceID, err := resTranslator(c.Req.Context(), c.OrgID, resourceID)
if err == nil {
gotParams[":resourceID"] = resourceID
web.SetURLParams(c.Req, gotParams)
} else {
c.JsonApiErr(http.StatusNotFound, "Not found", nil)
}
}
}(a.service.options.ResourceTranslator)
a.router.Group(fmt.Sprintf("/api/access-control/%s", a.service.options.Resource), func(r routing.RouteRegister) {
actionRead := fmt.Sprintf("%s.permissions:read", a.service.options.Resource)
actionWrite := fmt.Sprintf("%s.permissions:write", a.service.options.Resource)
scope := accesscontrol.Scope(a.service.options.Resource, a.service.options.ResourceAttribute, accesscontrol.Parameter(":resourceID"))
r.Get("/description", auth(accesscontrol.EvalPermission(actionRead)), routing.Wrap(a.getDescription))
r.Get("/:resourceID", teamUIDResolverResource, auth(accesscontrol.EvalPermission(actionRead, scope)), routing.Wrap(a.getPermissions))
r.Post("/:resourceID", teamUIDResolverResource, licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setPermissions))
r.Get("/:resourceID", resourceResolver, auth(accesscontrol.EvalPermission(actionRead, scope)), routing.Wrap(a.getPermissions))
r.Post("/:resourceID", resourceResolver, licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setPermissions))
if a.service.options.Assignments.Users {
r.Post("/:resourceID/users/:userID", licenseMW, teamUIDResolverResource, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission))
r.Post("/:resourceID/users/:userID", licenseMW, resourceResolver, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission))
}
if a.service.options.Assignments.Teams {
r.Post("/:resourceID/teams/:teamID", licenseMW, teamUIDResolverResource, teamUIDResolver, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setTeamPermission))
r.Post("/:resourceID/teams/:teamID", licenseMW, resourceResolver, teamUIDResolver, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setTeamPermission))
}
if a.service.options.Assignments.BuiltInRoles {
r.Post("/:resourceID/builtInRoles/:builtInRole", teamUIDResolverResource, licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setBuiltinRolePermission))
r.Post("/:resourceID/builtInRoles/:builtInRole", resourceResolver, licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setBuiltinRolePermission))
}
})
}
@@ -435,13 +445,3 @@ func permissionSetResponse(cmd setPermissionCommand) response.Response {
}
return response.Success(message)
}
func MiddlewareReceiverUIDResolver(paramName string) web.Handler {
return func(c *contextmodel.ReqContext) {
gotParams := web.Params(c.Req)
if uid, ok := gotParams[paramName]; ok {
gotParams[paramName] = alertingac.ScopeReceiversProvider.GetResourceIDFromUID(uid)
web.SetURLParams(c.Req, gotParams)
}
}
}
@@ -10,7 +10,7 @@ import (
type ResourceValidator func(ctx context.Context, orgID int64, resourceID string) error
type InheritedScopesSolver func(ctx context.Context, orgID int64, resourceID string) ([]string, error)
type ResourceTranslator func(ctx context.Context, orgID int64, resourceID string) (string, error)
type Options struct {
// Resource is the action and scope prefix that is generated
Resource string
@@ -18,6 +18,9 @@ type Options struct {
ResourceAttribute string
// OnlyManaged will tell the service to return all permissions if set to false and only managed permissions if set to true
OnlyManaged bool
// ResourceTranslator is a translator function that will be called before each action, it can be used to translate a resource id to a different format.
// If set to nil the translator will be skipped
ResourceTranslator ResourceTranslator
// ResourceValidator is a validator function that will be called before each assignment.
// If set to nil the validator will be skipped
ResourceValidator ResourceValidator