mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 15:45:43 -06:00
Plugin: Remove external service on plugin removal (#77712)
* Plugin: Remove external service on plugin removal * Add feature flag check in the service registration service * Initialize map * Add HasExternalService as suggested * Commit suggestion Co-authored-by: linoman <2051016+linoman@users.noreply.github.com> * Nit on test. Co-authored-by: linoman <2051016+linoman@users.noreply.github.com> --------- Co-authored-by: linoman <2051016+linoman@users.noreply.github.com>
This commit is contained in:
parent
7169bfdb30
commit
20a2840046
@ -13,5 +13,7 @@ type ExternalService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ExternalServiceRegistry interface {
|
type ExternalServiceRegistry interface {
|
||||||
RegisterExternalService(ctx context.Context, name string, pType plugindef.Type, svc *plugindef.ExternalServiceRegistration) (*ExternalService, error)
|
HasExternalService(ctx context.Context, pluginID string) bool
|
||||||
|
RegisterExternalService(ctx context.Context, pluginID string, pType plugindef.Type, svc *plugindef.ExternalServiceRegistration) (*ExternalService, error)
|
||||||
|
RemoveExternalService(ctx context.Context, pluginID string) error
|
||||||
}
|
}
|
||||||
|
@ -437,10 +437,18 @@ type FakeAuthService struct {
|
|||||||
Result *auth.ExternalService
|
Result *auth.ExternalService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeAuthService) RegisterExternalService(ctx context.Context, name string, pType plugindef.Type, svc *plugindef.ExternalServiceRegistration) (*auth.ExternalService, error) {
|
func (f *FakeAuthService) HasExternalService(ctx context.Context, pluginID string) bool {
|
||||||
|
return f.Result != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeAuthService) RegisterExternalService(ctx context.Context, pluginID string, pType plugindef.Type, svc *plugindef.ExternalServiceRegistration) (*auth.ExternalService, error) {
|
||||||
return f.Result, nil
|
return f.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FakeAuthService) RemoveExternalService(ctx context.Context, pluginID string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type FakeDiscoverer struct {
|
type FakeDiscoverer struct {
|
||||||
DiscoverFunc func(ctx context.Context, src plugins.PluginSource) ([]*plugins.FoundBundle, error)
|
DiscoverFunc func(ctx context.Context, src plugins.PluginSource) ([]*plugins.FoundBundle, error)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins/auth"
|
||||||
"github.com/grafana/grafana/pkg/plugins/config"
|
"github.com/grafana/grafana/pkg/plugins/config"
|
||||||
"github.com/grafana/grafana/pkg/plugins/log"
|
"github.com/grafana/grafana/pkg/plugins/log"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||||
@ -24,16 +25,18 @@ type PluginInstaller struct {
|
|||||||
pluginRegistry registry.Service
|
pluginRegistry registry.Service
|
||||||
pluginLoader loader.Service
|
pluginLoader loader.Service
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
serviceRegistry auth.ExternalServiceRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideInstaller(cfg *config.Cfg, pluginRegistry registry.Service, pluginLoader loader.Service,
|
func ProvideInstaller(cfg *config.Cfg, pluginRegistry registry.Service, pluginLoader loader.Service,
|
||||||
pluginRepo repo.Service) *PluginInstaller {
|
pluginRepo repo.Service, serviceRegistry auth.ExternalServiceRegistry) *PluginInstaller {
|
||||||
return New(pluginRegistry, pluginLoader, pluginRepo,
|
return New(pluginRegistry, pluginLoader, pluginRepo,
|
||||||
storage.FileSystem(log.NewPrettyLogger("installer.fs"), cfg.PluginsPath), storage.SimpleDirNameGeneratorFunc)
|
storage.FileSystem(log.NewPrettyLogger("installer.fs"), cfg.PluginsPath), storage.SimpleDirNameGeneratorFunc, serviceRegistry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(pluginRegistry registry.Service, pluginLoader loader.Service, pluginRepo repo.Service,
|
func New(pluginRegistry registry.Service, pluginLoader loader.Service, pluginRepo repo.Service,
|
||||||
pluginStorage storage.ZipExtractor, pluginStorageDirFunc storage.DirNameGeneratorFunc) *PluginInstaller {
|
pluginStorage storage.ZipExtractor, pluginStorageDirFunc storage.DirNameGeneratorFunc,
|
||||||
|
serviceRegistry auth.ExternalServiceRegistry) *PluginInstaller {
|
||||||
return &PluginInstaller{
|
return &PluginInstaller{
|
||||||
pluginLoader: pluginLoader,
|
pluginLoader: pluginLoader,
|
||||||
pluginRegistry: pluginRegistry,
|
pluginRegistry: pluginRegistry,
|
||||||
@ -41,6 +44,7 @@ func New(pluginRegistry registry.Service, pluginLoader loader.Service, pluginRep
|
|||||||
pluginStorage: pluginStorage,
|
pluginStorage: pluginStorage,
|
||||||
pluginStorageDirFunc: pluginStorageDirFunc,
|
pluginStorageDirFunc: pluginStorageDirFunc,
|
||||||
log: log.New("plugin.installer"),
|
log: log.New("plugin.installer"),
|
||||||
|
serviceRegistry: serviceRegistry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +160,9 @@ func (m *PluginInstaller) Remove(ctx context.Context, pluginID string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.serviceRegistry.HasExternalService(ctx, pluginID) {
|
||||||
|
return m.serviceRegistry.RemoveExternalService(ctx, pluginID)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ func TestPluginManager_Add_Remove(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := New(fakes.NewFakePluginRegistry(), loader, pluginRepo, fs, storage.SimpleDirNameGeneratorFunc)
|
inst := New(fakes.NewFakePluginRegistry(), loader, pluginRepo, fs, storage.SimpleDirNameGeneratorFunc, &fakes.FakeAuthService{})
|
||||||
err := inst.Add(context.Background(), pluginID, v1, testCompatOpts())
|
err := inst.Add(context.Background(), pluginID, v1, testCompatOpts())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ func TestPluginManager_Add_Remove(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pm := New(reg, &fakes.FakeLoader{}, &fakes.FakePluginRepo{}, &fakes.FakePluginStorage{}, storage.SimpleDirNameGeneratorFunc)
|
pm := New(reg, &fakes.FakeLoader{}, &fakes.FakePluginRepo{}, &fakes.FakePluginStorage{}, storage.SimpleDirNameGeneratorFunc, &fakes.FakeAuthService{})
|
||||||
err := pm.Add(context.Background(), p.ID, "3.2.0", testCompatOpts())
|
err := pm.Add(context.Background(), p.ID, "3.2.0", testCompatOpts())
|
||||||
require.ErrorIs(t, err, plugins.ErrInstallCorePlugin)
|
require.ErrorIs(t, err, plugins.ErrInstallCorePlugin)
|
||||||
|
|
||||||
|
@ -17,6 +17,12 @@ const (
|
|||||||
type AuthProvider string
|
type AuthProvider string
|
||||||
|
|
||||||
type ExternalServiceRegistry interface {
|
type ExternalServiceRegistry interface {
|
||||||
|
// HasExternalService returns whether an external service has been saved with that name.
|
||||||
|
HasExternalService(ctx context.Context, name string) bool
|
||||||
|
|
||||||
|
// RemoveExternalService removes an external service and its associated resources from the database (ex: service account, token).
|
||||||
|
RemoveExternalService(ctx context.Context, name string) error
|
||||||
|
|
||||||
// SaveExternalService creates or updates an external service in the database. Based on the requested auth provider,
|
// SaveExternalService creates or updates an external service in the database. Based on the requested auth provider,
|
||||||
// it generates client_id, secrets and any additional provider specificities (ex: rsa keys). It also ensures that the
|
// it generates client_id, secrets and any additional provider specificities (ex: rsa keys). It also ensures that the
|
||||||
// associated service account has the correct permissions.
|
// associated service account has the correct permissions.
|
||||||
|
@ -33,6 +33,8 @@ type OAuth2Server interface {
|
|||||||
// GetExternalService retrieves an external service from store by client_id. It populates the SelfPermissions and
|
// GetExternalService retrieves an external service from store by client_id. It populates the SelfPermissions and
|
||||||
// SignedInUser from the associated service account.
|
// SignedInUser from the associated service account.
|
||||||
GetExternalService(ctx context.Context, id string) (*OAuthExternalService, error)
|
GetExternalService(ctx context.Context, id string) (*OAuthExternalService, error)
|
||||||
|
// RemoveExternalService removes an external service and its associated resources from the store.
|
||||||
|
RemoveExternalService(ctx context.Context, name string) error
|
||||||
|
|
||||||
// HandleTokenRequest handles the client's OAuth2 query to obtain an access_token by presenting its authorization
|
// HandleTokenRequest handles the client's OAuth2 query to obtain an access_token by presenting its authorization
|
||||||
// grant (ex: client_credentials, jwtbearer).
|
// grant (ex: client_credentials, jwtbearer).
|
||||||
@ -45,6 +47,7 @@ type OAuth2Server interface {
|
|||||||
//go:generate mockery --name Store --structname MockStore --outpkg oastest --filename store_mock.go --output ./oastest/
|
//go:generate mockery --name Store --structname MockStore --outpkg oastest --filename store_mock.go --output ./oastest/
|
||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
|
DeleteExternalService(ctx context.Context, id string) error
|
||||||
GetExternalService(ctx context.Context, id string) (*OAuthExternalService, error)
|
GetExternalService(ctx context.Context, id string) (*OAuthExternalService, error)
|
||||||
GetExternalServiceByName(ctx context.Context, name string) (*OAuthExternalService, error)
|
GetExternalServiceByName(ctx context.Context, name string) (*OAuthExternalService, error)
|
||||||
GetExternalServicePublicKey(ctx context.Context, clientID string) (*jose.JSONWebKey, error)
|
GetExternalServicePublicKey(ctx context.Context, clientID string) (*jose.JSONWebKey, error)
|
||||||
|
@ -183,6 +183,33 @@ func (s *OAuth2ServiceImpl) setClientUser(ctx context.Context, client *oauthserv
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OAuth2ServiceImpl) RemoveExternalService(ctx context.Context, name string) error {
|
||||||
|
s.logger.Info("Remove external service", "service", name)
|
||||||
|
|
||||||
|
client, err := s.sqlstore.GetExternalServiceByName(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, oauthserver.ErrClientNotFound) {
|
||||||
|
s.logger.Debug("No external service linked to this name", "name", name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.logger.Error("Error fetching external service", "name", name, "error", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we will delete the service, clear cache entry
|
||||||
|
s.cache.Delete(client.ClientID)
|
||||||
|
|
||||||
|
// Delete the OAuth client info in store
|
||||||
|
if err := s.sqlstore.DeleteExternalService(ctx, client.ClientID); err != nil {
|
||||||
|
s.logger.Error("Error deleting external service", "name", name, "error", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Debug("Deleted external service", "name", name, "client_id", client.ClientID)
|
||||||
|
|
||||||
|
// Remove the associated service account
|
||||||
|
return s.saService.RemoveExtSvcAccount(ctx, oauthserver.TmpOrgID, slugify.Slugify(name))
|
||||||
|
}
|
||||||
|
|
||||||
// SaveExternalService creates or updates an external service in the database, it generates client_id and secrets and
|
// SaveExternalService creates or updates an external service in the database, it generates client_id and secrets and
|
||||||
// it ensures that the associated service account has the correct permissions.
|
// it ensures that the associated service account has the correct permissions.
|
||||||
// Database consistency is not guaranteed, consider changing this in the future.
|
// Database consistency is not guaranteed, consider changing this in the future.
|
||||||
@ -412,14 +439,13 @@ func (s *OAuth2ServiceImpl) handlePluginStateChanged(ctx context.Context, event
|
|||||||
s.logger.Info("Plugin state changed", "pluginId", event.PluginId, "enabled", event.Enabled)
|
s.logger.Info("Plugin state changed", "pluginId", event.PluginId, "enabled", event.Enabled)
|
||||||
|
|
||||||
// Retrieve client associated to the plugin
|
// Retrieve client associated to the plugin
|
||||||
slug := slugify.Slugify(event.PluginId)
|
client, err := s.sqlstore.GetExternalServiceByName(ctx, event.PluginId)
|
||||||
client, err := s.sqlstore.GetExternalServiceByName(ctx, slug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, oauthserver.ErrClientNotFound) {
|
if errors.Is(err, oauthserver.ErrClientNotFound) {
|
||||||
s.logger.Debug("No external service linked to this plugin", "pluginId", event.PluginId)
|
s.logger.Debug("No external service linked to this plugin", "pluginId", event.PluginId)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.logger.Error("Error fetching service", "pluginId", event.PluginId, "error", err)
|
s.logger.Error("Error fetching service", "pluginId", event.PluginId, "error", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,6 +408,51 @@ func assertArrayInMap[K comparable, V string](t *testing.T, m1 map[K][]V, m2 map
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOAuth2ServiceImpl_RemoveExternalService(t *testing.T) {
|
||||||
|
const serviceName = "my-ext-service"
|
||||||
|
const clientID = "RANDOMID"
|
||||||
|
|
||||||
|
dummyClient := &oauthserver.OAuthExternalService{
|
||||||
|
Name: serviceName,
|
||||||
|
ClientID: clientID,
|
||||||
|
ServiceAccountID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
init func(*TestEnv)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should do nothing on not found",
|
||||||
|
init: func(env *TestEnv) {
|
||||||
|
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, serviceName).Return(nil, oauthserver.ErrClientNotFoundFn(serviceName))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should remove the external service and its associated service account",
|
||||||
|
init: func(env *TestEnv) {
|
||||||
|
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, serviceName).Return(dummyClient, nil)
|
||||||
|
env.OAuthStore.On("DeleteExternalService", mock.Anything, clientID).Return(nil)
|
||||||
|
env.SAService.On("RemoveExtSvcAccount", mock.Anything, oauthserver.TmpOrgID, serviceName).Return(nil)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
env := setupTestEnv(t)
|
||||||
|
if tt.init != nil {
|
||||||
|
tt.init(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := env.S.RemoveExternalService(context.Background(), serviceName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
env.OAuthStore.AssertExpectations(t)
|
||||||
|
env.SAService.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTestOAuth2ServiceImpl_handleKeyOptions(t *testing.T) {
|
func TestTestOAuth2ServiceImpl_handleKeyOptions(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -25,6 +25,10 @@ func (s *FakeService) GetExternalService(ctx context.Context, id string) (*oauth
|
|||||||
return s.ExpectedClient, s.ExpectedErr
|
return s.ExpectedClient, s.ExpectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FakeService) RemoveExternalService(ctx context.Context, name string) error {
|
||||||
|
return s.ExpectedErr
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FakeService) HandleTokenRequest(rw http.ResponseWriter, req *http.Request) {}
|
func (s *FakeService) HandleTokenRequest(rw http.ResponseWriter, req *http.Request) {}
|
||||||
|
|
||||||
func (s *FakeService) HandleIntrospectionRequest(rw http.ResponseWriter, req *http.Request) {}
|
func (s *FakeService) HandleIntrospectionRequest(rw http.ResponseWriter, req *http.Request) {}
|
||||||
|
@ -16,6 +16,20 @@ type MockStore struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteExternalService provides a mock function with given fields: ctx, id
|
||||||
|
func (_m *MockStore) DeleteExternalService(ctx context.Context, id string) error {
|
||||||
|
ret := _m.Called(ctx, id)
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||||
|
r0 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// GetExternalService provides a mock function with given fields: ctx, id
|
// GetExternalService provides a mock function with given fields: ctx, id
|
||||||
func (_m *MockStore) GetExternalService(ctx context.Context, id string) (*oauthserver.OAuthExternalService, error) {
|
func (_m *MockStore) GetExternalService(ctx context.Context, id string) (*oauthserver.OAuthExternalService, error) {
|
||||||
ret := _m.Called(ctx, id)
|
ret := _m.Called(ctx, id)
|
||||||
|
@ -126,6 +126,7 @@ func (s *store) GetExternalService(ctx context.Context, id string) (*oauthserver
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
|
res = nil
|
||||||
return oauthserver.ErrClientNotFoundFn(id)
|
return oauthserver.ErrClientNotFoundFn(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,3 +224,18 @@ func (s *store) UpdateExternalServiceGrantTypes(ctx context.Context, clientID, g
|
|||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *store) DeleteExternalService(ctx context.Context, id string) error {
|
||||||
|
if id == "" {
|
||||||
|
return oauthserver.ErrClientRequiredID
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||||
|
if _, err := sess.Exec(`DELETE FROM oauth_client WHERE client_id = ?`, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sess.Exec(`DELETE FROM oauth_impersonate_permission WHERE client_id = ?`, id)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v3"
|
"github.com/go-jose/go-jose/v3"
|
||||||
@ -352,6 +353,88 @@ vuO8AU0bVoUmYMKhozkcCYHudkeS08hEjQIDAQAB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStore_RemoveExternalService(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
client1 := oauthserver.OAuthExternalService{
|
||||||
|
Name: "my-external-service",
|
||||||
|
ClientID: "ClientID",
|
||||||
|
ImpersonatePermissions: []accesscontrol.Permission{},
|
||||||
|
}
|
||||||
|
client2 := oauthserver.OAuthExternalService{
|
||||||
|
Name: "my-external-service-2",
|
||||||
|
ClientID: "ClientID2",
|
||||||
|
ImpersonatePermissions: []accesscontrol.Permission{
|
||||||
|
{Action: "dashboards:read", Scope: "folders:*"},
|
||||||
|
{Action: "dashboards:read", Scope: "dashboards:*"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init store
|
||||||
|
s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})}
|
||||||
|
require.NoError(t, s.SaveExternalService(context.Background(), &client1))
|
||||||
|
require.NoError(t, s.SaveExternalService(context.Background(), &client2))
|
||||||
|
|
||||||
|
// Check presence of clients in store
|
||||||
|
getState := func(t *testing.T) map[string]bool {
|
||||||
|
client, err := s.GetExternalService(ctx, "ClientID")
|
||||||
|
if err != nil && !errors.Is(err, oauthserver.ErrClientNotFound) {
|
||||||
|
require.Fail(t, "error fetching client")
|
||||||
|
}
|
||||||
|
|
||||||
|
client2, err := s.GetExternalService(ctx, "ClientID2")
|
||||||
|
if err != nil && !errors.Is(err, oauthserver.ErrClientNotFound) {
|
||||||
|
require.Fail(t, "error fetching client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]bool{
|
||||||
|
"ClientID": client != nil,
|
||||||
|
"ClientID2": client2 != nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
state map[string]bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no id provided",
|
||||||
|
state: map[string]bool{"ClientID": true, "ClientID2": true},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not found",
|
||||||
|
id: "ClientID3",
|
||||||
|
state: map[string]bool{"ClientID": true, "ClientID2": true},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove client 2",
|
||||||
|
id: "ClientID2",
|
||||||
|
state: map[string]bool{"ClientID": true, "ClientID2": false},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove client 1",
|
||||||
|
id: "ClientID",
|
||||||
|
state: map[string]bool{"ClientID": false, "ClientID2": false},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := s.DeleteExternalService(ctx, tt.id)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.EqualValues(t, tt.state, getState(t))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func compareClientToStored(t *testing.T, s *store, wanted *oauthserver.OAuthExternalService) {
|
func compareClientToStored(t *testing.T, s *store, wanted *oauthserver.OAuthExternalService) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
stored, err := s.GetExternalService(ctx, wanted.ClientID)
|
stored, err := s.GetExternalService(ctx, wanted.ClientID)
|
||||||
|
@ -2,8 +2,10 @@ package registry
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/slugify"
|
||||||
"github.com/grafana/grafana/pkg/services/extsvcauth"
|
"github.com/grafana/grafana/pkg/services/extsvcauth"
|
||||||
"github.com/grafana/grafana/pkg/services/extsvcauth/oauthserver"
|
"github.com/grafana/grafana/pkg/services/extsvcauth/oauthserver"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
@ -17,14 +19,53 @@ type Registry struct {
|
|||||||
logger log.Logger
|
logger log.Logger
|
||||||
oauthServer oauthserver.OAuth2Server
|
oauthServer oauthserver.OAuth2Server
|
||||||
saSvc *extsvcaccounts.ExtSvcAccountsService
|
saSvc *extsvcaccounts.ExtSvcAccountsService
|
||||||
|
|
||||||
|
extSvcProviders map[string]extsvcauth.AuthProvider
|
||||||
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideExtSvcRegistry(oauthServer oauthserver.OAuth2Server, saSvc *extsvcaccounts.ExtSvcAccountsService, features featuremgmt.FeatureToggles) *Registry {
|
func ProvideExtSvcRegistry(oauthServer oauthserver.OAuth2Server, saSvc *extsvcaccounts.ExtSvcAccountsService, features featuremgmt.FeatureToggles) *Registry {
|
||||||
return &Registry{
|
return &Registry{
|
||||||
features: features,
|
extSvcProviders: map[string]extsvcauth.AuthProvider{},
|
||||||
logger: log.New("extsvcauth.registry"),
|
features: features,
|
||||||
oauthServer: oauthServer,
|
lock: sync.Mutex{},
|
||||||
saSvc: saSvc,
|
logger: log.New("extsvcauth.registry"),
|
||||||
|
oauthServer: oauthServer,
|
||||||
|
saSvc: saSvc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasExternalService returns whether an external service has been saved with that name.
|
||||||
|
func (r *Registry) HasExternalService(ctx context.Context, name string) bool {
|
||||||
|
_, ok := r.extSvcProviders[slugify.Slugify(name)]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveExternalService removes an external service and its associated resources from the database (ex: service account, token).
|
||||||
|
func (r *Registry) RemoveExternalService(ctx context.Context, name string) error {
|
||||||
|
provider, ok := r.extSvcProviders[slugify.Slugify(name)]
|
||||||
|
if !ok {
|
||||||
|
r.logger.Debug("external service not found", "service", name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch provider {
|
||||||
|
case extsvcauth.ServiceAccounts:
|
||||||
|
if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) {
|
||||||
|
r.logger.Debug("Skipping External Service removal, flag disabled", "service", name, "flag", featuremgmt.FlagExternalServiceAccounts)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r.logger.Debug("Routing External Service removal to the External Service Account service", "service", name)
|
||||||
|
return r.saSvc.RemoveExternalService(ctx, name)
|
||||||
|
case extsvcauth.OAuth2Server:
|
||||||
|
if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) {
|
||||||
|
r.logger.Debug("Skipping External Service removal, flag disabled", "service", name, "flag", featuremgmt.FlagExternalServiceAccounts)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r.logger.Debug("Routing External Service removal to the OAuth2Server", "service", name)
|
||||||
|
return r.oauthServer.RemoveExternalService(ctx, name)
|
||||||
|
default:
|
||||||
|
return extsvcauth.ErrUnknownProvider.Errorf("unknow provider '%v'", provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,17 +73,22 @@ func ProvideExtSvcRegistry(oauthServer oauthserver.OAuth2Server, saSvc *extsvcac
|
|||||||
// it generates client_id, secrets and any additional provider specificities (ex: rsa keys). It also ensures that the
|
// it generates client_id, secrets and any additional provider specificities (ex: rsa keys). It also ensures that the
|
||||||
// associated service account has the correct permissions.
|
// associated service account has the correct permissions.
|
||||||
func (r *Registry) SaveExternalService(ctx context.Context, cmd *extsvcauth.ExternalServiceRegistration) (*extsvcauth.ExternalService, error) {
|
func (r *Registry) SaveExternalService(ctx context.Context, cmd *extsvcauth.ExternalServiceRegistration) (*extsvcauth.ExternalService, error) {
|
||||||
|
// Record provider in case of removal
|
||||||
|
r.lock.Lock()
|
||||||
|
r.extSvcProviders[slugify.Slugify(cmd.Name)] = cmd.AuthProvider
|
||||||
|
r.lock.Unlock()
|
||||||
|
|
||||||
switch cmd.AuthProvider {
|
switch cmd.AuthProvider {
|
||||||
case extsvcauth.ServiceAccounts:
|
case extsvcauth.ServiceAccounts:
|
||||||
if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) {
|
if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) {
|
||||||
r.logger.Warn("Skipping external service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAccounts)
|
r.logger.Warn("Skipping External Service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAccounts)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
r.logger.Debug("Routing the External Service registration to the External Service Account service", "service", cmd.Name)
|
r.logger.Debug("Routing the External Service registration to the External Service Account service", "service", cmd.Name)
|
||||||
return r.saSvc.SaveExternalService(ctx, cmd)
|
return r.saSvc.SaveExternalService(ctx, cmd)
|
||||||
case extsvcauth.OAuth2Server:
|
case extsvcauth.OAuth2Server:
|
||||||
if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) {
|
if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) {
|
||||||
r.logger.Warn("Skipping external service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAuth)
|
r.logger.Warn("Skipping External Service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAuth)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
r.logger.Debug("Routing the External Service registration to the OAuth2Server", "service", cmd.Name)
|
r.logger.Debug("Routing the External Service registration to the OAuth2Server", "service", cmd.Name)
|
||||||
|
@ -39,8 +39,7 @@ func newExternalServiceRegistration(cfg *config.Cfg, serviceRegistry auth.Extern
|
|||||||
|
|
||||||
// Register registers the external service with the external service registry, if the feature is enabled.
|
// Register registers the external service with the external service registry, if the feature is enabled.
|
||||||
func (r *ExternalServiceRegistration) Register(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
func (r *ExternalServiceRegistration) Register(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
||||||
if p.ExternalServiceRegistration != nil &&
|
if p.ExternalServiceRegistration != nil {
|
||||||
(r.cfg.Features.IsEnabled(featuremgmt.FlagExternalServiceAuth) || r.cfg.Features.IsEnabled(featuremgmt.FlagExternalServiceAccounts)) {
|
|
||||||
s, err := r.externalServiceRegistry.RegisterExternalService(ctx, p.ID, plugindef.Type(p.Type), p.ExternalServiceRegistration)
|
s, err := r.externalServiceRegistry.RegisterExternalService(ctx, p.ID, plugindef.Type(p.Type), p.ExternalServiceRegistration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.log.Error("Could not register an external service. Initialization skipped", "pluginId", p.ID, "error", err)
|
r.log.Error("Could not register an external service. Initialization skipped", "pluginId", p.ID, "error", err)
|
||||||
|
@ -5,32 +5,53 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/plugins/auth"
|
"github.com/grafana/grafana/pkg/plugins/auth"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins/config"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins/log"
|
||||||
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/extsvcauth"
|
"github.com/grafana/grafana/pkg/services/extsvcauth"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
reg extsvcauth.ExternalServiceRegistry
|
featureEnabled bool
|
||||||
settingsSvc pluginsettings.Service
|
log log.Logger
|
||||||
|
reg extsvcauth.ExternalServiceRegistry
|
||||||
|
settingsSvc pluginsettings.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(reg extsvcauth.ExternalServiceRegistry, settingsSvc pluginsettings.Service) *Service {
|
func ProvideService(cfg *config.Cfg, reg extsvcauth.ExternalServiceRegistry, settingsSvc pluginsettings.Service) *Service {
|
||||||
s := &Service{
|
s := &Service{
|
||||||
reg: reg,
|
featureEnabled: cfg.Features.IsEnabled(featuremgmt.FlagExternalServiceAuth) || cfg.Features.IsEnabled(featuremgmt.FlagExternalServiceAccounts),
|
||||||
settingsSvc: settingsSvc,
|
log: log.New("plugins.external.registration"),
|
||||||
|
reg: reg,
|
||||||
|
settingsSvc: settingsSvc,
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) HasExternalService(ctx context.Context, pluginID string) bool {
|
||||||
|
if !s.featureEnabled {
|
||||||
|
s.log.Debug("Skipping HasExternalService call. The feature is behind a feature toggle and needs to be enabled.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.reg.HasExternalService(ctx, pluginID)
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterExternalService is a simplified wrapper around SaveExternalService for the plugin use case.
|
// RegisterExternalService is a simplified wrapper around SaveExternalService for the plugin use case.
|
||||||
func (s *Service) RegisterExternalService(ctx context.Context, svcName string, pType plugindef.Type, svc *plugindef.ExternalServiceRegistration) (*auth.ExternalService, error) {
|
func (s *Service) RegisterExternalService(ctx context.Context, pluginID string, pType plugindef.Type, svc *plugindef.ExternalServiceRegistration) (*auth.ExternalService, error) {
|
||||||
|
if !s.featureEnabled {
|
||||||
|
s.log.Warn("Skipping External Service Registration. The feature is behind a feature toggle and needs to be enabled.")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Datasource plugins can only be enabled
|
// Datasource plugins can only be enabled
|
||||||
enabled := true
|
enabled := true
|
||||||
// App plugins can be disabled
|
// App plugins can be disabled
|
||||||
if pType == plugindef.TypeApp {
|
if pType == plugindef.TypeApp {
|
||||||
settings, err := s.settingsSvc.GetPluginSettingByPluginID(ctx, &pluginsettings.GetByPluginIDArgs{PluginID: svcName})
|
settings, err := s.settingsSvc.GetPluginSettingByPluginID(ctx, &pluginsettings.GetByPluginIDArgs{PluginID: pluginID})
|
||||||
if err != nil && !errors.Is(err, pluginsettings.ErrPluginSettingNotFound) {
|
if err != nil && !errors.Is(err, pluginsettings.ErrPluginSettingNotFound) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -56,7 +77,7 @@ func (s *Service) RegisterExternalService(ctx context.Context, svcName string, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
registration := &extsvcauth.ExternalServiceRegistration{
|
registration := &extsvcauth.ExternalServiceRegistration{
|
||||||
Name: svcName,
|
Name: pluginID,
|
||||||
Impersonation: impersonation,
|
Impersonation: impersonation,
|
||||||
Self: self,
|
Self: self,
|
||||||
}
|
}
|
||||||
@ -98,3 +119,13 @@ func toAccessControlPermissions(ps []plugindef.Permission) []accesscontrol.Permi
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveExternalService removes the external service account associated to a plugin
|
||||||
|
func (s *Service) RemoveExternalService(ctx context.Context, pluginID string) error {
|
||||||
|
if !s.featureEnabled {
|
||||||
|
s.log.Debug("Skipping External Service Removal. The feature is behind a feature toggle and needs to be enabled.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.reg.RemoveExternalService(ctx, pluginID)
|
||||||
|
}
|
||||||
|
@ -126,6 +126,37 @@ func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd *
|
|||||||
return &extsvcauth.ExternalService{Name: cmd.Name, ID: slug, Secret: token}, nil
|
return &extsvcauth.ExternalService{Name: cmd.Name, ID: slug, Secret: token}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (esa *ExtSvcAccountsService) RemoveExternalService(ctx context.Context, name string) error {
|
||||||
|
// This is double proofing, we should never reach here anyway the flags have already been checked.
|
||||||
|
if !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) {
|
||||||
|
esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return esa.RemoveExtSvcAccount(ctx, extsvcauth.TmpOrgID, slugify.Slugify(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (esa *ExtSvcAccountsService) RemoveExtSvcAccount(ctx context.Context, orgID int64, extSvcSlug string) error {
|
||||||
|
saID, errRetrieve := esa.saSvc.RetrieveServiceAccountIdByName(ctx, orgID, sa.ExtSvcPrefix+extSvcSlug)
|
||||||
|
if errRetrieve != nil && !errors.Is(errRetrieve, sa.ErrServiceAccountNotFound) {
|
||||||
|
return errRetrieve
|
||||||
|
}
|
||||||
|
|
||||||
|
if saID <= 0 {
|
||||||
|
esa.logger.Debug("No external service account associated with this service", "service", extSvcSlug, "orgID", orgID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := esa.deleteExtSvcAccount(ctx, orgID, extSvcSlug, saID); err != nil {
|
||||||
|
esa.logger.Error("Error occurred while deleting service account",
|
||||||
|
"service", extSvcSlug,
|
||||||
|
"saID", saID,
|
||||||
|
"error", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
esa.logger.Info("Deleted external service account", "service", extSvcSlug, "orgID", orgID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ManageExtSvcAccount creates, updates or deletes the service account associated with an external service
|
// ManageExtSvcAccount creates, updates or deletes the service account associated with an external service
|
||||||
func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd *sa.ManageExtSvcAccountCmd) (int64, error) {
|
func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd *sa.ManageExtSvcAccountCmd) (int64, error) {
|
||||||
// This is double proofing, we should never reach here anyway the flags have already been checked.
|
// This is double proofing, we should never reach here anyway the flags have already been checked.
|
||||||
@ -153,7 +184,6 @@ func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd *
|
|||||||
"error", err.Error())
|
"error", err.Error())
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
esa.metrics.deletedCount.Inc()
|
|
||||||
}
|
}
|
||||||
esa.logger.Info("Skipping service account creation, no permission",
|
esa.logger.Info("Skipping service account creation, no permission",
|
||||||
"service", cmd.ExtSvcSlug,
|
"service", cmd.ExtSvcSlug,
|
||||||
@ -173,8 +203,6 @@ func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd *
|
|||||||
esa.logger.Error("Could not save service account", "service", cmd.ExtSvcSlug, "error", errSave.Error())
|
esa.logger.Error("Could not save service account", "service", cmd.ExtSvcSlug, "error", errSave.Error())
|
||||||
return 0, errSave
|
return 0, errSave
|
||||||
}
|
}
|
||||||
esa.metrics.savedCount.Inc()
|
|
||||||
|
|
||||||
return saID, nil
|
return saID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +240,8 @@ func (esa *ExtSvcAccountsService) saveExtSvcAccount(ctx context.Context, cmd *sa
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esa.metrics.savedCount.Inc()
|
||||||
|
|
||||||
return cmd.SaID, nil
|
return cmd.SaID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +254,11 @@ func (esa *ExtSvcAccountsService) deleteExtSvcAccount(ctx context.Context, orgID
|
|||||||
if err := esa.acSvc.DeleteExternalServiceRole(ctx, slug); err != nil {
|
if err := esa.acSvc.DeleteExternalServiceRole(ctx, slug); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return esa.DeleteExtSvcCredentials(ctx, orgID, slug)
|
if err := esa.DeleteExtSvcCredentials(ctx, orgID, slug); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
esa.metrics.deletedCount.Inc()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExtSvcAccountToken get or create the token of an External Service
|
// getExtSvcAccountToken get or create the token of an External Service
|
||||||
|
@ -380,3 +380,64 @@ func TestExtSvcAccountsService_SaveExternalService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtSvcAccountsService_RemoveExtSvcAccount(t *testing.T) {
|
||||||
|
extSvcSlug := "grafana-test-app"
|
||||||
|
tmpOrgID := int64(1)
|
||||||
|
extSvcAccID := int64(10)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
init func(env *TestEnv)
|
||||||
|
slug string
|
||||||
|
checks func(t *testing.T, env *TestEnv)
|
||||||
|
want *extsvcauth.ExternalService
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should not fail if the service account does not exist",
|
||||||
|
init: func(env *TestEnv) {
|
||||||
|
// No previous service account was attached to this slug
|
||||||
|
env.SaSvc.On("RetrieveServiceAccountIdByName", mock.Anything, tmpOrgID, sa.ExtSvcPrefix+extSvcSlug).
|
||||||
|
Return(int64(0), sa.ErrServiceAccountNotFound.Errorf("not found"))
|
||||||
|
},
|
||||||
|
slug: extSvcSlug,
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should remove service account",
|
||||||
|
init: func(env *TestEnv) {
|
||||||
|
// A previous service account was attached to this slug
|
||||||
|
env.SaSvc.On("RetrieveServiceAccountIdByName", mock.Anything, tmpOrgID, sa.ExtSvcPrefix+extSvcSlug).
|
||||||
|
Return(extSvcAccID, nil)
|
||||||
|
env.SaSvc.On("DeleteServiceAccount", mock.Anything, tmpOrgID, extSvcAccID).Return(nil)
|
||||||
|
env.AcStore.On("DeleteExternalServiceRole", mock.Anything, extSvcSlug).Return(nil)
|
||||||
|
// A token was previously stored in the secret store
|
||||||
|
_ = env.SkvStore.Set(context.Background(), tmpOrgID, extSvcSlug, kvStoreType, "ExtSvcSecretToken")
|
||||||
|
},
|
||||||
|
slug: extSvcSlug,
|
||||||
|
checks: func(t *testing.T, env *TestEnv) {
|
||||||
|
_, ok, _ := env.SkvStore.Get(context.Background(), tmpOrgID, extSvcSlug, kvStoreType)
|
||||||
|
require.False(t, ok, "secret should have been removed from store")
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
env := setupTestEnv(t)
|
||||||
|
if tt.init != nil {
|
||||||
|
tt.init(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := env.S.RemoveExtSvcAccount(ctx, tmpOrgID, tt.slug)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if tt.checks != nil {
|
||||||
|
tt.checks(t, env)
|
||||||
|
}
|
||||||
|
env.SaSvc.AssertExpectations(t)
|
||||||
|
env.AcStore.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -41,6 +41,8 @@ type ExtSvcAccountsService interface {
|
|||||||
EnableExtSvcAccount(ctx context.Context, cmd *EnableExtSvcAccountCmd) error
|
EnableExtSvcAccount(ctx context.Context, cmd *EnableExtSvcAccountCmd) error
|
||||||
// ManageExtSvcAccount creates, updates or deletes the service account associated with an external service
|
// ManageExtSvcAccount creates, updates or deletes the service account associated with an external service
|
||||||
ManageExtSvcAccount(ctx context.Context, cmd *ManageExtSvcAccountCmd) (int64, error)
|
ManageExtSvcAccount(ctx context.Context, cmd *ManageExtSvcAccountCmd) (int64, error)
|
||||||
|
// RemoveExtSvcAccount removes the external service account associated with an external service
|
||||||
|
RemoveExtSvcAccount(ctx context.Context, orgID int64, extSvcSlug string) error
|
||||||
// RetrieveExtSvcAccount fetches an external service account by ID
|
// RetrieveExtSvcAccount fetches an external service account by ID
|
||||||
RetrieveExtSvcAccount(ctx context.Context, orgID, saID int64) (*ExtSvcAccount, error)
|
RetrieveExtSvcAccount(ctx context.Context, orgID, saID int64) (*ExtSvcAccount, error)
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,20 @@ func (_m *MockExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cm
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveExtSvcAccount provides a mock function with given fields: ctx, orgID, extSvcSlug
|
||||||
|
func (_m *MockExtSvcAccountsService) RemoveExtSvcAccount(ctx context.Context, orgID int64, extSvcSlug string) error {
|
||||||
|
ret := _m.Called(ctx, orgID, extSvcSlug)
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok {
|
||||||
|
r0 = rf(ctx, orgID, extSvcSlug)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// RetrieveExtSvcAccount provides a mock function with given fields: ctx, orgID, saID
|
// RetrieveExtSvcAccount provides a mock function with given fields: ctx, orgID, saID
|
||||||
func (_m *MockExtSvcAccountsService) RetrieveExtSvcAccount(ctx context.Context, orgID int64, saID int64) (*serviceaccounts.ExtSvcAccount, error) {
|
func (_m *MockExtSvcAccountsService) RetrieveExtSvcAccount(ctx context.Context, orgID int64, saID int64) (*serviceaccounts.ExtSvcAccount, error) {
|
||||||
ret := _m.Called(ctx, orgID, saID)
|
ret := _m.Called(ctx, orgID, saID)
|
||||||
|
Loading…
Reference in New Issue
Block a user