AuthN: Login error handling (#64239)

* Social: Fix type so it appears in error responses

* AuthN: construct errutil.Error from social.Error

* login: Check for errutil.Error and use public message

* Login: redirectURLWithErrorCookie for authn errors

Co-authored-by: Jo <joao.guerreiro@grafana.com>
This commit is contained in:
Karl Persson 2023-03-07 09:57:25 +01:00 committed by GitHub
parent c67bb07968
commit 872d2d1e1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 23 additions and 21 deletions

View File

@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
@ -441,5 +442,10 @@ func getLoginExternalError(err error) string {
return createTokenErr.ExternalErr return createTokenErr.ExternalErr
} }
gfErr := &errutil.Error{}
if errors.As(err, gfErr) {
return gfErr.Public().Message
}
return err.Error() return err.Error()
} }

View File

@ -24,7 +24,6 @@ import (
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
@ -90,7 +89,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
if code == "" { if code == "" {
redirect, err := hs.authnService.RedirectURL(ctx.Req.Context(), authn.ClientWithPrefix(name), req) redirect, err := hs.authnService.RedirectURL(ctx.Req.Context(), authn.ClientWithPrefix(name), req)
if err != nil { if err != nil {
hs.handleAuthnOAuthErr(ctx, "failed to generate oauth redirect url", err) ctx.Redirect(hs.redirectURLWithErrorCookie(ctx, err))
return return
} }
@ -109,7 +108,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
cookies.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg) cookies.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
if err != nil { if err != nil {
hs.handleAuthnOAuthErr(ctx, "failed to perform login for oauth request", err) ctx.Redirect(hs.redirectURLWithErrorCookie(ctx, err))
return return
} }
@ -380,19 +379,6 @@ func (hs *HTTPServer) hashStatecode(code, seed string) string {
return hex.EncodeToString(hashBytes[:]) return hex.EncodeToString(hashBytes[:])
} }
func (hs *HTTPServer) handleAuthnOAuthErr(c *contextmodel.ReqContext, msg string, err error) {
gfErr := &errutil.Error{}
if errors.As(err, gfErr) {
if gfErr.Public().Message != "" {
c.Handle(hs.Cfg, gfErr.Public().StatusCode, gfErr.Public().Message, err)
return
}
}
c.Logger.Warn(msg, "err", err)
c.Redirect(hs.Cfg.AppSubURL + "/login")
}
type LoginError struct { type LoginError struct {
HttpStatus int HttpStatus int
PublicMessage string PublicMessage string

View File

@ -12,7 +12,7 @@ import (
) )
var ( var (
errMissingGroupMembership = Error{"user not a member of one of the required groups"} errMissingGroupMembership = &Error{"user not a member of one of the required groups"}
) )
type httpGetResponse struct { type httpGetResponse struct {

View File

@ -6,6 +6,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -42,11 +43,16 @@ var (
errOAuthInvalidState = errutil.NewBase(errutil.StatusUnauthorized, "auth.oauth.state.invalid", errutil.WithPublicMessage("Provided state does not match stored state")) errOAuthInvalidState = errutil.NewBase(errutil.StatusUnauthorized, "auth.oauth.state.invalid", errutil.WithPublicMessage("Provided state does not match stored state"))
errOAuthTokenExchange = errutil.NewBase(errutil.StatusInternal, "auth.oauth.token.exchange", errutil.WithPublicMessage("Failed to get token from provider")) errOAuthTokenExchange = errutil.NewBase(errutil.StatusInternal, "auth.oauth.token.exchange", errutil.WithPublicMessage("Failed to get token from provider"))
errOAuthUserInfo = errutil.NewBase(errutil.StatusInternal, "auth.oauth.userinfo.error")
errOAuthMissingRequiredEmail = errutil.NewBase(errutil.StatusUnauthorized, "auth.oauth.email.missing") errOAuthMissingRequiredEmail = errutil.NewBase(errutil.StatusUnauthorized, "auth.oauth.email.missing", errutil.WithPublicMessage("Provider didn't return an email address"))
errOAuthEmailNotAllowed = errutil.NewBase(errutil.StatusUnauthorized, "auth.oauth.email.not-allowed") errOAuthEmailNotAllowed = errutil.NewBase(errutil.StatusUnauthorized, "auth.oauth.email.not-allowed", errutil.WithPublicMessage("Required email domain not fulfilled"))
) )
func fromSocialErr(err *social.Error) error {
return errutil.NewBase(errutil.StatusUnauthorized, "auth.oauth.userinfo.failed", errutil.WithPublicMessage(err.Error())).Errorf("%w", err)
}
var _ authn.RedirectClient = new(OAuth) var _ authn.RedirectClient = new(OAuth)
func ProvideOAuth( func ProvideOAuth(
@ -106,13 +112,17 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
// exchange auth code to a valid token // exchange auth code to a valid token
token, err := c.connector.Exchange(clientCtx, r.HTTPRequest.URL.Query().Get("code"), opts...) token, err := c.connector.Exchange(clientCtx, r.HTTPRequest.URL.Query().Get("code"), opts...)
if err != nil { if err != nil {
return nil, err return nil, errOAuthTokenExchange.Errorf("failed to exchange code to token: %w", err)
} }
token.TokenType = "Bearer" token.TokenType = "Bearer"
userInfo, err := c.connector.UserInfo(c.connector.Client(clientCtx, token), token) userInfo, err := c.connector.UserInfo(c.connector.Client(clientCtx, token), token)
if err != nil { if err != nil {
return nil, errOAuthTokenExchange.Errorf("failed to exchange code to token: %w", err) var sErr *social.Error
if errors.As(err, &sErr) {
return nil, fromSocialErr(sErr)
}
return nil, errOAuthUserInfo.Errorf("failed to get user info: %w", err)
} }
if userInfo.Email == "" { if userInfo.Email == "" {