git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add pagewriter to upstream proxy
befcdd9Joel Speed
CHANGELOG.md+1
@@ -8,6 +8,7 @@
8
8
9## Changes since v7.1.3
9## Changes since v7.1.3
10
10
11+- [#1142](https://github.com/oauth2-proxy/oauth2-proxy/pull/1142) Add pagewriter to upstream proxy (@JoelSpeed)
11- [#1181](https://github.com/oauth2-proxy/oauth2-proxy/pull/1181) Fix incorrect `cfg` name in show-debug-on-error flag (@iTaybb)
12- [#1181](https://github.com/oauth2-proxy/oauth2-proxy/pull/1181) Fix incorrect `cfg` name in show-debug-on-error flag (@iTaybb)
12
13
13# V7.1.3
14# V7.1.3
oauthproxy.go+1−1
@@ -124,7 +124,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
124 return nil, fmt.Errorf("error initialising page writer: %v", err)
124 return nil, fmt.Errorf("error initialising page writer: %v", err)
125 }
125 }
126
126
127− upstreamProxy, err := upstream.NewProxy(opts.UpstreamServers, opts.GetSignatureData(), pageWriter.ProxyErrorHandler)
127+ upstreamProxy, err := upstream.NewProxy(opts.UpstreamServers, opts.GetSignatureData(), pageWriter)
128 if err != nil {
128 if err != nil {
129 return nil, fmt.Errorf("error initialising upstream proxy: %v", err)
129 return nil, fmt.Errorf("error initialising upstream proxy: %v", err)
130 }
130 }
pkg/app/pagewriter/pagewriter.go+70
@@ -101,3 +101,73 @@ func NewWriter(opts Opts) (Writer, error) {
101 staticPageWriter: staticPages,
101 staticPageWriter: staticPages,
102 }, nil
102 }, nil
103}
103}
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 @@
1package pagewriter
1package pagewriter
2
2
3import (
3import (
4+ "errors"
5+ "fmt"
4 "io/ioutil"
6 "io/ioutil"
5 "net/http"
7 "net/http"
6 "net/http/httptest"
8 "net/http/httptest"
@@ -8,6 +10,7 @@ import (
8 "path/filepath"
10 "path/filepath"
9
11
10 . "github.com/onsi/ginkgo"
12 . "github.com/onsi/ginkgo"
13+ . "github.com/onsi/ginkgo/extensions/table"
11 . "github.com/onsi/gomega"
14 . "github.com/onsi/gomega"
12)
15)
13
16
@@ -135,4 +138,144 @@ var _ = Describe("Writer", func() {
135 })
138 })
136 })
139 })
137 })
140 })
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+ })
138})
281})
pkg/upstream/proxy.go+5−4
@@ -6,6 +6,7 @@ import (
6 "net/url"
6 "net/url"
7
7
8 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
8 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
9+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
9 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
10)
11)
11
12
@@ -15,7 +16,7 @@ type ProxyErrorHandler func(http.ResponseWriter, *http.Request, error)
15
16
16// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
17// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
17// multiple upstreams.
18// 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) {
19 m := &multiUpstreamProxy{
20 m := &multiUpstreamProxy{
20 serveMux: http.NewServeMux(),
21 serveMux: http.NewServeMux(),
21 }
22 }
@@ -34,7 +35,7 @@ func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, error
34 case fileScheme:
35 case fileScheme:
35 m.registerFileServer(upstream, u)
36 m.registerFileServer(upstream, u)
36 case httpScheme, httpsScheme:
37 case httpScheme, httpsScheme:
37− m.registerHTTPUpstreamProxy(upstream, u, sigData, errorHandler)
38+ m.registerHTTPUpstreamProxy(upstream, u, sigData, writer)
38 default:
39 default:
39 return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
40 return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
40 }
41 }
@@ -66,7 +67,7 @@ func (m *multiUpstreamProxy) registerFileServer(upstream options.Upstream, u *ur
66}
67}
67
68
68// registerHTTPUpstreamProxy registers a new httpUpstreamProxy based on the configuration given.
69// 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) {
70 logger.Printf("mapping path %q => upstream %q", upstream.Path, upstream.URI)
71 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))
72}
73}
pkg/upstream/proxy_test.go+7−4
@@ -9,6 +9,7 @@ import (
9
9
10 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
10 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
12 . "github.com/onsi/ginkgo"
13 . "github.com/onsi/ginkgo"
13 . "github.com/onsi/ginkgo/extensions/table"
14 . "github.com/onsi/ginkgo/extensions/table"
14 . "github.com/onsi/gomega"
15 . "github.com/onsi/gomega"
@@ -20,9 +21,11 @@ var _ = Describe("Proxy Suite", func() {
20 BeforeEach(func() {
21 BeforeEach(func() {
21 sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"}
22 sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"}
22
23
24+ writer := &pagewriter.WriterFuncs{
23− errorHandler := func(rw http.ResponseWriter, _ *http.Request, _ error) {
25+ ProxyErrorFunc: func(rw http.ResponseWriter, _ *http.Request, _ error) {
24− rw.WriteHeader(502)
26+ rw.WriteHeader(502)
25− rw.Write([]byte("Proxy Error"))
27+ rw.Write([]byte("Proxy Error"))
28+ },
26 }
29 }
27
30
28 ok := http.StatusOK
31 ok := http.StatusOK
@@ -58,7 +61,7 @@ var _ = Describe("Proxy Suite", func() {
58 }
61 }
59
62
60 var err error
63 var err error
61− upstreamServer, err = NewProxy(upstreams, sigData, errorHandler)
64+ upstreamServer, err = NewProxy(upstreams, sigData, writer)
62 Expect(err).ToNot(HaveOccurred())
65 Expect(err).ToNot(HaveOccurred())
63 })
66 })
64
67