SSO: Add prompt param to SSO settings (#107969)
* add prompt param to AzureAD oauth config * yarn i18n-extract * validate auth prompt value * make login_prompt available for all SSO providers * use base authCodeURL for azure and google * add docs for the new field for azure and generic oauth * fix typo * fix frontend unit test * add prompt parameter to docs for the other providers * remove prompt from okta * add unit tests for the other providers * address feedback * add back translations for prompt labels
This commit is contained in:
@@ -336,7 +336,7 @@ func (s *SocialAzureAD) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption)
|
||||
opts = append(opts, oauth2.SetAuthURLParam("domain_hint", domainHint))
|
||||
}
|
||||
|
||||
return s.Config.AuthCodeURL(state, opts...)
|
||||
return s.getAuthCodeURL(state, opts...)
|
||||
}
|
||||
|
||||
func (s *SocialAzureAD) validateIDTokenSignature(ctx context.Context, client *http.Client, parsedToken *jwt.JSONWebToken) (*azureClaims, error) {
|
||||
|
||||
@@ -1276,6 +1276,22 @@ func TestSocialAzureAD_Validate(t *testing.T) {
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
{
|
||||
name: "fails if login prompt is invalid",
|
||||
settings: ssoModels.SSOSettings{
|
||||
Settings: map[string]any{
|
||||
"client_authentication": "client_secret_post",
|
||||
"client_id": "client-id",
|
||||
"client_secret": "client_secret",
|
||||
"allowed_groups": "0bb9c9cc-4945-418f-9b6a-c1d3b81141b0, 6034d328-0e6a-4240-8d03-cb9f2c1f16e4",
|
||||
"allow_assign_grafana_admin": "true",
|
||||
"auth_url": "https://example.com/auth",
|
||||
"token_url": "https://example.com/token",
|
||||
"login_prompt": "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -1315,6 +1331,7 @@ func TestSocialAzureAD_Reload(t *testing.T) {
|
||||
"client_id": "new-client-id",
|
||||
"client_secret": "new-client-secret",
|
||||
"auth_url": "some-new-url",
|
||||
"login_prompt": "select_account",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -1322,6 +1339,7 @@ func TestSocialAzureAD_Reload(t *testing.T) {
|
||||
ClientId: "new-client-id",
|
||||
ClientSecret: "new-client-secret",
|
||||
AuthUrl: "some-new-url",
|
||||
LoginPrompt: "select_account",
|
||||
},
|
||||
expectedConfig: &oauth2.Config{
|
||||
ClientID: "new-client-id",
|
||||
|
||||
@@ -1230,6 +1230,20 @@ func TestSocialGenericOAuth_Validate(t *testing.T) {
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
{
|
||||
name: "fails if login prompt is invalid",
|
||||
settings: ssoModels.SSOSettings{
|
||||
Settings: map[string]any{
|
||||
"client_id": "client-id",
|
||||
"allow_assign_grafana_admin": "true",
|
||||
"teams_url": "https://example.com/teams",
|
||||
"auth_url": "https://example.com/auth",
|
||||
"token_url": "https://example.com/token",
|
||||
"login_prompt": "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -1269,6 +1283,7 @@ func TestSocialGenericOAuth_Reload(t *testing.T) {
|
||||
"client_id": "new-client-id",
|
||||
"client_secret": "new-client-secret",
|
||||
"auth_url": "some-new-url",
|
||||
"login_prompt": "login",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -1276,6 +1291,7 @@ func TestSocialGenericOAuth_Reload(t *testing.T) {
|
||||
ClientId: "new-client-id",
|
||||
ClientSecret: "new-client-secret",
|
||||
AuthUrl: "some-new-url",
|
||||
LoginPrompt: "login",
|
||||
},
|
||||
expectedConfig: &oauth2.Config{
|
||||
ClientID: "new-client-id",
|
||||
@@ -1357,6 +1373,7 @@ func TestGenericOAuth_Reload_ExtraFields(t *testing.T) {
|
||||
EmailAttributeName: "email-attr-name",
|
||||
GroupsAttributePath: "groups-attr-path",
|
||||
TeamIdsAttributePath: "team-ids-attr-path",
|
||||
LoginPrompt: "login",
|
||||
Extra: map[string]string{
|
||||
teamIdsKey: "team1",
|
||||
allowedOrganizationsKey: "org1",
|
||||
@@ -1374,6 +1391,7 @@ func TestGenericOAuth_Reload_ExtraFields(t *testing.T) {
|
||||
"email_attribute_name": "new-email-attr-name",
|
||||
"groups_attribute_path": "new-group-attr-path",
|
||||
"team_ids_attribute_path": "new-team-ids-attr-path",
|
||||
"login_prompt": "select_account",
|
||||
teamIdsKey: "team1,team2",
|
||||
allowedOrganizationsKey: "org1,org2",
|
||||
loginAttributePathKey: "new-login-attr-path",
|
||||
@@ -1389,6 +1407,7 @@ func TestGenericOAuth_Reload_ExtraFields(t *testing.T) {
|
||||
EmailAttributeName: "new-email-attr-name",
|
||||
GroupsAttributePath: "new-group-attr-path",
|
||||
TeamIdsAttributePath: "new-team-ids-attr-path",
|
||||
LoginPrompt: "select_account",
|
||||
Extra: map[string]string{
|
||||
teamIdsKey: "team1,team2",
|
||||
allowedOrganizationsKey: "org1,org2",
|
||||
|
||||
@@ -495,6 +495,7 @@ func TestSocialGitHub_Validate(t *testing.T) {
|
||||
"auth_url": "",
|
||||
"token_url": "",
|
||||
"api_url": "",
|
||||
"login_prompt": "select_account",
|
||||
},
|
||||
},
|
||||
requester: &user.SignedInUser{IsGrafanaAdmin: true},
|
||||
@@ -594,6 +595,17 @@ func TestSocialGitHub_Validate(t *testing.T) {
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
{
|
||||
name: "fails if login prompt is invalid",
|
||||
settings: ssoModels.SSOSettings{
|
||||
Settings: map[string]any{
|
||||
"client_id": "client-id",
|
||||
"allow_assign_grafana_admin": "true",
|
||||
"login_prompt": "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -634,6 +646,7 @@ func TestSocialGitHub_Reload(t *testing.T) {
|
||||
"client_id": "new-client-id",
|
||||
"client_secret": "new-client-secret",
|
||||
"auth_url": "some-new-url",
|
||||
"login_prompt": "login",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -641,6 +654,7 @@ func TestSocialGitHub_Reload(t *testing.T) {
|
||||
ClientId: "new-client-id",
|
||||
ClientSecret: "new-client-secret",
|
||||
AuthUrl: "some-new-url",
|
||||
LoginPrompt: "login",
|
||||
},
|
||||
expectedConfig: &oauth2.Config{
|
||||
ClientID: "new-client-id",
|
||||
|
||||
@@ -547,6 +547,7 @@ func TestSocialGitlab_Validate(t *testing.T) {
|
||||
"auth_url": "",
|
||||
"token_url": "",
|
||||
"api_url": "",
|
||||
"login_prompt": "select_account",
|
||||
},
|
||||
},
|
||||
requester: &user.SignedInUser{IsGrafanaAdmin: true},
|
||||
@@ -640,6 +641,17 @@ func TestSocialGitlab_Validate(t *testing.T) {
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
{
|
||||
name: "fails if login prompt is invalid",
|
||||
settings: ssoModels.SSOSettings{
|
||||
Settings: map[string]any{
|
||||
"client_id": "client-id",
|
||||
"allow_assign_grafana_admin": "true",
|
||||
"login_prompt": "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -680,6 +692,7 @@ func TestSocialGitlab_Reload(t *testing.T) {
|
||||
"client_id": "new-client-id",
|
||||
"client_secret": "new-client-secret",
|
||||
"auth_url": "some-new-url",
|
||||
"login_prompt": "login",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -687,6 +700,7 @@ func TestSocialGitlab_Reload(t *testing.T) {
|
||||
ClientId: "new-client-id",
|
||||
ClientSecret: "new-client-secret",
|
||||
AuthUrl: "some-new-url",
|
||||
LoginPrompt: "login",
|
||||
},
|
||||
expectedConfig: &oauth2.Config{
|
||||
ClientID: "new-client-id",
|
||||
|
||||
@@ -224,7 +224,8 @@ func (s *SocialGoogle) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption)
|
||||
if s.info.UseRefreshToken {
|
||||
opts = append(opts, oauth2.AccessTypeOffline, oauth2.ApprovalForce)
|
||||
}
|
||||
return s.Config.AuthCodeURL(state, opts...)
|
||||
|
||||
return s.getAuthCodeURL(state, opts...)
|
||||
}
|
||||
|
||||
func (s *SocialGoogle) extractFromToken(_ context.Context, _ *http.Client, token *oauth2.Token) (*googleUserData, error) {
|
||||
|
||||
@@ -724,6 +724,7 @@ func TestSocialGoogle_Validate(t *testing.T) {
|
||||
"auth_url": "",
|
||||
"token_url": "",
|
||||
"api_url": "",
|
||||
"login_prompt": "select_account",
|
||||
},
|
||||
},
|
||||
requester: &user.SignedInUser{IsGrafanaAdmin: true},
|
||||
@@ -830,6 +831,17 @@ func TestSocialGoogle_Validate(t *testing.T) {
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
{
|
||||
name: "fails if login prompt is invalid",
|
||||
settings: ssoModels.SSOSettings{
|
||||
Settings: map[string]any{
|
||||
"client_id": "client-id",
|
||||
"allow_assign_grafana_admin": "true",
|
||||
"login_prompt": "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: ssosettings.ErrBaseInvalidOAuthConfig,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -870,6 +882,7 @@ func TestSocialGoogle_Reload(t *testing.T) {
|
||||
"client_id": "new-client-id",
|
||||
"client_secret": "new-client-secret",
|
||||
"auth_url": "some-new-url",
|
||||
"login_prompt": "login",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -877,6 +890,7 @@ func TestSocialGoogle_Reload(t *testing.T) {
|
||||
ClientId: "new-client-id",
|
||||
ClientSecret: "new-client-secret",
|
||||
AuthUrl: "some-new-url",
|
||||
LoginPrompt: "login",
|
||||
},
|
||||
expectedConfig: &oauth2.Config{
|
||||
ClientID: "new-client-id",
|
||||
|
||||
@@ -85,6 +85,15 @@ func (s *SocialBase) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) st
|
||||
s.reloadMutex.RLock()
|
||||
defer s.reloadMutex.RUnlock()
|
||||
|
||||
return s.getAuthCodeURL(state, opts...)
|
||||
}
|
||||
|
||||
func (s *SocialBase) getAuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
||||
if s.info.LoginPrompt != "" {
|
||||
promptOpt := oauth2.SetAuthURLParam("prompt", s.info.LoginPrompt)
|
||||
opts = append(opts, promptOpt)
|
||||
}
|
||||
|
||||
return s.Config.AuthCodeURL(state, opts...)
|
||||
}
|
||||
|
||||
@@ -268,5 +277,7 @@ func validateInfo(info *social.OAuthInfo, oldInfo *social.OAuthInfo, requester i
|
||||
validation.AllowAssignGrafanaAdminValidator(info, oldInfo, requester),
|
||||
validation.SkipOrgRoleSyncAllowAssignGrafanaAdminValidator,
|
||||
validation.OrgAttributePathValidator(info, oldInfo, requester),
|
||||
validation.OrgMappingValidator(info, oldInfo, requester))
|
||||
validation.OrgMappingValidator(info, oldInfo, requester),
|
||||
validation.LoginPromptValidator,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ type OAuthInfo struct {
|
||||
TokenUrl string `mapstructure:"token_url" toml:"token_url"`
|
||||
UsePKCE bool `mapstructure:"use_pkce" toml:"use_pkce"`
|
||||
UseRefreshToken bool `mapstructure:"use_refresh_token" toml:"use_refresh_token"`
|
||||
LoginPrompt string `mapstructure:"login_prompt" toml:"login_prompt"`
|
||||
Extra map[string]string `mapstructure:",remain" toml:"extra,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ func (s *OAuthStrategy) loadSettingsForProvider(provider string) map[string]any
|
||||
"signout_redirect_url": section.Key("signout_redirect_url").Value(),
|
||||
"org_mapping": section.Key("org_mapping").Value(),
|
||||
"org_attribute_path": section.Key("org_attribute_path").Value(),
|
||||
"login_prompt": section.Key("login_prompt").Value(),
|
||||
}
|
||||
|
||||
extraKeys := extraKeysByProvider[provider]
|
||||
|
||||
@@ -58,6 +58,7 @@ var (
|
||||
signout_redirect_url = test_signout_redirect_url
|
||||
org_attribute_path = groups
|
||||
org_mapping = Group1:*:Editor
|
||||
login_prompt = select_account
|
||||
`
|
||||
|
||||
expectedOAuthInfo = map[string]any{
|
||||
@@ -104,6 +105,7 @@ var (
|
||||
"team_ids": "first, second",
|
||||
"org_attribute_path": "groups",
|
||||
"org_mapping": "Group1:*:Editor",
|
||||
"login_prompt": "select_account",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -51,6 +51,15 @@ func SkipOrgRoleSyncAllowAssignGrafanaAdminValidator(info *social.OAuthInfo, req
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoginPromptValidator(info *social.OAuthInfo, requester identity.Requester) error {
|
||||
prompt := info.LoginPrompt
|
||||
|
||||
if prompt != "" && prompt != "login" && prompt != "consent" && prompt != "select_account" {
|
||||
return ssosettings.ErrInvalidOAuthConfig("Invalid value for login_prompt. Valid values are: login, consent, select_account.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RequiredValidator(value string, name string) ssosettings.ValidateFunc[social.OAuthInfo] {
|
||||
return func(info *social.OAuthInfo, requester identity.Requester) error {
|
||||
if value == "" {
|
||||
|
||||
Reference in New Issue
Block a user