diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md index 5d5b5489061..a4ce61cd31a 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md @@ -544,6 +544,7 @@ The following table outlines the various Azure AD/Entra ID configuration options | `scopes` | No | Yes | List of comma- or space-separated OAuth2 scopes. | `openid email profile` | | `allow_sign_up` | No | Yes | Controls Grafana user creation through the Azure AD/Entra ID login. Only existing Grafana users can log in with Azure AD/Entra ID if set to `false`. | `true` | | `auto_login` | No | Yes | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` | +| `login_prompt` | No | Yes | Indicates the type of user interaction when the user logs in with Azure AD/Entra ID. Available values are `login`, `consent` and `select_account`. | | | `role_attribute_strict` | No | Yes | Set to `true` to deny user login if the Grafana org role cannot be extracted using `role_attribute_path` or `org_mapping`. For more information on user role mapping, refer to [Map roles](#map-roles). | `false` | | `org_attribute_path` | No | No | [JMESPath](http://jmespath.org/examples.html) expression to use for Grafana org to role lookup. Grafana will first evaluate the expression using the OAuth2 ID token. If no value is returned, the expression will be evaluated using the user information obtained from the UserInfo endpoint. The result of the evaluation will be mapped to org roles based on `org_mapping`. For more information on org to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | | | `org_mapping` | No | No | List of comma- or space-separated `::` mappings. Value can be `*` meaning "All users". Role is optional and can have the following values: `None`, `Viewer`, `Editor` or `Admin`. For more information on external organization to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | | diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md index 7b8d312b4af..9e10faee645 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md @@ -376,6 +376,7 @@ If the configuration option requires a JMESPath expression that includes a colon | `empty_scopes` | No | Yes | Set to `true` to use an empty scope during authentication. | `false` | | `allow_sign_up` | No | Yes | Controls Grafana user creation through the Generic OAuth login. Only existing Grafana users can log in with Generic OAuth if set to `false`. | `true` | | `auto_login` | No | Yes | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` | +| `login_prompt` | No | Yes | Indicates the type of user interaction when the user logs in with the IdP. Available values are `login`, `consent` and `select_account`. | | | `id_token_attribute_name` | No | Yes | The name of the key used to extract the ID token from the returned OAuth2 token. | `id_token` | | `login_attribute_path` | No | Yes | [JMESPath](http://jmespath.org/examples.html) expression to use for user login lookup from the user ID token. For more information on how user login is retrieved, refer to [Configure login](#configure-login). | | | `name_attribute_path` | No | Yes | [JMESPath](http://jmespath.org/examples.html) expression to use for user name lookup from the user ID token. This name will be used as the user's display name. For more information on how user display name is retrieved, refer to [Configure display name](#configure-display-name). | | diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md index 9546be11af3..7a327576cfd 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md @@ -247,6 +247,7 @@ If the configuration option requires a JMESPath expression that includes a colon | `scopes` | No | Yes | List of comma- or space-separated GitHub OAuth scopes. | `user:email,read:org` | | `allow_sign_up` | No | Yes | Whether to allow new Grafana user creation through GitHub login. If set to `false`, then only existing Grafana users can log in with GitHub OAuth. | `true` | | `auto_login` | No | Yes | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` | +| `login_prompt` | No | Yes | Indicates the type of user interaction when the user logs in with GitHub. Available values are `login`, `consent` and `select_account`. | | | `role_attribute_path` | No | Yes | [JMESPath](http://jmespath.org/examples.html) expression to use for Grafana role lookup. Grafana will first evaluate the expression using the user information obtained from the UserInfo endpoint. If no role is found, Grafana creates a JSON data with `groups` key that maps to GitHub teams obtained from GitHub's [`/api/user/teams`](https://docs.github.com/en/rest/teams/teams#list-teams-for-the-authenticated-user) endpoint, and evaluates the expression using this data. The result of the evaluation should be a valid Grafana role (`None`, `Viewer`, `Editor`, `Admin` or `GrafanaAdmin`). For more information on user role mapping, refer to [Configure role mapping](#org-roles-mapping-example). | | | `role_attribute_strict` | No | Yes | Set to `true` to deny user login if the Grafana org role cannot be extracted using `role_attribute_path` or `org_mapping`. For more information on user role mapping, refer to [Configure role mapping](#org-roles-mapping-example). | `false` | | `org_mapping` | No | No | List of comma- or space-separated `::` mappings. Value can be `*` meaning "All users". Role is optional and can have the following values: `None`, `Viewer`, `Editor` or `Admin`. For more information on external organization to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | | diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md index 16c3ba72032..3ab7695554c 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md @@ -270,6 +270,7 @@ If the configuration option requires a JMESPath expression that includes a colon | `scopes` | No | Yes | List of comma or space-separated GitLab OAuth scopes. | `openid email profile` | | `allow_sign_up` | No | Yes | Whether to allow new Grafana user creation through GitLab login. If set to `false`, then only existing Grafana users can log in with GitLab OAuth. | `true` | | `auto_login` | No | Yes | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` | +| `login_prompt` | No | Yes | Indicates the type of user interaction when the user logs in with GitLab. Available values are `login`, `consent` and `select_account`. | | | `role_attribute_path` | No | Yes | [JMESPath](http://jmespath.org/examples.html) expression to use for Grafana role lookup. Grafana will first evaluate the expression using the GitLab OAuth token. If no role is found, Grafana creates a JSON data with `groups` key that maps to groups obtained from GitLab's `/oauth/userinfo` endpoint, and evaluates the expression using this data. Finally, if a valid role is still not found, the expression is evaluated against the user information retrieved from `api_url/users` endpoint and groups retrieved from `api_url/groups` endpoint. The result of the evaluation should be a valid Grafana role (`None`, `Viewer`, `Editor`, `Admin` or `GrafanaAdmin`). For more information on user role mapping, refer to [Configure role mapping](#configure-role-mapping). | | | `role_attribute_strict` | No | Yes | Set to `true` to deny user login if the Grafana role cannot be extracted using `role_attribute_path`. For more information on user role mapping, refer to [Configure role mapping](#configure-role-mapping). | `false` | | `org_mapping` | No | No | List of comma- or space-separated `::` mappings. Value can be `*` meaning "All users". Role is optional and can have the following values: `None`, `Viewer`, `Editor` or `Admin`. For more information on external organization to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | | diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md index 8085e222252..59f2c6951a8 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md @@ -290,6 +290,7 @@ The following table outlines the various Google OAuth configuration options. You | `scopes` | No | Yes | List of comma- or space-separated OAuth2 scopes. | `openid email profile` | | `allow_sign_up` | No | Yes | Controls Grafana user creation through the Google login. Only existing Grafana users can log in with Google if set to `false`. | `true` | | `auto_login` | No | Yes | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` | +| `login_prompt` | No | Yes | Indicates the type of user interaction when the user logs in with Google. Available values are `login`, `consent` and `select_account`. | | | `hosted_domain` | No | Yes | Specifies the domain to restrict access to users from that domain. This value is appended to the authorization request using the `hd` parameter. | | | `validate_hd` | No | Yes | Set to `false` to disable the validation of the `hd` parameter from the Google ID token. For more informatiion, refer to [Enable Google OAuth in Grafana](#enable-google-oauth-in-grafana). | `true` | | `role_attribute_strict` | No | Yes | Set to `true` to deny user login if the Grafana org role cannot be extracted using `role_attribute_path` or `org_mapping`. For more information on user role mapping, refer to [Configure role mapping](#configure-role-mapping). | `false` | diff --git a/pkg/login/social/connectors/azuread_oauth.go b/pkg/login/social/connectors/azuread_oauth.go index 774cdb540ad..27660e61f77 100644 --- a/pkg/login/social/connectors/azuread_oauth.go +++ b/pkg/login/social/connectors/azuread_oauth.go @@ -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) { diff --git a/pkg/login/social/connectors/azuread_oauth_test.go b/pkg/login/social/connectors/azuread_oauth_test.go index 88e2f6039c3..f8c8bde01a6 100644 --- a/pkg/login/social/connectors/azuread_oauth_test.go +++ b/pkg/login/social/connectors/azuread_oauth_test.go @@ -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", diff --git a/pkg/login/social/connectors/generic_oauth_test.go b/pkg/login/social/connectors/generic_oauth_test.go index b4b7469cc05..9808cf2eebd 100644 --- a/pkg/login/social/connectors/generic_oauth_test.go +++ b/pkg/login/social/connectors/generic_oauth_test.go @@ -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", diff --git a/pkg/login/social/connectors/github_oauth_test.go b/pkg/login/social/connectors/github_oauth_test.go index b6cfeeb5aa3..21c9098f5f6 100644 --- a/pkg/login/social/connectors/github_oauth_test.go +++ b/pkg/login/social/connectors/github_oauth_test.go @@ -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", diff --git a/pkg/login/social/connectors/gitlab_oauth_test.go b/pkg/login/social/connectors/gitlab_oauth_test.go index 60fdc27d6ee..2526dd15af3 100644 --- a/pkg/login/social/connectors/gitlab_oauth_test.go +++ b/pkg/login/social/connectors/gitlab_oauth_test.go @@ -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", diff --git a/pkg/login/social/connectors/google_oauth.go b/pkg/login/social/connectors/google_oauth.go index 4e5d7a3f8f8..81c7cd31f9d 100644 --- a/pkg/login/social/connectors/google_oauth.go +++ b/pkg/login/social/connectors/google_oauth.go @@ -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) { diff --git a/pkg/login/social/connectors/google_oauth_test.go b/pkg/login/social/connectors/google_oauth_test.go index 0446a749adc..861167df9e4 100644 --- a/pkg/login/social/connectors/google_oauth_test.go +++ b/pkg/login/social/connectors/google_oauth_test.go @@ -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", diff --git a/pkg/login/social/connectors/social_base.go b/pkg/login/social/connectors/social_base.go index e6bda1c4f81..0db4d81baeb 100644 --- a/pkg/login/social/connectors/social_base.go +++ b/pkg/login/social/connectors/social_base.go @@ -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, + ) } diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index daad4db4ee7..ab127e50671 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -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"` } diff --git a/pkg/services/ssosettings/strategies/oauth_strategy.go b/pkg/services/ssosettings/strategies/oauth_strategy.go index a9689008235..8232512071d 100644 --- a/pkg/services/ssosettings/strategies/oauth_strategy.go +++ b/pkg/services/ssosettings/strategies/oauth_strategy.go @@ -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] diff --git a/pkg/services/ssosettings/strategies/oauth_strategy_test.go b/pkg/services/ssosettings/strategies/oauth_strategy_test.go index 568e94f6439..ce1a73379cd 100644 --- a/pkg/services/ssosettings/strategies/oauth_strategy_test.go +++ b/pkg/services/ssosettings/strategies/oauth_strategy_test.go @@ -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", } ) diff --git a/pkg/services/ssosettings/validation/oauth_validators.go b/pkg/services/ssosettings/validation/oauth_validators.go index 1ef1d2434ed..a05af9cccdb 100644 --- a/pkg/services/ssosettings/validation/oauth_validators.go +++ b/pkg/services/ssosettings/validation/oauth_validators.go @@ -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 == "" { diff --git a/public/app/features/auth-config/ProviderConfigForm.test.tsx b/public/app/features/auth-config/ProviderConfigForm.test.tsx index 310684532b3..c516b89237e 100644 --- a/public/app/features/auth-config/ProviderConfigForm.test.tsx +++ b/public/app/features/auth-config/ProviderConfigForm.test.tsx @@ -154,6 +154,7 @@ describe('ProviderConfigForm', () => { clientId: 'test-client-id', clientSecret: 'test-client-secret', enabled: true, + loginPrompt: '', name: 'GitHub', orgMapping: '["Group A:1:Editor","Group B:2:Admin"]', roleAttributePath: 'new-attribute-path', @@ -204,6 +205,7 @@ describe('ProviderConfigForm', () => { clientId: 'test-client-id', clientSecret: 'test-client-secret', enabled: false, + loginPrompt: '', name: 'GitHub', roleAttributePath: '', roleAttributeStrict: false, diff --git a/public/app/features/auth-config/fields.tsx b/public/app/features/auth-config/fields.tsx index ce38363d3d9..029e67b581a 100644 --- a/public/app/features/auth-config/fields.tsx +++ b/public/app/features/auth-config/fields.tsx @@ -44,6 +44,7 @@ export const getSectionFields = (): Section => { 'allowSignUp', 'autoLogin', 'signoutRedirectUrl', + 'loginPrompt', ], }, { @@ -87,6 +88,7 @@ export const getSectionFields = (): Section => { 'allowSignUp', 'autoLogin', 'signoutRedirectUrl', + 'loginPrompt', ], }, { @@ -132,7 +134,16 @@ export const getSectionFields = (): Section => { { name: generalSettingsLabel, id: 'general', - fields: ['name', 'clientId', 'clientSecret', 'scopes', 'allowSignUp', 'autoLogin', 'signoutRedirectUrl'], + fields: [ + 'name', + 'clientId', + 'clientSecret', + 'scopes', + 'allowSignUp', + 'autoLogin', + 'signoutRedirectUrl', + 'loginPrompt', + ], }, { name: userMappingLabel, @@ -166,7 +177,16 @@ export const getSectionFields = (): Section => { { name: generalSettingsLabel, id: 'general', - fields: ['name', 'clientId', 'clientSecret', 'scopes', 'allowSignUp', 'autoLogin', 'signoutRedirectUrl'], + fields: [ + 'name', + 'clientId', + 'clientSecret', + 'scopes', + 'allowSignUp', + 'autoLogin', + 'signoutRedirectUrl', + 'loginPrompt', + ], }, { name: userMappingLabel, @@ -199,7 +219,16 @@ export const getSectionFields = (): Section => { { name: generalSettingsLabel, id: 'general', - fields: ['name', 'clientId', 'clientSecret', 'scopes', 'allowSignUp', 'autoLogin', 'signoutRedirectUrl'], + fields: [ + 'name', + 'clientId', + 'clientSecret', + 'scopes', + 'allowSignUp', + 'autoLogin', + 'signoutRedirectUrl', + 'loginPrompt', + ], }, { name: userMappingLabel, @@ -890,6 +919,22 @@ export function fieldMap(provider: string): Record { message: t('auth-config.fields.domain-hint-valid-domain', 'This field must be a valid domain.'), }, }, + loginPrompt: { + label: t('auth-config.fields.login-prompt-label', 'Login prompt'), + type: 'select', + description: t( + 'auth-config.fields.login-prompt-description', + 'Indicates the type of user interaction when the user logs in with the IdP.' + ), + multi: false, + options: [ + { value: '', label: '' }, + { value: 'login', label: t('auth-config.fields.login-prompt-login', 'Login') }, + { value: 'consent', label: t('auth-config.fields.login-prompt-consent', 'Consent') }, + { value: 'select_account', label: t('auth-config.fields.login-prompt-select-account', 'Select account') }, + ], + defaultValue: { value: '', label: '' }, + }, }; } diff --git a/public/app/features/auth-config/types.ts b/public/app/features/auth-config/types.ts index 933642c82d5..c2040b3b80b 100644 --- a/public/app/features/auth-config/types.ts +++ b/public/app/features/auth-config/types.ts @@ -61,6 +61,7 @@ export type SSOProviderSettingsBase = { // For Azure AD forceUseGraphApi?: boolean; domainHint?: string; + loginPrompt?: string; // For Google validateHd?: boolean; }; diff --git a/public/app/features/auth-config/utils/data.ts b/public/app/features/auth-config/utils/data.ts index d11f78bdb6a..330b2e79cc9 100644 --- a/public/app/features/auth-config/utils/data.ts +++ b/public/app/features/auth-config/utils/data.ts @@ -25,6 +25,7 @@ export const emptySettings: SSOProviderDTO = { emailAttributePath: '', emptyScopes: false, enabled: false, + loginPrompt: '', extra: {}, groupsAttributePath: '', hostedDomain: '', diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index a62cabf891b..bd097338400 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3256,6 +3256,11 @@ "id-token-attribute-name-label": "ID token attribute name", "login-attribute-path-description": "JMESPath expression to use for user login lookup from the user ID token.", "login-attribute-path-label": "Login attribute path", + "login-prompt-consent": "Consent", + "login-prompt-description": "Indicates the type of user interaction when the user logs in with the IdP.", + "login-prompt-label": "Login prompt", + "login-prompt-login": "Login", + "login-prompt-select-account": "Select account", "managed-identity-client-id-description": "The managed identity client ID of the federated identity credential of your OAuth2 app.", "managed-identity-client-id-label": "FIC managed identity client ID", "name-attribute-path-description": "JMESPath expression to use for user name lookup from the user ID token. \nThis name will be used as the user's display name.",