git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add pagewriter to upstream proxy
commitbefcdd9d042fb745d8c22e6ca67e653b29492daf authorJoel Speed <joel.speed@hotmail.co.uk> dateWed 31 Mar 2021, 10:30 (6 years ago) parent725ae54
6 files changed+227−9
CHANGELOG.md+1 oauthproxy.go+1−1 pkg/app/pagewriter/pagewriter.go+70 pkg/app/pagewriter/pagewriter_test.go+143 pkg/upstream/proxy.go+5−4 pkg/upstream/proxy_test.go+7−4
CHANGELOG.md+1
@@ -8,6 +8,7 @@
88
99## Changes since v7.1.3
1010
11+- [#1142](https://github.com/oauth2-proxy/oauth2-proxy/pull/1142) Add pagewriter to upstream proxy (@JoelSpeed)
1112- [#1181](https://github.com/oauth2-proxy/oauth2-proxy/pull/1181) Fix incorrect `cfg` name in show-debug-on-error flag (@iTaybb)
1213
1314# V7.1.3
oauthproxy.go+1−1
@@ -124,7 +124,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
124124 return nil, fmt.Errorf("error initialising page writer: %v", err)
125125 }
126126
127− upstreamProxy, err := upstream.NewProxy(opts.UpstreamServers, opts.GetSignatureData(), pageWriter.ProxyErrorHandler)
127+ upstreamProxy, err := upstream.NewProxy(opts.UpstreamServers, opts.GetSignatureData(), pageWriter)
128128 if err != nil {
129129 return nil, fmt.Errorf("error initialising upstream proxy: %v", err)
130130 }
pkg/app/pagewriter/pagewriter.go+70
@@ -101,3 +101,73 @@ func NewWriter(opts Opts) (Writer, error) {
101101 staticPageWriter: staticPages,
102102 }, nil
103103}
104+
105+// WriterFuncs is an implementation of the PageWriter interface based
106+// on override functions.
107+// If any of the funcs are not provided, a default implementation will be used.
108+// This is primarily for us in testing.
109+type WriterFuncs struct {
110+ SignInPageFunc func(rw http.ResponseWriter, req *http.Request, redirectURL string)
111+ ErrorPageFunc func(rw http.ResponseWriter, opts ErrorPageOpts)
112+ ProxyErrorFunc func(rw http.ResponseWriter, req *http.Request, proxyErr error)
113+ RobotsTxtfunc func(rw http.ResponseWriter, req *http.Request)
114+}
115+
116+// WriteSignInPage implements the Writer interface.
117+// If the SignInPageFunc is provided, this will be used, else a default
118+// implementation will be used.
119+func (w *WriterFuncs) WriteSignInPage(rw http.ResponseWriter, req *http.Request, redirectURL string) {
120+ if w.SignInPageFunc != nil {
121+ w.SignInPageFunc(rw, req, redirectURL)
122+ return
123+ }
124+
125+ if _, err := rw.Write([]byte("Sign In")); err != nil {
126+ rw.WriteHeader(http.StatusInternalServerError)
127+ }
128+}
129+
130+// WriteErrorPage implements the Writer interface.
131+// If the ErrorPageFunc is provided, this will be used, else a default
132+// implementation will be used.
133+func (w *WriterFuncs) WriteErrorPage(rw http.ResponseWriter, opts ErrorPageOpts) {
134+ if w.ErrorPageFunc != nil {
135+ w.ErrorPageFunc(rw, opts)
136+ return
137+ }
138+
139+ rw.WriteHeader(opts.Status)
140+ errMsg := fmt.Sprintf("%d - %v", opts.Status, opts.AppError)
141+ if _, err := rw.Write([]byte(errMsg)); err != nil {
142+ rw.WriteHeader(http.StatusInternalServerError)
143+ }
144+}
145+
146+// ProxyErrorHandler implements the Writer interface.
147+// If the ProxyErrorFunc is provided, this will be used, else a default
148+// implementation will be used.
149+func (w *WriterFuncs) ProxyErrorHandler(rw http.ResponseWriter, req *http.Request, proxyErr error) {
150+ if w.ProxyErrorFunc != nil {
151+ w.ProxyErrorFunc(rw, req, proxyErr)
152+ return
153+ }
154+
155+ w.WriteErrorPage(rw, ErrorPageOpts{
156+ Status: http.StatusBadGateway,
157+ AppError: proxyErr.Error(),
158+ })
159+}
160+
161+// WriteRobotsTxt implements the Writer interface.
162+// If the RobotsTxtfunc is provided, this will be used, else a default
163+// implementation will be used.
164+func (w *WriterFuncs) WriteRobotsTxt(rw http.ResponseWriter, req *http.Request) {
165+ if w.RobotsTxtfunc != nil {
166+ w.RobotsTxtfunc(rw, req)
167+ return
168+ }
169+
170+ if _, err := rw.Write([]byte("Allow: *")); err != nil {
171+ rw.WriteHeader(http.StatusInternalServerError)
172+ }
173+}
pkg/app/pagewriter/pagewriter_test.go+143
@@ -1,6 +1,8 @@
11package pagewriter
22
33import (
4+ "errors"
5+ "fmt"
46 "io/ioutil"
57 "net/http"
68 "net/http/httptest"
@@ -8,6 +10,7 @@ import (
810 "path/filepath"
911
1012 . "github.com/onsi/ginkgo"
13+ . "github.com/onsi/ginkgo/extensions/table"
1114 . "github.com/onsi/gomega"
1215)
1316
@@ -135,4 +138,144 @@ var _ = Describe("Writer", func() {
135138 })
136139 })
137140 })
141+
142+ Context("WriterFuncs", func() {
143+ type writerFuncsTableInput struct {
144+ writer Writer
145+ expectedStatus int
146+ expectedBody string
147+ }
148+
149+ DescribeTable("WriteSignInPage",
150+ func(in writerFuncsTableInput) {
151+ rw := httptest.NewRecorder()
152+ req := httptest.NewRequest("", "/sign-in", nil)
153+ redirectURL := "<redirectURL>"
154+ in.writer.WriteSignInPage(rw, req, redirectURL)
155+
156+ Expect(rw.Result().StatusCode).To(Equal(in.expectedStatus))
157+
158+ body, err := ioutil.ReadAll(rw.Result().Body)
159+ Expect(err).ToNot(HaveOccurred())
160+ Expect(string(body)).To(Equal(in.expectedBody))
161+ },
162+ Entry("With no override", writerFuncsTableInput{
163+ writer: &WriterFuncs{},
164+ expectedStatus: 200,
165+ expectedBody: "Sign In",
166+ }),
167+ Entry("With an override function", writerFuncsTableInput{
168+ writer: &WriterFuncs{
169+ SignInPageFunc: func(rw http.ResponseWriter, req *http.Request, redirectURL string) {
170+ rw.WriteHeader(202)
171+ rw.Write([]byte(fmt.Sprintf("%s %s", req.URL.Path, redirectURL)))
172+ },
173+ },
174+ expectedStatus: 202,
175+ expectedBody: "/sign-in <redirectURL>",
176+ }),
177+ )
178+
179+ DescribeTable("WriteErrorPage",
180+ func(in writerFuncsTableInput) {
181+ rw := httptest.NewRecorder()
182+ in.writer.WriteErrorPage(rw, ErrorPageOpts{
183+ Status: http.StatusInternalServerError,
184+ RedirectURL: "<redirectURL>",
185+ RequestID: "12345",
186+ AppError: "application error",
187+ })
188+
189+ Expect(rw.Result().StatusCode).To(Equal(in.expectedStatus))
190+
191+ body, err := ioutil.ReadAll(rw.Result().Body)
192+ Expect(err).ToNot(HaveOccurred())
193+ Expect(string(body)).To(Equal(in.expectedBody))
194+ },
195+ Entry("With no override", writerFuncsTableInput{
196+ writer: &WriterFuncs{},
197+ expectedStatus: 500,
198+ expectedBody: "500 - application error",
199+ }),
200+ Entry("With an override function", writerFuncsTableInput{
201+ writer: &WriterFuncs{
202+ ErrorPageFunc: func(rw http.ResponseWriter, opts ErrorPageOpts) {
203+ rw.WriteHeader(503)
204+ rw.Write([]byte(fmt.Sprintf("%s %s", opts.RequestID, opts.RedirectURL)))
205+ },
206+ },
207+ expectedStatus: 503,
208+ expectedBody: "12345 <redirectURL>",
209+ }),
210+ )
211+
212+ DescribeTable("ProxyErrorHandler",
213+ func(in writerFuncsTableInput) {
214+ rw := httptest.NewRecorder()
215+ req := httptest.NewRequest("", "/proxy", nil)
216+ err := errors.New("proxy error")
217+ in.writer.ProxyErrorHandler(rw, req, err)
218+
219+ Expect(rw.Result().StatusCode).To(Equal(in.expectedStatus))
220+
221+ body, err := ioutil.ReadAll(rw.Result().Body)
222+ Expect(err).ToNot(HaveOccurred())
223+ Expect(string(body)).To(Equal(in.expectedBody))
224+ },
225+ Entry("With no override", writerFuncsTableInput{
226+ writer: &WriterFuncs{},
227+ expectedStatus: 502,
228+ expectedBody: "502 - proxy error",
229+ }),
230+ Entry("With an override function for the proxy handler", writerFuncsTableInput{
231+ writer: &WriterFuncs{
232+ ProxyErrorFunc: func(rw http.ResponseWriter, req *http.Request, proxyErr error) {
233+ rw.WriteHeader(503)
234+ rw.Write([]byte(fmt.Sprintf("%s %v", req.URL.Path, proxyErr)))
235+ },
236+ },
237+ expectedStatus: 503,
238+ expectedBody: "/proxy proxy error",
239+ }),
240+ Entry("With an override function for the error page", writerFuncsTableInput{
241+ writer: &WriterFuncs{
242+ ErrorPageFunc: func(rw http.ResponseWriter, opts ErrorPageOpts) {
243+ rw.WriteHeader(500)
244+ rw.Write([]byte("Internal Server Error"))
245+ },
246+ },
247+ expectedStatus: 500,
248+ expectedBody: "Internal Server Error",
249+ }),
250+ )
251+
252+ DescribeTable("WriteRobotsTxt",
253+ func(in writerFuncsTableInput) {
254+ rw := httptest.NewRecorder()
255+ req := httptest.NewRequest("", "/robots.txt", nil)
256+ in.writer.WriteRobotsTxt(rw, req)
257+
258+ Expect(rw.Result().StatusCode).To(Equal(in.expectedStatus))
259+
260+ body, err := ioutil.ReadAll(rw.Result().Body)
261+ Expect(err).ToNot(HaveOccurred())
262+ Expect(string(body)).To(Equal(in.expectedBody))
263+ },
264+ Entry("With no override", writerFuncsTableInput{
265+ writer: &WriterFuncs{},
266+ expectedStatus: 200,
267+ expectedBody: "Allow: *",
268+ }),
269+ Entry("With an override function", writerFuncsTableInput{
270+ writer: &WriterFuncs{
271+ RobotsTxtfunc: func(rw http.ResponseWriter, req *http.Request) {
272+ rw.WriteHeader(202)
273+ rw.Write([]byte("Disallow: *"))
274+ },
275+ },
276+ expectedStatus: 202,
277+ expectedBody: "Disallow: *",
278+ }),
279+ )
280+ })
138281})
pkg/upstream/proxy.go+5−4
@@ -6,6 +6,7 @@ import (
66 "net/url"
77
88 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
9+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
910 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1011)
1112
@@ -15,7 +16,7 @@ type ProxyErrorHandler func(http.ResponseWriter, *http.Request, error)
1516
1617// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
1718// multiple upstreams.
18−func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, errorHandler ProxyErrorHandler) (http.Handler, error) {
19+func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, writer pagewriter.Writer) (http.Handler, error) {
1920 m := &multiUpstreamProxy{
2021 serveMux: http.NewServeMux(),
2122 }
@@ -34,7 +35,7 @@ func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, error
3435 case fileScheme:
3536 m.registerFileServer(upstream, u)
3637 case httpScheme, httpsScheme:
37− m.registerHTTPUpstreamProxy(upstream, u, sigData, errorHandler)
38+ m.registerHTTPUpstreamProxy(upstream, u, sigData, writer)
3839 default:
3940 return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
4041 }
@@ -66,7 +67,7 @@ func (m *multiUpstreamProxy) registerFileServer(upstream options.Upstream, u *ur
6667}
6768
6869// registerHTTPUpstreamProxy registers a new httpUpstreamProxy based on the configuration given.
69−func (m *multiUpstreamProxy) registerHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) {
70+func (m *multiUpstreamProxy) registerHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, writer pagewriter.Writer) {
7071 logger.Printf("mapping path %q => upstream %q", upstream.Path, upstream.URI)
71− m.serveMux.Handle(upstream.Path, newHTTPUpstreamProxy(upstream, u, sigData, errorHandler))
72+ m.serveMux.Handle(upstream.Path, newHTTPUpstreamProxy(upstream, u, sigData, writer.ProxyErrorHandler))
7273}
pkg/upstream/proxy_test.go+7−4
@@ -9,6 +9,7 @@ import (
99
1010 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1111 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
1213 . "github.com/onsi/ginkgo"
1314 . "github.com/onsi/ginkgo/extensions/table"
1415 . "github.com/onsi/gomega"
@@ -20,9 +21,11 @@ var _ = Describe("Proxy Suite", func() {
2021 BeforeEach(func() {
2122 sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"}
2223
23− errorHandler := func(rw http.ResponseWriter, _ *http.Request, _ error) {
24− rw.WriteHeader(502)
25− rw.Write([]byte("Proxy Error"))
24+ writer := &pagewriter.WriterFuncs{
25+ ProxyErrorFunc: func(rw http.ResponseWriter, _ *http.Request, _ error) {
26+ rw.WriteHeader(502)
27+ rw.Write([]byte("Proxy Error"))
28+ },
2629 }
2730
2831 ok := http.StatusOK
@@ -58,7 +61,7 @@ var _ = Describe("Proxy Suite", func() {
5861 }
5962
6063 var err error
61− upstreamServer, err = NewProxy(upstreams, sigData, errorHandler)
64+ upstreamServer, err = NewProxy(upstreams, sigData, writer)
6265 Expect(err).ToNot(HaveOccurred())
6366 })
6467