mirror of
https://github.com/grafana/grafana.git
synced 2024-12-26 17:01:09 -06:00
Alerting: Managed receiver resource permission in receiver_svc (#93556)
* Alerting: Managed receiver resource permission in receiver_svc
This commit is contained in:
parent
ff37d477fd
commit
6652233493
@ -155,6 +155,8 @@ type ServiceAccountPermissionsService interface {
|
||||
|
||||
type ReceiverPermissionsService interface {
|
||||
PermissionsService
|
||||
SetDefaultPermissions(ctx context.Context, orgID int64, user identity.Requester, uid string)
|
||||
CopyPermissions(ctx context.Context, orgID int64, user identity.Requester, oldUID, newUID string) (int, error)
|
||||
}
|
||||
|
||||
type PermissionsService interface {
|
||||
|
@ -1,14 +1,22 @@
|
||||
package ossaccesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/authlib/claims"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
alertingac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -18,6 +26,14 @@ var ReceiversViewActions = []string{accesscontrol.ActionAlertingReceiversRead}
|
||||
var ReceiversEditActions = append(ReceiversViewActions, []string{accesscontrol.ActionAlertingReceiversUpdate, accesscontrol.ActionAlertingReceiversDelete}...)
|
||||
var ReceiversAdminActions = append(ReceiversEditActions, []string{accesscontrol.ActionAlertingReceiversReadSecrets, accesscontrol.ActionAlertingReceiversPermissionsRead, accesscontrol.ActionAlertingReceiversPermissionsWrite}...)
|
||||
|
||||
// defaultPermissions returns the default permissions for a newly created receiver.
|
||||
func defaultPermissions() []accesscontrol.SetResourcePermissionCommand {
|
||||
return []accesscontrol.SetResourcePermissionCommand{
|
||||
{BuiltinRole: string(org.RoleEditor), Permission: string(alertingac.ReceiverPermissionEdit)},
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: string(alertingac.ReceiverPermissionView)},
|
||||
}
|
||||
}
|
||||
|
||||
func ProvideReceiverPermissionsService(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl,
|
||||
license licensing.Licensing, service accesscontrol.Service,
|
||||
@ -50,11 +66,131 @@ func ProvideReceiverPermissionsService(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ReceiverPermissionsService{Service: srv}, nil
|
||||
return &ReceiverPermissionsService{srv, service, log.New("resourcepermissions.receivers")}, nil
|
||||
}
|
||||
|
||||
var _ accesscontrol.ReceiverPermissionsService = new(ReceiverPermissionsService)
|
||||
|
||||
type ReceiverPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
ac accesscontrol.Service
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// SetDefaultPermissions sets the default permissions for a newly created receiver.
|
||||
func (r ReceiverPermissionsService) SetDefaultPermissions(ctx context.Context, orgID int64, user identity.Requester, uid string) {
|
||||
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)
|
||||
} else {
|
||||
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
|
||||
UserID: userID, Permission: string(alertingac.ReceiverPermissionAdmin),
|
||||
})
|
||||
clearCache = true
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := r.SetPermissions(ctx, orgID, uid, permissions...); err != nil {
|
||||
r.log.Error("Could not set default permissions", "receiver_uid", uid, "error", err)
|
||||
}
|
||||
|
||||
if clearCache {
|
||||
// Clear permission cache for the user who created the receiver, so that new permissions are fetched for their next call
|
||||
// Required for cases when caller wants to immediately interact with the newly created object
|
||||
r.ac.ClearUserPermissionCache(user)
|
||||
}
|
||||
}
|
||||
|
||||
// copyPermissionUser returns a user with permissions to copy permissions from one receiver to another. This must include
|
||||
// permissions to read and write permissions for the receiver, as well as read permissions for users, service accounts, and teams.
|
||||
func copyPermissionUser(orgID int64) identity.Requester {
|
||||
return accesscontrol.BackgroundUser("receiver_access_service", orgID, org.RoleAdmin, accesscontrol.ConcatPermissions(
|
||||
accesscontrol.PermissionsForActions(ReceiversAdminActions, alertingac.ScopeReceiversAll),
|
||||
[]accesscontrol.Permission{ // Permissions needed for GetPermissions to return user, service account, and team permissions.
|
||||
{Action: accesscontrol.ActionOrgUsersRead, Scope: accesscontrol.ScopeUsersAll},
|
||||
{Action: serviceaccounts.ActionRead, Scope: serviceaccounts.ScopeAll},
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: accesscontrol.ScopeTeamsAll},
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// CopyPermissions copies the resource permissions from one receiver uid to a new uid. This is a temporary
|
||||
// method to be used during receiver renaming that is necessitated by receiver uids being generated from the receiver
|
||||
// name.
|
||||
func (r ReceiverPermissionsService) CopyPermissions(ctx context.Context, orgID int64, user identity.Requester, oldUID, newUID string) (int, error) {
|
||||
currentPermissions, err := r.GetPermissions(ctx, copyPermissionUser(orgID), oldUID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
setPermissionCommands := r.toSetResourcePermissionCommands(currentPermissions)
|
||||
if _, err := r.SetPermissions(ctx, orgID, newUID, setPermissionCommands...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Clear permission cache for the user who updated the receiver, so that new permissions are fetched for their next call
|
||||
// Required for cases when caller wants to immediately interact with the newly updated object
|
||||
if user != nil && user.IsIdentityType(claims.TypeUser) {
|
||||
r.ac.ClearUserPermissionCache(user)
|
||||
}
|
||||
|
||||
return countCustomPermissions(setPermissionCommands), nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
cmds := make([]accesscontrol.SetResourcePermissionCommand, 0, len(permissions))
|
||||
for _, p := range permissions {
|
||||
if !p.IsManaged {
|
||||
continue
|
||||
}
|
||||
permission := r.MapActions(p)
|
||||
if permission == "" {
|
||||
continue
|
||||
}
|
||||
cmds = append(cmds, accesscontrol.SetResourcePermissionCommand{
|
||||
Permission: permission,
|
||||
BuiltinRole: p.BuiltInRole,
|
||||
TeamID: p.TeamId,
|
||||
UserID: p.UserId,
|
||||
})
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
// countCustomPermissions counts the number of custom permissions in a list of set resource permission commands. A
|
||||
// custom permission is a permission that is not a default permission for a receiver.
|
||||
func countCustomPermissions(permissions []accesscontrol.SetResourcePermissionCommand) int {
|
||||
cacheKey := func(p accesscontrol.SetResourcePermissionCommand) accesscontrol.SetResourcePermissionCommand {
|
||||
return accesscontrol.SetResourcePermissionCommand{
|
||||
Permission: "",
|
||||
BuiltinRole: p.BuiltinRole,
|
||||
TeamID: p.TeamID,
|
||||
UserID: p.UserID,
|
||||
}
|
||||
}
|
||||
missingDefaults := make(map[accesscontrol.SetResourcePermissionCommand]string, 2)
|
||||
for _, p := range defaultPermissions() {
|
||||
missingDefaults[cacheKey(p)] = p.Permission
|
||||
}
|
||||
|
||||
diff := 0
|
||||
for _, p := range permissions {
|
||||
key := cacheKey(p)
|
||||
perm, ok := missingDefaults[key]
|
||||
if perm != p.Permission {
|
||||
diff++
|
||||
}
|
||||
if ok {
|
||||
delete(missingDefaults, key)
|
||||
}
|
||||
}
|
||||
|
||||
// missing + new
|
||||
return len(missingDefaults) + diff
|
||||
}
|
||||
|
@ -374,6 +374,7 @@ func createNotificationSrvSutFromEnv(t *testing.T, env *testEnvironment) Notific
|
||||
env.secrets,
|
||||
env.xact,
|
||||
env.log,
|
||||
fakes.NewFakeReceiverPermissionsService(),
|
||||
)
|
||||
return NotificationSrv{
|
||||
logger: env.log,
|
||||
|
@ -43,6 +43,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
secrets_fakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
@ -1901,6 +1902,7 @@ func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) Provisi
|
||||
env.secrets,
|
||||
env.xact,
|
||||
env.log,
|
||||
ngalertfakes.NewFakeReceiverPermissionsService(),
|
||||
)
|
||||
return ProvisioningSrv{
|
||||
log: env.log,
|
||||
|
@ -77,6 +77,7 @@ func ProvideService(
|
||||
tracer tracing.Tracer,
|
||||
ruleStore *store.DBstore,
|
||||
httpClientProvider httpclient.Provider,
|
||||
resourcePermissions accesscontrol.ReceiverPermissionsService,
|
||||
) (*AlertNG, error) {
|
||||
ng := &AlertNG{
|
||||
Cfg: cfg,
|
||||
@ -98,12 +99,13 @@ func ProvideService(
|
||||
dashboardService: dashboardService,
|
||||
renderService: renderService,
|
||||
bus: bus,
|
||||
accesscontrolService: accesscontrolService,
|
||||
AccesscontrolService: accesscontrolService,
|
||||
annotationsRepo: annotationsRepo,
|
||||
pluginsStore: pluginsStore,
|
||||
tracer: tracer,
|
||||
store: ruleStore,
|
||||
httpClientProvider: httpClientProvider,
|
||||
ResourcePermissions: resourcePermissions,
|
||||
}
|
||||
|
||||
if ng.IsDisabled() {
|
||||
@ -147,7 +149,8 @@ type AlertNG struct {
|
||||
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
|
||||
AlertsRouter *sender.AlertsRouter
|
||||
accesscontrol accesscontrol.AccessControl
|
||||
accesscontrolService accesscontrol.Service
|
||||
AccesscontrolService accesscontrol.Service
|
||||
ResourcePermissions accesscontrol.ReceiverPermissionsService
|
||||
annotationsRepo annotations.Repository
|
||||
store *store.DBstore
|
||||
|
||||
@ -423,6 +426,7 @@ func (ng *AlertNG) init() error {
|
||||
ng.SecretsService,
|
||||
ng.store,
|
||||
ng.Log,
|
||||
ng.ResourcePermissions,
|
||||
)
|
||||
provisioningReceiverService := notifier.NewReceiverService(
|
||||
ac.NewReceiverAccess[*models.Receiver](ng.accesscontrol, true),
|
||||
@ -432,6 +436,7 @@ func (ng *AlertNG) init() error {
|
||||
ng.SecretsService,
|
||||
ng.store,
|
||||
ng.Log,
|
||||
ng.ResourcePermissions,
|
||||
)
|
||||
|
||||
// Provisioning
|
||||
@ -489,7 +494,7 @@ func (ng *AlertNG) init() error {
|
||||
return key.LogContext(), true
|
||||
})
|
||||
|
||||
return DeclareFixedRoles(ng.accesscontrolService, ng.FeatureToggles)
|
||||
return DeclareFixedRoles(ng.AccesscontrolService, ng.FeatureToggles)
|
||||
}
|
||||
|
||||
func subscribeToFolderChanges(logger log.Logger, bus bus.Bus, dbStore api.RuleStore) {
|
||||
|
@ -19,7 +19,7 @@ func (rev *ConfigRevision) DeleteReceiver(uid string) {
|
||||
|
||||
func (rev *ConfigRevision) CreateReceiver(receiver *models.Receiver) (*definitions.PostableApiReceiver, error) {
|
||||
// Check if the receiver already exists.
|
||||
_, err := rev.GetReceiver(NameToUid(receiver.Name)) // get UID from name because the new receiver does not have UID yet.
|
||||
_, err := rev.GetReceiver(receiver.GetUID())
|
||||
if err == nil {
|
||||
return nil, ErrReceiverExists.Errorf("")
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
ac "github.com/grafana/grafana/pkg/services/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"
|
||||
@ -45,6 +46,7 @@ type ReceiverService struct {
|
||||
xact transactionManager
|
||||
log log.Logger
|
||||
provenanceValidator validation.ProvenanceStatusTransitionValidator
|
||||
resourcePermissions ac.ReceiverPermissionsService
|
||||
}
|
||||
|
||||
type alertRuleNotificationSettingsStore interface {
|
||||
@ -96,6 +98,7 @@ func NewReceiverService(
|
||||
encryptionService secretService,
|
||||
xact transactionManager,
|
||||
log log.Logger,
|
||||
resourcePermissions ac.ReceiverPermissionsService,
|
||||
) *ReceiverService {
|
||||
return &ReceiverService{
|
||||
authz: authz,
|
||||
@ -106,6 +109,7 @@ func NewReceiverService(
|
||||
xact: xact,
|
||||
log: log,
|
||||
provenanceValidator: validation.ValidateProvenanceRelaxed,
|
||||
resourcePermissions: resourcePermissions,
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,6 +316,12 @@ func (rs *ReceiverService) DeleteReceiver(ctx context.Context, uid string, calle
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.resourcePermissions != nil {
|
||||
err = rs.resourcePermissions.DeleteResourcePermissions(ctx, orgID, uid)
|
||||
if err != nil {
|
||||
rs.log.Error("Could not delete receiver permissions", "receiver", existing.Name, "error", err)
|
||||
}
|
||||
}
|
||||
return rs.deleteProvenances(ctx, orgID, existing.Integrations)
|
||||
})
|
||||
}
|
||||
@ -336,6 +346,9 @@ func (rs *ReceiverService) CreateReceiver(ctx context.Context, r *models.Receive
|
||||
return nil, legacy_storage.MakeErrReceiverInvalid(err)
|
||||
}
|
||||
|
||||
// Generate UID from name.
|
||||
createdReceiver.UID = legacy_storage.NameToUid(createdReceiver.Name)
|
||||
|
||||
created, err := revision.CreateReceiver(&createdReceiver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -346,6 +359,9 @@ func (rs *ReceiverService) CreateReceiver(ctx context.Context, r *models.Receive
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.resourcePermissions != nil {
|
||||
rs.resourcePermissions.SetDefaultPermissions(ctx, orgID, user, createdReceiver.GetUID())
|
||||
}
|
||||
return rs.setReceiverProvenance(ctx, orgID, &createdReceiver)
|
||||
})
|
||||
if err != nil {
|
||||
@ -422,6 +438,19 @@ func (rs *ReceiverService) UpdateReceiver(ctx context.Context, r *models.Receive
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Update receiver permissions
|
||||
if rs.resourcePermissions != nil {
|
||||
permissionsUpdated, err := rs.resourcePermissions.CopyPermissions(ctx, orgID, user, legacy_storage.NameToUid(existing.Name), legacy_storage.NameToUid(r.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if permissionsUpdated > 0 {
|
||||
rs.log.FromContext(ctx).Debug("Moved custom receiver permissions", "oldName", existing.Name, "newName", r.Name, "count", permissionsUpdated)
|
||||
}
|
||||
if err := rs.resourcePermissions.DeleteResourcePermissions(ctx, orgID, legacy_storage.NameToUid(existing.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
err = rs.cfgStore.Save(ctx, revision, orgID)
|
||||
if err != nil {
|
||||
|
@ -1483,6 +1483,7 @@ func createReceiverServiceSut(t *testing.T, encryptSvc secretService) *ReceiverS
|
||||
encryptSvc,
|
||||
xact,
|
||||
log.NewNopLogger(),
|
||||
fakes.NewFakeReceiverPermissionsService(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -493,6 +493,7 @@ func createContactPointServiceSutWithConfigStore(t *testing.T, secretService sec
|
||||
secretService,
|
||||
xact,
|
||||
log.NewNopLogger(),
|
||||
fakes.NewFakeReceiverPermissionsService(),
|
||||
)
|
||||
|
||||
return &ContactPointService{
|
||||
|
28
pkg/services/ngalert/tests/fakes/permissions.go
Normal file
28
pkg/services/ngalert/tests/fakes/permissions.go
Normal file
@ -0,0 +1,28 @@
|
||||
package fakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
)
|
||||
|
||||
type FakeReceiverPermissionsService struct {
|
||||
*actest.FakePermissionsService
|
||||
}
|
||||
|
||||
func NewFakeReceiverPermissionsService() *FakeReceiverPermissionsService {
|
||||
return &FakeReceiverPermissionsService{
|
||||
FakePermissionsService: &actest.FakePermissionsService{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f FakeReceiverPermissionsService) SetDefaultPermissions(ctx context.Context, orgID int64, user identity.Requester, uid string) {
|
||||
}
|
||||
|
||||
func (f FakeReceiverPermissionsService) CopyPermissions(ctx context.Context, orgID int64, user identity.Requester, oldUID, newUID string) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var _ accesscontrol.ReceiverPermissionsService = new(FakeReceiverPermissionsService)
|
@ -29,6 +29,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
@ -70,7 +71,7 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
|
||||
ng, err := ngalert.ProvideService(
|
||||
cfg, features, nil, nil, routing.NewRouteRegister(), sqlStore, kvstore.NewFakeKVStore(), nil, nil, quotatest.New(false, nil),
|
||||
secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac,
|
||||
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, httpclient.NewProvider(),
|
||||
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, httpclient.NewProvider(), ngalertfakes.NewFakeReceiverPermissionsService(),
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
return ng, &store.DBstore{
|
||||
|
@ -53,6 +53,7 @@ func ProvideService(
|
||||
quotaService quota.Service,
|
||||
secrectService secrets.Service,
|
||||
orgService org.Service,
|
||||
resourcePermissions accesscontrol.ReceiverPermissionsService,
|
||||
) (*ProvisioningServiceImpl, error) {
|
||||
s := &ProvisioningServiceImpl{
|
||||
Cfg: cfg,
|
||||
@ -76,6 +77,7 @@ func ProvideService(
|
||||
log: log.New("provisioning"),
|
||||
orgService: orgService,
|
||||
folderService: folderService,
|
||||
resourcePermissions: resourcePermissions,
|
||||
}
|
||||
|
||||
if err := s.setDashboardProvisioner(); err != nil {
|
||||
@ -154,6 +156,7 @@ type ProvisioningServiceImpl struct {
|
||||
quotaService quota.Service
|
||||
secretService secrets.Service
|
||||
folderService folder.Service
|
||||
resourcePermissions accesscontrol.ReceiverPermissionsService
|
||||
}
|
||||
|
||||
func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) error {
|
||||
@ -287,6 +290,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error
|
||||
ps.secretService,
|
||||
ps.SQLStore,
|
||||
ps.log,
|
||||
ps.resourcePermissions,
|
||||
)
|
||||
contactPointService := provisioning.NewContactPointService(configStore, ps.secretService,
|
||||
st, ps.SQLStore, receiverSvc, ps.log, &st)
|
||||
|
@ -502,7 +502,7 @@ func setupEnv(t *testing.T, replStore db.ReplDB, cfg *setting.Cfg, b bus.Bus, qu
|
||||
_, err = ngalert.ProvideService(
|
||||
cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, ngalertfakes.NewFakeKVStore(t), nil, nil, quotaService,
|
||||
secretsService, nil, m, &foldertest.FakeService{}, &acmock.Mock{}, &dashboards.FakeDashboardService{}, nil, b, &acmock.Mock{},
|
||||
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, httpclient.NewProvider(),
|
||||
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, httpclient.NewProvider(), ngalertfakes.NewFakeReceiverPermissionsService(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = storesrv.ProvideService(sqlStore, featuremgmt.WithFeatures(), cfg, quotaService, storesrv.ProvideSystemUsersService())
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@ -273,6 +274,42 @@ func (a apiClient) ReloadCachedPermissions(t *testing.T) {
|
||||
require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to reload permissions cache")
|
||||
}
|
||||
|
||||
// AssignReceiverPermission sends a request to access control API to assign permissions to a user, role, or team on a receiver.
|
||||
func (a apiClient) AssignReceiverPermission(t *testing.T, receiverUID string, cmd accesscontrol.SetResourcePermissionCommand) (int, string) {
|
||||
t.Helper()
|
||||
|
||||
var assignment string
|
||||
var assignTo string
|
||||
if cmd.UserID != 0 {
|
||||
assignment = "users"
|
||||
assignTo = fmt.Sprintf("%d", cmd.UserID)
|
||||
} else if cmd.TeamID != 0 {
|
||||
assignment = "teams"
|
||||
assignTo = fmt.Sprintf("%d", cmd.TeamID)
|
||||
} else {
|
||||
assignment = "builtInRoles"
|
||||
assignTo = cmd.BuiltinRole
|
||||
}
|
||||
|
||||
body := strings.NewReader(fmt.Sprintf(`{"permission": "%s"}`, cmd.Permission))
|
||||
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/access-control/receivers/%s/%s/%s", a.url, receiverUID, assignment, assignTo), body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
return resp.StatusCode, string(b)
|
||||
}
|
||||
|
||||
// CreateFolder creates a folder for storing our alerts, and then refreshes the permission cache to make sure that following requests will be accepted
|
||||
func (a apiClient) CreateFolder(t *testing.T, uID string, title string, parentUID ...string) {
|
||||
t.Helper()
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
alertingac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@ -126,6 +127,289 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestIntegrationResourcePermissions focuses on testing resource permissions for the alerting receiver resource. It
|
||||
// verifies that access is correctly set when creating resources and assigning permissions to users, teams, and roles.
|
||||
func TestIntegrationResourcePermissions(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
helper := getTestHelper(t)
|
||||
|
||||
org1 := helper.Org1
|
||||
|
||||
noneUser := helper.CreateUser("none", apis.Org1, org.RoleNone, nil)
|
||||
|
||||
creator := helper.CreateUser("creator", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
createWildcardPermission(
|
||||
accesscontrol.ActionAlertingReceiversCreate,
|
||||
),
|
||||
})
|
||||
|
||||
newClient := func(t *testing.T, user apis.User) notificationsv0alpha1.ReceiverInterface {
|
||||
k8sClient, err := versioned.NewForConfig(user.NewRestConfig())
|
||||
require.NoError(t, err)
|
||||
return k8sClient.NotificationsV0alpha1().Receivers("default")
|
||||
}
|
||||
|
||||
admin := org1.Admin
|
||||
viewer := org1.Viewer
|
||||
editor := org1.Editor
|
||||
adminClient := newClient(t, admin)
|
||||
|
||||
writeACMetadata := []string{"canWrite", "canDelete"}
|
||||
allACMetadata := []string{"canWrite", "canDelete", "canReadSecrets", "canAdmin"}
|
||||
|
||||
mustID := func(user apis.User) int64 {
|
||||
id, err := user.Identity.GetInternalID()
|
||||
require.NoError(t, err)
|
||||
return id
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
creatingUser apis.User
|
||||
testUser apis.User
|
||||
assignments []accesscontrol.SetResourcePermissionCommand
|
||||
expACMetadata []string
|
||||
expRead bool
|
||||
}{
|
||||
// Basic access.
|
||||
{
|
||||
name: "Admin creates and has all metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: admin,
|
||||
assignments: nil,
|
||||
expACMetadata: allACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
{
|
||||
name: "Creator creates and has all metadata and access",
|
||||
creatingUser: creator,
|
||||
testUser: creator,
|
||||
assignments: nil,
|
||||
expACMetadata: allACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, noneUser has no metadata and no access",
|
||||
creatingUser: admin,
|
||||
testUser: noneUser,
|
||||
assignments: nil,
|
||||
expACMetadata: nil,
|
||||
expRead: false,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, viewer has no metadata but has access",
|
||||
creatingUser: admin,
|
||||
testUser: viewer,
|
||||
expACMetadata: nil,
|
||||
expRead: true,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, editor has write metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: editor,
|
||||
expACMetadata: writeACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
// User-based assignments.
|
||||
{
|
||||
name: "Admin creates, assigns read, noneUser has no metadata but has access",
|
||||
creatingUser: admin,
|
||||
testUser: noneUser,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionView)}},
|
||||
expACMetadata: nil,
|
||||
expRead: true,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, assigns write, noneUser has write metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: noneUser,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionEdit)}},
|
||||
expACMetadata: writeACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, assigns admin, noneUser has all metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: noneUser,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
||||
expACMetadata: allACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
// Other users don't get assignments.
|
||||
{
|
||||
name: "Admin creates, assigns read to noneUser, creator has no metadata and no access",
|
||||
creatingUser: admin,
|
||||
testUser: creator,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionView)}},
|
||||
expACMetadata: nil,
|
||||
expRead: false,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, assigns write to noneUser, creator has no metadata and no access",
|
||||
creatingUser: admin,
|
||||
testUser: creator,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionEdit)}},
|
||||
expACMetadata: nil,
|
||||
expRead: false,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, assigns admin to noneUser, creator has no metadata and no access",
|
||||
creatingUser: admin,
|
||||
testUser: creator,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
||||
expACMetadata: nil,
|
||||
expRead: false,
|
||||
},
|
||||
// Role-based access.
|
||||
{
|
||||
name: "Admin creates, assigns editor, viewer has write metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: viewer,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(viewer), Permission: string(alertingac.ReceiverPermissionEdit)}},
|
||||
expACMetadata: writeACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, assigns admin, viewer has all metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: viewer,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(viewer), Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
||||
expACMetadata: allACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, assigns admin, editor has all metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: editor,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(editor), Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
||||
expACMetadata: allACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
// Team-based access. Staff team has editor+admin but not viewer in it.
|
||||
{
|
||||
name: "Admin creates, assigns admin to staff, viewer has no metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: viewer,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{TeamID: org1.Staff.ID, Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
||||
expACMetadata: nil,
|
||||
expRead: true,
|
||||
},
|
||||
{
|
||||
name: "Admin creates, assigns admin to staff, editor has all metadata and access",
|
||||
creatingUser: admin,
|
||||
testUser: editor,
|
||||
assignments: []accesscontrol.SetResourcePermissionCommand{{TeamID: org1.Staff.ID, Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
||||
expACMetadata: allACMetadata,
|
||||
expRead: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
createClient := newClient(t, tc.creatingUser)
|
||||
client := newClient(t, tc.testUser)
|
||||
|
||||
var created = &v0alpha1.Receiver{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v0alpha1.ReceiverSpec{
|
||||
Title: "receiver-1",
|
||||
Integrations: nil,
|
||||
},
|
||||
}
|
||||
d, err := json.Marshal(created)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create receiver with creatingUser
|
||||
created, err = createClient.Create(ctx, created, v1.CreateOptions{})
|
||||
require.NoErrorf(t, err, "Payload %s", string(d))
|
||||
require.NotNil(t, created)
|
||||
|
||||
defer func() {
|
||||
_ = adminClient.Delete(ctx, created.Name, v1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
// Assign resource permissions
|
||||
cliCfg := helper.Org1.Admin.NewRestConfig()
|
||||
alertingApi := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
||||
for _, permission := range tc.assignments {
|
||||
status, body := alertingApi.AssignReceiverPermission(t, created.Name, permission)
|
||||
require.Equalf(t, http.StatusOK, status, "Expected status 200 but got %d: %s", status, body)
|
||||
}
|
||||
|
||||
// Test read
|
||||
if tc.expRead {
|
||||
// Helper methods.
|
||||
extractReceiverFromList := func(list *v0alpha1.ReceiverList, name string) *v0alpha1.Receiver {
|
||||
for i := range list.Items {
|
||||
if list.Items[i].Name == name {
|
||||
return list.Items[i].DeepCopy()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtain expected responses using admin client as source of truth.
|
||||
expectedGetWithMetadata, expectedListWithMetadata := func() (*v0alpha1.Receiver, *v0alpha1.Receiver) {
|
||||
expectedGet, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, expectedGet)
|
||||
|
||||
// Set expected metadata.
|
||||
expectedGetWithMetadata := expectedGet.DeepCopy()
|
||||
// Clear any existing access control metadata.
|
||||
for _, k := range allACMetadata {
|
||||
delete(expectedGetWithMetadata.Annotations, v0alpha1.AccessControlAnnotation(k))
|
||||
}
|
||||
for _, ac := range tc.expACMetadata {
|
||||
expectedGetWithMetadata.SetAccessControl(ac)
|
||||
}
|
||||
|
||||
expectedList, err := adminClient.List(ctx, v1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
expectedListWithMetadata := extractReceiverFromList(expectedList, created.Name)
|
||||
require.NotNil(t, expectedListWithMetadata)
|
||||
expectedListWithMetadata = expectedListWithMetadata.DeepCopy()
|
||||
// Clear any existing access control metadata.
|
||||
for _, k := range allACMetadata {
|
||||
delete(expectedListWithMetadata.Annotations, v0alpha1.AccessControlAnnotation(k))
|
||||
}
|
||||
for _, ac := range tc.expACMetadata {
|
||||
expectedListWithMetadata.SetAccessControl(ac)
|
||||
}
|
||||
return expectedGetWithMetadata, expectedListWithMetadata
|
||||
}()
|
||||
|
||||
t.Run("should be able to list receivers", func(t *testing.T) {
|
||||
list, err := client.List(ctx, v1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
listedReceiver := extractReceiverFromList(list, created.Name)
|
||||
assert.Equalf(t, expectedListWithMetadata, listedReceiver, "Expected %v but got %v", expectedListWithMetadata, listedReceiver)
|
||||
})
|
||||
|
||||
t.Run("should be able to read receiver by resource identifier", func(t *testing.T) {
|
||||
got, err := client.Get(ctx, expectedGetWithMetadata.Name, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, expectedGetWithMetadata, got, "Expected %v but got %v", expectedGetWithMetadata, got)
|
||||
})
|
||||
} else {
|
||||
t.Run("should be forbidden to list receivers", func(t *testing.T) {
|
||||
_, err := client.List(ctx, v1.ListOptions{})
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
|
||||
t.Run("should be forbidden to read receiver by name", func(t *testing.T) {
|
||||
_, err := client.Get(ctx, created.Name, v1.GetOptions{})
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationAccessControl(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
@ -293,34 +577,36 @@ func TestIntegrationAccessControl(t *testing.T) {
|
||||
d, err := json.Marshal(expected)
|
||||
require.NoError(t, err)
|
||||
|
||||
newReceiver := expected.DeepCopy()
|
||||
newReceiver.Spec.Title = fmt.Sprintf("receiver-2-%s", tc.user.Identity.GetLogin())
|
||||
if tc.canCreate {
|
||||
t.Run("should be able to create receiver", func(t *testing.T) {
|
||||
newReceiver := expected
|
||||
|
||||
actual, err := client.Create(ctx, newReceiver, v1.CreateOptions{})
|
||||
require.NoErrorf(t, err, "Payload %s", string(d))
|
||||
|
||||
require.Equal(t, expected.Spec, actual.Spec)
|
||||
require.Equal(t, newReceiver.Spec, actual.Spec)
|
||||
|
||||
t.Run("should fail if already exists", func(t *testing.T) {
|
||||
_, err := client.Create(ctx, newReceiver, v1.CreateOptions{})
|
||||
require.Truef(t, errors.IsConflict(err), "expected bad request but got %s", err)
|
||||
})
|
||||
|
||||
expected = actual
|
||||
// Cleanup.
|
||||
require.NoError(t, adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{}))
|
||||
})
|
||||
} else {
|
||||
t.Run("should be forbidden to create", func(t *testing.T) {
|
||||
_, err := client.Create(ctx, expected, v1.CreateOptions{})
|
||||
_, err := client.Create(ctx, newReceiver, v1.CreateOptions{})
|
||||
require.Truef(t, errors.IsForbidden(err), "Payload %s", string(d))
|
||||
})
|
||||
|
||||
// create resource to proceed with other tests
|
||||
expected, err = adminClient.Create(ctx, expected, v1.CreateOptions{})
|
||||
require.NoErrorf(t, err, "Payload %s", string(d))
|
||||
require.NotNil(t, expected)
|
||||
}
|
||||
|
||||
// create resource to proceed with other tests. We don't use the one created above because the user will always
|
||||
// have admin permissions on it.
|
||||
expected, err = adminClient.Create(ctx, expected, v1.CreateOptions{})
|
||||
require.NoErrorf(t, err, "Payload %s", string(d))
|
||||
require.NotNil(t, expected)
|
||||
|
||||
if tc.canRead {
|
||||
// Set expected metadata.
|
||||
expectedWithMetadata := expected.DeepCopy()
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -29,6 +30,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/server"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
@ -434,7 +436,11 @@ func (c *K8sTestHelper) createTestUsers(orgName string) OrgUsers {
|
||||
Viewer: c.CreateUser("viewer", orgName, org.RoleViewer, nil),
|
||||
}
|
||||
users.Staff = c.CreateTeam("staff", "staff@"+orgName, users.Admin.Identity.GetOrgID())
|
||||
// TODO add admin and editor to staff
|
||||
|
||||
// Add Admin and Editor to Staff team as Admin and Member, respectively.
|
||||
c.AddOrUpdateTeamMember(users.Admin, users.Staff.ID, team.PermissionTypeAdmin)
|
||||
c.AddOrUpdateTeamMember(users.Editor, users.Staff.ID, team.PermissionTypeMember)
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
@ -532,6 +538,42 @@ func (c *K8sTestHelper) SetPermissions(user User, permissions []resourcepermissi
|
||||
}
|
||||
}
|
||||
|
||||
func (c *K8sTestHelper) AddOrUpdateTeamMember(user User, teamID int64, permission team.PermissionType) {
|
||||
teamSvc, err := teamimpl.ProvideService(c.env.ReadReplStore, c.env.Cfg, tracing.InitializeTracerForTest())
|
||||
require.NoError(c.t, err)
|
||||
|
||||
orgService, err := orgimpl.ProvideService(c.env.SQLStore, c.env.Cfg, c.env.Server.HTTPServer.QuotaService)
|
||||
require.NoError(c.t, err)
|
||||
|
||||
cache := localcache.ProvideService()
|
||||
userSvc, err := userimpl.ProvideService(
|
||||
c.env.SQLStore, orgService, c.env.Cfg, teamSvc,
|
||||
cache, tracing.InitializeTracerForTest(), c.env.Server.HTTPServer.QuotaService,
|
||||
supportbundlestest.NewFakeBundleService())
|
||||
require.NoError(c.t, err)
|
||||
|
||||
teampermissionSvc, err := ossaccesscontrol.ProvideTeamPermissions(
|
||||
c.env.Cfg,
|
||||
c.env.FeatureToggles,
|
||||
c.env.Server.HTTPServer.RouteRegister,
|
||||
c.env.SQLStore,
|
||||
c.env.Server.HTTPServer.AccessControl,
|
||||
c.env.Server.HTTPServer.License,
|
||||
c.env.Server.HTTPServer.AlertNG.AccesscontrolService,
|
||||
teamSvc,
|
||||
userSvc,
|
||||
resourcepermissions.NewActionSetService(c.env.FeatureToggles),
|
||||
)
|
||||
require.NoError(c.t, err)
|
||||
|
||||
id, err := user.Identity.GetInternalID()
|
||||
require.NoError(c.t, err)
|
||||
|
||||
teamIDString := strconv.FormatInt(teamID, 10)
|
||||
_, err = teampermissionSvc.SetUserPermission(context.Background(), user.Identity.GetOrgID(), accesscontrol.User{ID: id}, teamIDString, permission.String())
|
||||
require.NoError(c.t, err)
|
||||
}
|
||||
|
||||
func (c *K8sTestHelper) NewDiscoveryClient() *discovery.DiscoveryClient {
|
||||
c.t.Helper()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user