diff --git a/pkg/services/ssosettings/ssosettings.go b/pkg/services/ssosettings/ssosettings.go index 0f5c92199e3..56e79e58bfe 100644 --- a/pkg/services/ssosettings/ssosettings.go +++ b/pkg/services/ssosettings/ssosettings.go @@ -9,10 +9,6 @@ import ( ) var ( - // ConfigurableOAuthProviders is a list of OAuth providers that can be configured from the API - // TODO: make it configurable - ConfigurableOAuthProviders = []string{"github", "gitlab", "google", "generic_oauth", "azuread", "okta"} - AllOAuthProviders = []string{social.GitHubProviderName, social.GitlabProviderName, social.GoogleProviderName, social.GenericOAuthProviderName, social.GrafanaComProviderName, social.AzureADProviderName, social.OktaProviderName} ) diff --git a/pkg/services/ssosettings/strategies/saml_strategy.go b/pkg/services/ssosettings/strategies/saml_strategy.go new file mode 100644 index 00000000000..b8c8dfabf86 --- /dev/null +++ b/pkg/services/ssosettings/strategies/saml_strategy.go @@ -0,0 +1,66 @@ +package strategies + +import ( + "context" + "time" + + "github.com/grafana/grafana/pkg/services/ssosettings" + "github.com/grafana/grafana/pkg/setting" +) + +type SAMLStrategy struct { + settingsProvider setting.Provider +} + +var _ ssosettings.FallbackStrategy = (*SAMLStrategy)(nil) + +func NewSAMLStrategy(settingsProvider setting.Provider) *SAMLStrategy { + return &SAMLStrategy{ + settingsProvider: settingsProvider, + } +} + +func (s *SAMLStrategy) IsMatch(provider string) bool { + return provider == "saml" +} + +func (s *SAMLStrategy) GetProviderConfig(_ context.Context, provider string) (map[string]any, error) { + return s.loadSAMLSettings(), nil +} + +func (s *SAMLStrategy) loadSAMLSettings() map[string]any { + section := s.settingsProvider.Section("auth.saml") + result := map[string]any{ + "enabled": section.KeyValue("enabled").MustBool(false), + "single_logout": section.KeyValue("single_logout").MustBool(false), + "allow_sign_up": section.KeyValue("allow_sign_up").MustBool(false), + "auto_login": section.KeyValue("auto_login").MustBool(false), + "certificate": section.KeyValue("certificate").MustString(""), + "certificate_path": section.KeyValue("certificate_path").MustString(""), + "private_key": section.KeyValue("private_key").MustString(""), + "private_key_path": section.KeyValue("private_key_path").MustString(""), + "signature_algorithm": section.KeyValue("signature_algorithm").MustString(""), + "idp_metadata": section.KeyValue("idp_metadata").MustString(""), + "idp_metadata_path": section.KeyValue("idp_metadata_path").MustString(""), + "idp_metadata_url": section.KeyValue("idp_metadata_url").MustString(""), + "max_issue_delay": section.KeyValue("max_issue_delay").MustDuration(90 * time.Second), + "metadata_valid_duration": section.KeyValue("metadata_valid_duration").MustDuration(48 * time.Hour), + "allow_idp_initiated": section.KeyValue("allow_idp_initiated").MustBool(false), + "relay_state": section.KeyValue("relay_state").MustString(""), + "assertion_attribute_name": section.KeyValue("assertion_attribute_name").MustString(""), + "assertion_attribute_login": section.KeyValue("assertion_attribute_login").MustString(""), + "assertion_attribute_email": section.KeyValue("assertion_attribute_email").MustString(""), + "assertion_attribute_groups": section.KeyValue("assertion_attribute_groups").MustString(""), + "assertion_attribute_role": section.KeyValue("assertion_attribute_role").MustString(""), + "assertion_attribute_org": section.KeyValue("assertion_attribute_org").MustString(""), + "allowed_organizations": section.KeyValue("allowed_organizations").MustString(""), + "org_mapping": section.KeyValue("org_mapping").MustString(""), + "role_values_editor": section.KeyValue("role_values_editor").MustString(""), + "role_values_admin": section.KeyValue("role_values_admin").MustString(""), + "role_values_grafana_admin": section.KeyValue("role_values_grafana_admin").MustString(""), + "name_id_format": section.KeyValue("name_id_format").MustString(""), + "skip_org_role_sync": section.KeyValue("skip_org_role_sync").MustBool(false), + "role_values_none": section.KeyValue("role_values_none").MustString(""), + } + return result +} diff --git a/pkg/services/ssosettings/strategies/saml_strategy_test.go b/pkg/services/ssosettings/strategies/saml_strategy_test.go new file mode 100644 index 00000000000..974561949c6 --- /dev/null +++ b/pkg/services/ssosettings/strategies/saml_strategy_test.go @@ -0,0 +1,103 @@ +package strategies + +import ( + "context" + "testing" + "time" + + "gopkg.in/ini.v1" + + "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/require" +) + +var ( + iniSAMLContent = ` + [auth.saml] + enabled = true + single_logout = true + allow_sign_up = true + auto_login = false + certificate = devenv/docker/blocks/auth/saml-enterprise/cert.crt + certificate_path = /path/to/cert + private_key = dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0 + private_key_path = devenv/docker/blocks/auth/saml-enterprise/key.pem + signature_algorithm = rsa-sha256 + idp_metadata = dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0 + idp_metadata_path = /path/to/metadata + idp_metadata_url = http://localhost:8086/realms/grafana/protocol/saml/descriptor + max_issue_delay = 90s + metadata_valid_duration = 48h + allow_idp_initiated = false + relay_state = relay_state + assertion_attribute_name = name + assertion_attribute_login = login + assertion_attribute_email = email + assertion_attribute_groups = groups + assertion_attribute_role = roles + assertion_attribute_org = orgs + allowed_organizations = org1 org2 + org_mapping = org1:1:editor, *:2:viewer + role_values_editor = editor + role_values_admin = admin + role_values_grafana_admin = serveradmin + name_id_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + skip_org_role_sync = false + role_values_none = guest disabled + ` + + expectedSAMLInfo = map[string]any{ + "enabled": true, + "single_logout": true, + "allow_sign_up": true, + "auto_login": false, + "certificate": "devenv/docker/blocks/auth/saml-enterprise/cert.crt", + "certificate_path": "/path/to/cert", + "private_key": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", + "private_key_path": "devenv/docker/blocks/auth/saml-enterprise/key.pem", + "signature_algorithm": "rsa-sha256", + "idp_metadata": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", + "idp_metadata_path": "/path/to/metadata", + "idp_metadata_url": "http://localhost:8086/realms/grafana/protocol/saml/descriptor", + "max_issue_delay": 90 * time.Second, + "metadata_valid_duration": 48 * time.Hour, + "allow_idp_initiated": false, + "relay_state": "relay_state", + "assertion_attribute_name": "name", + "assertion_attribute_login": "login", + "assertion_attribute_email": "email", + "assertion_attribute_groups": "groups", + "assertion_attribute_role": "roles", + "assertion_attribute_org": "orgs", + "allowed_organizations": "org1 org2", + "org_mapping": "org1:1:editor, *:2:viewer", + "role_values_editor": "editor", + "role_values_admin": "admin", + "role_values_grafana_admin": "serveradmin", + "name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + "skip_org_role_sync": false, + "role_values_none": "guest disabled", + } +) + +func TestSAMLIsMatch(t *testing.T) { + cfg := setting.NewCfg() + strategy := NewSAMLStrategy(&setting.OSSImpl{Cfg: cfg}) + require.True(t, strategy.IsMatch("saml")) + require.False(t, strategy.IsMatch("oauth")) +} + +func TestSAMLGetProviderConfig(t *testing.T) { + configurationFile, err := ini.Load([]byte(iniSAMLContent)) + require.NoError(t, err) + + cfg := setting.NewCfg() + cfg.Raw = configurationFile + + strategy := NewSAMLStrategy(&setting.OSSImpl{Cfg: cfg}) + + result, err := strategy.GetProviderConfig(context.Background(), "saml") + require.NoError(t, err) + + require.Equal(t, expectedSAMLInfo, result) +}