mirror of
https://github.com/nais/wonderwall.git
synced 2026-05-06 16:36:51 +00:00
feat(reverseproxy): validate acr and redirect if applicable
This commit is contained in:
27
pkg/handler/acr/acr.go
Normal file
27
pkg/handler/acr/acr.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package acr
|
||||
|
||||
import (
|
||||
"github.com/nais/wonderwall/pkg/config"
|
||||
"github.com/nais/wonderwall/pkg/openid"
|
||||
"github.com/nais/wonderwall/pkg/session"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
Enabled bool
|
||||
ExpectedValue string
|
||||
}
|
||||
|
||||
func (h *Handler) Validate(sess *session.Session) error {
|
||||
if !h.Enabled || sess == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return openid.ValidateAcr(h.ExpectedValue, sess.Acr())
|
||||
}
|
||||
|
||||
func NewHandler(cfg *config.Config) *Handler {
|
||||
return &Handler{
|
||||
Enabled: len(cfg.OpenID.ACRValues) > 0,
|
||||
ExpectedValue: cfg.OpenID.ACRValues,
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/nais/wonderwall/pkg/config"
|
||||
"github.com/nais/wonderwall/pkg/cookie"
|
||||
"github.com/nais/wonderwall/pkg/crypto"
|
||||
"github.com/nais/wonderwall/pkg/handler/acr"
|
||||
"github.com/nais/wonderwall/pkg/handler/autologin"
|
||||
"github.com/nais/wonderwall/pkg/ingress"
|
||||
"github.com/nais/wonderwall/pkg/metrics"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
var _ router.Source = &Standalone{}
|
||||
|
||||
type Standalone struct {
|
||||
AcrHandler *acr.Handler
|
||||
AutoLogin *autologin.AutoLogin
|
||||
Client *openidclient.Client
|
||||
Config *config.Config
|
||||
@@ -77,6 +79,7 @@ func NewStandalone(
|
||||
}
|
||||
|
||||
return &Standalone{
|
||||
AcrHandler: acr.NewHandler(cfg),
|
||||
AutoLogin: autoLogin,
|
||||
Client: openidClient,
|
||||
Config: cfg,
|
||||
@@ -90,8 +93,8 @@ func NewStandalone(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Standalone) GetSession(r *http.Request) (*session.Session, error) {
|
||||
return s.SessionManager.GetOrRefresh(r)
|
||||
func (s *Standalone) GetAcrHandler() *acr.Handler {
|
||||
return s.AcrHandler
|
||||
}
|
||||
|
||||
func (s *Standalone) GetAutoLogin() *autologin.AutoLogin {
|
||||
@@ -115,6 +118,10 @@ func (s *Standalone) GetPath(r *http.Request) string {
|
||||
return GetPath(r, s.GetIngresses())
|
||||
}
|
||||
|
||||
func (s *Standalone) GetSession(r *http.Request) (*session.Session, error) {
|
||||
return s.SessionManager.GetOrRefresh(r)
|
||||
}
|
||||
|
||||
func (s *Standalone) Login(w http.ResponseWriter, r *http.Request) {
|
||||
canonicalRedirect := s.Redirect.Canonical(r)
|
||||
login, err := s.Client.Login(r)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/nais/wonderwall/pkg/config"
|
||||
"github.com/nais/wonderwall/pkg/crypto"
|
||||
"github.com/nais/wonderwall/pkg/handler/acr"
|
||||
"github.com/nais/wonderwall/pkg/handler/autologin"
|
||||
"github.com/nais/wonderwall/pkg/ingress"
|
||||
logentry "github.com/nais/wonderwall/pkg/middleware"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
var _ router.Source = &SSOProxy{}
|
||||
|
||||
type SSOProxy struct {
|
||||
AcrHandler *acr.Handler
|
||||
AutoLogin *autologin.AutoLogin
|
||||
Config *config.Config
|
||||
Ingresses *ingress.Ingresses
|
||||
@@ -59,6 +61,7 @@ func NewSSOProxy(cfg *config.Config, crypter crypto.Crypter) (*SSOProxy, error)
|
||||
}
|
||||
|
||||
return &SSOProxy{
|
||||
AcrHandler: acr.NewHandler(cfg),
|
||||
AutoLogin: autoLogin,
|
||||
Config: cfg,
|
||||
Ingresses: ingresses,
|
||||
@@ -70,8 +73,8 @@ func NewSSOProxy(cfg *config.Config, crypter crypto.Crypter) (*SSOProxy, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SSOProxy) GetSession(r *http.Request) (*session.Session, error) {
|
||||
return s.SessionReader.Get(r)
|
||||
func (s *SSOProxy) GetAcrHandler() *acr.Handler {
|
||||
return s.AcrHandler
|
||||
}
|
||||
|
||||
func (s *SSOProxy) GetAutoLogin() *autologin.AutoLogin {
|
||||
@@ -86,6 +89,10 @@ func (s *SSOProxy) GetPath(r *http.Request) string {
|
||||
return GetPath(r, s.GetIngresses())
|
||||
}
|
||||
|
||||
func (s *SSOProxy) GetSession(r *http.Request) (*session.Session, error) {
|
||||
return s.SessionReader.Get(r)
|
||||
}
|
||||
|
||||
func (s *SSOProxy) GetSSOServerURL() *urllib.URL {
|
||||
u := *s.SSOServerURL
|
||||
return &u
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/nais/wonderwall/pkg/handler/acr"
|
||||
"github.com/nais/wonderwall/pkg/handler/autologin"
|
||||
mw "github.com/nais/wonderwall/pkg/middleware"
|
||||
"github.com/nais/wonderwall/pkg/session"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
)
|
||||
|
||||
type ReverseProxySource interface {
|
||||
GetAcrHandler() *acr.Handler
|
||||
GetAutoLogin() *autologin.AutoLogin
|
||||
GetPath(r *http.Request) string
|
||||
GetSession(r *http.Request) (*session.Session, error)
|
||||
@@ -65,7 +67,7 @@ func (rp *ReverseProxy) Handler(src ReverseProxySource, w http.ResponseWriter, r
|
||||
logger := mw.LogEntryFrom(r)
|
||||
isAuthenticated := false
|
||||
|
||||
_, accessToken, err := getSessionWithValidToken(src, r)
|
||||
sess, accessToken, err := getSessionWithValidToken(src, r)
|
||||
switch {
|
||||
case err == nil:
|
||||
// add authentication if session checks out
|
||||
@@ -82,18 +84,14 @@ func (rp *ReverseProxy) Handler(src ReverseProxySource, w http.ResponseWriter, r
|
||||
logger.Errorf("default: unauthenticated: unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
err = src.GetAcrHandler().Validate(sess)
|
||||
if err != nil {
|
||||
loginRedirect(src, w, r, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if src.GetAutoLogin().NeedsLogin(r, isAuthenticated) {
|
||||
redirectTarget := r.URL.String()
|
||||
path := src.GetPath(r)
|
||||
|
||||
loginUrl := url.LoginRelative(path, redirectTarget)
|
||||
fields := logrus.Fields{
|
||||
"redirect_after_login": redirectTarget,
|
||||
"redirect_to": loginUrl,
|
||||
}
|
||||
|
||||
logger.WithFields(fields).Info("default: unauthenticated: request matches auto-login; redirecting to login...")
|
||||
http.Redirect(w, r, loginUrl, http.StatusFound)
|
||||
loginRedirect(src, w, r, "request matches autologin")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -120,6 +118,20 @@ func getSessionWithValidToken(src ReverseProxySource, r *http.Request) (*session
|
||||
return sess, accessToken, nil
|
||||
}
|
||||
|
||||
func loginRedirect(src ReverseProxySource, w http.ResponseWriter, r *http.Request, message string) {
|
||||
redirectTarget := r.URL.String()
|
||||
path := src.GetPath(r)
|
||||
|
||||
loginUrl := url.LoginRelative(path, redirectTarget)
|
||||
fields := logrus.Fields{
|
||||
"redirect_after_login": redirectTarget,
|
||||
"redirect_to": loginUrl,
|
||||
}
|
||||
|
||||
mw.LogEntryFrom(r).WithFields(fields).Infof("default: unauthenticated: %s; redirecting to login...", message)
|
||||
http.Redirect(w, r, loginUrl, http.StatusFound)
|
||||
}
|
||||
|
||||
type logrusErrorWriter struct{}
|
||||
|
||||
func (w logrusErrorWriter) Write(p []byte) (n int, err error) {
|
||||
|
||||
@@ -60,6 +60,10 @@ func (in *Session) AccessToken() (string, error) {
|
||||
return "", fmt.Errorf("%w: access token is expired", ErrInvalid)
|
||||
}
|
||||
|
||||
func (in *Session) Acr() string {
|
||||
return in.data.Acr
|
||||
}
|
||||
|
||||
func (in *Session) ExternalSessionID() string {
|
||||
return in.data.ExternalSessionID
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user