forked from oauth2-proxy/oauth2-proxy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
1,262 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package options | ||
|
||
import "github.com/spf13/pflag" | ||
|
||
// Templates includes options for configuring the sign in and error pages | ||
// appearance. | ||
type Templates struct { | ||
// Path is the path to a folder containing a sign_in.html and an error.html | ||
// template. | ||
// These files will be used instead of the default templates if present. | ||
// If either file is missing, the default will be used instead. | ||
Path string `flag:"custom-templates-dir" cfg:"custom_templates_dir"` | ||
|
||
// Banner overides the default sign_in page banner text. If unspecified, | ||
// the message will give users a list of allowed email domains. | ||
Banner string `flag:"banner" cfg:"banner"` | ||
|
||
// Footer overrides the default sign_in page footer text. | ||
Footer string `flag:"footer" cfg:"footer"` | ||
|
||
// DisplayLoginForm determines whether the sign_in page should render a | ||
// password form if a static passwords file (htpasswd file) has been | ||
// configured. | ||
DisplayLoginForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"` | ||
|
||
// Debug renders detailed errors when an error page is shown. | ||
// It is not advised to use this in production as errors may contain sensitive | ||
// information. | ||
// Use only for diagnosing backend errors. | ||
Debug bool `flag:"show-debug-on-error" cfg:"show-debug-on-error"` | ||
} | ||
|
||
func templatesFlagSet() *pflag.FlagSet { | ||
flagSet := pflag.NewFlagSet("templates", pflag.ExitOnError) | ||
|
||
flagSet.String("custom-templates-dir", "", "path to custom html templates") | ||
flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.") | ||
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.") | ||
flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided") | ||
flagSet.Bool("show-debug-on-error", false, "show detailed error information on error pages (WARNING: this may contain sensitive information - do not use in production)") | ||
|
||
return flagSet | ||
} | ||
|
||
// templatesDefaults creates a Templates and populates it with any default values | ||
func templatesDefaults() Templates { | ||
return Templates{ | ||
DisplayLoginForm: true, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package pagewriter | ||
|
||
import ( | ||
"fmt" | ||
"html/template" | ||
"net/http" | ||
|
||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||
) | ||
|
||
// errorMessages are default error messages for each of the the different | ||
// http status codes expected to be rendered in the error page. | ||
var errorMessages = map[int]string{ | ||
http.StatusInternalServerError: "Oops! Something went wrong. For more information contact your server administrator.", | ||
http.StatusNotFound: "We could not find the resource you were looking for.", | ||
http.StatusForbidden: "You do not have permission to access this resource.", | ||
http.StatusUnauthorized: "You need to be logged in to access this resource.", | ||
} | ||
|
||
// errorPageWriter is used to render error pages. | ||
type errorPageWriter struct { | ||
// template is the error page HTML template. | ||
template *template.Template | ||
|
||
// proxyPrefix is the prefix under which OAuth2 Proxy pages are served. | ||
proxyPrefix string | ||
|
||
// footer is the footer to be displayed at the bottom of the page. | ||
// If not set, a default footer will be used. | ||
footer string | ||
|
||
// version is the OAuth2 Proxy version to be used in the default footer. | ||
version string | ||
|
||
// debug determines whether errors pages should be rendered with detailed | ||
// errors. | ||
debug bool | ||
} | ||
|
||
// WriteErrorPage writes an error page to the given response writer. | ||
// It uses the passed redirectURL to give users the option to go back to where | ||
// they originally came from or try signing in again. | ||
func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, status int, redirectURL string, appError string, messages ...interface{}) { | ||
rw.WriteHeader(status) | ||
|
||
// We allow unescaped template.HTML since it is user configured options | ||
/* #nosec G203 */ | ||
data := struct { | ||
Title string | ||
Message string | ||
ProxyPrefix string | ||
StatusCode int | ||
Redirect string | ||
Footer template.HTML | ||
Version string | ||
}{ | ||
Title: http.StatusText(status), | ||
Message: e.getMessage(status, appError, messages...), | ||
ProxyPrefix: e.proxyPrefix, | ||
StatusCode: status, | ||
Redirect: redirectURL, | ||
Footer: template.HTML(e.footer), | ||
Version: e.version, | ||
} | ||
|
||
if err := e.template.Execute(rw, data); err != nil { | ||
logger.Printf("Error rendering error template: %v", err) | ||
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) | ||
} | ||
} | ||
|
||
// ProxyErrorHandler is used by the upstream ReverseProxy to render error pages | ||
// when there are issues with upstream servers. | ||
// It is expected to always render a bad gateway error. | ||
func (e *errorPageWriter) ProxyErrorHandler(rw http.ResponseWriter, req *http.Request, proxyErr error) { | ||
logger.Errorf("Error proxying to upstream server: %v", proxyErr) | ||
e.WriteErrorPage(rw, http.StatusBadGateway, "", proxyErr.Error(), "There was a problem connecting to the upstream server.") | ||
} | ||
|
||
// getMessage creates the message for the template parameters. | ||
// If the errorPagewriter.Debug is enabled, the application error takes precedence. | ||
// Otherwise, any messages will be used. | ||
// The first message is expected to be a format string. | ||
// If no messages are supplied, a default error message will be used. | ||
func (e *errorPageWriter) getMessage(status int, appError string, messages ...interface{}) string { | ||
if e.debug { | ||
return appError | ||
} | ||
if len(messages) > 0 { | ||
format := fmt.Sprintf("%v", messages[0]) | ||
return fmt.Sprintf(format, messages[1:]...) | ||
} | ||
if msg, ok := errorMessages[status]; ok { | ||
return msg | ||
} | ||
return "Unknown error" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package pagewriter | ||
|
||
import ( | ||
"errors" | ||
"html/template" | ||
"io/ioutil" | ||
"net/http/httptest" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
var _ = Describe("Error Page Writer", func() { | ||
var errorPage *errorPageWriter | ||
|
||
BeforeEach(func() { | ||
tmpl, err := template.New("").Parse("{{.Title}} {{.Message}} {{.ProxyPrefix}} {{.StatusCode}} {{.Redirect}} {{.Footer}} {{.Version}}") | ||
Expect(err).ToNot(HaveOccurred()) | ||
|
||
errorPage = &errorPageWriter{ | ||
template: tmpl, | ||
proxyPrefix: "/prefix/", | ||
footer: "Custom Footer Text", | ||
version: "v0.0.0-test", | ||
} | ||
}) | ||
|
||
Context("WriteErrorPage", func() { | ||
It("Writes the template to the response writer", func() { | ||
recorder := httptest.NewRecorder() | ||
errorPage.WriteErrorPage(recorder, 403, "/redirect", "Access Denied") | ||
|
||
body, err := ioutil.ReadAll(recorder.Result().Body) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(string(body)).To(Equal("Forbidden You do not have permission to access this resource. /prefix/ 403 /redirect Custom Footer Text v0.0.0-test")) | ||
}) | ||
|
||
It("With a different code, uses the stock message for the correct code", func() { | ||
recorder := httptest.NewRecorder() | ||
errorPage.WriteErrorPage(recorder, 500, "/redirect", "Access Denied") | ||
|
||
body, err := ioutil.ReadAll(recorder.Result().Body) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(string(body)).To(Equal("Internal Server Error Oops! Something went wrong. For more information contact your server administrator. /prefix/ 500 /redirect Custom Footer Text v0.0.0-test")) | ||
}) | ||
|
||
It("With a message override, uses the message", func() { | ||
recorder := httptest.NewRecorder() | ||
errorPage.WriteErrorPage(recorder, 403, "/redirect", "Access Denied", "An extra message: %s", "with more context.") | ||
|
||
body, err := ioutil.ReadAll(recorder.Result().Body) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(string(body)).To(Equal("Forbidden An extra message: with more context. /prefix/ 403 /redirect Custom Footer Text v0.0.0-test")) | ||
}) | ||
}) | ||
|
||
Context("ProxyErrorHandler", func() { | ||
It("Writes a bad gateway error the response writer", func() { | ||
req := httptest.NewRequest("", "/bad-gateway", nil) | ||
recorder := httptest.NewRecorder() | ||
errorPage.ProxyErrorHandler(recorder, req, errors.New("some upstream error")) | ||
|
||
body, err := ioutil.ReadAll(recorder.Result().Body) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(string(body)).To(Equal("Bad Gateway There was a problem connecting to the upstream server. /prefix/ 502 Custom Footer Text v0.0.0-test")) | ||
}) | ||
}) | ||
|
||
Context("With Debug enabled", func() { | ||
BeforeEach(func() { | ||
tmpl, err := template.New("").Parse("{{.Message}}") | ||
Expect(err).ToNot(HaveOccurred()) | ||
|
||
errorPage.template = tmpl | ||
errorPage.debug = true | ||
}) | ||
|
||
Context("WriteErrorPage", func() { | ||
It("Writes the detailed error in place of the message", func() { | ||
recorder := httptest.NewRecorder() | ||
errorPage.WriteErrorPage(recorder, 403, "/redirect", "Debug error") | ||
|
||
body, err := ioutil.ReadAll(recorder.Result().Body) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(string(body)).To(Equal("Debug error")) | ||
}) | ||
}) | ||
|
||
Context("ProxyErrorHandler", func() { | ||
It("Writes a bad gateway error the response writer", func() { | ||
req := httptest.NewRequest("", "/bad-gateway", nil) | ||
recorder := httptest.NewRecorder() | ||
errorPage.ProxyErrorHandler(recorder, req, errors.New("some upstream error")) | ||
|
||
body, err := ioutil.ReadAll(recorder.Result().Body) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(string(body)).To(Equal("some upstream error")) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package pagewriter | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
// Writer is an interface for rendering html templates for both sign-in and | ||
// error pages. | ||
// It can also be used to write errors for the http.ReverseProxy used in the | ||
// upstream package. | ||
type Writer interface { | ||
WriteSignInPage(rw http.ResponseWriter, redirectURL string) | ||
WriteErrorPage(rw http.ResponseWriter, status int, redirectURL string, appError string, messages ...interface{}) | ||
ProxyErrorHandler(rw http.ResponseWriter, req *http.Request, proxyErr error) | ||
} | ||
|
||
// pageWriter implements the Writer interface | ||
type pageWriter struct { | ||
*errorPageWriter | ||
*signInPageWriter | ||
} | ||
|
||
// Opts contains all options required to configure the template | ||
// rendering within OAuth2 Proxy. | ||
type Opts struct { | ||
// TemplatesPath is the path from which to load custom templates for the sign-in and error pages. | ||
TemplatesPath string | ||
|
||
// ProxyPrefix is the prefix under which OAuth2 Proxy pages are served. | ||
ProxyPrefix string | ||
|
||
// Footer is the footer to be displayed at the bottom of the page. | ||
// If not set, a default footer will be used. | ||
Footer string | ||
|
||
// Version is the OAuth2 Proxy version to be used in the default footer. | ||
Version string | ||
|
||
// Debug determines whether errors pages should be rendered with detailed | ||
// errors. | ||
Debug bool | ||
|
||
// DisplayLoginForm determines whether or not the basic auth password form is displayed on the sign-in page. | ||
DisplayLoginForm bool | ||
|
||
// ProviderName is the name of the provider that should be displayed on the login button. | ||
ProviderName string | ||
|
||
// SignInMessage is the messge displayed above the login button. | ||
SignInMessage string | ||
} | ||
|
||
// NewWriter constructs a Writer from the options given to allow | ||
// rendering of sign-in and error pages. | ||
func NewWriter(opts Opts) (Writer, error) { | ||
templates, err := loadTemplates(opts.TemplatesPath) | ||
if err != nil { | ||
return nil, fmt.Errorf("error loading templates: %v", err) | ||
} | ||
|
||
errorPage := &errorPageWriter{ | ||
template: templates.Lookup("error.html"), | ||
proxyPrefix: opts.ProxyPrefix, | ||
footer: opts.Footer, | ||
version: opts.Version, | ||
debug: opts.Debug, | ||
} | ||
|
||
signInPage := &signInPageWriter{ | ||
template: templates.Lookup("sign_in.html"), | ||
errorPageWriter: errorPage, | ||
proxyPrefix: opts.ProxyPrefix, | ||
providerName: opts.ProviderName, | ||
signInMessage: opts.SignInMessage, | ||
footer: opts.Footer, | ||
version: opts.Version, | ||
displayLoginForm: opts.DisplayLoginForm, | ||
} | ||
|
||
return &pageWriter{ | ||
errorPageWriter: errorPage, | ||
signInPageWriter: signInPage, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package pagewriter | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
func TestOptionsSuite(t *testing.T) { | ||
logger.SetOutput(GinkgoWriter) | ||
logger.SetErrOutput(GinkgoWriter) | ||
|
||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "App Suite") | ||
} |
Oops, something went wrong.