diff --git a/pkg/api/login.go b/pkg/api/login.go index 338ccf5a24e36..991a2c096b508 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -181,7 +181,7 @@ func (hs *HTTPServer) tryAutoLogin(c *contextmodel.ReqContext) bool { for providerName, provider := range oauthInfos { if provider.AutoLogin || hs.Cfg.OAuthAutoLogin { - redirectUrl := hs.Cfg.AppSubURL + "/login/" + providerName + redirectUrl := hs.Cfg.AppSubURL + "/login/" + providerName + "?autologin=true" if hs.Features.IsEnabledGlobally(featuremgmt.FlagUseSessionStorageForRedirection) { redirectUrl += hs.getRedirectToForAutoLogin(c) } diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 51a0857f96ad1..8ad9c04a0764c 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -1,7 +1,9 @@ package api import ( - "github.com/grafana/grafana/pkg/apimachinery/errutil" + // "github.com/grafana/grafana/pkg/apimachinery/errutil" + "strings" + "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/middleware/cookies" "github.com/grafana/grafana/pkg/services/authn" @@ -21,7 +23,9 @@ func (hs *HTTPServer) OAuthLogin(reqCtx *contextmodel.ReqContext) { if errorParam := reqCtx.Query("error"); errorParam != "" { errorDesc := reqCtx.Query("error_description") hs.log.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc) - hs.redirectWithError(reqCtx, errutil.Unauthorized("oauth.login", errutil.WithPublicMessage(hs.Cfg.OAuthLoginErrorMessage)).Errorf("Login provider denied login request")) + // hs.redirectWithError(reqCtx, errutil.Unauthorized("oauth.login", errutil.WithPublicMessage(hs.Cfg.OAuthLoginErrorMessage)).Errorf("Login provider denied login request")) + + reqCtx.Redirect("/login?disableAutoLogin=true") return } @@ -45,6 +49,14 @@ func (hs *HTTPServer) OAuthLogin(reqCtx *contextmodel.ReqContext) { cookies.WriteCookie(reqCtx.Resp, OauthPKCECookieName, pkce, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg) } + autoLogin := reqCtx.Query("autologin") + if autoLogin == "true" { + if strings.Contains(redirect.URL, "?") { + redirect.URL += "&prompt=none" + } else { + redirect.URL += "?prompt=none" + } + } reqCtx.Redirect(redirect.URL) return } diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index 12cf2dfd0a752..a8a28a867c8e7 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -77,6 +77,7 @@ type OAuthInfo struct { OrgMapping []string `mapstructure:"org_mapping"` Scopes []string `mapstructure:"scopes" toml:"scopes"` SignoutRedirectUrl string `mapstructure:"signout_redirect_url" toml:"signout_redirect_url"` + SignoutUrl string `mapstructure:"signout_url" toml:"signout_url"` SkipOrgRoleSync bool `mapstructure:"skip_org_role_sync" toml:"skip_org_role_sync"` TeamIdsAttributePath string `mapstructure:"team_ids_attribute_path" toml:"team_ids_attribute_path"` TeamsUrl string `mapstructure:"teams_url" toml:"teams_url"` diff --git a/pkg/models/usertoken/user_token.go b/pkg/models/usertoken/user_token.go index ea661d1d3221a..e7940a4d0ffd2 100644 --- a/pkg/models/usertoken/user_token.go +++ b/pkg/models/usertoken/user_token.go @@ -36,6 +36,7 @@ type UserToken struct { UpdatedAt int64 RevokedAt int64 UnhashedToken string + IdToken string `xorm:"-" json:"-"` } const UrgentRotateTime = 1 * time.Minute diff --git a/pkg/services/auth/authimpl/auth_token.go b/pkg/services/auth/authimpl/auth_token.go index a42784d267011..01e8fd1da75d2 100644 --- a/pkg/services/auth/authimpl/auth_token.go +++ b/pkg/services/auth/authimpl/auth_token.go @@ -94,6 +94,7 @@ func (s *UserAuthTokenService) CreateToken(ctx context.Context, cmd *auth.Create SeenAt: 0, RevokedAt: 0, AuthTokenSeen: false, + // IdToken: cmd.IdToken, } err = s.sqlStore.InTransaction(ctx, func(ctx context.Context) error { @@ -123,6 +124,10 @@ func (s *UserAuthTokenService) CreateToken(ctx context.Context, cmd *auth.Create var userToken auth.UserToken err = userAuthToken.toUserToken(&userToken) + if cmd.IdToken != "" { + userToken.IdToken = cmd.IdToken + } + return &userToken, err } diff --git a/pkg/services/auth/authimpl/model.go b/pkg/services/auth/authimpl/model.go index fa0e566cf8f2c..ebf20b09c9ade 100644 --- a/pkg/services/auth/authimpl/model.go +++ b/pkg/services/auth/authimpl/model.go @@ -21,6 +21,7 @@ type userAuthToken struct { RevokedAt int64 UnhashedToken string `xorm:"-"` ExternalSessionId int64 + IdToken string `xorm:"-"` } func userAuthTokenFromUserToken(ut *auth.UserToken) (*userAuthToken, error) { @@ -71,5 +72,6 @@ func (uat *userAuthToken) toUserToken(ut *auth.UserToken) error { ut.RevokedAt = uat.RevokedAt ut.UnhashedToken = uat.UnhashedToken ut.ExternalSessionId = uat.ExternalSessionId + ut.IdToken = uat.IdToken return nil } diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 4b24f92063b67..1603cdd69d70a 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -3,7 +3,9 @@ package authnimpl import ( "context" "errors" + "fmt" "net/http" + "net/url" "strconv" "strings" @@ -241,13 +243,19 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i s.log.FromContext(ctx).Debug("Failed to parse ip from address", "client", c.Name(), "id", id.ID, "addr", addr, "error", err) } + var sessionToken *auth.UserToken + var sessionTokenErr error externalSession := s.resolveExternalSessionFromIdentity(ctx, id, userID) + if externalSession != nil && externalSession.IDToken != "" { + sessionToken, sessionTokenErr = s.sessionService.CreateToken(ctx, &auth.CreateTokenCommand{User: &user.User{ID: userID}, ClientIP: ip, UserAgent: r.HTTPRequest.UserAgent(), ExternalSession: externalSession, IdToken: externalSession.IDToken}) + } else { + sessionToken, sessionTokenErr = s.sessionService.CreateToken(ctx, &auth.CreateTokenCommand{User: &user.User{ID: userID}, ClientIP: ip, UserAgent: r.HTTPRequest.UserAgent(), ExternalSession: externalSession}) + } - sessionToken, err := s.sessionService.CreateToken(ctx, &auth.CreateTokenCommand{User: &user.User{ID: userID}, ClientIP: ip, UserAgent: r.HTTPRequest.UserAgent(), ExternalSession: externalSession}) - if err != nil { + if sessionTokenErr != nil { s.metrics.failedLogin.WithLabelValues(client).Inc() - s.log.FromContext(ctx).Error("Failed to create session", "client", client, "id", id.ID, "err", err) - return nil, err + s.log.FromContext(ctx).Error("Failed to create session", "client", client, "id", id.ID, "err", sessionTokenErr) + return nil, sessionTokenErr } s.metrics.successfulLogin.WithLabelValues(client).Inc() @@ -327,6 +335,19 @@ func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionTo goto Default } + if s.cfg.SignoutRedirectUrl != "" { + rawRedirect := s.cfg.AppURL + "login?disableAutoLogin=true" + idToken := sessionToken.IdToken + + logoutURL := fmt.Sprintf( + "%s?post_logout_redirect_uri=%s&id_token_hint=%s", + s.cfg.SignoutUrl, + url.QueryEscape(rawRedirect), + idToken, + ) + clientRedirect.URL = logoutURL + } + redirect = clientRedirect } diff --git a/pkg/services/ssosettings/strategies/oauth_strategy.go b/pkg/services/ssosettings/strategies/oauth_strategy.go index 2a9d70a1012d2..b58c9f6bb12d0 100644 --- a/pkg/services/ssosettings/strategies/oauth_strategy.go +++ b/pkg/services/ssosettings/strategies/oauth_strategy.go @@ -102,6 +102,7 @@ func (s *OAuthStrategy) loadSettingsForProvider(provider string) map[string]any "auto_login": section.Key("auto_login").MustBool(false), "allowed_groups": section.Key("allowed_groups").Value(), "signout_redirect_url": section.Key("signout_redirect_url").Value(), + "signout_url": section.Key("signout_url").Value(), "org_mapping": section.Key("org_mapping").Value(), "org_attribute_path": section.Key("org_attribute_path").Value(), } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index f4b2aebc366bb..c3863cdbde246 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -242,6 +242,7 @@ type Cfg struct { AdminEmail string DisableLoginForm bool SignoutRedirectUrl string + SignoutUrl string IDResponseHeaderEnabled bool IDResponseHeaderPrefix string IDResponseHeaderNamespaces map[string]struct{} @@ -1629,6 +1630,7 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600) cfg.OAuthRefreshTokenServerLockMinWaitMs = auth.Key("oauth_refresh_token_server_lock_min_wait_ms").MustInt64(1000) cfg.SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "") + cfg.SignoutUrl = valueAsString(auth, "signout_url", "") // Deprecated cfg.OAuthSkipOrgRoleUpdateSync = false