Auth: Add request context to UserInfo calls (#70007)

* use context for UserInfo requests

* set timeouts for oauth http client

* Update pkg/login/social/common.go

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

---------

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Jo
2023-06-14 14:30:40 +02:00
committed by GitHub
co-authored by Ieva
parent 1445a7cc5c
commit 914daef0fd
19 changed files with 172 additions and 138 deletions
+49 -48
View File
@@ -70,61 +70,61 @@ func genPKCECode() (string, string, error) {
return string(ascii), pkce, nil
}
func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
name := web.Params(ctx.Req)[":name"]
func (hs *HTTPServer) OAuthLogin(reqCtx *contextmodel.ReqContext) {
name := web.Params(reqCtx.Req)[":name"]
loginInfo := loginservice.LoginInfo{AuthModule: name}
if errorParam := ctx.Query("error"); errorParam != "" {
errorDesc := ctx.Query("error_description")
if errorParam := reqCtx.Query("error"); errorParam != "" {
errorDesc := reqCtx.Query("error_description")
oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
hs.handleOAuthLoginErrorWithRedirect(reqCtx, loginInfo, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
return
}
code := ctx.Query("code")
code := reqCtx.Query("code")
if hs.Cfg.AuthBrokerEnabled {
req := &authn.Request{HTTPRequest: ctx.Req, Resp: ctx.Resp}
req := &authn.Request{HTTPRequest: reqCtx.Req, Resp: reqCtx.Resp}
if code == "" {
redirect, err := hs.authnService.RedirectURL(ctx.Req.Context(), authn.ClientWithPrefix(name), req)
redirect, err := hs.authnService.RedirectURL(reqCtx.Req.Context(), authn.ClientWithPrefix(name), req)
if err != nil {
ctx.Redirect(hs.redirectURLWithErrorCookie(ctx, err))
reqCtx.Redirect(hs.redirectURLWithErrorCookie(reqCtx, err))
return
}
if pkce := redirect.Extra[authn.KeyOAuthPKCE]; pkce != "" {
cookies.WriteCookie(ctx.Resp, OauthPKCECookieName, pkce, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
cookies.WriteCookie(reqCtx.Resp, OauthPKCECookieName, pkce, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
}
cookies.WriteCookie(ctx.Resp, OauthStateCookieName, redirect.Extra[authn.KeyOAuthState], hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
ctx.Redirect(redirect.URL)
cookies.WriteCookie(reqCtx.Resp, OauthStateCookieName, redirect.Extra[authn.KeyOAuthState], hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
reqCtx.Redirect(redirect.URL)
return
}
identity, err := hs.authnService.Login(ctx.Req.Context(), authn.ClientWithPrefix(name), req)
identity, err := hs.authnService.Login(reqCtx.Req.Context(), authn.ClientWithPrefix(name), req)
// NOTE: always delete these cookies, even if login failed
cookies.DeleteCookie(ctx.Resp, OauthPKCECookieName, hs.CookieOptionsFromCfg)
cookies.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
cookies.DeleteCookie(reqCtx.Resp, OauthPKCECookieName, hs.CookieOptionsFromCfg)
cookies.DeleteCookie(reqCtx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
if err != nil {
ctx.Redirect(hs.redirectURLWithErrorCookie(ctx, err))
reqCtx.Redirect(hs.redirectURLWithErrorCookie(reqCtx, err))
return
}
metrics.MApiLoginOAuth.Inc()
authn.HandleLoginRedirect(ctx.Req, ctx.Resp, hs.Cfg, identity, hs.ValidateRedirectTo)
authn.HandleLoginRedirect(reqCtx.Req, reqCtx.Resp, hs.Cfg, identity, hs.ValidateRedirectTo)
return
}
provider := hs.SocialService.GetOAuthInfoProvider(name)
if provider == nil {
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, errors.New("OAuth not enabled"))
hs.handleOAuthLoginErrorWithRedirect(reqCtx, loginInfo, errors.New("OAuth not enabled"))
return
}
connect, err := hs.SocialService.GetConnector(name)
if err != nil {
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, fmt.Errorf("no OAuth with name %s configured", name))
hs.handleOAuthLoginErrorWithRedirect(reqCtx, loginInfo, fmt.Errorf("no OAuth with name %s configured", name))
return
}
@@ -133,15 +133,15 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
if provider.UsePKCE {
ascii, pkce, err := genPKCECode()
if err != nil {
ctx.Logger.Error("Generating PKCE failed", "error", err)
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
reqCtx.Logger.Error("Generating PKCE failed", "error", err)
hs.handleOAuthLoginError(reqCtx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "An internal error occurred",
})
return
}
cookies.WriteCookie(ctx.Resp, OauthPKCECookieName, ascii, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
cookies.WriteCookie(reqCtx.Resp, OauthPKCECookieName, ascii, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
opts = append(opts,
oauth2.SetAuthURLParam("code_challenge", pkce),
@@ -151,8 +151,8 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
state, err := GenStateString()
if err != nil {
ctx.Logger.Error("Generating state string failed", "err", err)
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
reqCtx.Logger.Error("Generating state string failed", "err", err)
hs.handleOAuthLoginError(reqCtx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "An internal error occurred",
})
@@ -160,32 +160,32 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
}
hashedState := hs.hashStatecode(state, provider.ClientSecret)
cookies.WriteCookie(ctx.Resp, OauthStateCookieName, hashedState, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
cookies.WriteCookie(reqCtx.Resp, OauthStateCookieName, hashedState, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
if provider.HostedDomain != "" {
opts = append(opts, oauth2.SetAuthURLParam("hd", provider.HostedDomain))
}
ctx.Redirect(connect.AuthCodeURL(state, opts...))
reqCtx.Redirect(connect.AuthCodeURL(state, opts...))
return
}
cookieState := ctx.GetCookie(OauthStateCookieName)
cookieState := reqCtx.GetCookie(OauthStateCookieName)
// delete cookie
cookies.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
cookies.DeleteCookie(reqCtx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
if cookieState == "" {
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
hs.handleOAuthLoginError(reqCtx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(missing saved state)",
})
return
}
queryState := hs.hashStatecode(ctx.Query("state"), provider.ClientSecret)
queryState := hs.hashStatecode(reqCtx.Query("state"), provider.ClientSecret)
oauthLogger.Info("state check", "queryState", queryState, "cookieState", cookieState)
if cookieState != queryState {
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
hs.handleOAuthLoginError(reqCtx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(state mismatch)",
})
@@ -194,19 +194,20 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
oauthClient, err := hs.SocialService.GetOAuthHttpClient(name)
if err != nil {
ctx.Logger.Error("Failed to create OAuth http client", "error", err)
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
reqCtx.Logger.Error("Failed to create OAuth http client", "error", err)
hs.handleOAuthLoginError(reqCtx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(" + err.Error() + ")",
})
return
}
oauthCtx := context.WithValue(context.Background(), oauth2.HTTPClient, oauthClient)
ctx := reqCtx.Req.Context()
oauthCtx := context.WithValue(ctx, oauth2.HTTPClient, oauthClient)
opts := []oauth2.AuthCodeOption{}
codeVerifier := ctx.GetCookie(OauthPKCECookieName)
cookies.DeleteCookie(ctx.Resp, OauthPKCECookieName, hs.CookieOptionsFromCfg)
codeVerifier := reqCtx.GetCookie(OauthPKCECookieName)
cookies.DeleteCookie(reqCtx.Resp, OauthPKCECookieName, hs.CookieOptionsFromCfg)
if codeVerifier != "" {
opts = append(opts,
oauth2.SetAuthURLParam("code_verifier", codeVerifier),
@@ -216,7 +217,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
// get token from provider
token, err := connect.Exchange(oauthCtx, code, opts...)
if err != nil {
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
hs.handleOAuthLoginError(reqCtx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(NewTransportWithCode)",
Err: err,
@@ -245,13 +246,13 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
client := connect.Client(oauthCtx, token)
// get user info
userInfo, err := connect.UserInfo(client, token)
userInfo, err := connect.UserInfo(ctx, client, token)
if err != nil {
var sErr *social.Error
if errors.As(err, &sErr) {
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, sErr)
hs.handleOAuthLoginErrorWithRedirect(reqCtx, loginInfo, sErr)
} else {
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
hs.handleOAuthLoginError(reqCtx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: fmt.Sprintf("login.OAuthLogin(get info from %s)", name),
Err: err,
@@ -264,34 +265,34 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
// validate that we got at least an email address
if userInfo.Email == "" {
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrNoEmail)
hs.handleOAuthLoginErrorWithRedirect(reqCtx, loginInfo, login.ErrNoEmail)
return
}
// validate that the email is allowed to login to grafana
if !connect.IsEmailAllowed(userInfo.Email) {
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrEmailNotAllowed)
hs.handleOAuthLoginErrorWithRedirect(reqCtx, loginInfo, login.ErrEmailNotAllowed)
return
}
loginInfo.ExternalUser = *hs.buildExternalUserInfo(token, userInfo, name)
loginInfo.User, err = hs.SyncUser(ctx, &loginInfo.ExternalUser, connect)
loginInfo.User, err = hs.SyncUser(reqCtx, &loginInfo.ExternalUser, connect)
if err != nil {
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err)
hs.handleOAuthLoginErrorWithRedirect(reqCtx, loginInfo, err)
return
}
// login
if err := hs.loginUserWithUser(loginInfo.User, ctx); err != nil {
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err)
if err := hs.loginUserWithUser(loginInfo.User, reqCtx); err != nil {
hs.handleOAuthLoginErrorWithRedirect(reqCtx, loginInfo, err)
return
}
loginInfo.HTTPStatus = http.StatusOK
hs.HooksService.RunLoginHook(&loginInfo, ctx)
hs.HooksService.RunLoginHook(&loginInfo, reqCtx)
metrics.MApiLoginOAuth.Inc()
ctx.Redirect(hs.GetRedirectURL(ctx))
reqCtx.Redirect(hs.GetRedirectURL(reqCtx))
}
// buildExternalUserInfo returns a ExternalUserInfo struct from OAuth user profile