|
|
|
@ -11,6 +11,7 @@ import ( |
|
|
|
|
"github.com/grafana/grafana/pkg/api/routing" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/usagestats" |
|
|
|
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/secrets" |
|
|
|
@ -22,9 +23,9 @@ import ( |
|
|
|
|
"github.com/grafana/grafana/pkg/setting" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var _ ssosettings.Service = (*SSOSettingsService)(nil) |
|
|
|
|
var _ ssosettings.Service = (*Service)(nil) |
|
|
|
|
|
|
|
|
|
type SSOSettingsService struct { |
|
|
|
|
type Service struct { |
|
|
|
|
logger log.Logger |
|
|
|
|
cfg *setting.Cfg |
|
|
|
|
store ssosettings.Store |
|
|
|
@ -37,7 +38,7 @@ type SSOSettingsService struct { |
|
|
|
|
|
|
|
|
|
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, |
|
|
|
|
routeRegister routing.RouteRegister, features featuremgmt.FeatureToggles, |
|
|
|
|
secrets secrets.Service) *SSOSettingsService { |
|
|
|
|
secrets secrets.Service, usageStats usagestats.Service) *Service { |
|
|
|
|
strategies := []ssosettings.FallbackStrategy{ |
|
|
|
|
strategies.NewOAuthStrategy(cfg), |
|
|
|
|
// register other strategies here, for example SAML
|
|
|
|
@ -45,7 +46,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, |
|
|
|
|
|
|
|
|
|
store := database.ProvideStore(sqlStore) |
|
|
|
|
|
|
|
|
|
svc := &SSOSettingsService{ |
|
|
|
|
svc := &Service{ |
|
|
|
|
logger: log.New("ssosettings.service"), |
|
|
|
|
cfg: cfg, |
|
|
|
|
store: store, |
|
|
|
@ -55,6 +56,8 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, |
|
|
|
|
reloadables: make(map[string]ssosettings.Reloadable), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
usageStats.RegisterMetricsFunc(svc.getUsageStats) |
|
|
|
|
|
|
|
|
|
if features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsApi) { |
|
|
|
|
ssoSettingsApi := api.ProvideApi(svc, routeRegister, ac) |
|
|
|
|
ssoSettingsApi.RegisterAPIEndpoints() |
|
|
|
@ -63,9 +66,9 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, |
|
|
|
|
return svc |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var _ ssosettings.Service = (*SSOSettingsService)(nil) |
|
|
|
|
var _ ssosettings.Service = (*Service)(nil) |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) GetForProvider(ctx context.Context, provider string) (*models.SSOSettings, error) { |
|
|
|
|
func (s *Service) GetForProvider(ctx context.Context, provider string) (*models.SSOSettings, error) { |
|
|
|
|
dbSettings, err := s.store.Get(ctx, provider) |
|
|
|
|
if err != nil && !errors.Is(err, ssosettings.ErrNotFound) { |
|
|
|
|
return nil, err |
|
|
|
@ -87,7 +90,7 @@ func (s *SSOSettingsService) GetForProvider(ctx context.Context, provider string |
|
|
|
|
return s.mergeSSOSettings(dbSettings, systemSettings), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) GetForProviderWithRedactedSecrets(ctx context.Context, provider string) (*models.SSOSettings, error) { |
|
|
|
|
func (s *Service) GetForProviderWithRedactedSecrets(ctx context.Context, provider string) (*models.SSOSettings, error) { |
|
|
|
|
if !s.isProviderConfigurable(provider) { |
|
|
|
|
return nil, ssosettings.ErrNotConfigurable |
|
|
|
|
} |
|
|
|
@ -106,7 +109,7 @@ func (s *SSOSettingsService) GetForProviderWithRedactedSecrets(ctx context.Conte |
|
|
|
|
return storeSettings, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) List(ctx context.Context) ([]*models.SSOSettings, error) { |
|
|
|
|
func (s *Service) List(ctx context.Context) ([]*models.SSOSettings, error) { |
|
|
|
|
result := make([]*models.SSOSettings, 0, len(ssosettings.AllOAuthProviders)) |
|
|
|
|
storedSettings, err := s.store.List(ctx) |
|
|
|
|
|
|
|
|
@ -134,7 +137,7 @@ func (s *SSOSettingsService) List(ctx context.Context) ([]*models.SSOSettings, e |
|
|
|
|
return result, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) ListWithRedactedSecrets(ctx context.Context) ([]*models.SSOSettings, error) { |
|
|
|
|
func (s *Service) ListWithRedactedSecrets(ctx context.Context) ([]*models.SSOSettings, error) { |
|
|
|
|
storeSettings, err := s.List(ctx) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
@ -158,7 +161,7 @@ func (s *SSOSettingsService) ListWithRedactedSecrets(ctx context.Context) ([]*mo |
|
|
|
|
return configurableSettings, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) Upsert(ctx context.Context, settings *models.SSOSettings) error { |
|
|
|
|
func (s *Service) Upsert(ctx context.Context, settings *models.SSOSettings) error { |
|
|
|
|
if !s.isProviderConfigurable(settings.Provider) { |
|
|
|
|
return ssosettings.ErrNotConfigurable |
|
|
|
|
} |
|
|
|
@ -201,33 +204,33 @@ func (s *SSOSettingsService) Upsert(ctx context.Context, settings *models.SSOSet |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) Patch(ctx context.Context, provider string, data map[string]any) error { |
|
|
|
|
func (s *Service) Patch(ctx context.Context, provider string, data map[string]any) error { |
|
|
|
|
panic("not implemented") // TODO: Implement
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) Delete(ctx context.Context, provider string) error { |
|
|
|
|
func (s *Service) Delete(ctx context.Context, provider string) error { |
|
|
|
|
if !s.isProviderConfigurable(provider) { |
|
|
|
|
return ssosettings.ErrNotConfigurable |
|
|
|
|
} |
|
|
|
|
return s.store.Delete(ctx, provider) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) Reload(ctx context.Context, provider string) { |
|
|
|
|
func (s *Service) Reload(ctx context.Context, provider string) { |
|
|
|
|
panic("not implemented") // TODO: Implement
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) RegisterReloadable(provider string, reloadable ssosettings.Reloadable) { |
|
|
|
|
func (s *Service) RegisterReloadable(provider string, reloadable ssosettings.Reloadable) { |
|
|
|
|
if s.reloadables == nil { |
|
|
|
|
s.reloadables = make(map[string]ssosettings.Reloadable) |
|
|
|
|
} |
|
|
|
|
s.reloadables[provider] = reloadable |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) RegisterFallbackStrategy(providerRegex string, strategy ssosettings.FallbackStrategy) { |
|
|
|
|
func (s *Service) RegisterFallbackStrategy(providerRegex string, strategy ssosettings.FallbackStrategy) { |
|
|
|
|
s.fbStrategies = append(s.fbStrategies, strategy) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) loadSettingsUsingFallbackStrategy(ctx context.Context, provider string) (*models.SSOSettings, error) { |
|
|
|
|
func (s *Service) loadSettingsUsingFallbackStrategy(ctx context.Context, provider string) (*models.SSOSettings, error) { |
|
|
|
|
loadStrategy, ok := s.getFallbackStrategyFor(provider) |
|
|
|
|
if !ok { |
|
|
|
|
return nil, errors.New("no fallback strategy found for provider: " + provider) |
|
|
|
@ -254,7 +257,7 @@ func getSettingByProvider(provider string, settings []*models.SSOSettings) *mode |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) getFallbackStrategyFor(provider string) (ssosettings.FallbackStrategy, bool) { |
|
|
|
|
func (s *Service) getFallbackStrategyFor(provider string) (ssosettings.FallbackStrategy, bool) { |
|
|
|
|
for _, strategy := range s.fbStrategies { |
|
|
|
|
if strategy.IsMatch(provider) { |
|
|
|
|
return strategy, true |
|
|
|
@ -263,7 +266,7 @@ func (s *SSOSettingsService) getFallbackStrategyFor(provider string) (ssosetting |
|
|
|
|
return nil, false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) encryptSecrets(ctx context.Context, settings, storedSettings map[string]any) (map[string]any, error) { |
|
|
|
|
func (s *Service) encryptSecrets(ctx context.Context, settings, storedSettings map[string]any) (map[string]any, error) { |
|
|
|
|
result := make(map[string]any) |
|
|
|
|
for k, v := range settings { |
|
|
|
|
if isSecret(k) && v != "" { |
|
|
|
@ -289,7 +292,7 @@ func (s *SSOSettingsService) encryptSecrets(ctx context.Context, settings, store |
|
|
|
|
return result, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) Run(ctx context.Context) error { |
|
|
|
|
func (s *Service) Run(ctx context.Context) error { |
|
|
|
|
interval := s.cfg.SSOSettingsReloadInterval |
|
|
|
|
if interval == 0 { |
|
|
|
|
return nil |
|
|
|
@ -310,7 +313,7 @@ func (s *SSOSettingsService) Run(ctx context.Context) error { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) doReload(ctx context.Context) { |
|
|
|
|
func (s *Service) doReload(ctx context.Context) { |
|
|
|
|
s.logger.Debug("reloading SSO Settings for all providers") |
|
|
|
|
|
|
|
|
|
settingsList, err := s.List(ctx) |
|
|
|
@ -333,7 +336,7 @@ func (s *SSOSettingsService) doReload(ctx context.Context) { |
|
|
|
|
// mergeSSOSettings merges the settings from the database with the system settings
|
|
|
|
|
// Required because it is possible that the user has configured some of the settings (current Advanced OAuth settings)
|
|
|
|
|
// and the rest of the settings have to be loaded from the system settings
|
|
|
|
|
func (s *SSOSettingsService) mergeSSOSettings(dbSettings, systemSettings *models.SSOSettings) *models.SSOSettings { |
|
|
|
|
func (s *Service) mergeSSOSettings(dbSettings, systemSettings *models.SSOSettings) *models.SSOSettings { |
|
|
|
|
if dbSettings == nil { |
|
|
|
|
s.logger.Debug("No SSO Settings found in the database, using system settings") |
|
|
|
|
return systemSettings |
|
|
|
@ -352,7 +355,7 @@ func (s *SSOSettingsService) mergeSSOSettings(dbSettings, systemSettings *models |
|
|
|
|
return result |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) decryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) { |
|
|
|
|
func (s *Service) decryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) { |
|
|
|
|
for k, v := range settings { |
|
|
|
|
if isSecret(k) && v != "" { |
|
|
|
|
strValue, ok := v.(string) |
|
|
|
@ -379,7 +382,7 @@ func (s *SSOSettingsService) decryptSecrets(ctx context.Context, settings map[st |
|
|
|
|
return settings, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *SSOSettingsService) isProviderConfigurable(provider string) bool { |
|
|
|
|
func (s *Service) isProviderConfigurable(provider string) bool { |
|
|
|
|
_, ok := s.cfg.SSOSettingsConfigurableProviders[provider] |
|
|
|
|
return ok |
|
|
|
|
} |
|
|
|
|