feat(sso/proxy): implement login handler

This commit is contained in:
Trong Huu Nguyen
2023-02-03 14:01:38 +01:00
parent c3c0c01926
commit 1fdbe75c9e
6 changed files with 145 additions and 24 deletions

View File

@@ -75,7 +75,7 @@ func (h Handler) Retry(r *http.Request, loginCookie *openid.LoginCookie) string
redirect = loginCookie.Referer
}
return urlpkg.LoginURL(ingressPath, redirect)
return urlpkg.LoginRelative(ingressPath, redirect)
}
func (h Handler) respondError(w http.ResponseWriter, r *http.Request, statusCode int, cause error, level log.Level) {

View File

@@ -5,8 +5,13 @@ import (
"net/http"
urllib "net/url"
log "github.com/sirupsen/logrus"
"github.com/nais/wonderwall/pkg/config"
"github.com/nais/wonderwall/pkg/handler/url"
"github.com/nais/wonderwall/pkg/ingress"
mw "github.com/nais/wonderwall/pkg/middleware"
openidclient "github.com/nais/wonderwall/pkg/openid/client"
"github.com/nais/wonderwall/pkg/router"
"github.com/nais/wonderwall/pkg/router/paths"
)
@@ -30,6 +35,18 @@ func NewSSOProxyHandler(cfg *config.Config) (*SSOProxyHandler, error) {
return nil, fmt.Errorf("parsing sso server url: %w", err)
}
query := u.Query()
if len(cfg.OpenID.ACRValues) > 0 {
query.Set(openidclient.SecurityLevelURLParameter, cfg.OpenID.ACRValues)
}
if len(cfg.OpenID.UILocales) > 0 {
query.Set(openidclient.LocaleURLParameter, cfg.OpenID.UILocales)
}
u.RawQuery = query.Encode()
return &SSOProxyHandler{
Config: cfg,
Ingresses: ingresses,
@@ -38,8 +55,40 @@ func NewSSOProxyHandler(cfg *config.Config) (*SSOProxyHandler, error) {
}
func (s *SSOProxyHandler) Login(w http.ResponseWriter, r *http.Request) {
// TODO redirect to sso-server
panic("implement me")
target := *s.SSOServerURL
targetQuery := target.Query()
reqQuery := r.URL.Query()
if reqQuery.Has(openidclient.SecurityLevelURLParameter) {
targetQuery.Set(openidclient.SecurityLevelURLParameter, reqQuery.Get(openidclient.SecurityLevelURLParameter))
}
if reqQuery.Has(openidclient.LocaleURLParameter) {
targetQuery.Set(openidclient.LocaleURLParameter, reqQuery.Get(openidclient.LocaleURLParameter))
}
target.RawQuery = reqQuery.Encode()
redirect, err := url.Ingress(r)
if err != nil {
redirect = s.Ingresses.Single().NewURL()
}
parsedRedirect, err := urllib.ParseRequestURI(reqQuery.Get(url.RedirectURLParameter))
if err == nil {
redirect = redirect.JoinPath(parsedRedirect.Path)
}
ssoServerLoginURL := url.Login(&target, redirect.String())
mw.LogEntryFrom(r).
WithFields(log.Fields{
"redirect_to": ssoServerLoginURL,
"redirect_after_login": redirect.String(),
}).
Info("login: redirecting to sso server")
http.Redirect(w, r, ssoServerLoginURL, http.StatusTemporaryRedirect)
}
func (s *SSOProxyHandler) LoginCallback(w http.ResponseWriter, r *http.Request) {

View File

@@ -92,7 +92,7 @@ func (rp *ReverseProxy) Handler(src ReverseProxySource, w http.ResponseWriter, r
redirectTarget := r.URL.String()
path := src.GetPath(r)
loginUrl := url.LoginURL(path, redirectTarget)
loginUrl := url.LoginRelative(path, redirectTarget)
fields := logrus.Fields{
"redirect_after_login": redirectTarget,
"redirect_to": loginUrl,

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"net/http"
"net/url"
"path"
mw "github.com/nais/wonderwall/pkg/middleware"
"github.com/nais/wonderwall/pkg/router/paths"
@@ -61,17 +60,31 @@ func CanonicalRedirect(r *http.Request) string {
return redirect
}
func LoginURL(prefix, redirectTarget string) string {
u := new(url.URL)
u.Path = path.Join(prefix, paths.OAuth2, paths.Login)
// Login constructs a URL string that points to the login path for the given target URL.
// The given redirect string should point to the location to be redirected to after login.
func Login(target *url.URL, redirect string) string {
u := target.JoinPath(paths.OAuth2, paths.Login)
v := url.Values{}
v.Set(RedirectURLParameter, redirectTarget)
v := u.Query()
v.Set(RedirectURLParameter, redirect)
u.RawQuery = v.Encode()
return u.String()
}
// LoginRelative constructs the relative URL with an absolute path that points to the application's login path, given an optional path prefix.
// The given redirect string should point to the location to be redirected to after login.
func LoginRelative(prefix, redirect string) string {
u := new(url.URL)
u.Path = prefix
if prefix == "" {
u.Path = "/"
}
return Login(u, redirect)
}
func LoginCallbackURL(r *http.Request) (string, error) {
return makeCallbackURL(r, paths.LoginCallback)
}
@@ -81,18 +94,19 @@ func LogoutCallbackURL(r *http.Request) (string, error) {
}
func makeCallbackURL(r *http.Request, callbackPath string) (string, error) {
match, found := mw.IngressFrom(r.Context())
if !found {
return "", fmt.Errorf("request host does not match any configured ingresses")
u, err := Ingress(r)
if err != nil {
return "", err
}
targetPath := path.Join(match.Path(), paths.OAuth2, callbackPath)
targetUrl := url.URL{
Host: match.Host(),
Path: targetPath,
Scheme: match.Scheme,
}
return targetUrl.String(), nil
return u.JoinPath(paths.OAuth2, callbackPath).String(), nil
}
func Ingress(r *http.Request) (*url.URL, error) {
ing, found := mw.IngressFrom(r.Context())
if !found {
return nil, fmt.Errorf("request host does not match any configured ingresses")
}
return ing.NewURL(), nil
}

View File

@@ -143,7 +143,49 @@ func TestCanonicalRedirect(t *testing.T) {
})
}
func TestLoginURL(t *testing.T) {
func TestLogin(t *testing.T) {
for _, test := range []struct {
name string
targetURL string
redirectTarget string
want string
}{
{
name: "root path",
targetURL: "https://sso.wonderwall",
redirectTarget: "https://test.example.com?some=param&other=param2",
want: "https://sso.wonderwall/oauth2/login?redirect=https%3A%2F%2Ftest.example.com%3Fsome%3Dparam%26other%3Dparam2",
},
{
name: "with prefix",
targetURL: "https://sso.wonderwall/path",
redirectTarget: "https://test.example.com?some=param&other=param2",
want: "https://sso.wonderwall/path/oauth2/login?redirect=https%3A%2F%2Ftest.example.com%3Fsome%3Dparam%26other%3Dparam2",
},
{
name: "we need to go deeper",
targetURL: "https://sso.wonderwall/deeper/path",
redirectTarget: "https://test.example.com?some=param&other=param2",
want: "https://sso.wonderwall/deeper/path/oauth2/login?redirect=https%3A%2F%2Ftest.example.com%3Fsome%3Dparam%26other%3Dparam2",
},
{
name: "relative redirect target",
targetURL: "https://sso.wonderwall",
redirectTarget: "/path?some=param&other=param2",
want: "https://sso.wonderwall/oauth2/login?redirect=%2Fpath%3Fsome%3Dparam%26other%3Dparam2",
},
} {
t.Run(test.name, func(t *testing.T) {
targetURL, err := url.Parse(test.targetURL)
assert.NoError(t, err)
loginUrl := urlpkg.Login(targetURL, test.redirectTarget)
assert.Equal(t, test.want, loginUrl)
})
}
}
func TestLoginRelative(t *testing.T) {
for _, test := range []struct {
name string
prefix string
@@ -176,7 +218,7 @@ func TestLoginURL(t *testing.T) {
},
} {
t.Run(test.name, func(t *testing.T) {
loginUrl := urlpkg.LoginURL(test.prefix, test.redirectTarget)
loginUrl := urlpkg.LoginRelative(test.prefix, test.redirectTarget)
assert.Equal(t, test.want, loginUrl)
})
}