mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-01-24 05:26:55 +02:00
Merge pull request #1128 from oauth2-proxy/proxy-router
Use gorilla mux for OAuth Proxy routing
This commit is contained in:
commit
62436dbc02
@ -7,6 +7,8 @@
|
||||
## Breaking Changes
|
||||
|
||||
## Changes since v7.1.3
|
||||
|
||||
- [#1128](https://github.com/oauth2-proxy/oauth2-proxy/pull/1128) Use gorilla mux for OAuth Proxy routing (@JoelSpeed)
|
||||
- [#1238](https://github.com/oauth2-proxy/oauth2-proxy/pull/1238) Added ADFS provider (@samirachoadi)
|
||||
- [#1227](https://github.com/oauth2-proxy/oauth2-proxy/pull/1227) Fix Refresh Session not working for multiple cookies (@rishi1111)
|
||||
- [#1063](https://github.com/oauth2-proxy/oauth2-proxy/pull/1063) Add Redis lock feature to lock persistent sessions (@Bibob7)
|
||||
|
1
go.mod
1
go.mod
@ -16,6 +16,7 @@ require (
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-redis/redis/v8 v8.2.3
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
|
||||
github.com/mitchellh/mapstructure v1.1.2
|
||||
|
2
go.sum
2
go.sum
@ -188,6 +188,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
|
153
oauthproxy.go
153
oauthproxy.go
@ -15,6 +15,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/justinas/alice"
|
||||
ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
@ -38,6 +39,14 @@ const (
|
||||
schemeHTTP = "http"
|
||||
schemeHTTPS = "https"
|
||||
applicationJSON = "application/json"
|
||||
|
||||
robotsPath = "/robots.txt"
|
||||
signInPath = "/sign_in"
|
||||
signOutPath = "/sign_out"
|
||||
oauthStartPath = "/start"
|
||||
oauthCallbackPath = "/callback"
|
||||
authOnlyPath = "/auth"
|
||||
userInfoPath = "/userinfo"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -63,13 +72,7 @@ type OAuthProxy struct {
|
||||
CookieOptions *options.Cookie
|
||||
Validator func(string) bool
|
||||
|
||||
RobotsPath string
|
||||
SignInPath string
|
||||
SignOutPath string
|
||||
OAuthStartPath string
|
||||
OAuthCallbackPath string
|
||||
AuthOnlyPath string
|
||||
UserInfoPath string
|
||||
SignInPath string
|
||||
|
||||
allowedRoutes []allowedRoute
|
||||
redirectURL *url.URL // the url to receive requests at
|
||||
@ -78,18 +81,19 @@ type OAuthProxy struct {
|
||||
sessionStore sessionsapi.SessionStore
|
||||
ProxyPrefix string
|
||||
basicAuthValidator basic.Validator
|
||||
serveMux http.Handler
|
||||
SkipProviderButton bool
|
||||
skipAuthPreflight bool
|
||||
skipJwtBearerTokens bool
|
||||
realClientIPParser ipapi.RealClientIPParser
|
||||
trustedIPs *ip.NetSet
|
||||
|
||||
sessionChain alice.Chain
|
||||
headersChain alice.Chain
|
||||
preAuthChain alice.Chain
|
||||
pageWriter pagewriter.Writer
|
||||
server proxyhttp.Server
|
||||
sessionChain alice.Chain
|
||||
headersChain alice.Chain
|
||||
preAuthChain alice.Chain
|
||||
pageWriter pagewriter.Writer
|
||||
server proxyhttp.Server
|
||||
upstreamProxy http.Handler
|
||||
serveMux *mux.Router
|
||||
}
|
||||
|
||||
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
|
||||
@ -176,18 +180,11 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
||||
CookieOptions: &opts.Cookie,
|
||||
Validator: validator,
|
||||
|
||||
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),
|
||||
SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
|
||||
|
||||
ProxyPrefix: opts.ProxyPrefix,
|
||||
provider: opts.GetProvider(),
|
||||
sessionStore: sessionStore,
|
||||
serveMux: upstreamProxy,
|
||||
redirectURL: redirectURL,
|
||||
allowedRoutes: allowedRoutes,
|
||||
whitelistDomains: opts.WhitelistDomains,
|
||||
@ -202,7 +199,9 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
||||
headersChain: headersChain,
|
||||
preAuthChain: preAuthChain,
|
||||
pageWriter: pageWriter,
|
||||
upstreamProxy: upstreamProxy,
|
||||
}
|
||||
p.buildServeMux(opts.ProxyPrefix)
|
||||
|
||||
if err := p.setupServer(opts); err != nil {
|
||||
return nil, fmt.Errorf("error setting up server: %v", err)
|
||||
@ -258,6 +257,41 @@ func (p *OAuthProxy) setupServer(opts *options.Options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *OAuthProxy) buildServeMux(proxyPrefix string) {
|
||||
r := mux.NewRouter()
|
||||
// Everything served by the router must go through the preAuthChain first.
|
||||
r.Use(p.preAuthChain.Then)
|
||||
|
||||
// Register the robots path writer
|
||||
r.Path(robotsPath).HandlerFunc(p.pageWriter.WriteRobotsTxt)
|
||||
|
||||
// The authonly path should be registered separately to prevent it from getting no-cache headers.
|
||||
// We do this to allow users to have a short cache (via nginx) of the response to reduce the
|
||||
// likelihood of multiple reuests trying to referesh sessions simultaneously.
|
||||
r.Path(proxyPrefix + authOnlyPath).Handler(p.sessionChain.ThenFunc(p.AuthOnly))
|
||||
|
||||
// This will register all of the paths under the proxy prefix, except the auth only path so that no cache headers
|
||||
// are not applied.
|
||||
p.buildProxySubrouter(r.PathPrefix(proxyPrefix).Subrouter())
|
||||
|
||||
// Register serveHTTP last so it catches anything that isn't already caught earlier.
|
||||
// Anything that got to this point needs to have a session loaded.
|
||||
r.PathPrefix("/").Handler(p.sessionChain.ThenFunc(p.Proxy))
|
||||
p.serveMux = r
|
||||
}
|
||||
|
||||
func (p *OAuthProxy) buildProxySubrouter(s *mux.Router) {
|
||||
s.Use(prepareNoCacheMiddleware)
|
||||
|
||||
s.Path(signInPath).HandlerFunc(p.SignIn)
|
||||
s.Path(signOutPath).HandlerFunc(p.SignOut)
|
||||
s.Path(oauthStartPath).HandlerFunc(p.OAuthStart)
|
||||
s.Path(oauthCallbackPath).HandlerFunc(p.OAuthCallback)
|
||||
|
||||
// The userinfo endpoint needs to load sessions before handling the request
|
||||
s.Path(userInfoPath).Handler(p.sessionChain.ThenFunc(p.UserInfo))
|
||||
}
|
||||
|
||||
// buildPreAuthChain constructs a chain that should process every request before
|
||||
// the OAuth2 Proxy authentication logic kicks in.
|
||||
// For example forcing HTTPS or health checks.
|
||||
@ -478,39 +512,7 @@ func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
|
||||
}
|
||||
|
||||
func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
p.preAuthChain.Then(http.HandlerFunc(p.serveHTTP)).ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
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, req)
|
||||
case p.IsAllowedRequest(req):
|
||||
p.SkipAuthProxy(rw, req)
|
||||
case path == p.SignInPath:
|
||||
p.SignIn(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.AuthOnly(rw, req)
|
||||
case path == p.UserInfoPath:
|
||||
p.UserInfo(rw, req)
|
||||
default:
|
||||
p.Proxy(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
// RobotsTxt disallows scraping pages from the OAuthProxy
|
||||
func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter, req *http.Request) {
|
||||
p.pageWriter.WriteRobotsTxt(rw, req)
|
||||
p.serveMux.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// ErrorPage writes an error response
|
||||
@ -643,13 +645,22 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
if session == nil {
|
||||
if _, err := rw.Write([]byte("{}")); err != nil {
|
||||
logger.Printf("Error encoding empty user info: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
userInfo := struct {
|
||||
User string `json:"user"`
|
||||
Email string `json:"email"`
|
||||
@ -662,10 +673,7 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
|
||||
PreferredUsername: session.PreferredUsername,
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(rw).Encode(userInfo)
|
||||
if err != nil {
|
||||
if err := json.NewEncoder(rw).Encode(userInfo); err != nil {
|
||||
logger.Printf("Error encoding user info: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
@ -857,11 +865,6 @@ func (p *OAuthProxy) AuthOnly(rw http.ResponseWriter, req *http.Request) {
|
||||
})).ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// SkipAuthProxy proxies allowlisted requests and skips authentication
|
||||
func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) {
|
||||
p.headersChain.Then(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) {
|
||||
@ -870,7 +873,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
||||
case nil:
|
||||
// we are authenticated
|
||||
p.addHeadersForProxying(rw, session)
|
||||
p.headersChain.Then(p.serveMux).ServeHTTP(rw, req)
|
||||
p.headersChain.Then(p.upstreamProxy).ServeHTTP(rw, req)
|
||||
case ErrNeedsLogin:
|
||||
// we need to send the user to a login screen
|
||||
if isAjax(req) {
|
||||
@ -910,6 +913,13 @@ func prepareNoCache(w http.ResponseWriter) {
|
||||
}
|
||||
}
|
||||
|
||||
func prepareNoCacheMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
prepareNoCache(rw)
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// getOAuthRedirectURI returns the redirectURL that the upstream OAuth Provider will
|
||||
// redirect clients to once authenticated.
|
||||
// This is usually the OAuthProxy callback URL.
|
||||
@ -1095,12 +1105,12 @@ func validOptionalPort(port string) bool {
|
||||
// - `nil, ErrAccessDenied` if the authenticated user is not authorized
|
||||
// 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
|
||||
session := middlewareapi.GetRequestScope(req).Session
|
||||
|
||||
getSession := p.sessionChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
session = middlewareapi.GetRequestScope(req).Session
|
||||
}))
|
||||
getSession.ServeHTTP(rw, req)
|
||||
// Check this after loading the session so that if a valid session exists, we can add headers from it
|
||||
if p.IsAllowedRequest(req) {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
if session == nil {
|
||||
return nil, ErrNeedsLogin
|
||||
@ -1190,6 +1200,9 @@ func decodeState(req *http.Request) (string, string, error) {
|
||||
|
||||
// addHeadersForProxying adds the appropriate headers the request / response for proxying
|
||||
func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, session *sessionsapi.SessionState) {
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
if session.Email == "" {
|
||||
rw.Header().Set("GAP-Auth", session.User)
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user