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:
Karl Persson
2023-01-30 12:45:04 +01:00
committed by GitHub
parent e3bfc67d7b
commit efeb0daec6
8 changed files with 681 additions and 39 deletions
+83 -21
View File
@@ -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)
}
}
+1
View File
@@ -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(),
}
}