mirror of
https://github.com/grafana/grafana.git
synced 2025-09-20 05:02:31 +08:00
Auth: Split signout_redirect_url into per provider settings (#75269)
* Split signout_redirect_url into per provider settings * Split signout_redirect_url into per provider settings * Update docs/sources/setup-grafana/configure-security/configure-authentication/grafana/index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Split signout_redirect_url into per provider settings * Split signout_redirect_url into per provider settings * Split signout_redirect_url into per provider settings * Split signout_redirect_url into per provider settings * Split signout_redirect_url into per provider settings * Split signout_redirect_url into per provider settings * update docs * update devenvs * add missing struct tag --------- Co-authored-by: Rao, B V Chalapathi <b_v_chalapathi.rao@nokia.com> Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> Co-authored-by: jguer <me@jguer.space>
This commit is contained in:
@ -592,6 +592,7 @@ scopes = user:email,read:org
|
|||||||
auth_url = https://github.com/login/oauth/authorize
|
auth_url = https://github.com/login/oauth/authorize
|
||||||
token_url = https://github.com/login/oauth/access_token
|
token_url = https://github.com/login/oauth/access_token
|
||||||
api_url = https://api.github.com/user
|
api_url = https://api.github.com/user
|
||||||
|
signout_redirect_url =
|
||||||
allowed_domains =
|
allowed_domains =
|
||||||
team_ids =
|
team_ids =
|
||||||
allowed_organizations =
|
allowed_organizations =
|
||||||
@ -619,6 +620,7 @@ scopes = openid email profile
|
|||||||
auth_url = https://gitlab.com/oauth/authorize
|
auth_url = https://gitlab.com/oauth/authorize
|
||||||
token_url = https://gitlab.com/oauth/token
|
token_url = https://gitlab.com/oauth/token
|
||||||
api_url = https://gitlab.com/api/v4
|
api_url = https://gitlab.com/api/v4
|
||||||
|
signout_redirect_url =
|
||||||
allowed_domains =
|
allowed_domains =
|
||||||
allowed_groups =
|
allowed_groups =
|
||||||
role_attribute_path =
|
role_attribute_path =
|
||||||
@ -645,6 +647,7 @@ scopes = openid email profile
|
|||||||
auth_url = https://accounts.google.com/o/oauth2/v2/auth
|
auth_url = https://accounts.google.com/o/oauth2/v2/auth
|
||||||
token_url = https://oauth2.googleapis.com/token
|
token_url = https://oauth2.googleapis.com/token
|
||||||
api_url = https://openidconnect.googleapis.com/v1/userinfo
|
api_url = https://openidconnect.googleapis.com/v1/userinfo
|
||||||
|
signout_redirect_url =
|
||||||
allowed_domains =
|
allowed_domains =
|
||||||
hosted_domain =
|
hosted_domain =
|
||||||
allowed_groups =
|
allowed_groups =
|
||||||
@ -695,6 +698,7 @@ client_secret =
|
|||||||
scopes = openid email profile
|
scopes = openid email profile
|
||||||
auth_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
|
auth_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
|
||||||
token_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
|
token_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
|
||||||
|
signout_redirect_url =
|
||||||
allowed_domains =
|
allowed_domains =
|
||||||
allowed_groups =
|
allowed_groups =
|
||||||
allowed_organizations =
|
allowed_organizations =
|
||||||
@ -722,6 +726,7 @@ scopes = openid profile email groups
|
|||||||
auth_url = https://<tenant-id>.okta.com/oauth2/v1/authorize
|
auth_url = https://<tenant-id>.okta.com/oauth2/v1/authorize
|
||||||
token_url = https://<tenant-id>.okta.com/oauth2/v1/token
|
token_url = https://<tenant-id>.okta.com/oauth2/v1/token
|
||||||
api_url = https://<tenant-id>.okta.com/oauth2/v1/userinfo
|
api_url = https://<tenant-id>.okta.com/oauth2/v1/userinfo
|
||||||
|
signout_redirect_url =
|
||||||
allowed_domains =
|
allowed_domains =
|
||||||
allowed_groups =
|
allowed_groups =
|
||||||
role_attribute_path =
|
role_attribute_path =
|
||||||
@ -758,6 +763,7 @@ team_ids_attribute_path =
|
|||||||
auth_url =
|
auth_url =
|
||||||
token_url =
|
token_url =
|
||||||
api_url =
|
api_url =
|
||||||
|
signout_redirect_url =
|
||||||
teams_url =
|
teams_url =
|
||||||
allowed_domains =
|
allowed_domains =
|
||||||
allowed_groups =
|
allowed_groups =
|
||||||
@ -808,6 +814,7 @@ auto_sign_up = false
|
|||||||
url_login = false
|
url_login = false
|
||||||
allow_assign_grafana_admin = false
|
allow_assign_grafana_admin = false
|
||||||
skip_org_role_sync = false
|
skip_org_role_sync = false
|
||||||
|
signout_redirect_url =
|
||||||
|
|
||||||
#################################### Auth LDAP ###########################
|
#################################### Auth LDAP ###########################
|
||||||
[auth.ldap]
|
[auth.ldap]
|
||||||
|
@ -581,6 +581,7 @@
|
|||||||
;auth_url = https://github.com/login/oauth/authorize
|
;auth_url = https://github.com/login/oauth/authorize
|
||||||
;token_url = https://github.com/login/oauth/access_token
|
;token_url = https://github.com/login/oauth/access_token
|
||||||
;api_url = https://api.github.com/user
|
;api_url = https://api.github.com/user
|
||||||
|
;signout_redirect_url =
|
||||||
;allowed_domains =
|
;allowed_domains =
|
||||||
;team_ids =
|
;team_ids =
|
||||||
;allowed_organizations =
|
;allowed_organizations =
|
||||||
@ -602,6 +603,7 @@
|
|||||||
;auth_url = https://gitlab.com/oauth/authorize
|
;auth_url = https://gitlab.com/oauth/authorize
|
||||||
;token_url = https://gitlab.com/oauth/token
|
;token_url = https://gitlab.com/oauth/token
|
||||||
;api_url = https://gitlab.com/api/v4
|
;api_url = https://gitlab.com/api/v4
|
||||||
|
;signout_redirect_url =
|
||||||
;allowed_domains =
|
;allowed_domains =
|
||||||
;allowed_groups =
|
;allowed_groups =
|
||||||
;role_attribute_path =
|
;role_attribute_path =
|
||||||
@ -627,6 +629,7 @@
|
|||||||
;auth_url = https://accounts.google.com/o/oauth2/v2/auth
|
;auth_url = https://accounts.google.com/o/oauth2/v2/auth
|
||||||
;token_url = https://oauth2.googleapis.com/token
|
;token_url = https://oauth2.googleapis.com/token
|
||||||
;api_url = https://openidconnect.googleapis.com/v1/userinfo
|
;api_url = https://openidconnect.googleapis.com/v1/userinfo
|
||||||
|
;signout_redirect_url =
|
||||||
;allowed_domains =
|
;allowed_domains =
|
||||||
;hosted_domain =
|
;hosted_domain =
|
||||||
;allowed_groups =
|
;allowed_groups =
|
||||||
@ -661,6 +664,7 @@
|
|||||||
;scopes = openid email profile
|
;scopes = openid email profile
|
||||||
;auth_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
|
;auth_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
|
||||||
;token_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
|
;token_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
|
||||||
|
;signout_redirect_url =
|
||||||
;allowed_domains =
|
;allowed_domains =
|
||||||
;allowed_groups =
|
;allowed_groups =
|
||||||
;allowed_organizations =
|
;allowed_organizations =
|
||||||
@ -682,6 +686,7 @@
|
|||||||
;auth_url = https://<tenant-id>.okta.com/oauth2/v1/authorize
|
;auth_url = https://<tenant-id>.okta.com/oauth2/v1/authorize
|
||||||
;token_url = https://<tenant-id>.okta.com/oauth2/v1/token
|
;token_url = https://<tenant-id>.okta.com/oauth2/v1/token
|
||||||
;api_url = https://<tenant-id>.okta.com/oauth2/v1/userinfo
|
;api_url = https://<tenant-id>.okta.com/oauth2/v1/userinfo
|
||||||
|
;signout_redirect_url =
|
||||||
;allowed_domains =
|
;allowed_domains =
|
||||||
;allowed_groups =
|
;allowed_groups =
|
||||||
;role_attribute_path =
|
;role_attribute_path =
|
||||||
@ -708,6 +713,7 @@
|
|||||||
;auth_url = https://foo.bar/login/oauth/authorize
|
;auth_url = https://foo.bar/login/oauth/authorize
|
||||||
;token_url = https://foo.bar/login/oauth/access_token
|
;token_url = https://foo.bar/login/oauth/access_token
|
||||||
;api_url = https://foo.bar/user
|
;api_url = https://foo.bar/user
|
||||||
|
;signout_redirect_url =
|
||||||
;teams_url =
|
;teams_url =
|
||||||
;allowed_domains =
|
;allowed_domains =
|
||||||
;team_ids =
|
;team_ids =
|
||||||
|
@ -85,8 +85,6 @@ auth_url = http://localhost:9000/application/o/authorize/
|
|||||||
token_url = http://localhost:9000/application/o/token/
|
token_url = http://localhost:9000/application/o/token/
|
||||||
api_url = http://localhost:9000/application/o/userinfo/
|
api_url = http://localhost:9000/application/o/userinfo/
|
||||||
role_attribute_path = contains(groups[*], 'admin') && 'Admin' || contains(groups[*], 'editor') && 'Editor' || 'Viewer'
|
role_attribute_path = contains(groups[*], 'admin') && 'Admin' || contains(groups[*], 'editor') && 'Editor' || 'Viewer'
|
||||||
|
|
||||||
[auth]
|
|
||||||
signout_redirect_url = http://localhost:9000/application/o/grafana-oidc/end-session/
|
signout_redirect_url = http://localhost:9000/application/o/grafana-oidc/end-session/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
authentikdb:
|
authentikdb:
|
||||||
image: docker.io/library/postgres:12-alpine
|
image: docker.io/library/postgres:16-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: authentikdb
|
container_name: authentikdb
|
||||||
environment:
|
environment:
|
||||||
@ -39,7 +39,7 @@
|
|||||||
- "authentik:authentik"
|
- "authentik:authentik"
|
||||||
|
|
||||||
authentik:
|
authentik:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.5.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.4}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: authentik
|
container_name: authentik
|
||||||
command: server
|
command: server
|
||||||
@ -66,7 +66,7 @@
|
|||||||
- "authentikredis:authentikredis"
|
- "authentikredis:authentikredis"
|
||||||
|
|
||||||
authentik-worker:
|
authentik-worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.5.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.4}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: authentik-worker
|
container_name: authentik-worker
|
||||||
command: worker
|
command: worker
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
oauthkeycloakdb:
|
oauthkeycloakdb:
|
||||||
image: docker.io/library/postgres:12-alpine
|
image: docker.io/library/postgres:16-alpine
|
||||||
container_name: oauthkeycloakdb
|
container_name: oauthkeycloakdb
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: keycloak
|
POSTGRES_DB: keycloak
|
||||||
@ -10,7 +10,7 @@
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
oauthkeycloak:
|
oauthkeycloak:
|
||||||
image: quay.io/keycloak/keycloak:21.1
|
image: quay.io/keycloak/keycloak:23.0
|
||||||
container_name: oauthkeycloak
|
container_name: oauthkeycloak
|
||||||
command: start-dev
|
command: start-dev
|
||||||
environment:
|
environment:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
oauthkeycloakdb:
|
oauthkeycloakdb:
|
||||||
image: docker.io/library/postgres:12-alpine
|
image: docker.io/library/postgres:16-alpine
|
||||||
container_name: oauthkeycloakdb
|
container_name: oauthkeycloakdb
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: keycloak
|
POSTGRES_DB: keycloak
|
||||||
@ -10,7 +10,7 @@
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
oauthkeycloak:
|
oauthkeycloak:
|
||||||
image: quay.io/keycloak/keycloak:22.0
|
image: quay.io/keycloak/keycloak:23.0
|
||||||
container_name: oauthkeycloak
|
container_name: oauthkeycloak
|
||||||
command: start-dev
|
command: start-dev
|
||||||
environment:
|
environment:
|
||||||
|
@ -10,9 +10,6 @@ make devenv sources="auth/oauth"
|
|||||||
Here is the conf you need to add to your configuration file (conf/custom.ini):
|
Here is the conf you need to add to your configuration file (conf/custom.ini):
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[auth]
|
|
||||||
signout_redirect_url = http://localhost:8087/realms/grafana/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Flogin
|
|
||||||
|
|
||||||
[auth.generic_oauth]
|
[auth.generic_oauth]
|
||||||
enabled = true
|
enabled = true
|
||||||
name = Keycloak-OAuth
|
name = Keycloak-OAuth
|
||||||
@ -28,6 +25,7 @@ auth_url = http://localhost:8087/realms/grafana/protocol/openid-connect/auth
|
|||||||
token_url = http://localhost:8087/realms/grafana/protocol/openid-connect/token
|
token_url = http://localhost:8087/realms/grafana/protocol/openid-connect/token
|
||||||
role_attribute_path = contains(roles[*], 'grafanaadmin') && 'GrafanaAdmin' || contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'
|
role_attribute_path = contains(roles[*], 'grafanaadmin') && 'GrafanaAdmin' || contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'
|
||||||
allow_assign_grafana_admin = true
|
allow_assign_grafana_admin = true
|
||||||
|
signout_redirect_url = http://localhost:8087/realms/grafana/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Flogin
|
||||||
```
|
```
|
||||||
|
|
||||||
## Devenv setup jwt auth
|
## Devenv setup jwt auth
|
||||||
|
@ -205,10 +205,12 @@ disable_signout_menu = true
|
|||||||
|
|
||||||
### URL redirect after signing out
|
### URL redirect after signing out
|
||||||
|
|
||||||
URL to redirect the user to after signing out from Grafana. This can for example be used to enable signout from OAuth provider.
|
URL to redirect the user to after signing out from Grafana. This can for example be used to enable signout from an OAuth provider.
|
||||||
|
|
||||||
|
Example for Generic OAuth:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[auth]
|
[auth.generic_oauth]
|
||||||
signout_redirect_url =
|
signout_redirect_url =
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -121,9 +121,12 @@ disable_signout_menu = true
|
|||||||
|
|
||||||
### URL redirect after signing out
|
### URL redirect after signing out
|
||||||
|
|
||||||
URL to redirect the user to after signing out from Grafana. This can for example be used to enable signout from oauth provider.
|
The URL to redirect the user to after signing out from Grafana can be configured under `[auth]` or under a specific OAuth provider section (for example, `[auth.generic_oauth]`). The URL configured under a specific OAuth provider section takes precedence over the URL configured in `[auth]` section. This can, for example, enable signout from the OAuth provider.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
[auth.generic_oauth]
|
||||||
|
signout_redirect_url =
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
signout_redirect_url =
|
signout_redirect_url =
|
||||||
```
|
```
|
||||||
|
@ -136,7 +136,7 @@ groups_attribute_path = reverse("Global:department")
|
|||||||
To enable Single Logout, you need to add the following option to the configuration of Grafana:
|
To enable Single Logout, you need to add the following option to the configuration of Grafana:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[auth]
|
[auth.generic_oauth]
|
||||||
signout_redirect_url = https://<PROVIDER_DOMAIN>/auth/realms/<REALM_NAME>/protocol/openid-connect/logout?post_logout_redirect_uri=https%3A%2F%2<GRAFANA_DOMAIN>%2Flogin
|
signout_redirect_url = https://<PROVIDER_DOMAIN>/auth/realms/<REALM_NAME>/protocol/openid-connect/logout?post_logout_redirect_uri=https%3A%2F%2<GRAFANA_DOMAIN>%2Flogin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
@ -214,6 +215,40 @@ func setupScenarioContext(t *testing.T, url string) *scenarioContext {
|
|||||||
return sc
|
return sc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupScenarioContextSamlLogout(t *testing.T, url string) *scenarioContext {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
//seed sections and keys
|
||||||
|
cfg.Raw.DeleteSection("DEFAULT")
|
||||||
|
saml, err := cfg.Raw.NewSection("auth.saml")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = saml.NewKey("enabled", "true")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = saml.NewKey("allow_idp_initiated", "false")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = saml.NewKey("single_logout", "true")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ctxHdlr := getContextHandler(t, cfg)
|
||||||
|
sc := &scenarioContext{
|
||||||
|
url: url,
|
||||||
|
t: t,
|
||||||
|
cfg: cfg,
|
||||||
|
ctxHdlr: ctxHdlr,
|
||||||
|
}
|
||||||
|
viewsPath, err := filepath.Abs("../../public/views")
|
||||||
|
require.NoError(t, err)
|
||||||
|
exists, err := fs.Exists(viewsPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Truef(t, exists, "Views should be in %q", viewsPath)
|
||||||
|
|
||||||
|
sc.m = web.New()
|
||||||
|
sc.m.UseMiddleware(web.Renderer(viewsPath, "[[", "]]"))
|
||||||
|
sc.m.Use(ctxHdlr.Middleware)
|
||||||
|
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This user should not be anonymous
|
||||||
func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
|
func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
|
||||||
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
|
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
|
||||||
}
|
}
|
||||||
|
@ -248,19 +248,29 @@ func (hs *HTTPServer) Logout(c *contextmodel.ReqContext) {
|
|||||||
hs.log.Error("failed to retrieve user ID", "error", errID)
|
hs.log.Error("failed to retrieve user ID", "error", errID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oauthProviderSignoutRedirectUrl := ""
|
||||||
|
getAuthQuery := loginservice.GetAuthInfoQuery{UserId: userID}
|
||||||
|
authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &getAuthQuery)
|
||||||
|
if err == nil {
|
||||||
// If SAML is enabled and this is a SAML user use saml logout
|
// If SAML is enabled and this is a SAML user use saml logout
|
||||||
if hs.samlSingleLogoutEnabled() {
|
if hs.samlSingleLogoutEnabled() {
|
||||||
getAuthQuery := loginservice.GetAuthInfoQuery{UserId: userID}
|
|
||||||
if authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &getAuthQuery); err == nil {
|
|
||||||
if authInfo.AuthModule == loginservice.SAMLAuthModule {
|
if authInfo.AuthModule == loginservice.SAMLAuthModule {
|
||||||
c.Redirect(hs.Cfg.AppSubURL + "/logout/saml")
|
c.Redirect(hs.Cfg.AppSubURL + "/logout/saml")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
oauthProvider := hs.SocialService.GetOAuthInfoProvider(strings.TrimPrefix(authInfo.AuthModule, "oauth_"))
|
||||||
|
oauthProviderSignoutRedirectUrl = oauthProvider.SignoutRedirectUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hs.log.Debug("Logout Redirect url", "auth.SignoutRedirectUrl:", hs.Cfg.SignoutRedirectUrl)
|
||||||
|
hs.log.Debug("Logout Redirect url", "oauth provider redirect url:", oauthProviderSignoutRedirectUrl)
|
||||||
|
|
||||||
|
signOutRedirectUrl := getSignOutRedirectUrl(hs.Cfg.SignoutRedirectUrl, oauthProviderSignoutRedirectUrl)
|
||||||
|
|
||||||
|
hs.log.Debug("Logout Redirect url", "signOurRedirectUrl:", signOutRedirectUrl)
|
||||||
idTokenHint := ""
|
idTokenHint := ""
|
||||||
oidcLogout := isPostLogoutRedirectConfigured(hs.Cfg.SignoutRedirectUrl)
|
oidcLogout := isPostLogoutRedirectConfigured(signOutRedirectUrl)
|
||||||
|
|
||||||
// Invalidate the OAuth tokens in case the User logged in with OAuth or the last external AuthEntry is an OAuth one
|
// Invalidate the OAuth tokens in case the User logged in with OAuth or the last external AuthEntry is an OAuth one
|
||||||
if entry, exists, _ := hs.oauthTokenService.HasOAuthEntry(c.Req.Context(), c.SignedInUser); exists {
|
if entry, exists, _ := hs.oauthTokenService.HasOAuthEntry(c.Req.Context(), c.SignedInUser); exists {
|
||||||
@ -278,17 +288,17 @@ func (hs *HTTPServer) Logout(c *contextmodel.ReqContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := hs.AuthTokenService.RevokeToken(c.Req.Context(), c.UserToken, false)
|
err = hs.AuthTokenService.RevokeToken(c.Req.Context(), c.UserToken, false)
|
||||||
if err != nil && !errors.Is(err, auth.ErrUserTokenNotFound) {
|
if err != nil && !errors.Is(err, auth.ErrUserTokenNotFound) {
|
||||||
hs.log.Error("failed to revoke auth token", "error", err)
|
hs.log.Error("failed to revoke auth token", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authn.DeleteSessionCookie(c.Resp, hs.Cfg)
|
authn.DeleteSessionCookie(c.Resp, hs.Cfg)
|
||||||
|
|
||||||
rdUrl := hs.Cfg.SignoutRedirectUrl
|
rdUrl := signOutRedirectUrl
|
||||||
if rdUrl != "" {
|
if rdUrl != "" {
|
||||||
if oidcLogout {
|
if oidcLogout {
|
||||||
rdUrl = getPostRedirectUrl(hs.Cfg.SignoutRedirectUrl, idTokenHint)
|
rdUrl = getPostRedirectUrl(signOutRedirectUrl, idTokenHint)
|
||||||
}
|
}
|
||||||
c.Redirect(rdUrl)
|
c.Redirect(rdUrl)
|
||||||
} else {
|
} else {
|
||||||
@ -443,3 +453,12 @@ func getPostRedirectUrl(rdUrl string, tokenHint string) string {
|
|||||||
|
|
||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSignOutRedirectUrl(gRdUrl string, oauthProviderUrl string) string {
|
||||||
|
if oauthProviderUrl != "" {
|
||||||
|
return oauthProviderUrl
|
||||||
|
} else if gRdUrl != "" {
|
||||||
|
return gRdUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -29,7 +29,9 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/hooks"
|
"github.com/grafana/grafana/pkg/services/hooks"
|
||||||
"github.com/grafana/grafana/pkg/services/licensing"
|
"github.com/grafana/grafana/pkg/services/licensing"
|
||||||
|
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
|
||||||
loginservice "github.com/grafana/grafana/pkg/services/login"
|
loginservice "github.com/grafana/grafana/pkg/services/login"
|
||||||
|
"github.com/grafana/grafana/pkg/services/login/authinfotest"
|
||||||
"github.com/grafana/grafana/pkg/services/navtree"
|
"github.com/grafana/grafana/pkg/services/navtree"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||||
@ -644,6 +646,37 @@ func setupAuthProxyLoginTest(t *testing.T, enableLoginToken bool) *scenarioConte
|
|||||||
return sc
|
return sc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLogoutSaml(t *testing.T) {
|
||||||
|
fakeSetIndexViewData(t)
|
||||||
|
fakeViewIndex(t)
|
||||||
|
sc := setupScenarioContextSamlLogout(t, "/logout")
|
||||||
|
license := licensingtest.NewFakeLicensing()
|
||||||
|
license.On("FeatureEnabled", "saml").Return(true)
|
||||||
|
|
||||||
|
hs := &HTTPServer{
|
||||||
|
Cfg: sc.cfg,
|
||||||
|
SettingsProvider: &setting.OSSImpl{Cfg: sc.cfg},
|
||||||
|
License: license,
|
||||||
|
SocialService: &mockSocialService{},
|
||||||
|
Features: featuremgmt.WithFeatures(),
|
||||||
|
authInfoService: &authinfotest.FakeService{
|
||||||
|
ExpectedUserAuth: &loginservice.UserAuth{AuthModule: loginservice.SAMLAuthModule},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, true, hs.samlSingleLogoutEnabled())
|
||||||
|
sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
|
||||||
|
c.SignedInUser = &user.SignedInUser{
|
||||||
|
UserID: 1,
|
||||||
|
}
|
||||||
|
hs.Logout(c)
|
||||||
|
return response.Empty(http.StatusOK)
|
||||||
|
})
|
||||||
|
sc.m.Get(sc.url, sc.defaultHandler)
|
||||||
|
sc.fakeReqNoAssertions("GET", sc.url).exec()
|
||||||
|
require.Equal(t, 302, sc.resp.Code)
|
||||||
|
}
|
||||||
|
|
||||||
type mockSocialService struct {
|
type mockSocialService struct {
|
||||||
oAuthInfo *social.OAuthInfo
|
oAuthInfo *social.OAuthInfo
|
||||||
oAuthInfos map[string]*social.OAuthInfo
|
oAuthInfos map[string]*social.OAuthInfo
|
||||||
|
@ -47,6 +47,7 @@ skip_org_role_sync = true
|
|||||||
use_refresh_token = true
|
use_refresh_token = true
|
||||||
empty_scopes =
|
empty_scopes =
|
||||||
hosted_domain = test_hosted_domain
|
hosted_domain = test_hosted_domain
|
||||||
|
signout_redirect_url = https://oauth.com/signout?post_logout_redirect_uri=https://grafana.com
|
||||||
`
|
`
|
||||||
|
|
||||||
iniFile, err := ini.Load([]byte(iniContent))
|
iniFile, err := ini.Load([]byte(iniContent))
|
||||||
@ -83,6 +84,7 @@ hosted_domain = test_hosted_domain
|
|||||||
AllowAssignGrafanaAdmin: true,
|
AllowAssignGrafanaAdmin: true,
|
||||||
UseRefreshToken: true,
|
UseRefreshToken: true,
|
||||||
HostedDomain: "test_hosted_domain",
|
HostedDomain: "test_hosted_domain",
|
||||||
|
SignoutRedirectUrl: "https://oauth.com/signout?post_logout_redirect_uri=https://grafana.com",
|
||||||
Extra: map[string]string{
|
Extra: map[string]string{
|
||||||
"allowed_organizations": "org1, org2",
|
"allowed_organizations": "org1, org2",
|
||||||
"id_token_attribute_name": "id_token",
|
"id_token_attribute_name": "id_token",
|
||||||
|
@ -74,6 +74,7 @@ type OAuthInfo struct {
|
|||||||
TlsSkipVerify bool `mapstructure:"tls_skip_verify_insecure" toml:"tls_skip_verify_insecure"`
|
TlsSkipVerify bool `mapstructure:"tls_skip_verify_insecure" toml:"tls_skip_verify_insecure"`
|
||||||
UsePKCE bool `mapstructure:"use_pkce" toml:"use_pkce"`
|
UsePKCE bool `mapstructure:"use_pkce" toml:"use_pkce"`
|
||||||
UseRefreshToken bool `mapstructure:"use_refresh_token" toml:"use_refresh_token"`
|
UseRefreshToken bool `mapstructure:"use_refresh_token" toml:"use_refresh_token"`
|
||||||
|
SignoutRedirectUrl string `mapstructure:"signout_redirect_url" toml:"signout_redirect_url"`
|
||||||
Extra map[string]string `mapstructure:",remain" toml:"extra,omitempty"`
|
Extra map[string]string `mapstructure:",remain" toml:"extra,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user