AuthN: Add oauth clients and perform oauth authentication with authn.Service (#62072)
* AuthN: Update signature of redirect client and RedirectURL function * OAuth: use authn.Service to perform oauth authentication and login if feature toggle is enabled * AuthN: register oauth clients * AuthN: set auth module metadata * AuthN: add logs for failed login attempts * AuthN: Don't use enable disabled setting * OAuth: only run hooks when authnService feature toggle is disabled * OAuth: Add function to handle oauth errors from authn.Service
This commit is contained in:
+83
-21
@@ -17,11 +17,14 @@ import (
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/login/social"
|
||||
"github.com/grafana/grafana/pkg/middleware/cookies"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
loginservice "github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
@@ -70,11 +73,59 @@ func genPKCECode() (string, string, error) {
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
|
||||
loginInfo := loginservice.LoginInfo{
|
||||
AuthModule: "oauth",
|
||||
}
|
||||
name := web.Params(ctx.Req)[":name"]
|
||||
loginInfo.AuthModule = name
|
||||
loginInfo := loginservice.LoginInfo{AuthModule: name}
|
||||
|
||||
if errorParam := ctx.Query("error"); errorParam != "" {
|
||||
errorDesc := ctx.Query("error_description")
|
||||
oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
|
||||
return
|
||||
}
|
||||
|
||||
code := ctx.Query("code")
|
||||
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagAuthnService) {
|
||||
req := &authn.Request{HTTPRequest: ctx.Req, Resp: ctx.Resp}
|
||||
if code == "" {
|
||||
redirect, err := hs.authnService.RedirectURL(ctx.Req.Context(), authn.ClientWithPrefix(name), req)
|
||||
if err != nil {
|
||||
hs.handleAuthnOAuthErr(ctx, "failed to generate oauth redirect url", err)
|
||||
return
|
||||
}
|
||||
|
||||
if pkce := redirect.Extra[authn.KeyOAuthPKCE]; pkce != "" {
|
||||
cookies.WriteCookie(ctx.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)
|
||||
return
|
||||
}
|
||||
|
||||
identity, err := hs.authnService.Login(ctx.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)
|
||||
|
||||
if err != nil {
|
||||
hs.handleAuthnOAuthErr(ctx, "failed to perform login for oauth request", err)
|
||||
return
|
||||
}
|
||||
|
||||
metrics.MApiLoginOAuth.Inc()
|
||||
cookies.WriteSessionCookie(ctx, hs.Cfg, identity.SessionToken.UnhashedToken, hs.Cfg.LoginMaxLifetime)
|
||||
|
||||
redirectURL := setting.AppSubUrl + "/"
|
||||
if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && hs.ValidateRedirectTo(redirectTo) == nil {
|
||||
redirectURL = redirectTo
|
||||
cookies.DeleteCookie(ctx.Resp, "redirect_to", hs.CookieOptionsFromCfg)
|
||||
}
|
||||
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
|
||||
provider := hs.SocialService.GetOAuthInfoProvider(name)
|
||||
if provider == nil {
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, errors.New("OAuth not enabled"))
|
||||
@@ -87,15 +138,6 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
errorParam := ctx.Query("error")
|
||||
if errorParam != "" {
|
||||
errorDesc := ctx.Query("error_description")
|
||||
oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
|
||||
return
|
||||
}
|
||||
|
||||
code := ctx.Query("code")
|
||||
if code == "" {
|
||||
var opts []oauth2.AuthCodeOption
|
||||
if provider.UsePKCE {
|
||||
@@ -106,6 +148,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *contextmodel.ReqContext) {
|
||||
HttpStatus: http.StatusInternalServerError,
|
||||
PublicMessage: "An internal error occurred",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cookies.WriteCookie(ctx.Resp, OauthPKCECookieName, ascii, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
|
||||
@@ -345,6 +388,19 @@ func (hs *HTTPServer) hashStatecode(code, seed string) string {
|
||||
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 {
|
||||
HttpStatus int
|
||||
PublicMessage string
|
||||
@@ -354,18 +410,24 @@ type LoginError struct {
|
||||
func (hs *HTTPServer) handleOAuthLoginError(ctx *contextmodel.ReqContext, info loginservice.LoginInfo, err LoginError) {
|
||||
ctx.Handle(hs.Cfg, err.HttpStatus, err.PublicMessage, err.Err)
|
||||
|
||||
info.Error = err.Err
|
||||
if info.Error == nil {
|
||||
info.Error = errors.New(err.PublicMessage)
|
||||
}
|
||||
info.HTTPStatus = err.HttpStatus
|
||||
// login hooks is handled by authn.Service
|
||||
if !hs.Features.IsEnabled(featuremgmt.FlagAuthnService) {
|
||||
info.Error = err.Err
|
||||
if info.Error == nil {
|
||||
info.Error = errors.New(err.PublicMessage)
|
||||
}
|
||||
info.HTTPStatus = err.HttpStatus
|
||||
|
||||
hs.HooksService.RunLoginHook(&info, ctx)
|
||||
hs.HooksService.RunLoginHook(&info, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) handleOAuthLoginErrorWithRedirect(ctx *contextmodel.ReqContext, info loginservice.LoginInfo, err error, v ...interface{}) {
|
||||
hs.redirectWithError(ctx, err, v...)
|
||||
|
||||
info.Error = err
|
||||
hs.HooksService.RunLoginHook(&info, ctx)
|
||||
// login hooks is handled by authn.Service
|
||||
if !hs.Features.IsEnabled(featuremgmt.FlagAuthnService) {
|
||||
info.Error = err
|
||||
hs.HooksService.RunLoginHook(&info, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ func setupSocialHTTPServerWithConfig(t *testing.T, cfg *setting.Cfg) *HTTPServer
|
||||
SocialService: social.ProvideService(cfg, featuremgmt.WithFeatures()),
|
||||
HooksService: hooks.ProvideService(),
|
||||
SecretsService: fakes.NewFakeSecretsService(),
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user