git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add option for custom logos on the sign in page
23e545aJoel Speed
CHANGELOG.md+1
@@ -8,6 +8,7 @@
8
8
9## Changes since v7.0.1
9## Changes since v7.0.1
10
10
11+- [#1056](https://github.com/oauth2-proxy/oauth2-proxy/pull/1056) Add option for custom logos on the sign in page (@JoelSpeed)
11- [#1054](https://github.com/oauth2-proxy/oauth2-proxy/pull/1054) Update to Go 1.16 (@JoelSpeed)
12- [#1054](https://github.com/oauth2-proxy/oauth2-proxy/pull/1054) Update to Go 1.16 (@JoelSpeed)
12- [#1052](https://github.com/oauth2-proxy/oauth2-proxy/pull/1052) Update golangci-lint to latest version (v1.36.0) (@JoelSpeed)
13- [#1052](https://github.com/oauth2-proxy/oauth2-proxy/pull/1052) Update golangci-lint to latest version (v1.36.0) (@JoelSpeed)
13- [#1043](https://github.com/oauth2-proxy/oauth2-proxy/pull/1043) Refactor Sign In Page rendering and capture all page rendering code in pagewriter package (@JoelSpeed)
14- [#1043](https://github.com/oauth2-proxy/oauth2-proxy/pull/1043) Refactor Sign In Page rendering and capture all page rendering code in pagewriter package (@JoelSpeed)
docs/docs/configuration/overview.md+1
@@ -40,6 +40,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
40| `--cookie-secure` | bool | set [secure (HTTPS only) cookie flag](https://owasp.org/www-community/controls/SecureFlag) | true |
40| `--cookie-secure` | bool | set [secure (HTTPS only) cookie flag](https://owasp.org/www-community/controls/SecureFlag) | true |
41| `--cookie-samesite` | string | set SameSite cookie attribute (`"lax"`, `"strict"`, `"none"`, or `""`). | `""` |
41| `--cookie-samesite` | string | set SameSite cookie attribute (`"lax"`, `"strict"`, `"none"`, or `""`). | `""` |
42| `--custom-templates-dir` | string | path to custom html templates | |
42| `--custom-templates-dir` | string | path to custom html templates | |
43+| `--custom-sign-in-logo` | string | path to an custom image for the sign_in page logo. Use \"-\" to disable default logo. |
43| `--display-htpasswd-form` | bool | display username / password login form if an htpasswd file is provided | true |
44| `--display-htpasswd-form` | bool | display username / password login form if an htpasswd file is provided | true |
44| `--email-domain` | string \| list | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | |
45| `--email-domain` | string \| list | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | |
45| `--errors-to-info-log` | bool | redirects error-level logging to default log channel instead of stderr | |
46| `--errors-to-info-log` | bool | redirects error-level logging to default log channel instead of stderr | |
oauthproxy.go+1
@@ -123,6 +123,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
123
123
124 pageWriter, err := pagewriter.NewWriter(pagewriter.Opts{
124 pageWriter, err := pagewriter.NewWriter(pagewriter.Opts{
125 TemplatesPath: opts.Templates.Path,
125 TemplatesPath: opts.Templates.Path,
126+ CustomLogo: opts.Templates.CustomLogo,
126 ProxyPrefix: opts.ProxyPrefix,
127 ProxyPrefix: opts.ProxyPrefix,
127 Footer: opts.Templates.Footer,
128 Footer: opts.Templates.Footer,
128 Version: VERSION,
129 Version: VERSION,
pkg/apis/options/app.go+7
@@ -11,6 +11,12 @@ type Templates struct {
11 // If either file is missing, the default will be used instead.
11 // If either file is missing, the default will be used instead.
12 Path string `flag:"custom-templates-dir" cfg:"custom_templates_dir"`
12 Path string `flag:"custom-templates-dir" cfg:"custom_templates_dir"`
13
13
14+ // CustomLogo is the path to a logo that should replace the default logo
15+ // on the sign_in page template.
16+ // Supported formats are .svg, .png, .jpg and .jpeg.
17+ // To disable the default logo, set this value to "-".
18+ CustomLogo string `flag:"custom-sign-in-logo" cfg:"custom_sign_in_logo"`
19+
14 // Banner overides the default sign_in page banner text. If unspecified,
20 // Banner overides the default sign_in page banner text. If unspecified,
15 // the message will give users a list of allowed email domains.
21 // the message will give users a list of allowed email domains.
16 Banner string `flag:"banner" cfg:"banner"`
22 Banner string `flag:"banner" cfg:"banner"`
@@ -34,6 +40,7 @@ func templatesFlagSet() *pflag.FlagSet {
34 flagSet := pflag.NewFlagSet("templates", pflag.ExitOnError)
40 flagSet := pflag.NewFlagSet("templates", pflag.ExitOnError)
35
41
36 flagSet.String("custom-templates-dir", "", "path to custom html templates")
42 flagSet.String("custom-templates-dir", "", "path to custom html templates")
43+ flagSet.String("custom-sign-in-logo", "", "path to an custom image for the sign_in page logo. Use \"-\" to disable default logo.")
37 flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.")
44 flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.")
38 flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
45 flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
39 flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
46 flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
pkg/app/pagewriter/default_logo.svgnew file+1
@@ -0,0 +1 @@
1+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 840.55645 412.45904"><defs><style>.cls-1{fill:#333;}.cls-2{fill:#7c7c82;}.cls-3{fill:#06ed94;}.cls-4{fill:#059b61;}.cls-5{fill:#1957ba;}.cls-6{fill:#6ca8ff;}</style></defs><title>OAuth2_Proxy_logo_v3</title><path class="cls-1" d="M474.20933,196.63116a41.46679,41.46679,0,0,1-16.33506-3.18964,39.80757,39.80757,0,0,1-21.67092-21.83518,42.99409,42.99409,0,0,1,0-32.23,39.80765,39.80765,0,0,1,21.67092-21.83518,43.41665,43.41665,0,0,1,32.67012,0,39.8101,39.8101,0,0,1,21.66989,21.83518,42.994,42.994,0,0,1,0,32.23,39.81,39.81,0,0,1-21.66989,21.83518A41.485,41.485,0,0,1,474.20933,196.63116Zm0-14.07985a26.63714,26.63714,0,0,0,10.28431-1.9799,25.89889,25.89889,0,0,0,8.36072-5.50012,25.04229,25.04229,0,0,0,5.60963-8.525,31.04294,31.04294,0,0,0,0-22.11,25.07963,25.07963,0,0,0-5.60963-8.52549,25.9145,25.9145,0,0,0-8.36072-5.49961,27.6962,27.6962,0,0,0-20.56965,0,25.89233,25.89233,0,0,0-8.36072,5.49961,25.03434,25.03434,0,0,0-5.60962,8.52549,31.04279,31.04279,0,0,0,0,22.11,24.99714,24.99714,0,0,0,5.60962,8.525,25.87675,25.87675,0,0,0,8.36072,5.50012A26.63112,26.63112,0,0,0,474.20933,182.55131Z"/><path class="cls-1" d="M548.12931,116.11083h16.94044l29.59051,78.76048H578.26942L571.77961,176.061H541.41945l-6.48981,18.81033H518.53983Zm18.81032,46.3104-7.03941-19.69-2.8606-9.56994h-.88018l-2.85957,9.56994-7.04044,19.69Z"/><path class="cls-1" d="M636.67873,187.83138h-.88019a18.26469,18.26469,0,0,1-6.82039,6.43506,19.94541,19.94541,0,0,1-9.78947,2.36472q-10.23059,0-15.23483-6.26977-5.00682-6.27052-5.00527-16.7204V140.97087H613.359v30.91027q0,5.832,2.58477,8.635,2.58321,2.80482,7.53425,2.80533a10.50006,10.50006,0,0,0,5.22532-1.265,12.21407,12.21407,0,0,0,3.85029-3.41019,15.44293,15.44293,0,0,0,2.41947-5.06,22.69746,22.69746,0,0,0,.82543-6.215V140.97087H650.209v53.90044H636.67873Z"/><path class="cls-1" d="M669.01723,153.29138h-9.461V140.97087h9.461V124.471h14.4094v16.49984h13.2007v12.32051h-13.2007V174.829a15.54561,15.54561,0,0,0,.38534,3.57135,5.7936,5.7936,0,0,0,1.48454,2.77744,5.44914,5.44914,0,0,0,4.39988,1.81357,8.00547,8.00547,0,0,0,5.06-1.4303l4.07034,12.65007a23.83924,23.83924,0,0,1-5.44434,1.81512,31.8029,31.8029,0,0,1-6.43505.60487,19.66528,19.66528,0,0,1-7.31524-1.29084,15.54151,15.54151,0,0,1-5.44537-3.53675q-5.17107-5.05331-5.16953-14.37272Z"/><path class="cls-1" d="M707.735,116.11083h14.4094v22.1105l-.87915,9.79h.87915a17.85447,17.85447,0,0,1,6.65511-6.325,19.31492,19.31492,0,0,1,9.73574-2.47526q10.44909,0,15.45488,6.16026,5.00371,6.16128,5.00527,16.8299v32.67013H744.585V163.961q0-5.60962-2.64055-8.525a9.44018,9.44018,0,0,0-7.37-2.91484,10.82482,10.82482,0,0,0-5.28007,1.265,11.76092,11.76092,0,0,0-3.904,3.465,16.09429,16.09429,0,0,0-2.4205,5.11478,22.748,22.748,0,0,0-.82544,6.215v26.29034H707.735Z"/><path class="cls-1" d="M769.77353,194.87131V181.78115q6.48979-6.37824,11.98992-11.87991,2.31048-2.3097,4.73048-4.73,2.4174-2.41818,4.50939-4.50991,2.08888-2.08889,3.74078-3.795,1.6488-1.70458,2.53-2.69531,1.75726-2.08811,3.13436-3.73975a18.16413,18.16413,0,0,0,2.20046-3.24542,16.47472,16.47472,0,0,0,1.26449-3.18963,13.02445,13.02445,0,0,0,.44009-3.465,8.4276,8.4276,0,0,0-2.85956-6.325,10.93308,10.93308,0,0,0-7.81009-2.6953q-4.951,0-7.70058,2.80533a14.59773,14.59773,0,0,0-3.73975,6.325l-13.09015-5.3901a24.55547,24.55547,0,0,1,2.8606-6.105,22.76637,22.76637,0,0,1,5.06-5.61014,26.44875,26.44875,0,0,1,7.31421-4.07034,26.86184,26.86184,0,0,1,9.5157-1.59456,29.71878,29.71878,0,0,1,10.3401,1.70458,23.00883,23.00883,0,0,1,7.81008,4.67521,20.92923,20.92923,0,0,1,4.94949,6.98517,21.28551,21.28551,0,0,1,1.76037,8.635,25.00612,25.00612,0,0,1-3.465,12.92486,56.87664,56.87664,0,0,1-8.41546,10.945l-9.45992,9.46043q-3.74077,3.74077-8.47022,8.35968l.44009.66014h30.4707v12.65007Z"/><path class="cls-2" d="M447.03623,265.05824v31.9h-10.12V218.19774h26.84045a24.25664,24.25664,0,0,1,17.38054,6.82039,22.75761,22.75761,0,0,1,0,33.32974,24.22463,24.22463,0,0,1-17.38054,6.71037Zm0-37.18055v27.50059h16.94045a13.378,13.378,0,0,0,10.01054-4.07033,13.80577,13.80577,0,0,0-10.01054-23.43026Z"/><path class="cls-2" d="M507.31645,296.95821h-10.12V243.05777h9.68v8.80029h.44009a14.60805,14.60805,0,0,1,6.32451-7.31523,17.56035,17.56035,0,0,1,9.40516-3.02486,18.97706,18.97706,0,0,1,7.48054,1.31976l-3.08065,9.7905a16.79182,16.79182,0,0,0-5.94021-.77017A13.09674,13.09674,0,0,0,511.551,256.478a15.45278,15.45278,0,0,0-4.23459,10.78019Z"/><path class="cls-2" d="M530.74567,270.00825q0-12.42951,7.81009-20.57018a26.6222,26.6222,0,0,1,19.91055-8.14015q11.98785,0,19.8,8.14015,7.92011,8.14172,7.91959,20.57018,0,12.53952-7.91959,20.57017-7.81164,8.1417-19.8,8.13964a26.616,26.616,0,0,1-19.91055-8.13964Q530.74412,282.439,530.74567,270.00825Zm10.12,0q0,8.691,5.06,14.07984a17.28222,17.28222,0,0,0,25.08008,0q5.05952-5.388,5.06-14.07984,0-8.58025-5.06-13.97034a17.04544,17.04544,0,0,0-25.08008,0Q540.86469,261.429,540.86572,270.00825Z"/><path class="cls-2" d="M619.95626,269.56816l19.25041,27.39005H627.32625l-13.3102-19.91-13.09016,19.91H589.04651l19.03036-27.39005L589.4866,243.05777h11.43929l13.31021,19.03037,12.65006-19.03037h11.88042Z"/><path class="cls-2" d="M694.4248,243.05777l-33.77035,77.66076H650.20484L662.74437,293.548l-22.21949-50.49024h11.00024l16.06026,38.72036h.219l15.62017-38.72036Z"/><path class="cls-3" d="M211.931,392.63536c-101.38027,0-183.85932-82.47852-183.85932-183.8588S110.55071,24.91776,211.931,24.91776,395.7903,107.39629,395.7903,208.77656,313.31125,392.63536,211.931,392.63536Zm0-354.70389c-94.2045,0-170.84457,76.64111-170.84457,170.84509S117.72648,379.62165,211.931,379.62165s170.84457-76.6411,170.84457-170.84509S306.13548,37.93147,211.931,37.93147Z"/><polygon class="cls-4" points="244.602 112.007 239.295 131.843 281.959 166.856 322.055 161.912 244.602 112.007"/><polygon class="cls-3" points="348.449 195.562 348.449 158.734 290.209 109.179 244.602 112.007 300.209 156.67 76.642 156.67 76.642 198.05 348.449 195.562"/><polygon class="cls-5" points="180.489 297.175 185.796 277.34 141.87 240.558 103.037 247.271 180.489 297.175"/><polygon class="cls-6" points="76.642 213.62 76.642 250.448 134.882 300.004 180.489 297.175 124.882 252.512 348.449 252.512 348.449 211.132 76.642 213.62"/></svg>
pkg/app/pagewriter/pagewriter.go+10
@@ -49,6 +49,10 @@ type Opts struct {
49
49
50 // SignInMessage is the messge displayed above the login button.
50 // SignInMessage is the messge displayed above the login button.
51 SignInMessage string
51 SignInMessage string
52+
53+ // CustomLogo is the path to a logo to be displayed on the sign in page.
54+ // The logo can be either PNG, JPG/JPEG or SVG.
55+ CustomLogo string
52}
56}
53
57
54// NewWriter constructs a Writer from the options given to allow
58// NewWriter constructs a Writer from the options given to allow
@@ -59,6 +63,11 @@ func NewWriter(opts Opts) (Writer, error) {
59 return nil, fmt.Errorf("error loading templates: %v", err)
63 return nil, fmt.Errorf("error loading templates: %v", err)
60 }
64 }
61
65
66+ logoData, err := loadCustomLogo(opts.CustomLogo)
67+ if err != nil {
68+ return nil, fmt.Errorf("error loading logo: %v", err)
69+ }
70+
62 errorPage := &errorPageWriter{
71 errorPage := &errorPageWriter{
63 template: templates.Lookup("error.html"),
72 template: templates.Lookup("error.html"),
64 proxyPrefix: opts.ProxyPrefix,
73 proxyPrefix: opts.ProxyPrefix,
@@ -76,6 +85,7 @@ func NewWriter(opts Opts) (Writer, error) {
76 footer: opts.Footer,
85 footer: opts.Footer,
77 version: opts.Version,
86 version: opts.Version,
78 displayLoginForm: opts.DisplayLoginForm,
87 displayLoginForm: opts.DisplayLoginForm,
88+ logoData: logoData,
79 }
89 }
80
90
81 return &pageWriter{
91 return &pageWriter{
pkg/app/pagewriter/sign_in.html+9
@@ -15,6 +15,9 @@
15 max-width: 400px;
15 max-width: 400px;
16 margin: 1.25rem auto;
16 margin: 1.25rem auto;
17 }
17 }
18+ .logo-box {
19+ margin: 1.5rem 3rem;
20+ }
18 footer a {
21 footer a {
19 text-decoration: underline;
22 text-decoration: underline;
20 }
23 }
@@ -40,6 +43,12 @@
40 <body class="has-background-light">
43 <body class="has-background-light">
41 <section class="section">
44 <section class="section">
42 <div class="box block sign-in-box has-text-centered">
45 <div class="box block sign-in-box has-text-centered">
46+ {{ if .LogoData }}
47+ <div class="block logo-box">
48+ {{.LogoData}}
49+ </div>
50+ {{ end }}
51+
43 <form method="GET" action="{{.ProxyPrefix}}/start">
52 <form method="GET" action="{{.ProxyPrefix}}/start">
44 <input type="hidden" name="rd" value="{{.Redirect}}">
53 <input type="hidden" name="rd" value="{{.Redirect}}">
45 {{ if .SignInMessage }}
54 {{ if .SignInMessage }}
pkg/app/pagewriter/sign_in_page.go+57
@@ -1,12 +1,24 @@
1package pagewriter
1package pagewriter
2
2
3import (
3import (
4+ // Import embed to allow importing default logo
5+ _ "embed"
6+
7+ "encoding/base64"
8+ "fmt"
9+ "os"
10+ "path/filepath"
11+ "strings"
12+
4 "html/template"
13 "html/template"
5 "net/http"
14 "net/http"
6
15
7 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
8)
17)
9
18
19+//go:embed default_logo.svg
20+var defaultLogoData string
21+
10// signInPageWriter is used to render sign-in pages.
22// signInPageWriter is used to render sign-in pages.
11type signInPageWriter struct {
23type signInPageWriter struct {
12 // Template is the sign-in page HTML template.
24 // Template is the sign-in page HTML template.
@@ -33,6 +45,10 @@ type signInPageWriter struct {
33
45
34 // DisplayLoginForm determines whether or not the basic auth password form is displayed on the sign-in page.
46 // DisplayLoginForm determines whether or not the basic auth password form is displayed on the sign-in page.
35 displayLoginForm bool
47 displayLoginForm bool
48+
49+ // LogoData is the logo to render in the template.
50+ // This should contain valid html.
51+ logoData string
36}
52}
37
53
38// WriteSignInPage writes the sign-in page to the given response writer.
54// WriteSignInPage writes the sign-in page to the given response writer.
@@ -48,6 +64,7 @@ func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, redirectURL s
48 Version string
64 Version string
49 ProxyPrefix string
65 ProxyPrefix string
50 Footer template.HTML
66 Footer template.HTML
67+ LogoData template.HTML
51 }{
68 }{
52 ProviderName: s.providerName,
69 ProviderName: s.providerName,
53 SignInMessage: template.HTML(s.signInMessage),
70 SignInMessage: template.HTML(s.signInMessage),
@@ -56,6 +73,7 @@ func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, redirectURL s
56 Version: s.version,
73 Version: s.version,
57 ProxyPrefix: s.proxyPrefix,
74 ProxyPrefix: s.proxyPrefix,
58 Footer: template.HTML(s.footer),
75 Footer: template.HTML(s.footer),
76+ LogoData: template.HTML(s.logoData),
59 }
77 }
60
78
61 err := s.template.Execute(rw, t)
79 err := s.template.Execute(rw, t)
@@ -64,3 +82,42 @@ func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, redirectURL s
64 s.errorPageWriter.WriteErrorPage(rw, http.StatusInternalServerError, redirectURL, err.Error())
82 s.errorPageWriter.WriteErrorPage(rw, http.StatusInternalServerError, redirectURL, err.Error())
65 }
83 }
66}
84}
85+
86+// loadCustomLogo loads the logo file from the path and encodes it to an HTML
87+// entity. If no custom logo is provided, the OAuth2 Proxy Icon is used instead.
88+func loadCustomLogo(logoPath string) (string, error) {
89+ if logoPath == "" {
90+ // The default logo is an SVG so this will be valid to just return.
91+ return defaultLogoData, nil
92+ }
93+
94+ if logoPath == "-" {
95+ // Return no logo when the custom logo is set to `-`.
96+ // This disables the logo rendering.
97+ return "", nil
98+ }
99+
100+ logoData, err := os.ReadFile(logoPath)
101+ if err != nil {
102+ return "", fmt.Errorf("could not read logo file: %v", err)
103+ }
104+
105+ extension := strings.ToLower(filepath.Ext(logoPath))
106+ switch extension {
107+ case ".svg":
108+ return string(logoData), nil
109+ case ".jpg", ".jpeg":
110+ return encodeImg(logoData, "jpeg"), nil
111+ case ".png":
112+ return encodeImg(logoData, "png"), nil
113+ default:
114+ return "", fmt.Errorf("unknown extension: %q, supported extensions are .svg, .jpg, .jpeg and .png", extension)
115+ }
116+}
117+
118+// encodeImg takes the raw image data and converts it to an HTML Img tag with
119+// a base64 data source.
120+func encodeImg(data []byte, format string) string {
121+ b64Data := base64.StdEncoding.EncodeToString(data)
122+ return fmt.Sprintf("<img src=\"data:image/%s;base64,%s\" alt=\"Logo\" />", format, b64Data)
123+}
pkg/app/pagewriter/sign_in_page_test.go+131−38
@@ -1,61 +1,154 @@
1package pagewriter
1package pagewriter
2
2
3import (
3import (
4+ "errors"
5+ "fmt"
4 "html/template"
6 "html/template"
5 "io/ioutil"
7 "io/ioutil"
6 "net/http/httptest"
8 "net/http/httptest"
9+ "os"
10+ "path/filepath"
11+ "strings"
7
12
8 . "github.com/onsi/ginkgo"
13 . "github.com/onsi/ginkgo"
14+ . "github.com/onsi/ginkgo/extensions/table"
9 . "github.com/onsi/gomega"
15 . "github.com/onsi/gomega"
10)
16)
11
17
12−var _ = Describe("SignIn Page Writer", func() {
18+var _ = Describe("SignIn Page", func() {
13− var signInPage *signInPageWriter
14
19
15− BeforeEach(func() {
20+ Context("SignIn Page Writer", func() {
16− errorTmpl, err := template.New("").Parse("{{.Title}}")
17− Expect(err).ToNot(HaveOccurred())
18− errorPage := &errorPageWriter{
19− template: errorTmpl,
20− }
21−
22− tmpl, err := template.New("").Parse("{{.ProxyPrefix}} {{.ProviderName}} {{.SignInMessage}} {{.Footer}} {{.Version}} {{.Redirect}} {{.CustomLogin}}")
23− Expect(err).ToNot(HaveOccurred())
24−
25− signInPage = &signInPageWriter{
21+ var signInPage *signInPageWriter
26− template: tmpl,
27− errorPageWriter: errorPage,
28− proxyPrefix: "/prefix/",
29− providerName: "My Provider",
30− signInMessage: "Sign In Here",
31− footer: "Custom Footer Text",
32− version: "v0.0.0-test",
33− displayLoginForm: true,
34− }
35− })
36
22
37− Context("WriteSignInPage", func() {
23+ BeforeEach(func() {
38− It("Writes the template to the response writer", func() {
24+ errorTmpl, err := template.New("").Parse("{{.Title}}")
39− recorder := httptest.NewRecorder()
25+ Expect(err).ToNot(HaveOccurred())
40− signInPage.WriteSignInPage(recorder, "/redirect")
26+ errorPage := &errorPageWriter{
27+ template: errorTmpl,
28+ }
41
29
42− body, err := ioutil.ReadAll(recorder.Result().Body)
30+ tmpl, err := template.New("").Parse("{{.ProxyPrefix}} {{.ProviderName}} {{.SignInMessage}} {{.Footer}} {{.Version}} {{.Redirect}} {{.CustomLogin}} {{.LogoData}}")
43 Expect(err).ToNot(HaveOccurred())
31 Expect(err).ToNot(HaveOccurred())
44− Expect(string(body)).To(Equal("/prefix/ My Provider Sign In Here Custom Footer Text v0.0.0-test /redirect true"))
32+
33+ signInPage = &signInPageWriter{
34+ template: tmpl,
35+ errorPageWriter: errorPage,
36+ proxyPrefix: "/prefix/",
37+ providerName: "My Provider",
38+ signInMessage: "Sign In Here",
39+ footer: "Custom Footer Text",
40+ version: "v0.0.0-test",
41+ displayLoginForm: true,
42+ logoData: "Logo Data",
43+ }
45 })
44 })
46
45
46+ Context("WriteSignInPage", func() {
47+ It("Writes the template to the response writer", func() {
48+ recorder := httptest.NewRecorder()
49+ signInPage.WriteSignInPage(recorder, "/redirect")
50+
51+ body, err := ioutil.ReadAll(recorder.Result().Body)
52+ Expect(err).ToNot(HaveOccurred())
53+ Expect(string(body)).To(Equal("/prefix/ My Provider Sign In Here Custom Footer Text v0.0.0-test /redirect true Logo Data"))
54+ })
55+
47− It("Writes an error if the template can't be rendered", func() {
56+ It("Writes an error if the template can't be rendered", func() {
48− // Overwrite the template with something bad
57+ // Overwrite the template with something bad
49− tmpl, err := template.New("").Parse("{{.Unknown}}")
58+ tmpl, err := template.New("").Parse("{{.Unknown}}")
50− Expect(err).ToNot(HaveOccurred())
59+ Expect(err).ToNot(HaveOccurred())
51− signInPage.template = tmpl
60+ signInPage.template = tmpl
52
61
53− recorder := httptest.NewRecorder()
62+ recorder := httptest.NewRecorder()
54− signInPage.WriteSignInPage(recorder, "/redirect")
63+ signInPage.WriteSignInPage(recorder, "/redirect")
55
64
56− body, err := ioutil.ReadAll(recorder.Result().Body)
65+ body, err := ioutil.ReadAll(recorder.Result().Body)
66+ Expect(err).ToNot(HaveOccurred())
67+ Expect(string(body)).To(Equal("Internal Server Error"))
68+ })
69+ })
70+ })
71+
72+ Context("loadCustomLogo", func() {
73+ var customDir string
74+
75+ const fakeImageData = "Fake Image Data"
76+
77+ BeforeEach(func() {
78+ var err error
79+ customDir, err = ioutil.TempDir("", "oauth2-proxy-sign-in-page-test")
57 Expect(err).ToNot(HaveOccurred())
80 Expect(err).ToNot(HaveOccurred())
58− Expect(string(body)).To(Equal("Internal Server Error"))
81+
82+ for _, ext := range []string{".svg", ".png", ".jpg", ".jpeg", ".gif"} {
83+ fileName := filepath.Join(customDir, fmt.Sprintf("logo%s", ext))
84+ Expect(ioutil.WriteFile(fileName, []byte(fakeImageData), 0600)).To(Succeed())
85+ }
86+ })
87+
88+ AfterEach(func() {
89+ Expect(os.RemoveAll(customDir)).To(Succeed())
59 })
90 })
91+
92+ type loadCustomLogoTableInput struct {
93+ logoPath string
94+ expectedErr error
95+ expectedData string
96+ }
97+
98+ DescribeTable("should load the logo based on configuration", func(in loadCustomLogoTableInput) {
99+ logoPath := in.logoPath
100+ if strings.HasPrefix(logoPath, "customDir/") {
101+ logoPath = filepath.Join(customDir, strings.TrimLeft(logoPath, "customDir/"))
102+ }
103+
104+ data, err := loadCustomLogo(logoPath)
105+ if in.expectedErr != nil {
106+ Expect(err).To(MatchError(in.expectedErr.Error()))
107+ } else {
108+ Expect(err).ToNot(HaveOccurred())
109+ }
110+ Expect(data).To(Equal(in.expectedData))
111+ },
112+ Entry("with no custom logo path", loadCustomLogoTableInput{
113+ logoPath: "",
114+ expectedErr: nil,
115+ expectedData: defaultLogoData,
116+ }),
117+ Entry("when disabling the logo display", loadCustomLogoTableInput{
118+ logoPath: "-",
119+ expectedErr: nil,
120+ expectedData: "",
121+ }),
122+ Entry("with an svg custom logo", loadCustomLogoTableInput{
123+ logoPath: "customDir/logo.svg",
124+ expectedErr: nil,
125+ expectedData: fakeImageData,
126+ }),
127+ Entry("with a png custom logo", loadCustomLogoTableInput{
128+ logoPath: "customDir/logo.png",
129+ expectedErr: nil,
130+ expectedData: "<img src=\"data:image/png;base64,RmFrZSBJbWFnZSBEYXRh\" alt=\"Logo\" />",
131+ }),
132+ Entry("with a jpg custom logo", loadCustomLogoTableInput{
133+ logoPath: "customDir/logo.jpg",
134+ expectedErr: nil,
135+ expectedData: "<img src=\"data:image/jpeg;base64,RmFrZSBJbWFnZSBEYXRh\" alt=\"Logo\" />",
136+ }),
137+ Entry("with a jpeg custom logo", loadCustomLogoTableInput{
138+ logoPath: "customDir/logo.jpeg",
139+ expectedErr: nil,
140+ expectedData: "<img src=\"data:image/jpeg;base64,RmFrZSBJbWFnZSBEYXRh\" alt=\"Logo\" />",
141+ }),
142+ Entry("with a gif custom logo", loadCustomLogoTableInput{
143+ logoPath: "customDir/logo.gif",
144+ expectedErr: errors.New("unknown extension: \".gif\", supported extensions are .svg, .jpg, .jpeg and .png"),
145+ expectedData: "",
146+ }),
147+ Entry("when the logo does not exist", loadCustomLogoTableInput{
148+ logoPath: "unknown.svg",
149+ expectedErr: errors.New("could not read logo file: open unknown.svg: no such file or directory"),
150+ expectedData: "",
151+ }),
152+ )
60 })
153 })
61})
154})
pkg/app/pagewriter/templates_test.go+2
@@ -45,6 +45,7 @@ var _ = Describe("Templates", func() {
45 SignInMessage string
45 SignInMessage string
46 ProviderName string
46 ProviderName string
47 CustomLogin bool
47 CustomLogin bool
48+ LogoData string
48
49
49 // For default error template
50 // For default error template
50 StatusCode int
51 StatusCode int
@@ -61,6 +62,7 @@ var _ = Describe("Templates", func() {
61 SignInMessage: "<sign-in-message>",
62 SignInMessage: "<sign-in-message>",
62 ProviderName: "<provider-name>",
63 ProviderName: "<provider-name>",
63 CustomLogin: false,
64 CustomLogin: false,
65+ LogoData: "<logo>",
64
66
65 StatusCode: 404,
67 StatusCode: 404,
66 Title: "<title>",
68 Title: "<title>",