Alerting: Implement receiver auth service (#90857)

This commit is contained in:
Matthew Jacobson 2024-07-29 15:49:10 -04:00 committed by GitHub
parent b982259950
commit 62f67e38b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 334 additions and 65 deletions

View File

@ -1,17 +1,19 @@
package accesscontrol
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
)
var (
ErrAuthorizationBase = errutil.Forbidden("alerting.unauthorized")
)
func NewAuthorizationErrorWithPermissions(action string, eval accesscontrol.Evaluator) error {
func NewAuthorizationErrorWithPermissions(action string, eval ac.Evaluator) error {
msg := fmt.Sprintf("user is not authorized to %s", action)
err := ErrAuthorizationBase.Errorf(msg)
err.PublicMessage = msg
@ -26,3 +28,93 @@ func NewAuthorizationErrorWithPermissions(action string, eval accesscontrol.Eval
func NewAuthorizationErrorGeneric(action string) error {
return NewAuthorizationErrorWithPermissions(action, nil)
}
// actionAccess is a helper struct that provides common access control methods for a specific resource type and action.
type actionAccess[T any] struct {
genericService
// authorizeSome evaluates to true if user has access to some (any) resources.
// This is used as a precondition check, if this evaluates to false then user does not have access to any resources.
authorizeSome ac.Evaluator
// authorizeAll evaluates to true if user has access to all resources.
authorizeAll ac.Evaluator
// authorizeOne returns an evaluator that checks if user has access to a specific resource.
authorizeOne func(T) ac.Evaluator
// action is the action that user is trying to perform on the resource. Used in error messages.
action string
// resource is the name of the resource. Used in error messages.
resource string
}
// Filter filters the given list of resources based on access control permissions of the user.
// This method is preferred when many resources need to be checked.
func (s actionAccess[T]) Filter(ctx context.Context, user identity.Requester, resources ...T) ([]T, error) {
canAll, err := s.authorizePreConditions(ctx, user)
if err != nil {
return nil, err
}
if canAll {
return resources, nil
}
result := make([]T, 0, len(resources))
for _, r := range resources {
if hasAccess := s.authorize(ctx, user, r); hasAccess == nil {
result = append(result, r)
}
}
return result, nil
}
// Authorize checks if user has access to a resource. Returns an error if user does not have access.
func (s actionAccess[T]) Authorize(ctx context.Context, user identity.Requester, resource T) error {
canAll, err := s.authorizePreConditions(ctx, user)
if canAll || err != nil { // Return early if user can either access all or there is an error.
return err
}
return s.authorize(ctx, user, resource)
}
// Has checks if user has access to a resource. Returns false if user does not have access.
func (s actionAccess[T]) Has(ctx context.Context, user identity.Requester, resource T) (bool, error) {
canAll, err := s.authorizePreConditions(ctx, user)
if canAll || err != nil { // Return early if user can either access all or there is an error.
return canAll, err
}
return s.has(ctx, user, resource)
}
// authorizePreConditions checks necessary preconditions for resources. Returns true if user has access for all
// resources. Returns error if user does not have access to on any resources.
func (s actionAccess[T]) authorizePreConditions(ctx context.Context, user identity.Requester) (bool, error) {
canAll, err := s.HasAccess(ctx, user, s.authorizeAll)
if canAll || err != nil { // Return early if user can either access all or there is an error.
return canAll, err
}
can, err := s.HasAccess(ctx, user, s.authorizeSome)
if err != nil {
return false, err
}
if !can { // User does not have any resource permissions at all.
return false, NewAuthorizationErrorWithPermissions(fmt.Sprintf("%s any %s", s.action, s.resource), s.authorizeSome)
}
return false, nil
}
// authorize checks if user has access to a specific resource given precondition checks have already passed. Returns an error if user does not have access.
func (s actionAccess[T]) authorize(ctx context.Context, user identity.Requester, resource T) error {
return s.HasAccessOrError(ctx, user, s.authorizeOne(resource), func() string {
return fmt.Sprintf("%s %s", s.action, s.resource)
})
}
// has checks if user has access to a specific resource given precondition checks have already passed. Returns false if user does not have access.
func (s actionAccess[T]) has(ctx context.Context, user identity.Requester, resource T) (bool, error) {
return s.HasAccess(ctx, user, s.authorizeOne(resource))
}

View File

@ -0,0 +1,174 @@
package accesscontrol
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/models"
)
var (
// Asserts pre-conditions for read access to redacted receivers. If this evaluates to false, the user cannot read any redacted receivers.
readRedactedReceiversPreConditionsEval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingNotificationsRead), // Global action for all AM config. Org scope.
ac.EvalPermission(ac.ActionAlertingReceiversRead), // Action for redacted receivers. UID scope.
readDecryptedReceiversPreConditionsEval,
)
// Asserts pre-conditions for read access to decrypted receivers. If this evaluates to false, the user cannot read any decrypted receivers.
readDecryptedReceiversPreConditionsEval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets), // Action for decrypted receivers. UID scope.
)
// Asserts read-only access to all redacted receivers.
readRedactedAllReceiversEval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
// TODO: The following should be scoped, but are currently interpreted as global. Needs a db migration when scope is added.
ac.EvalPermission(ac.ActionAlertingReceiversRead), // TODO: Add global scope with fgac.
readDecryptedAllReceiversEval,
)
// Asserts read-only access to all decrypted receivers.
readDecryptedAllReceiversEval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets), // TODO: Add global scope with fgac.
)
// Asserts read-only access to a specific redacted receiver.
readRedactedReceiverEval = func(uid string) ac.Evaluator {
return ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
// TODO: The following should be scoped, but are currently interpreted as global. Needs a db migration when scope is added.
ac.EvalPermission(ac.ActionAlertingReceiversRead), // TODO: Add uid scope with fgac.
readDecryptedReceiverEval(uid),
)
}
// Asserts read-only access to a specific decrypted receiver.
readDecryptedReceiverEval = func(uid string) ac.Evaluator {
return ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets), // TODO: Add uid scope with fgac.
)
}
// Asserts read-only access to list redacted receivers. // TODO: Remove this with fgac.
readRedactedReceiversListEval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingReceiversList),
)
// Extra permissions that give read-only access to all redacted receivers when called from provisioning api.
provisioningExtraReadRedactedPermissions = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingProvisioningRead), // Global provisioning action for all AM config. Org scope.
ac.EvalPermission(ac.ActionAlertingNotificationsProvisioningRead), // Global provisioning action for receivers. Org scope.
provisioningExtraReadDecryptedPermissions,
)
// Extra permissions that give read-only access to all decrypted receivers when called from provisioning api.
provisioningExtraReadDecryptedPermissions = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), // Global provisioning action for all AM config + secrets. Org scope.
)
)
type ReceiverAccess[T models.Identified] struct {
read actionAccess[T]
readDecrypted actionAccess[T]
}
// NewReceiverAccess creates a new ReceiverAccess service. If includeProvisioningActions is true, the service will include
// permissions specific to the provisioning API.
func NewReceiverAccess[T models.Identified](a ac.AccessControl, includeProvisioningActions bool) *ReceiverAccess[T] {
rcvAccess := &ReceiverAccess[T]{
read: actionAccess[T]{
genericService: genericService{
ac: a,
},
resource: "receiver",
action: "read",
authorizeSome: readRedactedReceiversPreConditionsEval,
authorizeOne: func(receiver T) ac.Evaluator {
return readRedactedReceiverEval(receiver.GetUID())
},
authorizeAll: readRedactedAllReceiversEval,
},
readDecrypted: actionAccess[T]{
genericService: genericService{
ac: a,
},
resource: "decrypted receiver",
action: "read",
authorizeSome: readDecryptedReceiversPreConditionsEval,
authorizeOne: func(receiver T) ac.Evaluator {
return readDecryptedReceiverEval(receiver.GetUID())
},
authorizeAll: readDecryptedAllReceiversEval,
},
}
// If this service is meant for the provisioning API, we include the provisioning actions as possible permissions.
if includeProvisioningActions {
rcvAccess.read.authorizeSome = ac.EvalAny(provisioningExtraReadRedactedPermissions, rcvAccess.read.authorizeSome)
rcvAccess.readDecrypted.authorizeSome = ac.EvalAny(provisioningExtraReadDecryptedPermissions, rcvAccess.readDecrypted.authorizeSome)
rcvAccess.read.authorizeOne = func(receiver T) ac.Evaluator {
return ac.EvalAny(provisioningExtraReadRedactedPermissions, rcvAccess.read.authorizeOne(receiver))
}
rcvAccess.readDecrypted.authorizeOne = func(receiver T) ac.Evaluator {
return ac.EvalAny(provisioningExtraReadDecryptedPermissions, rcvAccess.readDecrypted.authorizeOne(receiver))
}
rcvAccess.read.authorizeAll = ac.EvalAny(provisioningExtraReadRedactedPermissions, rcvAccess.read.authorizeAll)
rcvAccess.readDecrypted.authorizeAll = ac.EvalAny(provisioningExtraReadDecryptedPermissions, rcvAccess.readDecrypted.authorizeAll)
}
return rcvAccess
}
// HasList checks if user has access to list redacted receivers. Returns false if user does not have access.
func (s ReceiverAccess[T]) HasList(ctx context.Context, user identity.Requester) (bool, error) { // TODO: Remove this with fgac.
return s.read.HasAccess(ctx, user, readRedactedReceiversListEval)
}
// FilterRead filters the given list of receivers based on the read redacted access control permissions of the user.
// This method is preferred when many receivers need to be checked.
func (s ReceiverAccess[T]) FilterRead(ctx context.Context, user identity.Requester, receivers ...T) ([]T, error) {
return s.read.Filter(ctx, user, receivers...)
}
// AuthorizeRead checks if user has access to read a redacted receiver. Returns an error if user does not have access.
func (s ReceiverAccess[T]) AuthorizeRead(ctx context.Context, user identity.Requester, receiver T) error {
return s.read.Authorize(ctx, user, receiver)
}
// HasRead checks if user has access to read a redacted receiver. Returns false if user does not have access.
func (s ReceiverAccess[T]) HasRead(ctx context.Context, user identity.Requester, receiver T) (bool, error) {
return s.read.Has(ctx, user, receiver)
}
// HasReadAll checks if user has access to read all redacted receivers. Returns false if user does not have access.
func (s ReceiverAccess[T]) HasReadAll(ctx context.Context, user identity.Requester) (bool, error) { // TODO: Temporary for legacy compatibility.
return s.read.HasAccess(ctx, user, s.read.authorizeAll)
}
// FilterReadDecrypted filters the given list of receivers based on the read decrypted access control permissions of the user.
// This method is preferred when many receivers need to be checked.
func (s ReceiverAccess[T]) FilterReadDecrypted(ctx context.Context, user identity.Requester, receivers ...T) ([]T, error) {
return s.readDecrypted.Filter(ctx, user, receivers...)
}
// AuthorizeReadDecrypted checks if user has access to read a decrypted receiver.
func (s ReceiverAccess[T]) AuthorizeReadDecrypted(ctx context.Context, user identity.Requester, receiver T) error {
return s.readDecrypted.Authorize(ctx, user, receiver)
}
// HasReadDecrypted checks if user has access to read a decrypted receiver. Returns false if user does not have access.
func (s ReceiverAccess[T]) HasReadDecrypted(ctx context.Context, user identity.Requester, receiver T) (bool, error) {
return s.readDecrypted.Has(ctx, user, receiver)
}
// AuthorizeReadDecryptedAll checks if user has access to read all decrypted receiver. Returns an error if user does not have access.
func (s ReceiverAccess[T]) AuthorizeReadDecryptedAll(ctx context.Context, user identity.Requester) error { // TODO: Temporary for legacy compatibility.
return s.readDecrypted.HasAccessOrError(ctx, user, s.readDecrypted.authorizeAll, func() string {
return fmt.Sprintf("%s %s", s.readDecrypted.action, s.readDecrypted.resource)
})
}

View File

@ -202,7 +202,7 @@ func TestRouteGetReceiversResponses(t *testing.T) {
env := createTestEnv(t, testConfig)
env.ac = &recordingAccessControlFake{
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingProvisioningReadSecrets) {
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingReceiversReadSecrets) {
recPermCheck = true
}
return false, nil
@ -397,7 +397,7 @@ func createNotificationSrvSutFromEnv(t *testing.T, env *testEnvironment) Notific
t.Helper()
receiverSvc := notifier.NewReceiverService(
env.ac,
ac.NewReceiverAccess[*models.Receiver](env.ac, false),
legacy_storage.NewAlertmanagerConfigStore(env.configs),
env.prov,
env.secrets,

View File

@ -35,6 +35,7 @@ import (
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/guardian"
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
@ -1583,6 +1584,9 @@ func TestProvisioningApiContactPointExport(t *testing.T) {
env := createTestEnv(t, testConfig)
env.ac = &recordingAccessControlFake{
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingReceiversList) {
return true, nil
}
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingProvisioningReadSecrets) {
recPermCheck = true
}
@ -1888,7 +1892,7 @@ func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) Provisi
configStore := legacy_storage.NewAlertmanagerConfigStore(env.configs)
receiverSvc := notifier.NewReceiverService(
env.ac,
ac.NewReceiverAccess[*models.Receiver](env.ac, true),
configStore,
env.prov,
env.secrets,

View File

@ -1,5 +1,7 @@
package models
import "github.com/grafana/alerting/notify"
// GetReceiverQuery represents a query for a single receiver.
type GetReceiverQuery struct {
OrgID int64
@ -15,3 +17,20 @@ type GetReceiversQuery struct {
Offset int
Decrypt bool
}
// Receiver is the domain model representation of a receiver / contact point.
type Receiver struct {
UID string
Name string
Integrations []*notify.GrafanaIntegrationConfig
Provenance Provenance
}
// Identified describes a class of resources that have a UID. Created to abstract required fields for authorization.
type Identified interface {
GetUID() string
}
func (r Receiver) GetUID() string {
return r.UID
}

View File

@ -411,7 +411,15 @@ func (ng *AlertNG) init() error {
configStore := legacy_storage.NewAlertmanagerConfigStore(ng.store)
receiverService := notifier.NewReceiverService(
ng.accesscontrol,
ac.NewReceiverAccess[*models.Receiver](ng.accesscontrol, true), // TODO: Remove provisioning actions from regular API.
configStore,
ng.store,
ng.SecretsService,
ng.store,
ng.Log,
)
provisioningReceiverService := notifier.NewReceiverService(
ac.NewReceiverAccess[*models.Receiver](ng.accesscontrol, true),
configStore,
ng.store,
ng.SecretsService,
@ -421,7 +429,7 @@ func (ng *AlertNG) init() error {
// Provisioning
policyService := provisioning.NewNotificationPolicyService(configStore, ng.store, ng.store, ng.Cfg.UnifiedAlerting, ng.Log)
contactPointService := provisioning.NewContactPointService(configStore, ng.SecretsService, ng.store, ng.store, receiverService, ng.Log, ng.store)
contactPointService := provisioning.NewContactPointService(configStore, ng.SecretsService, ng.store, ng.store, provisioningReceiverService, ng.Log, ng.store)
templateService := provisioning.NewTemplateService(configStore, ng.store, ng.store, ng.Log)
muteTimingService := provisioning.NewMuteTimingService(configStore, ng.store, ng.store, ng.Log, ng.store)
alertRuleService := provisioning.NewAlertRuleService(ng.store, ng.store, ng.folderService, ng.QuotaService, ng.store,

View File

@ -9,8 +9,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
@ -25,7 +23,7 @@ var (
// ReceiverService is the service for managing alertmanager receivers.
type ReceiverService struct {
ac accesscontrol.AccessControl
authz receiverAccessControlService
provisioningStore provisoningStore
cfgStore alertmanagerConfigStore
encryptionService secrets.Service
@ -34,6 +32,13 @@ type ReceiverService struct {
validator validation.ProvenanceStatusTransitionValidator
}
// receiverAccessControlService provides access control for receivers.
type receiverAccessControlService interface {
HasList(ctx context.Context, user identity.Requester) (bool, error)
HasReadAll(ctx context.Context, user identity.Requester) (bool, error)
AuthorizeReadDecryptedAll(ctx context.Context, user identity.Requester) error
}
type alertmanagerConfigStore interface {
Get(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error)
Save(ctx context.Context, revision *legacy_storage.ConfigRevision, orgID int64) error
@ -50,7 +55,7 @@ type transactionManager interface {
}
func NewReceiverService(
ac accesscontrol.AccessControl,
authz receiverAccessControlService,
cfgStore alertmanagerConfigStore,
provisioningStore provisoningStore,
encryptionService secrets.Service,
@ -58,7 +63,7 @@ func NewReceiverService(
log log.Logger,
) *ReceiverService {
return &ReceiverService{
ac: ac,
authz: authz,
provisioningStore: provisioningStore,
cfgStore: cfgStore,
encryptionService: encryptionService,
@ -69,50 +74,19 @@ func NewReceiverService(
}
func (rs *ReceiverService) shouldDecrypt(ctx context.Context, user identity.Requester, reqDecrypt bool) (bool, error) {
decryptAccess, err := rs.hasReadDecrypted(ctx, user)
if err != nil {
if !reqDecrypt {
return false, nil
}
if err := rs.authz.AuthorizeReadDecryptedAll(ctx, user); err != nil {
return false, err
}
if reqDecrypt && !decryptAccess {
return false, ac.NewAuthorizationErrorWithPermissions("read any decrypted receiver", nil) // TODO: Replace with authz service.
}
return decryptAccess && reqDecrypt, nil
}
// hasReadDecrypted checks if the user has permission to read decrypted secure settings.
func (rs *ReceiverService) hasReadDecrypted(ctx context.Context, user identity.Requester) (bool, error) {
return rs.ac.Evaluate(ctx, user, accesscontrol.EvalAny(
accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversReadSecrets),
accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningReadSecrets), // TODO: Add scope all when we implement FGAC.
))
}
// hasReadRedacted checks if the user has permission to read redacted secure settings.
func (rs *ReceiverService) hasReadRedacted(ctx context.Context, user identity.Requester) (bool, error) {
return rs.ac.Evaluate(ctx, user, accesscontrol.EvalAny(
accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningRead),
accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningReadSecrets),
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsProvisioningRead),
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsRead),
//accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversRead, ScopeReceiversProvider.GetResourceAllScope()), // TODO: Add new permissions.
//accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversReadSecrets, ScopeReceiversProvider.GetResourceAllScope(),
))
}
// hasList checks if the user has permission to list receivers.
func (rs *ReceiverService) hasList(ctx context.Context, user identity.Requester) (bool, error) {
return rs.ac.Evaluate(ctx, user, accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversList))
return true, nil
}
// GetReceiver returns a receiver by name.
// The receiver's secure settings are decrypted if requested and the user has access to do so.
func (rs *ReceiverService) GetReceiver(ctx context.Context, q models.GetReceiverQuery, user identity.Requester) (definitions.GettableApiReceiver, error) {
if q.Decrypt && user == nil {
return definitions.GettableApiReceiver{}, ac.NewAuthorizationErrorWithPermissions("read any decrypted receiver", nil) // TODO: Replace with authz service.
}
revision, err := rs.cfgStore.Get(ctx, q.OrgID)
if err != nil {
return definitions.GettableApiReceiver{}, err
@ -139,10 +113,6 @@ func (rs *ReceiverService) GetReceiver(ctx context.Context, q models.GetReceiver
// GetReceivers returns a list of receivers a user has access to.
// Receivers can be filtered by name, and secure settings are decrypted if requested and the user has access to do so.
func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceiversQuery, user identity.Requester) ([]definitions.GettableApiReceiver, error) {
if q.Decrypt && user == nil {
return nil, ac.NewAuthorizationErrorWithPermissions("read any decrypted receiver", nil) // TODO: Replace with authz service.
}
uids := make([]string, 0, len(q.Names))
for _, name := range q.Names {
uids = append(uids, legacy_storage.NameToUid(name))
@ -159,12 +129,17 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
return nil, err
}
readRedactedAccess, err := rs.hasReadRedacted(ctx, user)
decrypt, err := rs.shouldDecrypt(ctx, user, q.Decrypt)
if err != nil {
return nil, err
}
listAccess, err := rs.hasList(ctx, user)
readRedactedAccess, err := rs.authz.HasReadAll(ctx, user)
if err != nil {
return nil, err
}
listAccess, err := rs.authz.HasList(ctx, user)
if err != nil {
return nil, err
}
@ -172,18 +147,13 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
// User doesn't have any permissions on the receivers.
// This is mostly a safeguard as it should not be possible with current API endpoints + middleware authentication.
if !listAccess && !readRedactedAccess {
return nil, ac.NewAuthorizationErrorWithPermissions("read any receiver", nil) // TODO: Replace with authz service.
return nil, nil
}
var output []definitions.GettableApiReceiver
for i := q.Offset; i < len(postables); i++ {
r := postables[i]
decrypt, err := rs.shouldDecrypt(ctx, user, q.Decrypt)
if err != nil {
return nil, err
}
decryptFn := rs.decryptOrRedact(ctx, decrypt, r.Name, "")
// Only has permission to list. This reduces from:

View File

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
"github.com/grafana/grafana/pkg/services/featuremgmt"
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
@ -123,13 +124,13 @@ func TestReceiverService_DecryptRedact(t *testing.T) {
name: "service returns error when trying to decrypt without permission",
decrypt: true,
user: readUser,
err: "[alerting.unauthorized] user is not authorized to read any decrypted receiver",
err: "[alerting.unauthorized] user is not authorized to read decrypted receiver",
},
{
name: "service returns error if user is nil and decrypt is true",
decrypt: true,
user: nil,
err: "[alerting.unauthorized] user is not authorized to read any decrypted receiver",
err: "[alerting.unauthorized] user is not authorized to read decrypted receiver",
},
{
name: "service decrypts receivers with permission",
@ -189,7 +190,7 @@ func createReceiverServiceSut(t *testing.T, encryptSvc secrets.Service) *Receive
provisioningStore := fakes.NewFakeProvisioningStore()
return NewReceiverService(
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
ac.NewReceiverAccess[*models.Receiver](acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), true),
legacy_storage.NewAlertmanagerConfigStore(store),
provisioningStore,
encryptSvc,

View File

@ -338,7 +338,7 @@ func createContactPointServiceSutWithConfigStore(t *testing.T, secretService sec
provisioningStore := fakes.NewFakeProvisioningStore()
receiverService := notifier.NewReceiverService(
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
ac.NewReceiverAccess[*models.Receiver](acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), true),
legacy_storage.NewAlertmanagerConfigStore(configStore),
provisioningStore,
secretService,

View File

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/folder"
alertingauthz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
@ -273,7 +274,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error
)
configStore := legacy_storage.NewAlertmanagerConfigStore(&st)
receiverSvc := notifier.NewReceiverService(
ps.ac,
alertingauthz.NewReceiverAccess[*ngmodels.Receiver](ps.ac, true),
configStore,
st,
ps.secretService,