git.iko.soy/typeable/oauth2-proxy
on master help sign in
Integrate redirect package with OAuth2 Proxy
commit273ab1f591733ebc88124604b59b3d03eb79e5c2 authorJoel Speed <joel.speed@hotmail.co.uk> dateSat 5 Jun 2021, 12:34 (5 years ago) parente1764d4
2 files changed+24−678
oauthproxy.go+24−215 oauthproxy_test.go−463
oauthproxy.go+24−215
@@ -22,6 +22,7 @@ import (
2222 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
2323 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
2424 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
25+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/redirect"
2526 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic"
2627 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
2728 proxyhttp "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/http"
@@ -55,10 +56,6 @@ var (
5556
5657 // ErrAccessDenied means the user should receive a 401 Unauthorized response
5758 ErrAccessDenied = errors.New("access denied")
58−
59− // Used to check final redirects are not susceptible to open redirects.
60− // Matches //, /\ and both of these with whitespace in between (eg / / or / \).
61− invalidRedirectRegex = regexp.MustCompile(`[/\\](?:[\s\v]*|\.{1,2})[/\\]`)
6259)
6360
6461// allowedRoute manages method + path based allowlists
@@ -87,13 +84,15 @@ type OAuthProxy struct {
8784 realClientIPParser ipapi.RealClientIPParser
8885 trustedIPs *ip.NetSet
8986
90− sessionChain alice.Chain
91− headersChain alice.Chain
92− preAuthChain alice.Chain
93− pageWriter pagewriter.Writer
94− server proxyhttp.Server
95− upstreamProxy http.Handler
96− serveMux *mux.Router
87+ sessionChain alice.Chain
88+ headersChain alice.Chain
89+ preAuthChain alice.Chain
90+ pageWriter pagewriter.Writer
91+ server proxyhttp.Server
92+ upstreamProxy http.Handler
93+ serveMux *mux.Router
94+ redirectValidator redirect.Validator
95+ appDirector redirect.AppDirector
9796}
9897
9998// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
@@ -176,6 +175,12 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
176175 return nil, fmt.Errorf("could not build headers chain: %v", err)
177176 }
178177
178+ redirectValidator := redirect.NewValidator(opts.WhitelistDomains)
179+ appDirector := redirect.NewAppDirector(redirect.AppDirectorOpts{
180+ ProxyPrefix: opts.ProxyPrefix,
181+ Validator: redirectValidator,
182+ })
183+
179184 p := &OAuthProxy{
180185 CookieOptions: &opts.Cookie,
181186 Validator: validator,
@@ -200,6 +205,8 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
200205 preAuthChain: preAuthChain,
201206 pageWriter: pageWriter,
202207 upstreamProxy: upstreamProxy,
208+ redirectValidator: redirectValidator,
209+ appDirector: appDirector,
203210 }
204211 p.buildServeMux(opts.ProxyPrefix)
205212
@@ -465,59 +472,13 @@ func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *s
465472 return p.sessionStore.Save(rw, req, s)
466473}
467474
468−// IsValidRedirect checks whether the redirect URL is whitelisted
469−func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
470− switch {
471− case redirect == "":
472− // The user didn't specify a redirect, should fallback to `/`
473− return false
474− case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !invalidRedirectRegex.MatchString(redirect):
475− return true
476− case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
477− redirectURL, err := url.Parse(redirect)
478− if err != nil {
479− logger.Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect)
480− return false
481− }
482− redirectHostname := redirectURL.Hostname()
483−
484− for _, allowedDomain := range p.whitelistDomains {
485− allowedHost, allowedPort := splitHostPort(allowedDomain)
486− if allowedHost == "" {
487− continue
488− }
489−
490− if redirectHostname == strings.TrimPrefix(allowedHost, ".") ||
491− (strings.HasPrefix(allowedHost, ".") &&
492− strings.HasSuffix(redirectHostname, allowedHost)) {
493− // the domain names match, now validate the ports
494− // if the whitelisted domain's port is '*', allow all ports
495− // if the whitelisted domain contains a specific port, only allow that port
496− // if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https
497− redirectPort := redirectURL.Port()
498− if allowedPort == "*" ||
499− allowedPort == redirectPort ||
500− (allowedPort == "" && redirectPort == "") {
501− return true
502− }
503− }
504− }
505−
506− logger.Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect)
507− return false
508− default:
509− logger.Printf("Rejecting invalid redirect %q: not an absolute or relative URL", redirect)
510− return false
511− }
512−}
513−
514475func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
515476 p.serveMux.ServeHTTP(rw, req)
516477}
517478
518479// ErrorPage writes an error response
519480func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, req *http.Request, code int, appError string, messages ...interface{}) {
520− redirectURL, err := p.getAppRedirect(req)
481+ redirectURL, err := p.appDirector.GetRedirect(req)
521482 if err != nil {
522483 logger.Errorf("Error obtaining redirect: %v", err)
523484 }
@@ -582,7 +543,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
582543 }
583544 rw.WriteHeader(code)
584545
585− redirectURL, err := p.getAppRedirect(req)
546+ redirectURL, err := p.appDirector.GetRedirect(req)
586547 if err != nil {
587548 logger.Errorf("Error obtaining redirect: %v", err)
588549 p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
@@ -617,7 +578,7 @@ func (p *OAuthProxy) ManualSignIn(req *http.Request) (string, bool) {
617578
618579// SignIn serves a page prompting users to sign in
619580func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
620− redirect, err := p.getAppRedirect(req)
581+ redirect, err := p.appDirector.GetRedirect(req)
621582 if err != nil {
622583 logger.Errorf("Error obtaining redirect: %v", err)
623584 p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
@@ -681,7 +642,7 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
681642
682643// SignOut sends a response to clear the authentication cookie
683644func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
684− redirect, err := p.getAppRedirect(req)
645+ redirect, err := p.appDirector.GetRedirect(req)
685646 if err != nil {
686647 logger.Errorf("Error obtaining redirect: %v", err)
687648 p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
@@ -707,7 +668,7 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
707668 return
708669 }
709670
710− appRedirect, err := p.getAppRedirect(req)
671+ appRedirect, err := p.appDirector.GetRedirect(req)
711672 if err != nil {
712673 logger.Errorf("Error obtaining application redirect: %v", err)
713674 p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
@@ -790,7 +751,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
790751 csrf.SetSessionNonce(session)
791752 p.provider.ValidateSession(req.Context(), session)
792753
793− if !p.IsValidRedirect(appRedirect) {
754+ if !p.redirectValidator.IsValidRedirect(appRedirect) {
794755 appRedirect = "/"
795756 }
796757
@@ -947,158 +908,6 @@ func (p *OAuthProxy) getOAuthRedirectURI(req *http.Request) string {
947908 return rd.String()
948909}
949910
950−// getAppRedirect determines the full URL or URI path to redirect clients to
951−// once authenticated with the OAuthProxy
952−// Strategy priority (first legal result is used):
953−// - `rd` querysting parameter
954−// - `X-Auth-Request-Redirect` header
955−// - `X-Forwarded-(Proto|Host|Uri)` headers (when ReverseProxy mode is enabled)
956−// - `X-Forwarded-(Proto|Host)` if `Uri` has the ProxyPath (i.e. /oauth2/*)
957−// - `X-Forwarded-Uri` direct URI path (when ReverseProxy mode is enabled)
958−// - `req.URL.RequestURI` if not under the ProxyPath (i.e. /oauth2/*)
959−// - `/`
960−func (p *OAuthProxy) getAppRedirect(req *http.Request) (string, error) {
961− err := req.ParseForm()
962− if err != nil {
963− return "", err
964− }
965−
966− // These redirect getter functions are strategies ordered by priority
967− // for figuring out the redirect URL.
968− type redirectGetter func(req *http.Request) string
969− for _, rdGetter := range []redirectGetter{
970− p.getRdQuerystringRedirect,
971− p.getXAuthRequestRedirect,
972− p.getXForwardedHeadersRedirect,
973− p.getURIRedirect,
974− } {
975− redirect := rdGetter(req)
976− // Call `p.IsValidRedirect` again here a final time to be safe
977− if redirect != "" && p.IsValidRedirect(redirect) {
978− return redirect, nil
979− }
980− }
981−
982− return "/", nil
983−}
984−
985−func isForwardedRequest(req *http.Request) bool {
986− return requestutil.IsProxied(req) &&
987− req.Host != requestutil.GetRequestHost(req)
988−}
989−
990−func (p *OAuthProxy) hasProxyPrefix(path string) bool {
991− return strings.HasPrefix(path, fmt.Sprintf("%s/", p.ProxyPrefix))
992−}
993−
994−func (p *OAuthProxy) validateRedirect(redirect string, errorFormat string) string {
995− if p.IsValidRedirect(redirect) {
996− return redirect
997− }
998− if redirect != "" {
999− logger.Errorf(errorFormat, redirect)
1000− }
1001− return ""
1002−}
1003−
1004−// getRdQuerystringRedirect handles this getAppRedirect strategy:
1005−// - `rd` querysting parameter
1006−func (p *OAuthProxy) getRdQuerystringRedirect(req *http.Request) string {
1007− return p.validateRedirect(
1008− req.Form.Get("rd"),
1009− "Invalid redirect provided in rd querystring parameter: %s",
1010− )
1011−}
1012−
1013−// getXAuthRequestRedirect handles this getAppRedirect strategy:
1014−// - `X-Auth-Request-Redirect` Header
1015−func (p *OAuthProxy) getXAuthRequestRedirect(req *http.Request) string {
1016− return p.validateRedirect(
1017− req.Header.Get("X-Auth-Request-Redirect"),
1018− "Invalid redirect provided in X-Auth-Request-Redirect header: %s",
1019− )
1020−}
1021−
1022−// getXForwardedHeadersRedirect handles these getAppRedirect strategies:
1023−// - `X-Forwarded-(Proto|Host|Uri)` headers (when ReverseProxy mode is enabled)
1024−// - `X-Forwarded-(Proto|Host)` if `Uri` has the ProxyPath (i.e. /oauth2/*)
1025−func (p *OAuthProxy) getXForwardedHeadersRedirect(req *http.Request) string {
1026− if !isForwardedRequest(req) {
1027− return ""
1028− }
1029−
1030− uri := requestutil.GetRequestURI(req)
1031− if p.hasProxyPrefix(uri) {
1032− uri = "/"
1033− }
1034−
1035− redirect := fmt.Sprintf(
1036− "%s://%s%s",
1037− requestutil.GetRequestProto(req),
1038− requestutil.GetRequestHost(req),
1039− uri,
1040− )
1041−
1042− return p.validateRedirect(redirect,
1043− "Invalid redirect generated from X-Forwarded-* headers: %s")
1044−}
1045−
1046−// getURIRedirect handles these getAppRedirect strategies:
1047−// - `X-Forwarded-Uri` direct URI path (when ReverseProxy mode is enabled)
1048−// - `req.URL.RequestURI` if not under the ProxyPath (i.e. /oauth2/*)
1049−// - `/`
1050−func (p *OAuthProxy) getURIRedirect(req *http.Request) string {
1051− redirect := p.validateRedirect(
1052− requestutil.GetRequestURI(req),
1053− "Invalid redirect generated from X-Forwarded-Uri header: %s",
1054− )
1055− if redirect == "" {
1056− redirect = req.URL.RequestURI()
1057− }
1058−
1059− if p.hasProxyPrefix(redirect) {
1060− return "/"
1061− }
1062− return redirect
1063−}
1064−
1065−// splitHostPort separates host and port. If the port is not valid, it returns
1066−// the entire input as host, and it doesn't check the validity of the host.
1067−// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric.
1068−// *** taken from net/url, modified validOptionalPort() to accept ":*"
1069−func splitHostPort(hostport string) (host, port string) {
1070− host = hostport
1071−
1072− colon := strings.LastIndexByte(host, ':')
1073− if colon != -1 && validOptionalPort(host[colon:]) {
1074− host, port = host[:colon], host[colon+1:]
1075− }
1076−
1077− if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
1078− host = host[1 : len(host)-1]
1079− }
1080−
1081− return
1082−}
1083−
1084−// validOptionalPort reports whether port is either an empty string
1085−// or matches /^:\d*$/
1086−// *** taken from net/url, modified to accept ":*"
1087−func validOptionalPort(port string) bool {
1088− if port == "" || port == ":*" {
1089− return true
1090− }
1091− if port[0] != ':' {
1092− return false
1093− }
1094− for _, b := range port[1:] {
1095− if b < '0' || b > '9' {
1096− return false
1097− }
1098− }
1099− return true
1100−}
1101−
1102911// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so
1103912// Returns:
1104913// - `nil, ErrNeedsLogin` if user needs to login.
oauthproxy_test.go−463
@@ -1,7 +1,6 @@
11package main
22
33import (
4− "bufio"
54 "context"
65 "crypto"
76 "encoding/base64"
@@ -11,7 +10,6 @@ import (
1110 "net/http"
1211 "net/http/httptest"
1312 "net/url"
14− "os"
1513 "regexp"
1614 "strings"
1715 "testing"
@@ -19,7 +17,6 @@ import (
1917
2018 "github.com/coreos/go-oidc"
2119 "github.com/mbland/hmacauth"
22− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
2320 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
2421 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
2522 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
@@ -28,10 +25,7 @@ import (
2825 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
2926 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
3027 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
31− . "github.com/onsi/ginkgo"
32− . "github.com/onsi/gomega"
3328 "github.com/stretchr/testify/assert"
34− "github.com/stretchr/testify/require"
3529)
3630
3731const (
@@ -63,304 +57,6 @@ func TestRobotsTxt(t *testing.T) {
6357 assert.Equal(t, "User-agent: *\nDisallow: /\n", rw.Body.String())
6458}
6559
66−func TestIsValidRedirect(t *testing.T) {
67− opts := baseTestOptions()
68− // Should match domains that are exactly foo.bar and any subdomain of bar.foo
69− opts.WhitelistDomains = []string{
70− "foo.bar",
71− ".bar.foo",
72− "port.bar:8080",
73− ".sub.port.bar:8080",
74− "anyport.bar:*",
75− ".sub.anyport.bar:*",
76− }
77− err := validation.Validate(opts)
78− assert.NoError(t, err)
79−
80− proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
81− if err != nil {
82− t.Fatal(err)
83− }
84−
85− testCases := []struct {
86− Desc, Redirect string
87− ExpectedResult bool
88− }{
89− {
90− Desc: "noRD",
91− Redirect: "",
92− ExpectedResult: false,
93− },
94− {
95− Desc: "singleSlash",
96− Redirect: "/redirect",
97− ExpectedResult: true,
98− },
99− {
100− Desc: "doubleSlash",
101− Redirect: "//redirect",
102− ExpectedResult: false,
103− },
104− {
105− Desc: "validHTTP",
106− Redirect: "http://foo.bar/redirect",
107− ExpectedResult: true,
108− },
109− {
110− Desc: "validHTTPS",
111− Redirect: "https://foo.bar/redirect",
112− ExpectedResult: true,
113− },
114− {
115− Desc: "invalidHTTPSubdomain",
116− Redirect: "http://baz.foo.bar/redirect",
117− ExpectedResult: false,
118− },
119− {
120− Desc: "invalidHTTPSSubdomain",
121− Redirect: "https://baz.foo.bar/redirect",
122− ExpectedResult: false,
123− },
124− {
125− Desc: "validHTTPSubdomain",
126− Redirect: "http://baz.bar.foo/redirect",
127− ExpectedResult: true,
128− },
129− {
130− Desc: "validHTTPSSubdomain",
131− Redirect: "https://baz.bar.foo/redirect",
132− ExpectedResult: true,
133− },
134− {
135− Desc: "validHTTPDomain",
136− Redirect: "http://bar.foo/redirect",
137− ExpectedResult: true,
138− },
139− {
140− Desc: "invalidHTTP1",
141− Redirect: "http://foo.bar.evil.corp/redirect",
142− ExpectedResult: false,
143− },
144− {
145− Desc: "invalidHTTPS1",
146− Redirect: "https://foo.bar.evil.corp/redirect",
147− ExpectedResult: false,
148− },
149− {
150− Desc: "invalidHTTP2",
151− Redirect: "http://evil.corp/redirect?rd=foo.bar",
152− ExpectedResult: false,
153− },
154− {
155− Desc: "invalidHTTPS2",
156− Redirect: "https://evil.corp/redirect?rd=foo.bar",
157− ExpectedResult: false,
158− },
159− {
160− Desc: "invalidPort",
161− Redirect: "https://evil.corp:3838/redirect",
162− ExpectedResult: false,
163− },
164− {
165− Desc: "invalidEmptyPort",
166− Redirect: "http://foo.bar:3838/redirect",
167− ExpectedResult: false,
168− },
169− {
170− Desc: "invalidEmptyPortSubdomain",
171− Redirect: "http://baz.bar.foo:3838/redirect",
172− ExpectedResult: false,
173− },
174− {
175− Desc: "validSpecificPort",
176− Redirect: "http://port.bar:8080/redirect",
177− ExpectedResult: true,
178− },
179− {
180− Desc: "invalidSpecificPort",
181− Redirect: "http://port.bar:3838/redirect",
182− ExpectedResult: false,
183− },
184− {
185− Desc: "validSpecificPortSubdomain",
186− Redirect: "http://foo.sub.port.bar:8080/redirect",
187− ExpectedResult: true,
188− },
189− {
190− Desc: "invalidSpecificPortSubdomain",
191− Redirect: "http://foo.sub.port.bar:3838/redirect",
192− ExpectedResult: false,
193− },
194− {
195− Desc: "validAnyPort1",
196− Redirect: "http://anyport.bar:8080/redirect",
197− ExpectedResult: true,
198− },
199− {
200− Desc: "validAnyPort2",
201− Redirect: "http://anyport.bar:8081/redirect",
202− ExpectedResult: true,
203− },
204− {
205− Desc: "validAnyPortSubdomain1",
206− Redirect: "http://a.sub.anyport.bar:8080/redirect",
207− ExpectedResult: true,
208− },
209− {
210− Desc: "validAnyPortSubdomain2",
211− Redirect: "http://a.sub.anyport.bar:8081/redirect",
212− ExpectedResult: true,
213− },
214− {
215− Desc: "openRedirect1",
216− Redirect: "/\\evil.com",
217− ExpectedResult: false,
218− },
219− {
220− Desc: "openRedirectSpace1",
221− Redirect: "/ /evil.com",
222− ExpectedResult: false,
223− },
224− {
225− Desc: "openRedirectSpace2",
226− Redirect: "/ \\evil.com",
227− ExpectedResult: false,
228− },
229− {
230− Desc: "openRedirectTab1",
231− Redirect: "/\t/evil.com",
232− ExpectedResult: false,
233− },
234− {
235− Desc: "openRedirectTab2",
236− Redirect: "/\t\\evil.com",
237− ExpectedResult: false,
238− },
239− {
240− Desc: "openRedirectVerticalTab1",
241− Redirect: "/\v/evil.com",
242− ExpectedResult: false,
243− },
244− {
245− Desc: "openRedirectVerticalTab2",
246− Redirect: "/\v\\evil.com",
247− ExpectedResult: false,
248− },
249− {
250− Desc: "openRedirectNewLine1",
251− Redirect: "/\n/evil.com",
252− ExpectedResult: false,
253− },
254− {
255− Desc: "openRedirectNewLine2",
256− Redirect: "/\n\\evil.com",
257− ExpectedResult: false,
258− },
259− {
260− Desc: "openRedirectCarriageReturn1",
261− Redirect: "/\r/evil.com",
262− ExpectedResult: false,
263− },
264− {
265− Desc: "openRedirectCarriageReturn2",
266− Redirect: "/\r\\evil.com",
267− ExpectedResult: false,
268− },
269− {
270− Desc: "openRedirectTripleTab",
271− Redirect: "/\t\t/\t/evil.com",
272− ExpectedResult: false,
273− },
274− {
275− Desc: "openRedirectTripleTab2",
276− Redirect: "/\t\t\\\t/evil.com",
277− ExpectedResult: false,
278− },
279− {
280− Desc: "openRedirectQuadTab1",
281− Redirect: "/\t\t/\t\t\\evil.com",
282− ExpectedResult: false,
283− },
284− {
285− Desc: "openRedirectQuadTab2",
286− Redirect: "/\t\t\\\t\t/evil.com",
287− ExpectedResult: false,
288− },
289− {
290− Desc: "openRedirectPeriod1",
291− Redirect: "/./\\evil.com",
292− ExpectedResult: false,
293− },
294− {
295− Desc: "openRedirectPeriod2",
296− Redirect: "/./../../\\evil.com",
297− ExpectedResult: false,
298− },
299− {
300− Desc: "openRedirectDoubleTab",
301− Redirect: "/\t/\t\\evil.com",
302− ExpectedResult: false,
303− },
304− {
305− Desc: "openRedirectPartialSubdomain",
306− Redirect: "http://evilbar.foo",
307− ExpectedResult: false,
308− },
309− }
310−
311− for _, tc := range testCases {
312− t.Run(tc.Desc, func(t *testing.T) {
313− result := proxy.IsValidRedirect(tc.Redirect)
314−
315− if result != tc.ExpectedResult {
316− t.Errorf("expected %t got %t", tc.ExpectedResult, result)
317− }
318− })
319− }
320−}
321−
322−var _ = Describe("OpenRedirect Tests", func() {
323− var proxy *OAuthProxy
324−
325− BeforeEach(func() {
326− opts := baseTestOptions()
327− // Should match domains that are exactly foo.bar and any subdomain of bar.foo
328− opts.WhitelistDomains = []string{
329− "foo.bar",
330− ".bar.foo",
331− "port.bar:8080",
332− ".sub.port.bar:8080",
333− "anyport.bar:*",
334− ".sub.anyport.bar:*",
335− "www.whitelisteddomain.tld",
336− }
337− Expect(validation.Validate(opts)).To(Succeed())
338−
339− var err error
340− proxy, err = NewOAuthProxy(opts, func(string) bool { return true })
341− Expect(err).ToNot(HaveOccurred())
342− })
343−
344− file, err := os.Open("./testdata/openredirects.txt")
345− Expect(err).ToNot(HaveOccurred())
346− defer func() {
347− Expect(file.Close()).To(Succeed())
348− }()
349−
350− scanner := bufio.NewScanner(file)
351− for scanner.Scan() {
352− rd := scanner.Text()
353− It(rd, func() {
354− rdUnescaped, err := url.QueryUnescape(rd)
355− Expect(err).ToNot(HaveOccurred())
356−
357− Expect(proxy.IsValidRedirect(rdUnescaped)).To(BeFalse(), "Expected redirect not to be valid")
358− })
359− }
360−
361− Expect(scanner.Err()).ToNot(HaveOccurred())
362−})
363−
36460type TestProvider struct {
36561 *providers.ProviderData
36662 EmailAddress string
@@ -1770,165 +1466,6 @@ func TestRequestSignature(t *testing.T) {
17701466 }
17711467}
17721468
1773−func Test_getAppRedirect(t *testing.T) {
1774− opts := baseTestOptions()
1775− opts.WhitelistDomains = append(opts.WhitelistDomains, ".example.com", ".example.com:8443")
1776− err := validation.Validate(opts)
1777− assert.NoError(t, err)
1778− require.NotEmpty(t, opts.ProxyPrefix)
1779− proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1780− if err != nil {
1781− t.Fatal(err)
1782− }
1783−
1784− tests := []struct {
1785− name string
1786− url string
1787− headers map[string]string
1788− reverseProxy bool
1789− expectedRedirect string
1790− }{
1791− {
1792− name: "request outside of ProxyPrefix redirects to original URL",
1793− url: "/foo/bar",
1794− headers: nil,
1795− reverseProxy: false,
1796− expectedRedirect: "/foo/bar",
1797− },
1798− {
1799− name: "request with query preserves query",
1800− url: "/foo?bar",
1801− headers: nil,
1802− reverseProxy: false,
1803− expectedRedirect: "/foo?bar",
1804− },
1805− {
1806− name: "request under ProxyPrefix redirects to root",
1807− url: proxy.ProxyPrefix + "/foo/bar",
1808− headers: nil,
1809− reverseProxy: false,
1810− expectedRedirect: "/",
1811− },
1812− {
1813− name: "proxied request outside of ProxyPrefix redirects to proxied URL",
1814− url: "https://oauth.example.com/foo/bar",
1815− headers: map[string]string{
1816− "X-Forwarded-Proto": "https",
1817− "X-Forwarded-Host": "a-service.example.com",
1818− "X-Forwarded-Uri": "/foo/bar",
1819− },
1820− reverseProxy: true,
1821− expectedRedirect: "https://a-service.example.com/foo/bar",
1822− },
1823− {
1824− name: "non-proxied request with spoofed proxy headers wouldn't redirect",
1825− url: "https://oauth.example.com/foo?bar",
1826− headers: map[string]string{
1827− "X-Forwarded-Proto": "https",
1828− "X-Forwarded-Host": "a-service.example.com",
1829− "X-Forwarded-Uri": "/foo/bar",
1830− },
1831− reverseProxy: false,
1832− expectedRedirect: "/foo?bar",
1833− },
1834− {
1835− name: "proxied request under ProxyPrefix redirects to root",
1836− url: "https://oauth.example.com" + proxy.ProxyPrefix + "/foo/bar",
1837− headers: map[string]string{
1838− "X-Forwarded-Proto": "https",
1839− "X-Forwarded-Host": "a-service.example.com",
1840− "X-Forwarded-Uri": proxy.ProxyPrefix + "/foo/bar",
1841− },
1842− reverseProxy: true,
1843− expectedRedirect: "https://a-service.example.com/",
1844− },
1845− {
1846− name: "proxied request with port under ProxyPrefix redirects to root",
1847− url: "https://oauth.example.com" + proxy.ProxyPrefix + "/foo/bar",
1848− headers: map[string]string{
1849− "X-Forwarded-Proto": "https",
1850− "X-Forwarded-Host": "a-service.example.com:8443",
1851− "X-Forwarded-Uri": proxy.ProxyPrefix + "/foo/bar",
1852− },
1853− reverseProxy: true,
1854− expectedRedirect: "https://a-service.example.com:8443/",
1855− },
1856− {
1857− name: "proxied request with missing uri header would still redirect to desired redirect",
1858− url: "https://oauth.example.com/foo?bar",
1859− headers: map[string]string{
1860− "X-Forwarded-Proto": "https",
1861− "X-Forwarded-Host": "a-service.example.com",
1862− },
1863− reverseProxy: true,
1864− expectedRedirect: "https://a-service.example.com/foo?bar",
1865− },
1866− {
1867− name: "request with headers proxy not being set (and reverse proxy enabled) would still redirect to desired redirect",
1868− url: "https://oauth.example.com/foo?bar",
1869− headers: nil,
1870− reverseProxy: true,
1871− expectedRedirect: "/foo?bar",
1872− },
1873− {
1874− name: "proxied request with X-Auth-Request-Redirect being set outside of ProxyPrefix redirects to proxied URL",
1875− url: "https://oauth.example.com/foo/bar",
1876− headers: map[string]string{
1877− "X-Auth-Request-Redirect": "https://a-service.example.com/foo/bar",
1878− },
1879− reverseProxy: true,
1880− expectedRedirect: "https://a-service.example.com/foo/bar",
1881− },
1882− {
1883− name: "proxied request with rd query string redirects to proxied URL",
1884− url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fbar",
1885− headers: nil,
1886− reverseProxy: false,
1887− expectedRedirect: "https://a-service.example.com/foo/bar",
1888− },
1889− {
1890− name: "proxied request with rd query string and all headers set (and reverse proxy not enabled) redirects to proxied URL on rd query string",
1891− url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fjazz",
1892− headers: map[string]string{
1893− "X-Auth-Request-Redirect": "https://a-service.example.com/foo/baz",
1894− "X-Forwarded-Proto": "http",
1895− "X-Forwarded-Host": "another-service.example.com",
1896− "X-Forwarded-Uri": "/seasons/greetings",
1897− },
1898− reverseProxy: false,
1899− expectedRedirect: "https://a-service.example.com/foo/jazz",
1900− },
1901− {
1902− name: "proxied request with rd query string and some headers set redirects to proxied URL on rd query string",
1903− url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fbaz",
1904− headers: map[string]string{
1905− "X-Forwarded-Proto": "https",
1906− "X-Forwarded-Host": "another-service.example.com",
1907− "X-Forwarded-Uri": "/seasons/greetings",
1908− },
1909− reverseProxy: true,
1910− expectedRedirect: "https://a-service.example.com/foo/baz",
1911− },
1912− }
1913− for _, tt := range tests {
1914− t.Run(tt.name, func(t *testing.T) {
1915− req, _ := http.NewRequest("GET", tt.url, nil)
1916− for header, value := range tt.headers {
1917− if value != "" {
1918− req.Header.Add(header, value)
1919− }
1920− }
1921− req = middleware.AddRequestScope(req, &middleware.RequestScope{
1922− ReverseProxy: tt.reverseProxy,
1923− })
1924− redirect, err := proxy.getAppRedirect(req)
1925−
1926− assert.NoError(t, err)
1927− assert.Equal(t, tt.expectedRedirect, redirect)
1928− })
1929− }
1930−}
1931−
19321469type ajaxRequestTest struct {
19331470 opts *options.Options
19341471 proxy *OAuthProxy