diff --git a/pkg/api/plugin_resource_test.go b/pkg/api/plugin_resource_test.go index 66d69f50215..edcefce3212 100644 --- a/pkg/api/plugin_resource_test.go +++ b/pkg/api/plugin_resource_test.go @@ -72,7 +72,7 @@ func TestCallResource(t *testing.T) { l := loader.ProvideService(pCfg, fakes.NewFakeLicensingService(), signature.NewUnsignedAuthorizer(pCfg), reg, provider.ProvideService(coreRegistry), finder.NewLocalFinder(pCfg), fakes.NewFakeRoleRegistry(), assetpath.ProvideService(pluginscdn.ProvideService(pCfg)), signature.ProvideService(pCfg, statickey.New()), - angularInspector) + angularInspector, &fakes.FakeOauthService{}) srcs := sources.ProvideService(cfg, pCfg) ps, err := store.ProvideService(reg, srcs, l) require.NoError(t, err) diff --git a/pkg/plugins/config/config.go b/pkg/plugins/config/config.go index 022192befce..cd40e433c8b 100644 --- a/pkg/plugins/config/config.go +++ b/pkg/plugins/config/config.go @@ -38,6 +38,8 @@ type Cfg struct { GrafanaComURL string + GrafanaAppURL string + Features plugins.FeatureToggles AngularSupportEnabled bool @@ -45,7 +47,7 @@ type Cfg struct { func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string, awsAllowedAuthProviders []string, awsAssumeRoleEnabled bool, azure *azsettings.AzureSettings, secureSocksDSProxy setting.SecureSocksDSProxySettings, - grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, tracing Tracing, features plugins.FeatureToggles, angularSupportEnabled bool, + grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, appURL string, tracing Tracing, features plugins.FeatureToggles, angularSupportEnabled bool, grafanaComURL string) *Cfg { return &Cfg{ log: log.New("plugin.cfg"), @@ -62,6 +64,7 @@ func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSetti PluginsCDNURLTemplate: pluginsCDNURLTemplate, Tracing: tracing, GrafanaComURL: grafanaComURL, + GrafanaAppURL: appURL, Features: features, AngularSupportEnabled: angularSupportEnabled, } diff --git a/pkg/plugins/envvars/envvars.go b/pkg/plugins/envvars/envvars.go index 0001db3e44e..6b80cfed23b 100644 --- a/pkg/plugins/envvars/envvars.go +++ b/pkg/plugins/envvars/envvars.go @@ -16,7 +16,7 @@ import ( ) type Provider interface { - Get(ctx context.Context, p *plugins.Plugin) []string + Get(ctx context.Context, p *plugins.Plugin) ([]string, error) } type Service struct { @@ -31,7 +31,7 @@ func NewProvider(cfg *config.Cfg, license plugins.Licensing) *Service { } } -func (s *Service) Get(_ context.Context, p *plugins.Plugin) []string { +func (s *Service) Get(ctx context.Context, p *plugins.Plugin) ([]string, error) { hostEnv := []string{ fmt.Sprintf("GF_VERSION=%s", s.cfg.BuildVersion), } @@ -46,13 +46,23 @@ func (s *Service) Get(_ context.Context, p *plugins.Plugin) []string { hostEnv = append(hostEnv, s.license.Environment()...) } + if p.ExternalService != nil { + hostEnv = append( + hostEnv, + fmt.Sprintf("GF_APP_URL=%s", s.cfg.GrafanaAppURL), + fmt.Sprintf("GF_PLUGIN_APP_CLIENT_ID=%s", p.ExternalService.ClientID), + fmt.Sprintf("GF_PLUGIN_APP_CLIENT_SECRET=%s", p.ExternalService.ClientSecret), + fmt.Sprintf("GF_PLUGIN_APP_PRIVATE_KEY=%s", p.ExternalService.PrivateKey), + ) + } + hostEnv = append(hostEnv, s.awsEnvVars()...) hostEnv = append(hostEnv, s.secureSocksProxyEnvVars()...) hostEnv = append(hostEnv, azsettings.WriteToEnvStr(s.cfg.Azure)...) hostEnv = append(hostEnv, s.tracingEnvVars(p)...) ev := getPluginSettings(p.ID, s.cfg).asEnvVar("GF_PLUGIN", hostEnv) - return ev + return ev, nil } func (s *Service) tracingEnvVars(plugin *plugins.Plugin) []string { diff --git a/pkg/plugins/envvars/envvars_test.go b/pkg/plugins/envvars/envvars_test.go index f340180dad1..5fdcd8ba34d 100644 --- a/pkg/plugins/envvars/envvars_test.go +++ b/pkg/plugins/envvars/envvars_test.go @@ -11,6 +11,8 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/manager/fakes" + "github.com/grafana/grafana/pkg/plugins/oauth" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" ) @@ -37,7 +39,8 @@ func TestInitializer_envVars(t *testing.T) { }, }, licensing) - envVars := envVarsProvider.Get(context.Background(), p) + envVars, err := envVarsProvider.Get(context.Background(), p) + require.NoError(t, err) assert.Len(t, envVars, 6) assert.Equal(t, "GF_PLUGIN_CUSTOM_ENV_VAR=customVal", envVars[0]) assert.Equal(t, "GF_VERSION=", envVars[1]) @@ -297,8 +300,39 @@ func TestInitializer_tracingEnvironmentVariables(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { envVarsProvider := NewProvider(tc.cfg, nil) - envVars := envVarsProvider.Get(context.Background(), tc.plugin) + envVars, err := envVarsProvider.Get(context.Background(), tc.plugin) + require.NoError(t, err) tc.exp(t, envVars) }) } } + +func TestInitializer_oauthEnvVars(t *testing.T) { + t.Run("backend datasource with oauth registration", func(t *testing.T) { + p := &plugins.Plugin{ + JSONData: plugins.JSONData{ + ID: "test", + ExternalServiceRegistration: &oauth.ExternalServiceRegistration{}, + }, + ExternalService: &oauth.ExternalService{ + ClientID: "clientID", + ClientSecret: "clientSecret", + PrivateKey: "privatePem", + }, + } + + envVarsProvider := NewProvider(&config.Cfg{ + GrafanaAppURL: "https://myorg.com/", + Features: featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAuth), + }, nil) + envVars, err := envVarsProvider.Get(context.Background(), p) + + require.NoError(t, err) + assert.Len(t, envVars, 5) + assert.Equal(t, "GF_VERSION=", envVars[0]) + assert.Equal(t, "GF_APP_URL=https://myorg.com/", envVars[1]) + assert.Equal(t, "GF_PLUGIN_APP_CLIENT_ID=clientID", envVars[2]) + assert.Equal(t, "GF_PLUGIN_APP_CLIENT_SECRET=clientSecret", envVars[3]) + assert.Equal(t, "GF_PLUGIN_APP_PRIVATE_KEY=privatePem", envVars[4]) + }) +} diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index a7cf999ad68..3efe42f2a7b 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/log" + "github.com/grafana/grafana/pkg/plugins/oauth" "github.com/grafana/grafana/pkg/plugins/repo" "github.com/grafana/grafana/pkg/plugins/storage" ) @@ -422,3 +423,11 @@ func (f *FakePluginFileStore) File(ctx context.Context, pluginID, filename strin } return nil, nil } + +type FakeOauthService struct { + Result *oauth.ExternalService +} + +func (f *FakeOauthService) RegisterExternalService(ctx context.Context, name string, svc *oauth.ExternalServiceRegistration) (*oauth.ExternalService, error) { + return f.Result, nil +} diff --git a/pkg/plugins/manager/loader/initializer/initializer.go b/pkg/plugins/manager/loader/initializer/initializer.go index 2c932a4d079..50937826e5f 100644 --- a/pkg/plugins/manager/loader/initializer/initializer.go +++ b/pkg/plugins/manager/loader/initializer/initializer.go @@ -28,7 +28,10 @@ func (i *Initializer) Initialize(ctx context.Context, p *plugins.Plugin) error { return errors.New("could not find backend factory for plugin") } - env := i.envVarProvider.Get(ctx, p) + env, err := i.envVarProvider.Get(ctx, p) + if err != nil { + return err + } if backendClient, err := backendFactory(p.ID, p.Logger(), env); err != nil { return err } else { diff --git a/pkg/plugins/manager/loader/initializer/initializer_test.go b/pkg/plugins/manager/loader/initializer/initializer_test.go index ca0b1c93dd1..f553d93949d 100644 --- a/pkg/plugins/manager/loader/initializer/initializer_test.go +++ b/pkg/plugins/manager/loader/initializer/initializer_test.go @@ -138,9 +138,9 @@ type fakeEnvVarsProvider struct { GetFunc func(ctx context.Context, p *plugins.Plugin) []string } -func (f *fakeEnvVarsProvider) Get(ctx context.Context, p *plugins.Plugin) []string { +func (f *fakeEnvVarsProvider) Get(ctx context.Context, p *plugins.Plugin) ([]string, error) { if f.GetFunc != nil { - return f.GetFunc(ctx, p) + return f.GetFunc(ctx, p), nil } - return nil + return nil, nil } diff --git a/pkg/plugins/manager/loader/loader.go b/pkg/plugins/manager/loader/loader.go index aedb7171d2e..d94a556817a 100644 --- a/pkg/plugins/manager/loader/loader.go +++ b/pkg/plugins/manager/loader/loader.go @@ -20,22 +20,25 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/process" "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/plugins/manager/signature" + "github.com/grafana/grafana/pkg/plugins/oauth" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/util" ) var _ plugins.ErrorResolver = (*Loader)(nil) type Loader struct { - pluginFinder finder.Finder - processManager process.Service - pluginRegistry registry.Service - roleRegistry plugins.RoleRegistry - pluginInitializer initializer.Initializer - signatureValidator signature.Validator - signatureCalculator plugins.SignatureCalculator - assetPath *assetpath.Service - log log.Logger - cfg *config.Cfg + pluginFinder finder.Finder + processManager process.Service + pluginRegistry registry.Service + roleRegistry plugins.RoleRegistry + pluginInitializer initializer.Initializer + signatureValidator signature.Validator + signatureCalculator plugins.SignatureCalculator + externalServiceRegistry oauth.ExternalServiceRegistry + assetPath *assetpath.Service + log log.Logger + cfg *config.Cfg angularInspector angularinspector.Inspector @@ -45,29 +48,30 @@ type Loader struct { func ProvideService(cfg *config.Cfg, license plugins.Licensing, authorizer plugins.PluginLoaderAuthorizer, pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider, pluginFinder finder.Finder, roleRegistry plugins.RoleRegistry, assetPath *assetpath.Service, signatureCalculator plugins.SignatureCalculator, - angularInspector angularinspector.Inspector) *Loader { + angularInspector angularinspector.Inspector, externalServiceRegistry oauth.ExternalServiceRegistry) *Loader { return New(cfg, license, authorizer, pluginRegistry, backendProvider, process.NewManager(pluginRegistry), - roleRegistry, assetPath, pluginFinder, signatureCalculator, angularInspector) + roleRegistry, assetPath, pluginFinder, signatureCalculator, angularInspector, externalServiceRegistry) } func New(cfg *config.Cfg, license plugins.Licensing, authorizer plugins.PluginLoaderAuthorizer, pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider, processManager process.Service, roleRegistry plugins.RoleRegistry, assetPath *assetpath.Service, pluginFinder finder.Finder, signatureCalculator plugins.SignatureCalculator, - angularInspector angularinspector.Inspector) *Loader { + angularInspector angularinspector.Inspector, externalServiceRegistry oauth.ExternalServiceRegistry) *Loader { return &Loader{ - pluginFinder: pluginFinder, - pluginRegistry: pluginRegistry, - pluginInitializer: initializer.New(cfg, backendProvider, license), - signatureValidator: signature.NewValidator(authorizer), - signatureCalculator: signatureCalculator, - processManager: processManager, - errs: make(map[string]*plugins.SignatureError), - log: log.New("plugin.loader"), - roleRegistry: roleRegistry, - cfg: cfg, - assetPath: assetPath, - angularInspector: angularInspector, + pluginFinder: pluginFinder, + pluginRegistry: pluginRegistry, + pluginInitializer: initializer.New(cfg, backendProvider, license), + signatureValidator: signature.NewValidator(authorizer), + signatureCalculator: signatureCalculator, + processManager: processManager, + errs: make(map[string]*plugins.SignatureError), + log: log.New("plugin.loader"), + roleRegistry: roleRegistry, + cfg: cfg, + assetPath: assetPath, + angularInspector: angularInspector, + externalServiceRegistry: externalServiceRegistry, } } @@ -202,6 +206,15 @@ func (l *Loader) loadPlugins(ctx context.Context, src plugins.PluginSource, foun } } + if p.ExternalServiceRegistration != nil && l.cfg.Features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + s, err := l.externalServiceRegistry.RegisterExternalService(ctx, p.ID, p.ExternalServiceRegistration) + if err != nil { + l.log.Error("Could not register an external service. Initialization skipped", "pluginID", p.ID, "err", err) + continue + } + p.ExternalService = s + } + err := l.pluginInitializer.Initialize(ctx, p) if err != nil { l.log.Error("Could not initialize plugin", "pluginId", p.ID, "err", err) diff --git a/pkg/plugins/manager/loader/loader_test.go b/pkg/plugins/manager/loader/loader_test.go index c3f00ade9df..87bc6c8ed10 100644 --- a/pkg/plugins/manager/loader/loader_test.go +++ b/pkg/plugins/manager/loader/loader_test.go @@ -1443,7 +1443,7 @@ func newLoader(t *testing.T, cfg *config.Cfg, cbs ...func(loader *Loader)) *Load l := New(cfg, &fakes.FakeLicensingService{}, signature.NewUnsignedAuthorizer(cfg), fakes.NewFakePluginRegistry(), fakes.NewFakeBackendProcessProvider(), fakes.NewFakeProcessManager(), fakes.NewFakeRoleRegistry(), assetpath.ProvideService(pluginscdn.ProvideService(cfg)), finder.NewLocalFinder(cfg), - signature.ProvideService(cfg, statickey.New()), angularInspector) + signature.ProvideService(cfg, statickey.New()), angularInspector, &fakes.FakeOauthService{}) for _, cb := range cbs { cb(l) diff --git a/pkg/plugins/manager/manager_integration_test.go b/pkg/plugins/manager/manager_integration_test.go index 2f8ace10ddb..2dc7bc2f8c6 100644 --- a/pkg/plugins/manager/manager_integration_test.go +++ b/pkg/plugins/manager/manager_integration_test.go @@ -123,7 +123,7 @@ func TestIntegrationPluginManager(t *testing.T) { l := loader.ProvideService(pCfg, lic, signature.NewUnsignedAuthorizer(pCfg), reg, provider.ProvideService(coreRegistry), finder.NewLocalFinder(pCfg), fakes.NewFakeRoleRegistry(), assetpath.ProvideService(pluginscdn.ProvideService(pCfg)), signature.ProvideService(pCfg, statickey.New()), - angularInspector) + angularInspector, &fakes.FakeOauthService{}) srcs := sources.ProvideService(cfg, pCfg) ps, err := store.ProvideService(reg, srcs, l) require.NoError(t, err) diff --git a/pkg/plugins/oauth/models.go b/pkg/plugins/oauth/models.go new file mode 100644 index 00000000000..dbae05270b7 --- /dev/null +++ b/pkg/plugins/oauth/models.go @@ -0,0 +1,37 @@ +package oauth + +import ( + "context" + + "github.com/grafana/grafana/pkg/services/accesscontrol" +) + +// SelfCfg is a subset of oauthserver.SelfCfg making some fields optional +type SelfCfg struct { + Enabled *bool `json:"enabled,omitempty"` + Permissions []accesscontrol.Permission `json:"permissions,omitempty"` +} + +// ImpersonationCfg is a subset of oauthserver.ImpersonationCfg making some fields optional +type ImpersonationCfg struct { + Enabled *bool `json:"enabled,omitempty"` + Groups *bool `json:"groups,omitempty"` + Permissions []accesscontrol.Permission `json:"permissions,omitempty"` +} + +// PluginExternalServiceRegistration is a subset of oauthserver.ExternalServiceRegistration +// simplified for the plugin use case. +type ExternalServiceRegistration struct { + Impersonation *ImpersonationCfg `json:"impersonation,omitempty"` + Self *SelfCfg `json:"self,omitempty"` +} + +type ExternalService struct { + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` + PrivateKey string `json:"privateKey"` +} + +type ExternalServiceRegistry interface { + RegisterExternalService(ctx context.Context, name string, svc *ExternalServiceRegistration) (*ExternalService, error) +} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 1d63339968d..451d752e35e 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" "github.com/grafana/grafana/pkg/plugins/log" + "github.com/grafana/grafana/pkg/plugins/oauth" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/util" ) @@ -53,6 +54,8 @@ type Plugin struct { AngularDetected bool + ExternalService *oauth.ExternalService + Renderer pluginextensionv2.RendererPlugin SecretsManager secretsmanagerplugin.SecretsManagerPlugin client backendplugin.Plugin @@ -150,6 +153,9 @@ type JSONData struct { // Backend (Datasource + Renderer + SecretsManager) Executable string `json:"executable,omitempty"` + + // Oauth App Service Registration + ExternalServiceRegistration *oauth.ExternalServiceRegistration `json:"externalServiceRegistration,omitempty"` } func ReadPluginJSON(reader io.Reader) (JSONData, error) { diff --git a/pkg/services/oauthserver/oasimpl/service.go b/pkg/services/oauthserver/oasimpl/service.go index 47feab26fc5..69577c9867a 100644 --- a/pkg/services/oauthserver/oasimpl/service.go +++ b/pkg/services/oauthserver/oasimpl/service.go @@ -33,7 +33,6 @@ import ( "github.com/grafana/grafana/pkg/services/oauthserver/store" "github.com/grafana/grafana/pkg/services/oauthserver/utils" "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/secrets/kvstore" "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/signingkeys" "github.com/grafana/grafana/pkg/services/team" @@ -62,7 +61,7 @@ type OAuth2ServiceImpl struct { publicKey interface{} } -func ProvideService(router routing.RouteRegister, db db.DB, cfg *setting.Cfg, skv kvstore.SecretsKVStore, +func ProvideService(router routing.RouteRegister, db db.DB, cfg *setting.Cfg, svcAccSvc serviceaccounts.Service, accessControl ac.AccessControl, acSvc ac.Service, userSvc user.Service, teamSvc team.Service, keySvc signingkeys.Service, fmgmt *featuremgmt.FeatureManager) (*OAuth2ServiceImpl, error) { if !fmgmt.IsEnabled(featuremgmt.FlagExternalServiceAuth) { diff --git a/pkg/services/pluginsintegration/config/config.go b/pkg/services/pluginsintegration/config/config.go index 6348266609a..ab333980eaa 100644 --- a/pkg/services/pluginsintegration/config/config.go +++ b/pkg/services/pluginsintegration/config/config.go @@ -39,6 +39,7 @@ func ProvideConfig(settingProvider setting.Provider, grafanaCfg *setting.Cfg, fe grafanaCfg.BuildVersion, grafanaCfg.PluginLogBackendRequests, grafanaCfg.PluginsCDNURLTemplate, + grafanaCfg.AppURL, tracingCfg, featuremgmt.ProvideToggles(features), grafanaCfg.AngularSupportEnabled, diff --git a/pkg/services/pluginsintegration/pluginsintegration.go b/pkg/services/pluginsintegration/pluginsintegration.go index 8c8cdcdcd03..53afa936696 100644 --- a/pkg/services/pluginsintegration/pluginsintegration.go +++ b/pkg/services/pluginsintegration/pluginsintegration.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/signature" "github.com/grafana/grafana/pkg/plugins/manager/sources" "github.com/grafana/grafana/pkg/plugins/manager/store" + "github.com/grafana/grafana/pkg/plugins/oauth" "github.com/grafana/grafana/pkg/plugins/pluginscdn" "github.com/grafana/grafana/pkg/plugins/repo" "github.com/grafana/grafana/pkg/services/caching" @@ -35,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" + "github.com/grafana/grafana/pkg/services/pluginsintegration/serviceregistration" "github.com/grafana/grafana/pkg/setting" ) @@ -80,6 +82,8 @@ var WireSet = wire.NewSet( wire.Bind(new(plugins.KeyRetriever), new(*keyretriever.Service)), keyretriever.ProvideService, dynamic.ProvideService, + serviceregistration.ProvideService, + wire.Bind(new(oauth.ExternalServiceRegistry), new(*serviceregistration.Service)), ) // WireExtensionSet provides a wire.ProviderSet of plugin providers that can be diff --git a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go new file mode 100644 index 00000000000..9912401cff3 --- /dev/null +++ b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go @@ -0,0 +1,62 @@ +package serviceregistration + +import ( + "context" + + "github.com/grafana/grafana/pkg/plugins/oauth" + "github.com/grafana/grafana/pkg/services/oauthserver" +) + +type Service struct { + os oauthserver.OAuth2Server +} + +func ProvideService(os oauthserver.OAuth2Server) *Service { + s := &Service{ + os: os, + } + return s +} + +// RegisterExternalService is a simplified wrapper around SaveExternalService for the plugin use case. +func (s *Service) RegisterExternalService(ctx context.Context, svcName string, svc *oauth.ExternalServiceRegistration) (*oauth.ExternalService, error) { + impersonation := oauthserver.ImpersonationCfg{} + if svc.Impersonation != nil { + impersonation.Permissions = svc.Impersonation.Permissions + if svc.Impersonation.Enabled != nil { + impersonation.Enabled = *svc.Impersonation.Enabled + } else { + impersonation.Enabled = true + } + if svc.Impersonation.Groups != nil { + impersonation.Groups = *svc.Impersonation.Groups + } else { + impersonation.Groups = true + } + } + + self := oauthserver.SelfCfg{} + if svc.Self != nil { + self.Permissions = svc.Self.Permissions + if svc.Self.Enabled != nil { + self.Enabled = *svc.Self.Enabled + } else { + self.Enabled = true + } + } + extSvc, err := s.os.SaveExternalService(ctx, &oauthserver.ExternalServiceRegistration{ + Name: svcName, + Impersonation: impersonation, + Self: self, + Key: &oauthserver.KeyOption{Generate: true}, + }) + if err != nil { + return nil, err + } + + return &oauth.ExternalService{ + ClientID: extSvc.ID, + ClientSecret: extSvc.Secret, + PrivateKey: extSvc.KeyResult.PrivatePem, + }, nil +}