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:
co-authored by
Christopher Moyer
Rao, B V Chalapathi
jguer
parent
73776f37eb
commit
e152323a33
@@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
@@ -214,6 +215,40 @@ func setupScenarioContext(t *testing.T, url string) *scenarioContext {
|
||||
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 {
|
||||
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
|
||||
}
|
||||
|
||||
+27
-8
@@ -248,19 +248,29 @@ func (hs *HTTPServer) Logout(c *contextmodel.ReqContext) {
|
||||
hs.log.Error("failed to retrieve user ID", "error", errID)
|
||||
}
|
||||
|
||||
// If SAML is enabled and this is a SAML user use saml logout
|
||||
if hs.samlSingleLogoutEnabled() {
|
||||
getAuthQuery := loginservice.GetAuthInfoQuery{UserId: userID}
|
||||
if authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &getAuthQuery); err == nil {
|
||||
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 hs.samlSingleLogoutEnabled() {
|
||||
if authInfo.AuthModule == loginservice.SAMLAuthModule {
|
||||
c.Redirect(hs.Cfg.AppSubURL + "/logout/saml")
|
||||
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 := ""
|
||||
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
|
||||
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) {
|
||||
hs.log.Error("failed to revoke auth token", "error", err)
|
||||
}
|
||||
|
||||
authn.DeleteSessionCookie(c.Resp, hs.Cfg)
|
||||
|
||||
rdUrl := hs.Cfg.SignoutRedirectUrl
|
||||
rdUrl := signOutRedirectUrl
|
||||
if rdUrl != "" {
|
||||
if oidcLogout {
|
||||
rdUrl = getPostRedirectUrl(hs.Cfg.SignoutRedirectUrl, idTokenHint)
|
||||
rdUrl = getPostRedirectUrl(signOutRedirectUrl, idTokenHint)
|
||||
}
|
||||
c.Redirect(rdUrl)
|
||||
} else {
|
||||
@@ -443,3 +453,12 @@ func getPostRedirectUrl(rdUrl string, tokenHint string) 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/hooks"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
|
||||
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/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
@@ -644,6 +646,37 @@ func setupAuthProxyLoginTest(t *testing.T, enableLoginToken bool) *scenarioConte
|
||||
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 {
|
||||
oAuthInfo *social.OAuthInfo
|
||||
oAuthInfos map[string]*social.OAuthInfo
|
||||
|
||||
Reference in New Issue
Block a user