* outposts: initial cookie domain implementation Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: add cookie domain setting Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: replace forward_auth_mode with general mode Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: rebuild proxy provider form Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: re-add forward_auth_mode for backwards compat Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: fix data.mode not being set Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * root: always set log level to debug when testing Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: use new mode attribute Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: only ingress /akprox on forward_domain Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: fix lint error Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: fix error on ProxyProviderForm when not using proxy mode Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: fix default for outpost form's type missing Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: add additional desc for proxy modes Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outposts: fix service account permissions not always being updated Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outpost/proxy: fix redirecting to incorrect host for domain mode Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: improve error handling for network errors Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outpost: fix image naming not matching main imaeg Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outposts/proxy: fix redirects for domain mode and traefik Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: fix colour for paragraphs Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/flows: fix consent stage not showing permissions correctly Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * website/docs: add domain-level docs Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * website/docs: fix broken links Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outposts/proxy: remove dead code Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/flows: fix missing id for #header-text Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
		
			
				
	
	
		
			496 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			496 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package proxy
 | 
						|
 | 
						|
import (
 | 
						|
	b64 "encoding/base64"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"html/template"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/coreos/go-oidc"
 | 
						|
	"github.com/justinas/alice"
 | 
						|
	ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
 | 
						|
	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
 | 
						|
	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
 | 
						|
	"github.com/oauth2-proxy/oauth2-proxy/pkg/middleware"
 | 
						|
	"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
 | 
						|
	"github.com/oauth2-proxy/oauth2-proxy/pkg/upstream"
 | 
						|
	"github.com/oauth2-proxy/oauth2-proxy/providers"
 | 
						|
	"goauthentik.io/outpost/api"
 | 
						|
 | 
						|
	log "github.com/sirupsen/logrus"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	httpScheme  = "http"
 | 
						|
	httpsScheme = "https"
 | 
						|
 | 
						|
	applicationJSON = "application/json"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// ErrNeedsLogin means the user should be redirected to the login page
 | 
						|
	ErrNeedsLogin = errors.New("redirect to login page")
 | 
						|
 | 
						|
	// Used to check final redirects are not susceptible to open redirects.
 | 
						|
	// Matches //, /\ and both of these with whitespace in between (eg / / or / \).
 | 
						|
	invalidRedirectRegex = regexp.MustCompile(`[/\\](?:[\s\v]*|\.{1,2})[/\\]`)
 | 
						|
)
 | 
						|
 | 
						|
// OAuthProxy is the main authentication proxy
 | 
						|
type OAuthProxy struct {
 | 
						|
	client *http.Client
 | 
						|
 | 
						|
	CookieSeed     string
 | 
						|
	CookieName     string
 | 
						|
	CSRFCookieName string
 | 
						|
	CookieDomains  []string
 | 
						|
	CookiePath     string
 | 
						|
	CookieSecure   bool
 | 
						|
	CookieHTTPOnly bool
 | 
						|
	CookieExpire   time.Duration
 | 
						|
	CookieRefresh  time.Duration
 | 
						|
	CookieSameSite string
 | 
						|
 | 
						|
	RobotsPath        string
 | 
						|
	SignInPath        string
 | 
						|
	SignOutPath       string
 | 
						|
	OAuthStartPath    string
 | 
						|
	OAuthCallbackPath string
 | 
						|
	AuthOnlyPath      string
 | 
						|
	UserInfoPath      string
 | 
						|
 | 
						|
	mode                       api.ProxyMode
 | 
						|
	redirectURL                *url.URL // the url to receive requests at
 | 
						|
	whitelistDomains           []string
 | 
						|
	provider                   providers.Provider
 | 
						|
	sessionStore               sessionsapi.SessionStore
 | 
						|
	ProxyPrefix                string
 | 
						|
	serveMux                   http.Handler
 | 
						|
	SetXAuthRequest            bool
 | 
						|
	SetBasicAuth               bool
 | 
						|
	PassUserHeaders            bool
 | 
						|
	BasicAuthUserAttribute     string
 | 
						|
	BasicAuthPasswordAttribute string
 | 
						|
	ExternalHost               string
 | 
						|
	PassAccessToken            bool
 | 
						|
	SetAuthorization           bool
 | 
						|
	PassAuthorization          bool
 | 
						|
	PreferEmailToUser          bool
 | 
						|
	skipAuthRegex              []string
 | 
						|
	skipAuthPreflight          bool
 | 
						|
	skipAuthStripHeaders       bool
 | 
						|
	mainJwtBearerVerifier      *oidc.IDTokenVerifier
 | 
						|
	extraJwtBearerVerifiers    []*oidc.IDTokenVerifier
 | 
						|
	compiledRegex              []*regexp.Regexp
 | 
						|
	templates                  *template.Template
 | 
						|
	realClientIPParser         ipapi.RealClientIPParser
 | 
						|
 | 
						|
	sessionChain alice.Chain
 | 
						|
 | 
						|
	logger *log.Entry
 | 
						|
}
 | 
						|
 | 
						|
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
 | 
						|
func NewOAuthProxy(opts *options.Options, provider api.ProxyOutpostConfig, c *http.Client) (*OAuthProxy, error) {
 | 
						|
	logger := log.WithField("logger", "authentik.outpost.proxy").WithField("provider", provider.Name)
 | 
						|
	sessionStore, err := sessions.NewSessionStore(&opts.Session, &opts.Cookie)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("error initialising session store: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	templates := getTemplates()
 | 
						|
	proxyErrorHandler := upstream.NewProxyErrorHandler(templates.Lookup("error.html"), opts.ProxyPrefix)
 | 
						|
	upstreamProxy, err := upstream.NewProxy(opts.UpstreamServers, opts.GetSignatureData(), proxyErrorHandler)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("error initialising upstream proxy: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	for _, u := range opts.GetCompiledRegex() {
 | 
						|
		logger.Printf("compiled skip-auth-regex => %q", u)
 | 
						|
	}
 | 
						|
 | 
						|
	redirectURL := opts.GetRedirectURL()
 | 
						|
	if redirectURL.Path == "" {
 | 
						|
		redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
 | 
						|
	}
 | 
						|
 | 
						|
	logger.Printf("proxy instance configured for Client ID: %s", opts.ClientID)
 | 
						|
 | 
						|
	sessionChain := buildSessionChain(opts, sessionStore)
 | 
						|
 | 
						|
	return &OAuthProxy{
 | 
						|
		client:         c,
 | 
						|
		CookieName:     opts.Cookie.Name,
 | 
						|
		CSRFCookieName: fmt.Sprintf("%v_%v", opts.Cookie.Name, "csrf"),
 | 
						|
		CookieSeed:     opts.Cookie.Secret,
 | 
						|
		CookieDomains:  opts.Cookie.Domains,
 | 
						|
		CookiePath:     opts.Cookie.Path,
 | 
						|
		CookieSecure:   opts.Cookie.Secure,
 | 
						|
		CookieHTTPOnly: opts.Cookie.HTTPOnly,
 | 
						|
		CookieExpire:   opts.Cookie.Expire,
 | 
						|
		CookieRefresh:  opts.Cookie.Refresh,
 | 
						|
		CookieSameSite: opts.Cookie.SameSite,
 | 
						|
 | 
						|
		mode:              *provider.Mode,
 | 
						|
		RobotsPath:        "/robots.txt",
 | 
						|
		SignInPath:        fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
 | 
						|
		SignOutPath:       fmt.Sprintf("%s/sign_out", opts.ProxyPrefix),
 | 
						|
		OAuthStartPath:    fmt.Sprintf("%s/start", opts.ProxyPrefix),
 | 
						|
		OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix),
 | 
						|
		AuthOnlyPath:      fmt.Sprintf("%s/auth", opts.ProxyPrefix),
 | 
						|
		UserInfoPath:      fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
 | 
						|
 | 
						|
		ProxyPrefix:             opts.ProxyPrefix,
 | 
						|
		provider:                opts.GetProvider(),
 | 
						|
		sessionStore:            sessionStore,
 | 
						|
		serveMux:                upstreamProxy,
 | 
						|
		redirectURL:             redirectURL,
 | 
						|
		whitelistDomains:        opts.WhitelistDomains,
 | 
						|
		skipAuthRegex:           opts.SkipAuthRegex,
 | 
						|
		skipAuthPreflight:       opts.SkipAuthPreflight,
 | 
						|
		skipAuthStripHeaders:    opts.SkipAuthStripHeaders,
 | 
						|
		mainJwtBearerVerifier:   opts.GetOIDCVerifier(),
 | 
						|
		extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
 | 
						|
		compiledRegex:           opts.GetCompiledRegex(),
 | 
						|
		realClientIPParser:      opts.GetRealClientIPParser(),
 | 
						|
		SetXAuthRequest:         opts.SetXAuthRequest,
 | 
						|
		SetBasicAuth:            opts.SetBasicAuth,
 | 
						|
		PassUserHeaders:         opts.PassUserHeaders,
 | 
						|
		PassAccessToken:         opts.PassAccessToken,
 | 
						|
		SetAuthorization:        opts.SetAuthorization,
 | 
						|
		PassAuthorization:       opts.PassAuthorization,
 | 
						|
		PreferEmailToUser:       opts.PreferEmailToUser,
 | 
						|
		templates:               templates,
 | 
						|
 | 
						|
		sessionChain: sessionChain,
 | 
						|
 | 
						|
		logger: logger,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore) alice.Chain {
 | 
						|
	chain := alice.New(middleware.NewScope())
 | 
						|
 | 
						|
	chain = chain.Append(middleware.NewStoredSessionLoader(&middleware.StoredSessionLoaderOptions{
 | 
						|
		SessionStore:           sessionStore,
 | 
						|
		RefreshPeriod:          opts.Cookie.Refresh,
 | 
						|
		RefreshSessionIfNeeded: opts.GetProvider().RefreshSessionIfNeeded,
 | 
						|
		ValidateSessionState:   opts.GetProvider().ValidateSessionState,
 | 
						|
	}))
 | 
						|
 | 
						|
	return chain
 | 
						|
}
 | 
						|
 | 
						|
// RobotsTxt disallows scraping pages from the OAuthProxy
 | 
						|
func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
 | 
						|
	_, err := fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
 | 
						|
	if err != nil {
 | 
						|
		p.logger.Printf("Error writing robots.txt: %v", err)
 | 
						|
		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
 | 
						|
		return
 | 
						|
	}
 | 
						|
	rw.WriteHeader(http.StatusOK)
 | 
						|
}
 | 
						|
 | 
						|
// ErrorPage writes an error response
 | 
						|
func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
 | 
						|
	rw.WriteHeader(code)
 | 
						|
	t := struct {
 | 
						|
		Title       string
 | 
						|
		Message     string
 | 
						|
		ProxyPrefix string
 | 
						|
	}{
 | 
						|
		Title:       fmt.Sprintf("%d %s", code, title),
 | 
						|
		Message:     message,
 | 
						|
		ProxyPrefix: p.ProxyPrefix,
 | 
						|
	}
 | 
						|
	err := p.templates.ExecuteTemplate(rw, "error.html", t)
 | 
						|
	if err != nil {
 | 
						|
		p.logger.Printf("Error rendering error.html template: %v", err)
 | 
						|
		http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en
 | 
						|
var noCacheHeaders = map[string]string{
 | 
						|
	"Expires":         time.Unix(0, 0).Format(time.RFC1123),
 | 
						|
	"Cache-Control":   "no-cache, no-store, must-revalidate, max-age=0",
 | 
						|
	"X-Accel-Expires": "0", // https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/
 | 
						|
}
 | 
						|
 | 
						|
// prepareNoCache prepares headers for preventing browser caching.
 | 
						|
func prepareNoCache(w http.ResponseWriter) {
 | 
						|
	// Set NoCache headers
 | 
						|
	for k, v := range noCacheHeaders {
 | 
						|
		w.Header().Set(k, v)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 | 
						|
	if req.URL.Path != p.AuthOnlyPath && strings.HasPrefix(req.URL.Path, p.ProxyPrefix) {
 | 
						|
		prepareNoCache(rw)
 | 
						|
	}
 | 
						|
 | 
						|
	switch path := req.URL.Path; {
 | 
						|
	case path == p.RobotsPath:
 | 
						|
		p.RobotsTxt(rw)
 | 
						|
	case p.IsWhitelistedRequest(req):
 | 
						|
		p.SkipAuthProxy(rw, req)
 | 
						|
	case path == p.SignInPath:
 | 
						|
		p.OAuthStart(rw, req)
 | 
						|
	case path == p.SignOutPath:
 | 
						|
		p.SignOut(rw, req)
 | 
						|
	case path == p.OAuthStartPath:
 | 
						|
		p.OAuthStart(rw, req)
 | 
						|
	case path == p.OAuthCallbackPath:
 | 
						|
		p.OAuthCallback(rw, req)
 | 
						|
	case path == p.AuthOnlyPath:
 | 
						|
		p.AuthenticateOnly(rw, req)
 | 
						|
	case path == p.UserInfoPath:
 | 
						|
		p.UserInfo(rw, req)
 | 
						|
	default:
 | 
						|
		p.Proxy(rw, req)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
//UserInfo endpoint outputs session email and preferred username in JSON format
 | 
						|
func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
 | 
						|
 | 
						|
	session, err := p.getAuthenticatedSession(rw, req)
 | 
						|
	if err != nil {
 | 
						|
		http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	userInfo := struct {
 | 
						|
		Email             string `json:"email"`
 | 
						|
		PreferredUsername string `json:"preferredUsername,omitempty"`
 | 
						|
	}{
 | 
						|
		Email:             session.Email,
 | 
						|
		PreferredUsername: session.PreferredUsername,
 | 
						|
	}
 | 
						|
	rw.Header().Set("Content-Type", "application/json")
 | 
						|
	rw.WriteHeader(http.StatusOK)
 | 
						|
	err = json.NewEncoder(rw).Encode(userInfo)
 | 
						|
	if err != nil {
 | 
						|
		p.logger.Printf("Error encoding user info: %v", err)
 | 
						|
		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SignOut sends a response to clear the authentication cookie
 | 
						|
func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
 | 
						|
	redirect, err := p.GetRedirect(req)
 | 
						|
	if err != nil {
 | 
						|
		p.logger.Errorf("Error obtaining redirect: %v", err)
 | 
						|
		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
 | 
						|
		return
 | 
						|
	}
 | 
						|
	err = p.ClearSessionCookie(rw, req)
 | 
						|
	if err != nil {
 | 
						|
		p.logger.Errorf("Error clearing session cookie: %v", err)
 | 
						|
		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
 | 
						|
		return
 | 
						|
	}
 | 
						|
	http.Redirect(rw, req, redirect, http.StatusFound)
 | 
						|
}
 | 
						|
 | 
						|
// AuthenticateOnly checks whether the user is currently logged in
 | 
						|
func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
 | 
						|
	session, err := p.getAuthenticatedSession(rw, req)
 | 
						|
	if err != nil {
 | 
						|
		if p.mode == api.PROXYMODE_FORWARD_SINGLE || p.mode == api.PROXYMODE_FORWARD_DOMAIN {
 | 
						|
			if _, ok := req.URL.Query()["nginx"]; ok {
 | 
						|
				rw.WriteHeader(401)
 | 
						|
				return
 | 
						|
			}
 | 
						|
			if _, ok := req.URL.Query()["traefik"]; ok {
 | 
						|
				host := ""
 | 
						|
				// Optional suffix, which is appended to the URL
 | 
						|
				suffix := ""
 | 
						|
				if p.mode == api.PROXYMODE_FORWARD_SINGLE {
 | 
						|
					host = getHost(req)
 | 
						|
				} else if p.mode == api.PROXYMODE_FORWARD_DOMAIN {
 | 
						|
					host = p.ExternalHost
 | 
						|
					// set the ?rd flag to the current URL we have, since we redirect
 | 
						|
					// to a (possibly) different domain, but we want to be redirected back
 | 
						|
					// to the application
 | 
						|
					v := url.Values{
 | 
						|
						// see https://doc.traefik.io/traefik/middlewares/forwardauth/
 | 
						|
						// X-Forwarded-Uri is only the path, so we need to build the entire URL
 | 
						|
						"rd": []string{fmt.Sprintf(
 | 
						|
							"%s://%s%s",
 | 
						|
							req.Header.Get("X-Forwarded-Proto"),
 | 
						|
							req.Header.Get("X-Forwarded-Host"),
 | 
						|
							req.Header.Get("X-Forwarded-Uri"),
 | 
						|
						)},
 | 
						|
					}
 | 
						|
					suffix = fmt.Sprintf("?%s", v.Encode())
 | 
						|
				}
 | 
						|
				proto := req.Header.Get("X-Forwarded-Proto")
 | 
						|
				if proto != "" {
 | 
						|
					proto = proto + ":"
 | 
						|
				}
 | 
						|
				rdFinal := fmt.Sprintf("%s//%s%s%s", proto, host, p.OAuthStartPath, suffix)
 | 
						|
				p.logger.WithField("url", rdFinal).Debug("Redirecting to login")
 | 
						|
				http.Redirect(rw, req, rdFinal, http.StatusTemporaryRedirect)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
		http.Error(rw, "unauthorized request", http.StatusUnauthorized)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	// we are authenticated
 | 
						|
	p.addHeadersForProxying(rw, req, session)
 | 
						|
	if p.mode == api.PROXYMODE_FORWARD_SINGLE || p.mode == api.PROXYMODE_FORWARD_DOMAIN {
 | 
						|
		for headerKey, headers := range req.Header {
 | 
						|
			for _, value := range headers {
 | 
						|
				rw.Header().Set(headerKey, value)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	rw.WriteHeader(http.StatusAccepted)
 | 
						|
}
 | 
						|
 | 
						|
// SkipAuthProxy proxies whitelisted requests and skips authentication
 | 
						|
func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) {
 | 
						|
	if p.skipAuthStripHeaders {
 | 
						|
		p.stripAuthHeaders(req)
 | 
						|
	}
 | 
						|
	p.serveMux.ServeHTTP(rw, req)
 | 
						|
}
 | 
						|
 | 
						|
// Proxy proxies the user request if the user is authenticated else it prompts
 | 
						|
// them to authenticate
 | 
						|
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
 | 
						|
	session, err := p.getAuthenticatedSession(rw, req)
 | 
						|
	switch err {
 | 
						|
	case nil:
 | 
						|
		// we are authenticated
 | 
						|
		p.addHeadersForProxying(rw, req, session)
 | 
						|
		p.serveMux.ServeHTTP(rw, req)
 | 
						|
 | 
						|
	case ErrNeedsLogin:
 | 
						|
		// we need to send the user to a login screen
 | 
						|
		if isAjax(req) {
 | 
						|
			// no point redirecting an AJAX request
 | 
						|
			p.ErrorJSON(rw, http.StatusUnauthorized)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		p.OAuthStart(rw, req)
 | 
						|
 | 
						|
	default:
 | 
						|
		// unknown error
 | 
						|
		p.logger.Errorf("Unexpected internal error: %v", err)
 | 
						|
		p.ErrorPage(rw, http.StatusInternalServerError,
 | 
						|
			"Internal Error", "Internal Error")
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so
 | 
						|
// Returns nil, ErrNeedsLogin if user needs to login.
 | 
						|
// Set-Cookie headers may be set on the response as a side-effect of calling this method.
 | 
						|
func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) {
 | 
						|
	var session *sessionsapi.SessionState
 | 
						|
 | 
						|
	getSession := p.sessionChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 | 
						|
		session = middleware.GetRequestScope(req).Session
 | 
						|
	}))
 | 
						|
	getSession.ServeHTTP(rw, req)
 | 
						|
 | 
						|
	if session == nil {
 | 
						|
		return nil, ErrNeedsLogin
 | 
						|
	}
 | 
						|
 | 
						|
	return session, nil
 | 
						|
}
 | 
						|
 | 
						|
// addHeadersForProxying adds the appropriate headers the request / response for proxying
 | 
						|
func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) {
 | 
						|
	req.Header["X-Forwarded-User"] = []string{session.User}
 | 
						|
	if session.Email != "" {
 | 
						|
		req.Header["X-Forwarded-Email"] = []string{session.Email}
 | 
						|
	}
 | 
						|
 | 
						|
	if session.PreferredUsername != "" {
 | 
						|
		req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername}
 | 
						|
		req.Header["X-Auth-Username"] = []string{session.PreferredUsername}
 | 
						|
	} else {
 | 
						|
		req.Header.Del("X-Forwarded-Preferred-Username")
 | 
						|
		req.Header.Del("X-Auth-Username")
 | 
						|
	}
 | 
						|
 | 
						|
	claims := Claims{}
 | 
						|
	err := claims.FromIDToken(session.IDToken)
 | 
						|
	if err != nil {
 | 
						|
		log.WithError(err).Warning("Failed to parse IDToken")
 | 
						|
	}
 | 
						|
	userAttributes := claims.Proxy.UserAttributes
 | 
						|
	// Attempt to set basic auth based on user's attributes
 | 
						|
	if p.SetBasicAuth {
 | 
						|
		var ok bool
 | 
						|
		var password string
 | 
						|
		if password, ok = userAttributes[p.BasicAuthPasswordAttribute].(string); !ok {
 | 
						|
			password = ""
 | 
						|
		}
 | 
						|
		// Check if we should use email or a custom attribute as username
 | 
						|
		var username string
 | 
						|
		if username, ok = userAttributes[p.BasicAuthUserAttribute].(string); !ok {
 | 
						|
			username = session.Email
 | 
						|
		}
 | 
						|
		authVal := b64.StdEncoding.EncodeToString([]byte(username + ":" + password))
 | 
						|
		req.Header["Authorization"] = []string{fmt.Sprintf("Basic %s", authVal)}
 | 
						|
	}
 | 
						|
	// Check if user has additional headers set that we should sent
 | 
						|
	if additionalHeaders, ok := userAttributes["additionalHeaders"].(map[string]string); ok {
 | 
						|
		if additionalHeaders == nil {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		for key, value := range additionalHeaders {
 | 
						|
			req.Header.Set(key, value)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// stripAuthHeaders removes Auth headers for whitelisted routes from skipAuthRegex
 | 
						|
func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
 | 
						|
	if p.PassUserHeaders {
 | 
						|
		req.Header.Del("X-Forwarded-User")
 | 
						|
		req.Header.Del("X-Forwarded-Email")
 | 
						|
		req.Header.Del("X-Forwarded-Preferred-Username")
 | 
						|
	}
 | 
						|
 | 
						|
	if p.PassAccessToken {
 | 
						|
		req.Header.Del("X-Forwarded-Access-Token")
 | 
						|
	}
 | 
						|
 | 
						|
	if p.PassAuthorization {
 | 
						|
		req.Header.Del("Authorization")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// isAjax checks if a request is an ajax request
 | 
						|
func isAjax(req *http.Request) bool {
 | 
						|
	acceptValues := req.Header.Values("Accept")
 | 
						|
	const ajaxReq = applicationJSON
 | 
						|
	for _, v := range acceptValues {
 | 
						|
		if v == ajaxReq {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// ErrorJSON returns the error code with an application/json mime type
 | 
						|
func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) {
 | 
						|
	rw.Header().Set("Content-Type", applicationJSON)
 | 
						|
	rw.WriteHeader(code)
 | 
						|
}
 |