diff --git a/pkg/services/accesscontrol/ossaccesscontrol/receivers.go b/pkg/services/accesscontrol/ossaccesscontrol/receivers.go index a818d8b3e84..e29578af729 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/receivers.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/receivers.go @@ -76,12 +76,13 @@ type ReceiverPermissionsService struct { // SetDefaultPermissions sets the default permissions for a newly created receiver. func (r ReceiverPermissionsService) SetDefaultPermissions(ctx context.Context, orgID int64, user identity.Requester, uid string) { r.log.Debug("Setting default permissions for receiver", "receiver_uid", uid) + resourceId := alertingac.ScopeReceiversProvider.GetResourceIDFromUID(uid) permissions := defaultPermissions() clearCache := false if user != nil && user.IsIdentityType(claims.TypeUser) { userID, err := user.GetInternalID() if err != nil { - r.log.Error("Could not make user admin", "receiver_uid", uid, "id", user.GetID(), "error", err) + r.log.Error("Could not make user admin", "receiver_uid", uid, "resource_id", resourceId, "id", user.GetID(), "error", err) } else { permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ UserID: userID, Permission: string(alertingac.ReceiverPermissionAdmin), @@ -90,8 +91,8 @@ func (r ReceiverPermissionsService) SetDefaultPermissions(ctx context.Context, o } } - if _, err := r.SetPermissions(ctx, orgID, uid, permissions...); err != nil { - r.log.Error("Could not set default permissions", "receiver_uid", uid, "error", err) + if _, err := r.SetPermissions(ctx, orgID, resourceId, permissions...); err != nil { + r.log.Error("Could not set default permissions", "receiver_uid", uid, "resource_id", resourceId, "id", "error", err) } if clearCache { @@ -120,13 +121,15 @@ func copyPermissionUser(orgID int64) identity.Requester { // name. func (r ReceiverPermissionsService) CopyPermissions(ctx context.Context, orgID int64, user identity.Requester, oldUID, newUID string) (int, error) { r.log.Debug("Copying permissions from receiver", "old_uid", oldUID, "new_uid", newUID) - currentPermissions, err := r.GetPermissions(ctx, copyPermissionUser(orgID), oldUID) + oldResourceId := alertingac.ScopeReceiversProvider.GetResourceIDFromUID(oldUID) + newResourceId := alertingac.ScopeReceiversProvider.GetResourceIDFromUID(newUID) + currentPermissions, err := r.GetPermissions(ctx, copyPermissionUser(orgID), oldResourceId) if err != nil { return 0, err } setPermissionCommands := r.toSetResourcePermissionCommands(currentPermissions) - if _, err := r.SetPermissions(ctx, orgID, newUID, setPermissionCommands...); err != nil { + if _, err := r.SetPermissions(ctx, orgID, newResourceId, setPermissionCommands...); err != nil { return 0, err } @@ -146,6 +149,10 @@ func (r ReceiverPermissionsService) CopyPermissions(ctx context.Context, orgID i return countCustomPermissions(setPermissionCommands), nil } +func (r ReceiverPermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, uid string) error { + return r.Service.DeleteResourcePermissions(ctx, orgID, alertingac.ScopeReceiversProvider.GetResourceIDFromUID(uid)) +} + // toSetResourcePermissionCommands converts a list of resource permissions to a list of set resource permission commands. // Only includes managed permissions. func (r ReceiverPermissionsService) toSetResourcePermissionCommands(permissions []accesscontrol.ResourcePermission) []accesscontrol.SetResourcePermissionCommand { diff --git a/pkg/services/accesscontrol/resourcepermissions/api.go b/pkg/services/accesscontrol/resourcepermissions/api.go index 4dc8aafd51f..006139b6539 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api.go +++ b/pkg/services/accesscontrol/resourcepermissions/api.go @@ -5,16 +5,18 @@ import ( "net/http" "strconv" + "go.opentelemetry.io/otel" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "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" "github.com/grafana/grafana/pkg/web" - "go.opentelemetry.io/otel" ) var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/resourcepermissions") @@ -48,6 +50,9 @@ func (a *api) registerEndpoints() { if a.service.options.Resource == "teams" { teamUIDResolverResource = team.MiddlewareTeamUIDResolver(a.service.teamService, ":resourceID") } + if a.service.options.Resource == "receivers" { + teamUIDResolverResource = MiddlewareReceiverUIDResolver(":resourceID") + } 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) @@ -430,3 +435,13 @@ 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) + } + } +} diff --git a/pkg/services/ngalert/accesscontrol/receivers.go b/pkg/services/ngalert/accesscontrol/receivers.go index 47e101e7cd9..1016f246a41 100644 --- a/pkg/services/ngalert/accesscontrol/receivers.go +++ b/pkg/services/ngalert/accesscontrol/receivers.go @@ -2,10 +2,14 @@ package accesscontrol import ( "context" + // #nosec G505 Used only for shortening the uid, not for security purposes. + "crypto/sha1" + "encoding/hex" "github.com/grafana/grafana/pkg/apimachinery/identity" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/util" ) const ( @@ -13,10 +17,30 @@ const ( ) var ( - ScopeReceiversProvider = ac.NewScopeProvider(ScopeReceiversRoot) + ScopeReceiversProvider = ReceiverScopeProvider{ac.NewScopeProvider(ScopeReceiversRoot)} ScopeReceiversAll = ScopeReceiversProvider.GetResourceAllScope() ) +type ReceiverScopeProvider struct { + ac.ScopeProvider +} + +func (p ReceiverScopeProvider) GetResourceScopeUID(uid string) string { + return ScopeReceiversProvider.ScopeProvider.GetResourceScopeUID(p.GetResourceIDFromUID(uid)) +} + +// GetResourceIDFromUID converts a receiver uid to a resource id. This is necessary as resource ids are limited to 40 characters. +// If the uid is already less than or equal to 40 characters, it is returned as is. +func (p ReceiverScopeProvider) GetResourceIDFromUID(uid string) string { + if len(uid) <= util.MaxUIDLength { + return uid + } + // #nosec G505 Used only for shortening the uid, not for security purposes. + h := sha1.New() + h.Write([]byte(uid)) + return hex.EncodeToString(h.Sum(nil)) +} + // ReceiverPermission is a type for representing a receiver permission. type ReceiverPermission string diff --git a/pkg/services/ngalert/notifier/receiver_svc_test.go b/pkg/services/ngalert/notifier/receiver_svc_test.go index fd0df8142f0..2318dd5a938 100644 --- a/pkg/services/ngalert/notifier/receiver_svc_test.go +++ b/pkg/services/ngalert/notifier/receiver_svc_test.go @@ -851,7 +851,7 @@ func TestReceiverServiceAC_Read(t *testing.T) { emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email")) recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() - recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() + recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() allReceivers := func() []models.Receiver { return []models.Receiver{recv1, recv2, recv3} } @@ -990,7 +990,7 @@ func TestReceiverServiceAC_Create(t *testing.T) { emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email")) recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() - recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() + recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() allReceivers := func() []models.Receiver { return []models.Receiver{recv1, recv2, recv3} } @@ -1078,7 +1078,7 @@ func TestReceiverServiceAC_Update(t *testing.T) { emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email")) recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() - recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() + recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() allReceivers := func() []models.Receiver { return []models.Receiver{recv1, recv2, recv3} } @@ -1230,7 +1230,7 @@ func TestReceiverServiceAC_Delete(t *testing.T) { emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email")) recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() - recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() + recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() allReceivers := func() []models.Receiver { return []models.Receiver{recv1, recv2, recv3} } diff --git a/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go b/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go index 2aa4fcdcf20..8965613379f 100644 --- a/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go +++ b/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go @@ -481,6 +481,14 @@ func TestIntegrationAccessControl(t *testing.T) { )...), }) + // Test receivers with uids longer than 40 characters. User name is used in receiver name. + adminLikeUserLongName := helper.CreateUser("adminLikeUserCreatingAReallyLongReceiverName", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{ + createWildcardPermission(append( + []string{accesscontrol.ActionAlertingReceiversCreate}, + ossaccesscontrol.ReceiversAdminActions..., + )...), + }) + // endregion testCases := []testCase{ @@ -555,6 +563,15 @@ func TestIntegrationAccessControl(t *testing.T) { canAdmin: true, canReadSecrets: true, }, + { + user: adminLikeUserLongName, + canRead: true, + canCreate: true, + canUpdate: true, + canDelete: true, + canAdmin: true, + canReadSecrets: true, + }, } admin := org1.Admin