feat(authn-oidc): use full urls for login callback/logout options
This commit is contained in:
@ -1,11 +1,16 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
@ -17,7 +22,9 @@ import (
|
||||
)
|
||||
|
||||
type Authenticator struct {
|
||||
store sessions.Store
|
||||
store sessions.Store
|
||||
httpTransport *http.Transport
|
||||
httpClientTimeout time.Duration
|
||||
}
|
||||
|
||||
func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request, layer *store.Layer) error {
|
||||
@ -28,7 +35,9 @@ func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
options, err := fromStoreOptions(layer.Options)
|
||||
baseURL := originalURL.Scheme + "://" + originalURL.Host
|
||||
|
||||
options, err := fromStoreOptions(layer.Options, baseURL)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@ -38,16 +47,30 @@ func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request
|
||||
logger.Error(ctx, "could not retrieve session", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
redirectURL := a.getRedirectURL(layer.Proxy, layer.Name, originalURL, options)
|
||||
logoutURL := a.getLogoutURL(layer.Proxy, layer.Name, originalURL, options)
|
||||
|
||||
client, err := a.getClient(options, redirectURL.String())
|
||||
loginCallbackURL, err := a.getLoginCallbackURL(layer.Proxy, layer.Name, options)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
case redirectURL.Path:
|
||||
client, err := a.getClient(options, loginCallbackURL.String())
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
loginCallbackURLPattern, err := a.templatize(options.OIDC.MatchLoginCallbackURL, layer.Proxy, layer.Name)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
logoutURLPattern, err := a.templatize(options.OIDC.MatchLogoutURL, layer.Proxy, layer.Name)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "checking url", logger.F("loginCallbackURLPattern", loginCallbackURLPattern), logger.F("logoutURLPattern", logoutURLPattern))
|
||||
|
||||
switch {
|
||||
case wildcard.Match(originalURL.String(), loginCallbackURLPattern):
|
||||
if err := client.HandleCallback(w, r, sess); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@ -57,7 +80,7 @@ func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request
|
||||
metricLabelProxy: string(layer.Proxy),
|
||||
}).Add(1)
|
||||
|
||||
case logoutURL.Path:
|
||||
case wildcard.Match(originalURL.String(), logoutURLPattern):
|
||||
postLogoutRedirectURL := options.OIDC.PostLogoutRedirectURL
|
||||
if options.OIDC.PostLogoutRedirectURL == "" {
|
||||
postLogoutRedirectURL = originalURL.Scheme + "://" + originalURL.Host
|
||||
@ -85,7 +108,9 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
options, err := fromStoreOptions(layer.Options)
|
||||
baseURL := originalURL.Scheme + "://" + originalURL.Host
|
||||
|
||||
options, err := fromStoreOptions(layer.Options, baseURL)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
@ -117,9 +142,12 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay
|
||||
sess.Options.SameSite = http.SameSiteDefaultMode
|
||||
}
|
||||
|
||||
redirectURL := a.getRedirectURL(layer.Proxy, layer.Name, originalURL, options)
|
||||
loginCallbackURL, err := a.getLoginCallbackURL(layer.Proxy, layer.Name, options)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
client, err := a.getClient(options, redirectURL.String())
|
||||
client, err := a.getClient(options, loginCallbackURL.String())
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
@ -138,7 +166,7 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
user, err := a.toUser(idToken, layer.Proxy, layer.Name, originalURL, options, sess)
|
||||
user, err := a.toUser(idToken, layer.Proxy, layer.Name, options, sess)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
@ -196,7 +224,7 @@ func (c claims) AsAttrs() map[string]any {
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (a *Authenticator) toUser(idToken *oidc.IDToken, proxyName store.ProxyName, layerName store.LayerName, originalURL *url.URL, options *LayerOptions, sess *sessions.Session) (*authn.User, error) {
|
||||
func (a *Authenticator) toUser(idToken *oidc.IDToken, proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions, sess *sessions.Session) (*authn.User, error) {
|
||||
var claims claims
|
||||
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
@ -209,7 +237,11 @@ func (a *Authenticator) toUser(idToken *oidc.IDToken, proxyName store.ProxyName,
|
||||
|
||||
attrs := claims.AsAttrs()
|
||||
|
||||
logoutURL := a.getLogoutURL(proxyName, layerName, originalURL, options)
|
||||
logoutURL, err := a.getLogoutURL(proxyName, layerName, options)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
attrs["logout_url"] = logoutURL.String()
|
||||
|
||||
if accessToken, exists := sess.Values[sessionKeyAccessToken]; exists && accessToken != nil {
|
||||
@ -229,25 +261,80 @@ func (a *Authenticator) toUser(idToken *oidc.IDToken, proxyName store.ProxyName,
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) getRedirectURL(proxyName store.ProxyName, layerName store.LayerName, u *url.URL, options *LayerOptions) *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: u.Scheme,
|
||||
Host: u.Host,
|
||||
Path: fmt.Sprintf(options.OIDC.LoginCallbackPath, fmt.Sprintf("%s/%s", proxyName, layerName)),
|
||||
func (a *Authenticator) getLoginCallbackURL(proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions) (*url.URL, error) {
|
||||
url, err := a.generateURL(options.OIDC.LoginCallbackURL, proxyName, layerName)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) getLogoutURL(proxyName store.ProxyName, layerName store.LayerName, u *url.URL, options *LayerOptions) *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: u.Scheme,
|
||||
Host: u.Host,
|
||||
Path: fmt.Sprintf(options.OIDC.LogoutPath, fmt.Sprintf("%s/%s", proxyName, layerName)),
|
||||
func (a *Authenticator) getLogoutURL(proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions) (*url.URL, error) {
|
||||
url, err := a.generateURL(options.OIDC.LogoutURL, proxyName, layerName)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) generateURL(rawURLTemplate string, proxyName store.ProxyName, layerName store.LayerName) (*url.URL, error) {
|
||||
rawURL, err := a.templatize(rawURLTemplate, proxyName, layerName)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
url, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) templatize(rawTemplate string, proxyName store.ProxyName, layerName store.LayerName) (string, error) {
|
||||
tmpl, err := template.New("").Parse(rawTemplate)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
var raw bytes.Buffer
|
||||
|
||||
err = tmpl.Execute(&raw, struct {
|
||||
ProxyName store.ProxyName
|
||||
LayerName store.LayerName
|
||||
}{
|
||||
ProxyName: proxyName,
|
||||
LayerName: layerName,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return raw.String(), nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*Client, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
transport := a.httpTransport.Clone()
|
||||
|
||||
if options.OIDC.TLSInsecureSkipVerify {
|
||||
if transport.TLSClientConfig == nil {
|
||||
transport.TLSClientConfig = &tls.Config{}
|
||||
}
|
||||
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Timeout: a.httpClientTimeout,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
ctx = oidc.ClientContext(ctx, httpClient)
|
||||
|
||||
if options.OIDC.SkipIssuerVerification {
|
||||
ctx = oidc.InsecureIssuerURLContext(ctx, options.OIDC.IssuerURL)
|
||||
}
|
||||
@ -263,6 +350,7 @@ func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*C
|
||||
WithRedirectURL(redirectURL),
|
||||
WithScopes(options.OIDC.Scopes...),
|
||||
WithAuthParams(options.OIDC.AuthParams),
|
||||
WithHTTPClient(httpClient),
|
||||
)
|
||||
|
||||
return client, nil
|
||||
|
Reference in New Issue
Block a user