From 10e90548119bd8e230f46d677418ffe30d191eca Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Wed, 15 Jan 2020 13:04:48 -0500 Subject: [PATCH 1/5] Use http.FileSystem for web assets Signed-off-by: Rui Yang Co-authored-by: Aidan Oldershaw --- cmd/dex/config_test.go | 4 - server/handlers_test.go | 2 +- server/server.go | 19 +--- server/server_test.go | 4 +- server/templates.go | 219 ++++++++++++++++---------------------- web/templates/header.html | 4 +- 6 files changed, 104 insertions(+), 148 deletions(-) diff --git a/cmd/dex/config_test.go b/cmd/dex/config_test.go index 8ee02d5a..9a36afcf 100644 --- a/cmd/dex/config_test.go +++ b/cmd/dex/config_test.go @@ -74,7 +74,6 @@ web: http: 127.0.0.1:5556 frontend: - dir: ./web extra: foo: bar @@ -144,7 +143,6 @@ logger: HTTP: "127.0.0.1:5556", }, Frontend: server.WebConfig{ - Dir: "./web", Extra: map[string]string{ "foo": "bar", }, @@ -274,7 +272,6 @@ web: http: 127.0.0.1:5556 frontend: - dir: ./web extra: foo: bar @@ -352,7 +349,6 @@ logger: HTTP: "127.0.0.1:5556", }, Frontend: server.WebConfig{ - Dir: "./web", Extra: map[string]string{ "foo": "bar", }, diff --git a/server/handlers_test.go b/server/handlers_test.go index 8ad59d94..96d228af 100644 --- a/server/handlers_test.go +++ b/server/handlers_test.go @@ -135,7 +135,7 @@ func TestHandleInvalidSAMLCallbacks(t *testing.T) { func TestConnectorLoginDoesNotAllowToChangeConnectorForAuthRequest(t *testing.T) { memStorage := memory.New(logger) - templates, err := loadTemplates(webConfig{}, "../web/templates") + templates, err := loadTemplates(WebConfig{Dir: http.Dir("../web")}, "../web/templates") if err != nil { t.Fatal("failed to load templates") } diff --git a/server/server.go b/server/server.go index a79b7cfd..79c5f5a0 100644 --- a/server/server.go +++ b/server/server.go @@ -108,7 +108,7 @@ type WebConfig struct { // * templates - HTML templates controlled by dex. // * themes/(theme) - Static static served at "( issuer URL )/theme". // - Dir string + Dir http.FileSystem // Defaults to "( issuer URL )/theme/logo.png" LogoURL string @@ -203,18 +203,9 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) supported[respType] = true } - web := webConfig{ - dir: c.Web.Dir, - logoURL: c.Web.LogoURL, - issuerURL: c.Issuer, - issuer: c.Web.Issuer, - theme: c.Web.Theme, - extra: c.Web.Extra, - } - - static, theme, tmpls, err := loadWebConfig(web) + tmpls, err := loadTemplates(c.Web, issuerURL.Path) if err != nil { - return nil, fmt.Errorf("server: failed to load web static: %v", err) + return nil, fmt.Errorf("server: failed to load templates: %v", err) } now := c.Now @@ -343,8 +334,8 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) } fmt.Fprintf(w, "Health check passed") })) - handlePrefix("/static", static) - handlePrefix("/theme", theme) + handlePrefix("/", http.FileServer(c.Web.Dir)) + s.mux = r s.startKeyRotation(ctx, rotationStrategy, now) diff --git a/server/server_test.go b/server/server_test.go index 87ca6c17..b0bacfd3 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -93,7 +93,7 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi Issuer: s.URL, Storage: memory.New(logger), Web: WebConfig{ - Dir: "../web", + Dir: http.Dir("../web"), }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), @@ -132,7 +132,7 @@ func newTestServerMultipleConnectors(ctx context.Context, t *testing.T, updateCo Issuer: s.URL, Storage: memory.New(logger), Web: WebConfig{ - Dir: "../web", + Dir: http.Dir("../web"), }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), diff --git a/server/templates.go b/server/templates.go index bed1c6c8..b23083ba 100644 --- a/server/templates.go +++ b/server/templates.go @@ -1,13 +1,12 @@ package server import ( + "bytes" "fmt" "html/template" "io" - "io/ioutil" "net/http" "net/url" - "os" "path" "path/filepath" "sort" @@ -22,18 +21,10 @@ const ( tmplError = "error.html" tmplDevice = "device.html" tmplDeviceSuccess = "device_success.html" + tmplHeader = "header.html" + tmplFooter = "footer.html" ) -var requiredTmpls = []string{ - tmplApproval, - tmplLogin, - tmplPassword, - tmplOOB, - tmplError, - tmplDevice, - tmplDeviceSuccess, -} - type templates struct { loginTmpl *template.Template approvalTmpl *template.Template @@ -44,131 +35,93 @@ type templates struct { deviceSuccessTmpl *template.Template } -type webConfig struct { - dir string - logoURL string - issuer string - theme string - issuerURL string - extra map[string]string -} - -func dirExists(dir string) error { - stat, err := os.Stat(dir) - if err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("directory %q does not exist", dir) - } - return fmt.Errorf("stat directory %q: %v", dir, err) - } - if !stat.IsDir() { - return fmt.Errorf("path %q is a file not a directory", dir) - } - return nil -} - -// loadWebConfig returns static assets, theme assets, and templates used by the frontend by -// reading the directory specified in the webConfig. -// -// The directory layout is expected to be: -// -// ( web directory ) -// |- static -// |- themes -// | |- (theme name) -// |- templates -// -func loadWebConfig(c webConfig) (http.Handler, http.Handler, *templates, error) { - // fallback to the default theme if the legacy theme name is provided - if c.theme == "coreos" || c.theme == "tectonic" { - c.theme = "" - } - if c.theme == "" { - c.theme = "light" - } - if c.issuer == "" { - c.issuer = "dex" - } - if c.dir == "" { - c.dir = "./web" - } - if c.logoURL == "" { - c.logoURL = "theme/logo.png" - } - - if err := dirExists(c.dir); err != nil { - return nil, nil, nil, fmt.Errorf("load web dir: %v", err) - } - - staticDir := filepath.Join(c.dir, "static") - templatesDir := filepath.Join(c.dir, "templates") - themeDir := filepath.Join(c.dir, "themes", c.theme) - - for _, dir := range []string{staticDir, templatesDir, themeDir} { - if err := dirExists(dir); err != nil { - return nil, nil, nil, fmt.Errorf("load dir: %v", err) - } - } - - static := http.FileServer(http.Dir(staticDir)) - theme := http.FileServer(http.Dir(themeDir)) - - templates, err := loadTemplates(c, templatesDir) - return static, theme, templates, err -} - // loadTemplates parses the expected templates from the provided directory. -func loadTemplates(c webConfig, templatesDir string) (*templates, error) { - files, err := ioutil.ReadDir(templatesDir) +func loadTemplates(c WebConfig, issuerPath string) (*templates, error) { + // fallback to the default theme if the legacy theme name is provided + if c.Theme == "coreos" || c.Theme == "tectonic" { + c.Theme = "" + } + if c.Theme == "" { + c.Theme = "light" + } + + if c.Issuer == "" { + c.Issuer = "dex" + } + + if c.LogoURL == "" { + c.LogoURL = "theme/logo.png" + } + + funcs := template.FuncMap{ + "issuer": func() string { return c.Issuer }, + "logo": func() string { return c.LogoURL }, + "url": func(reqPath, assetPath string) string { return relativeURL(issuerPath, reqPath, assetPath) }, + "theme": func(reqPath, assetPath string) string { + return relativeURL(issuerPath, reqPath, path.Join("themes", c.Theme, assetPath)) + }, + "lower": strings.ToLower, + "extra": func(k string) string { return c.Extra[k] }, + } + + group := template.New("") + + // load all of our templates individually. + // some http.FilSystem implementations don't implement Readdir + + loginTemplate, err := loadTemplate(c.Dir, tmplLogin, funcs, group) if err != nil { - return nil, fmt.Errorf("read dir: %v", err) + return nil, err } - filenames := []string{} - for _, file := range files { - if file.IsDir() { - continue - } - filenames = append(filenames, filepath.Join(templatesDir, file.Name())) - } - if len(filenames) == 0 { - return nil, fmt.Errorf("no files in template dir %q", templatesDir) - } - - issuerURL, err := url.Parse(c.issuerURL) + approvalTemplate, err := loadTemplate(c.Dir, tmplApproval, funcs, group) if err != nil { - return nil, fmt.Errorf("error parsing issuerURL: %v", err) + return nil, err } - funcs := map[string]interface{}{ - "issuer": func() string { return c.issuer }, - "logo": func() string { return c.logoURL }, - "url": func(reqPath, assetPath string) string { return relativeURL(issuerURL.Path, reqPath, assetPath) }, - "lower": strings.ToLower, - "extra": func(k string) string { return c.extra[k] }, - } - - tmpls, err := template.New("").Funcs(funcs).ParseFiles(filenames...) + passwordTemplate, err := loadTemplate(c.Dir, tmplPassword, funcs, group) if err != nil { - return nil, fmt.Errorf("parse files: %v", err) + return nil, err } - missingTmpls := []string{} - for _, tmplName := range requiredTmpls { - if tmpls.Lookup(tmplName) == nil { - missingTmpls = append(missingTmpls, tmplName) - } + + oobTemplate, err := loadTemplate(c.Dir, tmplOOB, funcs, group) + if err != nil { + return nil, err } - if len(missingTmpls) > 0 { - return nil, fmt.Errorf("missing template(s): %s", missingTmpls) + + errorTemplate, err := loadTemplate(c.Dir, tmplError, funcs, group) + if err != nil { + return nil, err } + + deviceTemplate, err := loadTemplate(c.Dir, tmplDevice, funcs, group) + if err != nil { + return nil, err + } + + deviceSuccessTemplate, err := loadTemplate(c.Dir, tmplDeviceSuccess, funcs, group) + if err != nil { + return nil, err + } + + _, err = loadTemplate(c.Dir, tmplHeader, funcs, group) + if err != nil { + // we don't actually care if this template exists + } + + _, err = loadTemplate(c.Dir, tmplFooter, funcs, group) + if err != nil { + // we don't actually care if this template exists + } + return &templates{ - loginTmpl: tmpls.Lookup(tmplLogin), - approvalTmpl: tmpls.Lookup(tmplApproval), - passwordTmpl: tmpls.Lookup(tmplPassword), - oobTmpl: tmpls.Lookup(tmplOOB), - errorTmpl: tmpls.Lookup(tmplError), - deviceTmpl: tmpls.Lookup(tmplDevice), - deviceSuccessTmpl: tmpls.Lookup(tmplDeviceSuccess), + loginTmpl: loginTemplate, + approvalTmpl: approvalTemplate, + passwordTmpl: passwordTemplate, + oobTmpl: oobTemplate, + errorTmpl: errorTemplate, + deviceTmpl: deviceTemplate, + deviceSuccessTmpl: deviceSuccessTemplate, }, nil } @@ -239,6 +192,22 @@ func relativeURL(serverPath, reqPath, assetPath string) string { return relativeURL } +// load a template by name from the templates dir +func loadTemplate(dir http.FileSystem, name string, funcs template.FuncMap, group *template.Template) (*template.Template, error) { + file, err := dir.Open(filepath.Join("templates", name)) + if err != nil { + return nil, err + } + + defer file.Close() + + var buffer bytes.Buffer + buffer.ReadFrom(file) + contents := buffer.String() + + return group.New(name).Funcs(funcs).Parse(contents) +} + var scopeDescriptions = map[string]string{ "offline_access": "Have offline access", "profile": "View basic profile information", diff --git a/web/templates/header.html b/web/templates/header.html index 0d4fea0f..78dde15e 100644 --- a/web/templates/header.html +++ b/web/templates/header.html @@ -6,8 +6,8 @@ {{ issuer }} - - + + From 1eab25f89fd305e630aa5b362e0532c72bb51718 Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Mon, 21 Sep 2020 23:26:35 -0400 Subject: [PATCH 2/5] use web host url for asset hosting Signed-off-by: Rui Yang Co-authored-by: Aidan Oldershaw --- server/server.go | 3 +++ server/templates.go | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index 79c5f5a0..baf2b30a 100644 --- a/server/server.go +++ b/server/server.go @@ -121,6 +121,9 @@ type WebConfig struct { // Map of extra values passed into the templates Extra map[string]string + + // Defaults to issuer URL + HostURL string } func value(val, defaultValue time.Duration) time.Duration { diff --git a/server/templates.go b/server/templates.go index b23083ba..1c04a68e 100644 --- a/server/templates.go +++ b/server/templates.go @@ -53,12 +53,17 @@ func loadTemplates(c WebConfig, issuerPath string) (*templates, error) { c.LogoURL = "theme/logo.png" } + hostURL := issuerPath + if c.HostURL != "" { + hostURL = c.HostURL + } + funcs := template.FuncMap{ "issuer": func() string { return c.Issuer }, "logo": func() string { return c.LogoURL }, - "url": func(reqPath, assetPath string) string { return relativeURL(issuerPath, reqPath, assetPath) }, + "url": func(reqPath, assetPath string) string { return relativeURL(hostURL, reqPath, assetPath) }, "theme": func(reqPath, assetPath string) string { - return relativeURL(issuerPath, reqPath, path.Join("themes", c.Theme, assetPath)) + return relativeURL(hostURL, reqPath, path.Join("themes", c.Theme, assetPath)) }, "lower": strings.ToLower, "extra": func(k string) string { return c.Extra[k] }, From 7b50cbf0acf384af5cdc30c2cbb5531dc3972cee Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Wed, 14 Oct 2020 21:19:23 -0400 Subject: [PATCH 3/5] use pkger for embedding static contents Co-authored-by: Vikram Yadav Signed-off-by: Rui Yang --- Makefile | 1 + cmd/dex/config_test.go | 3 ++ cmd/dex/main.go | 2 + go.mod | 1 + go.sum | 16 +++----- server/handlers_test.go | 2 +- server/server.go | 13 ++++++- server/server_test.go | 4 +- server/templates.go | 82 +++++---------------------------------- server/templates_test.go | 51 ------------------------ web/templates/header.html | 8 ++-- 11 files changed, 39 insertions(+), 144 deletions(-) delete mode 100644 server/templates_test.go diff --git a/Makefile b/Makefile index 82b266a6..c5bb237a 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ build: bin/dex bin/dex: @mkdir -p bin/ + @go generate ./... @go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex examples: bin/grpc-client bin/example-app diff --git a/cmd/dex/config_test.go b/cmd/dex/config_test.go index 9a36afcf..c62d18b5 100644 --- a/cmd/dex/config_test.go +++ b/cmd/dex/config_test.go @@ -74,6 +74,7 @@ web: http: 127.0.0.1:5556 frontend: + dir: /web extra: foo: bar @@ -143,6 +144,7 @@ logger: HTTP: "127.0.0.1:5556", }, Frontend: server.WebConfig{ + Dir: "/web", Extra: map[string]string{ "foo": "bar", }, @@ -349,6 +351,7 @@ logger: HTTP: "127.0.0.1:5556", }, Frontend: server.WebConfig{ + Dir: "/web", Extra: map[string]string{ "foo": "bar", }, diff --git a/cmd/dex/main.go b/cmd/dex/main.go index be334d92..978bcea1 100644 --- a/cmd/dex/main.go +++ b/cmd/dex/main.go @@ -1,3 +1,5 @@ +//go:generate pkger + package main import ( diff --git a/go.mod b/go.mod index 6291284c..6fd434c6 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/kylelemons/godebug v1.1.0 github.com/lib/pq v1.10.0 + github.com/markbates/pkger v0.17.1 github.com/mattermost/xml-roundtrip-validator v0.0.0-20201219040909-8fd2afad43d1 github.com/mattn/go-sqlite3 v1.14.6 github.com/oklog/run v1.1.0 diff --git a/go.sum b/go.sum index 69b3bcf4..36971d11 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,9 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -188,6 +189,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201219040909-8fd2afad43d1 h1:x37Q11fexMtlhecRnkdzLL6dgnS1NF1nzAJ1vic22BY= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201219040909-8fd2afad43d1/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -264,7 +267,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -280,7 +282,6 @@ github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -293,7 +294,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -339,7 +339,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -363,13 +362,11 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -378,7 +375,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -408,7 +404,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -456,7 +451,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= @@ -493,6 +487,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= @@ -501,7 +496,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM= diff --git a/server/handlers_test.go b/server/handlers_test.go index 96d228af..d52a45c0 100644 --- a/server/handlers_test.go +++ b/server/handlers_test.go @@ -135,7 +135,7 @@ func TestHandleInvalidSAMLCallbacks(t *testing.T) { func TestConnectorLoginDoesNotAllowToChangeConnectorForAuthRequest(t *testing.T) { memStorage := memory.New(logger) - templates, err := loadTemplates(WebConfig{Dir: http.Dir("../web")}, "../web/templates") + templates, err := loadTemplates(WebConfig{Dir: "/web"}, "/web/templates") if err != nil { t.Fatal("failed to load templates") } diff --git a/server/server.go b/server/server.go index baf2b30a..df81a253 100644 --- a/server/server.go +++ b/server/server.go @@ -19,6 +19,7 @@ import ( "github.com/felixge/httpsnoop" "github.com/gorilla/handlers" "github.com/gorilla/mux" + "github.com/markbates/pkger" "github.com/prometheus/client_golang/prometheus" "golang.org/x/crypto/bcrypt" @@ -108,7 +109,7 @@ type WebConfig struct { // * templates - HTML templates controlled by dex. // * themes/(theme) - Static static served at "( issuer URL )/theme". // - Dir http.FileSystem + Dir string // Defaults to "( issuer URL )/theme/logo.png" LogoURL string @@ -206,6 +207,10 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) supported[respType] = true } + if c.Web.Dir == "" { + c.Web.Dir = pkger.Include("/web") + } + tmpls, err := loadTemplates(c.Web, issuerURL.Path) if err != nil { return nil, fmt.Errorf("server: failed to load templates: %v", err) @@ -337,7 +342,11 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) } fmt.Fprintf(w, "Health check passed") })) - handlePrefix("/", http.FileServer(c.Web.Dir)) + + staticDir := path.Join(c.Web.Dir, "static") + themeDir := path.Join(c.Web.Dir, "themes", c.Web.Theme) + handlePrefix("/static", http.FileServer(pkger.Dir(staticDir))) + handlePrefix(path.Join("/themes", c.Web.Theme), http.FileServer(pkger.Dir(themeDir))) s.mux = r diff --git a/server/server_test.go b/server/server_test.go index b0bacfd3..03bedbe6 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -93,7 +93,7 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi Issuer: s.URL, Storage: memory.New(logger), Web: WebConfig{ - Dir: http.Dir("../web"), + Dir: "/web", }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), @@ -132,7 +132,7 @@ func newTestServerMultipleConnectors(ctx context.Context, t *testing.T, updateCo Issuer: s.URL, Storage: memory.New(logger), Web: WebConfig{ - Dir: http.Dir("../web"), + Dir: "/web", }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), diff --git a/server/templates.go b/server/templates.go index 1c04a68e..49f70d1d 100644 --- a/server/templates.go +++ b/server/templates.go @@ -6,11 +6,12 @@ import ( "html/template" "io" "net/http" - "net/url" "path" "path/filepath" "sort" "strings" + + "github.com/markbates/pkger" ) const ( @@ -61,9 +62,11 @@ func loadTemplates(c WebConfig, issuerPath string) (*templates, error) { funcs := template.FuncMap{ "issuer": func() string { return c.Issuer }, "logo": func() string { return c.LogoURL }, - "url": func(reqPath, assetPath string) string { return relativeURL(hostURL, reqPath, assetPath) }, - "theme": func(reqPath, assetPath string) string { - return relativeURL(hostURL, reqPath, path.Join("themes", c.Theme, assetPath)) + "static": func(assetPath string) string { + return path.Join(hostURL, "static", assetPath) + }, + "theme": func(assetPath string) string { + return path.Join(hostURL, "themes", c.Theme, assetPath) }, "lower": strings.ToLower, "extra": func(k string) string { return c.Extra[k] }, @@ -130,76 +133,9 @@ func loadTemplates(c WebConfig, issuerPath string) (*templates, error) { }, nil } -// relativeURL returns the URL of the asset relative to the URL of the request path. -// The serverPath is consulted to trim any prefix due in case it is not listening -// to the root path. -// -// Algorithm: -// 1. Remove common prefix of serverPath and reqPath -// 2. Remove common prefix of assetPath and reqPath -// 3. For each part of reqPath remaining(minus one), go up one level (..) -// 4. For each part of assetPath remaining, append it to result -// -// eg -// server listens at localhost/dex so serverPath is dex -// reqPath is /dex/auth -// assetPath is static/main.css -// relativeURL("/dex", "/dex/auth", "static/main.css") = "../static/main.css" -func relativeURL(serverPath, reqPath, assetPath string) string { - if u, err := url.ParseRequestURI(assetPath); err == nil && u.Scheme != "" { - // assetPath points to the external URL, no changes needed - return assetPath - } - - splitPath := func(p string) []string { - res := []string{} - parts := strings.Split(path.Clean(p), "/") - for _, part := range parts { - if part != "" { - res = append(res, part) - } - } - return res - } - - stripCommonParts := func(s1, s2 []string) ([]string, []string) { - min := len(s1) - if len(s2) < min { - min = len(s2) - } - - splitIndex := min - for i := 0; i < min; i++ { - if s1[i] != s2[i] { - splitIndex = i - break - } - } - return s1[splitIndex:], s2[splitIndex:] - } - - server, req, asset := splitPath(serverPath), splitPath(reqPath), splitPath(assetPath) - - // Remove common prefix of request path with server path - _, req = stripCommonParts(server, req) - - // Remove common prefix of request path with asset path - asset, req = stripCommonParts(asset, req) - - // For each part of the request remaining (minus one) -> go up one level (..) - // For each part of the asset remaining -> append it - var relativeURL string - for i := 0; i < len(req)-1; i++ { - relativeURL = path.Join("..", relativeURL) - } - relativeURL = path.Join(relativeURL, path.Join(asset...)) - - return relativeURL -} - // load a template by name from the templates dir -func loadTemplate(dir http.FileSystem, name string, funcs template.FuncMap, group *template.Template) (*template.Template, error) { - file, err := dir.Open(filepath.Join("templates", name)) +func loadTemplate(dir string, name string, funcs template.FuncMap, group *template.Template) (*template.Template, error) { + file, err := pkger.Open(filepath.Join(dir, "templates", name)) if err != nil { return nil, err } diff --git a/server/templates_test.go b/server/templates_test.go deleted file mode 100644 index defb2e5e..00000000 --- a/server/templates_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package server - -import "testing" - -func TestRelativeURL(t *testing.T) { - tests := []struct { - name string - serverPath string - reqPath string - assetPath string - expected string - }{ - { - name: "server-root-req-one-level-asset-two-level", - serverPath: "/", - reqPath: "/auth", - assetPath: "/theme/main.css", - expected: "theme/main.css", - }, - { - name: "server-one-level-req-one-level-asset-two-level", - serverPath: "/dex", - reqPath: "/dex/auth", - assetPath: "/theme/main.css", - expected: "theme/main.css", - }, - { - name: "server-root-req-two-level-asset-three-level", - serverPath: "/dex", - reqPath: "/dex/auth/connector", - assetPath: "assets/css/main.css", - expected: "../assets/css/main.css", - }, - { - name: "external-url", - serverPath: "/dex", - reqPath: "/dex/auth/connector", - assetPath: "https://kubernetes.io/images/favicon.png", - expected: "https://kubernetes.io/images/favicon.png", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := relativeURL(test.serverPath, test.reqPath, test.assetPath) - if actual != test.expected { - t.Fatalf("Got '%s'. Expected '%s'", actual, test.expected) - } - }) - } -} diff --git a/web/templates/header.html b/web/templates/header.html index 78dde15e..bbd1c13f 100644 --- a/web/templates/header.html +++ b/web/templates/header.html @@ -5,15 +5,15 @@ {{ issuer }} - - - + + +
- +
From 4e569024fdfd520cdd4998847eb66736bbd8717c Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Thu, 15 Oct 2020 10:50:39 -0400 Subject: [PATCH 4/5] use go 1.16 new package io/fs Unify the interface for reading web statics. Now it could read an OS directory or get the content on live One could use //go:embed static var webFiles embed.FS anywhere and config dex server to take the file system by setting WebConfig{WebFS: webFiles} Signed-off-by: Rui Yang Co-authored-by: Aidan Oldershaw --- Makefile | 1 - cmd/dex/config_test.go | 7 +- cmd/dex/main.go | 2 - go.mod | 1 - go.sum | 10 -- server/handlers_test.go | 3 +- server/server.go | 32 ++--- server/server_test.go | 4 +- server/templates.go | 251 ++++++++++++++++++++++++-------------- server/templates_test.go | 51 ++++++++ web/templates/header.html | 9 +- 11 files changed, 243 insertions(+), 128 deletions(-) create mode 100644 server/templates_test.go diff --git a/Makefile b/Makefile index c5bb237a..82b266a6 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,6 @@ build: bin/dex bin/dex: @mkdir -p bin/ - @go generate ./... @go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex examples: bin/grpc-client bin/example-app diff --git a/cmd/dex/config_test.go b/cmd/dex/config_test.go index c62d18b5..8ee02d5a 100644 --- a/cmd/dex/config_test.go +++ b/cmd/dex/config_test.go @@ -74,7 +74,7 @@ web: http: 127.0.0.1:5556 frontend: - dir: /web + dir: ./web extra: foo: bar @@ -144,7 +144,7 @@ logger: HTTP: "127.0.0.1:5556", }, Frontend: server.WebConfig{ - Dir: "/web", + Dir: "./web", Extra: map[string]string{ "foo": "bar", }, @@ -274,6 +274,7 @@ web: http: 127.0.0.1:5556 frontend: + dir: ./web extra: foo: bar @@ -351,7 +352,7 @@ logger: HTTP: "127.0.0.1:5556", }, Frontend: server.WebConfig{ - Dir: "/web", + Dir: "./web", Extra: map[string]string{ "foo": "bar", }, diff --git a/cmd/dex/main.go b/cmd/dex/main.go index 978bcea1..be334d92 100644 --- a/cmd/dex/main.go +++ b/cmd/dex/main.go @@ -1,5 +1,3 @@ -//go:generate pkger - package main import ( diff --git a/go.mod b/go.mod index 6fd434c6..6291284c 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,6 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/kylelemons/godebug v1.1.0 github.com/lib/pq v1.10.0 - github.com/markbates/pkger v0.17.1 github.com/mattermost/xml-roundtrip-validator v0.0.0-20201219040909-8fd2afad43d1 github.com/mattn/go-sqlite3 v1.14.6 github.com/oklog/run v1.1.0 diff --git a/go.sum b/go.sum index 36971d11..3e624f53 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -15,7 +14,6 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AppsFlyer/go-sundheit v0.3.1 h1:Zqnr3wV3WQmXonc234k9XZAoV2KHUHw3osR5k2iHQZE= github.com/AppsFlyer/go-sundheit v0.3.1/go.mod h1:iZ8zWMS7idcvmqewf5mEymWWgoOiG/0WD4+aeh+heX4= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -42,9 +40,7 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= @@ -74,7 +70,6 @@ github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8S github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -86,8 +81,6 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= @@ -189,8 +182,6 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno= -github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201219040909-8fd2afad43d1 h1:x37Q11fexMtlhecRnkdzLL6dgnS1NF1nzAJ1vic22BY= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201219040909-8fd2afad43d1/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -487,7 +478,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/server/handlers_test.go b/server/handlers_test.go index d52a45c0..d195af64 100644 --- a/server/handlers_test.go +++ b/server/handlers_test.go @@ -7,6 +7,7 @@ import ( "errors" "net/http" "net/http/httptest" + "os" "testing" "time" @@ -135,7 +136,7 @@ func TestHandleInvalidSAMLCallbacks(t *testing.T) { func TestConnectorLoginDoesNotAllowToChangeConnectorForAuthRequest(t *testing.T) { memStorage := memory.New(logger) - templates, err := loadTemplates(WebConfig{Dir: "/web"}, "/web/templates") + templates, err := loadTemplates(webConfig{webFS: os.DirFS("../web")}, "templates") if err != nil { t.Fatal("failed to load templates") } diff --git a/server/server.go b/server/server.go index df81a253..46b23771 100644 --- a/server/server.go +++ b/server/server.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "net/http" "net/url" "path" @@ -19,7 +20,6 @@ import ( "github.com/felixge/httpsnoop" "github.com/gorilla/handlers" "github.com/gorilla/mux" - "github.com/markbates/pkger" "github.com/prometheus/client_golang/prometheus" "golang.org/x/crypto/bcrypt" @@ -101,7 +101,7 @@ type Config struct { // WebConfig holds the server's frontend templates and asset configuration. type WebConfig struct { - // A filepath to web static. + // A file path to web static. If set, WebFS will be ignored. // // It is expected to contain the following directories: // @@ -111,6 +111,10 @@ type WebConfig struct { // Dir string + // A file system includes web static. Will be overwritten by Dir + // It is expected to contain the directories as Dir. + WebFS fs.FS + // Defaults to "( issuer URL )/theme/logo.png" LogoURL string @@ -122,9 +126,6 @@ type WebConfig struct { // Map of extra values passed into the templates Extra map[string]string - - // Defaults to issuer URL - HostURL string } func value(val, defaultValue time.Duration) time.Duration { @@ -207,13 +208,19 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) supported[respType] = true } - if c.Web.Dir == "" { - c.Web.Dir = pkger.Include("/web") + web := webConfig{ + dir: c.Web.Dir, + webFS: c.Web.WebFS, + logoURL: c.Web.LogoURL, + issuerURL: c.Issuer, + issuer: c.Web.Issuer, + theme: c.Web.Theme, + extra: c.Web.Extra, } - tmpls, err := loadTemplates(c.Web, issuerURL.Path) + static, theme, tmpls, err := loadWebConfig(web) if err != nil { - return nil, fmt.Errorf("server: failed to load templates: %v", err) + return nil, fmt.Errorf("server: failed to load web static: %v", err) } now := c.Now @@ -343,11 +350,8 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) fmt.Fprintf(w, "Health check passed") })) - staticDir := path.Join(c.Web.Dir, "static") - themeDir := path.Join(c.Web.Dir, "themes", c.Web.Theme) - handlePrefix("/static", http.FileServer(pkger.Dir(staticDir))) - handlePrefix(path.Join("/themes", c.Web.Theme), http.FileServer(pkger.Dir(themeDir))) - + handlePrefix("/static", static) + handlePrefix("/theme", theme) s.mux = r s.startKeyRotation(ctx, rotationStrategy, now) diff --git a/server/server_test.go b/server/server_test.go index 03bedbe6..87ca6c17 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -93,7 +93,7 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi Issuer: s.URL, Storage: memory.New(logger), Web: WebConfig{ - Dir: "/web", + Dir: "../web", }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), @@ -132,7 +132,7 @@ func newTestServerMultipleConnectors(ctx context.Context, t *testing.T, updateCo Issuer: s.URL, Storage: memory.New(logger), Web: WebConfig{ - Dir: "/web", + Dir: "../web", }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), diff --git a/server/templates.go b/server/templates.go index 49f70d1d..0d191644 100644 --- a/server/templates.go +++ b/server/templates.go @@ -1,17 +1,17 @@ package server import ( - "bytes" "fmt" "html/template" "io" + "io/fs" "net/http" + "net/url" + "os" "path" "path/filepath" "sort" "strings" - - "github.com/markbates/pkger" ) const ( @@ -22,10 +22,18 @@ const ( tmplError = "error.html" tmplDevice = "device.html" tmplDeviceSuccess = "device_success.html" - tmplHeader = "header.html" - tmplFooter = "footer.html" ) +var requiredTmpls = []string{ + tmplApproval, + tmplLogin, + tmplPassword, + tmplOOB, + tmplError, + tmplDevice, + tmplDeviceSuccess, +} + type templates struct { loginTmpl *template.Template approvalTmpl *template.Template @@ -36,117 +44,182 @@ type templates struct { deviceSuccessTmpl *template.Template } -// loadTemplates parses the expected templates from the provided directory. -func loadTemplates(c WebConfig, issuerPath string) (*templates, error) { +type webConfig struct { + dir string + webFS fs.FS + logoURL string + issuer string + theme string + issuerURL string + extra map[string]string +} + +// loadWebConfig returns static assets, theme assets, and templates used by the frontend by +// reading the dir specified in the webConfig. If directory is not specified it will +// use the file system specified by webFS. +// +// The directory layout is expected to be: +// +// ( web directory ) +// |- static +// |- themes +// | |- (theme name) +// |- templates +// +func loadWebConfig(c webConfig) (http.Handler, http.Handler, *templates, error) { // fallback to the default theme if the legacy theme name is provided - if c.Theme == "coreos" || c.Theme == "tectonic" { - c.Theme = "" + if c.theme == "coreos" || c.theme == "tectonic" { + c.theme = "" } - if c.Theme == "" { - c.Theme = "light" + if c.theme == "" { + c.theme = "light" + } + if c.issuer == "" { + c.issuer = "dex" + } + if c.dir != "" { + c.webFS = os.DirFS(c.dir) + } + if c.logoURL == "" { + c.logoURL = "theme/logo.png" } - if c.Issuer == "" { - c.Issuer = "dex" - } - - if c.LogoURL == "" { - c.LogoURL = "theme/logo.png" - } - - hostURL := issuerPath - if c.HostURL != "" { - hostURL = c.HostURL - } - - funcs := template.FuncMap{ - "issuer": func() string { return c.Issuer }, - "logo": func() string { return c.LogoURL }, - "static": func(assetPath string) string { - return path.Join(hostURL, "static", assetPath) - }, - "theme": func(assetPath string) string { - return path.Join(hostURL, "themes", c.Theme, assetPath) - }, - "lower": strings.ToLower, - "extra": func(k string) string { return c.Extra[k] }, - } - - group := template.New("") - - // load all of our templates individually. - // some http.FilSystem implementations don't implement Readdir - - loginTemplate, err := loadTemplate(c.Dir, tmplLogin, funcs, group) + staticFiles, err := fs.Sub(c.webFS, "static") if err != nil { - return nil, err + return nil, nil, nil, fmt.Errorf("read static dir: %v", err) } - - approvalTemplate, err := loadTemplate(c.Dir, tmplApproval, funcs, group) + themeFiles, err := fs.Sub(c.webFS, filepath.Join("themes", c.theme)) if err != nil { - return nil, err + return nil, nil, nil, fmt.Errorf("read themes dir: %v", err) } - passwordTemplate, err := loadTemplate(c.Dir, tmplPassword, funcs, group) + static := http.FileServer(http.FS(staticFiles)) + theme := http.FileServer(http.FS(themeFiles)) + + templates, err := loadTemplates(c, "templates") + return static, theme, templates, err +} + +// loadTemplates parses the expected templates from the provided directory. +func loadTemplates(c webConfig, templatesDir string) (*templates, error) { + files, err := fs.ReadDir(c.webFS, templatesDir) if err != nil { - return nil, err + return nil, fmt.Errorf("read dir: %v", err) } - oobTemplate, err := loadTemplate(c.Dir, tmplOOB, funcs, group) + filenames := []string{} + for _, file := range files { + if file.IsDir() { + continue + } + filenames = append(filenames, filepath.Join(templatesDir, file.Name())) + } + if len(filenames) == 0 { + return nil, fmt.Errorf("no files in template dir %q", templatesDir) + } + + issuerURL, err := url.Parse(c.issuerURL) if err != nil { - return nil, err + return nil, fmt.Errorf("error parsing issuerURL: %v", err) } - errorTemplate, err := loadTemplate(c.Dir, tmplError, funcs, group) + funcs := map[string]interface{}{ + "issuer": func() string { return c.issuer }, + "logo": func() string { return c.logoURL }, + "url": func(reqPath, assetPath string) string { return relativeURL(issuerURL.Path, reqPath, assetPath) }, + "lower": strings.ToLower, + "extra": func(k string) string { return c.extra[k] }, + } + + tmpls, err := template.New("").Funcs(funcs).ParseFS(c.webFS, filenames...) if err != nil { - return nil, err + return nil, fmt.Errorf("parse files: %v", err) } - - deviceTemplate, err := loadTemplate(c.Dir, tmplDevice, funcs, group) - if err != nil { - return nil, err + missingTmpls := []string{} + for _, tmplName := range requiredTmpls { + if tmpls.Lookup(tmplName) == nil { + missingTmpls = append(missingTmpls, tmplName) + } } - - deviceSuccessTemplate, err := loadTemplate(c.Dir, tmplDeviceSuccess, funcs, group) - if err != nil { - return nil, err + if len(missingTmpls) > 0 { + return nil, fmt.Errorf("missing template(s): %s", missingTmpls) } - - _, err = loadTemplate(c.Dir, tmplHeader, funcs, group) - if err != nil { - // we don't actually care if this template exists - } - - _, err = loadTemplate(c.Dir, tmplFooter, funcs, group) - if err != nil { - // we don't actually care if this template exists - } - return &templates{ - loginTmpl: loginTemplate, - approvalTmpl: approvalTemplate, - passwordTmpl: passwordTemplate, - oobTmpl: oobTemplate, - errorTmpl: errorTemplate, - deviceTmpl: deviceTemplate, - deviceSuccessTmpl: deviceSuccessTemplate, + loginTmpl: tmpls.Lookup(tmplLogin), + approvalTmpl: tmpls.Lookup(tmplApproval), + passwordTmpl: tmpls.Lookup(tmplPassword), + oobTmpl: tmpls.Lookup(tmplOOB), + errorTmpl: tmpls.Lookup(tmplError), + deviceTmpl: tmpls.Lookup(tmplDevice), + deviceSuccessTmpl: tmpls.Lookup(tmplDeviceSuccess), }, nil } -// load a template by name from the templates dir -func loadTemplate(dir string, name string, funcs template.FuncMap, group *template.Template) (*template.Template, error) { - file, err := pkger.Open(filepath.Join(dir, "templates", name)) - if err != nil { - return nil, err +// relativeURL returns the URL of the asset relative to the URL of the request path. +// The serverPath is consulted to trim any prefix due in case it is not listening +// to the root path. +// +// Algorithm: +// 1. Remove common prefix of serverPath and reqPath +// 2. Remove common prefix of assetPath and reqPath +// 3. For each part of reqPath remaining(minus one), go up one level (..) +// 4. For each part of assetPath remaining, append it to result +// +// eg +// server listens at localhost/dex so serverPath is dex +// reqPath is /dex/auth +// assetPath is static/main.css +// relativeURL("/dex", "/dex/auth", "static/main.css") = "../static/main.css" +func relativeURL(serverPath, reqPath, assetPath string) string { + if u, err := url.ParseRequestURI(assetPath); err == nil && u.Scheme != "" { + // assetPath points to the external URL, no changes needed + return assetPath } - defer file.Close() + splitPath := func(p string) []string { + res := []string{} + parts := strings.Split(path.Clean(p), "/") + for _, part := range parts { + if part != "" { + res = append(res, part) + } + } + return res + } - var buffer bytes.Buffer - buffer.ReadFrom(file) - contents := buffer.String() + stripCommonParts := func(s1, s2 []string) ([]string, []string) { + min := len(s1) + if len(s2) < min { + min = len(s2) + } - return group.New(name).Funcs(funcs).Parse(contents) + splitIndex := min + for i := 0; i < min; i++ { + if s1[i] != s2[i] { + splitIndex = i + break + } + } + return s1[splitIndex:], s2[splitIndex:] + } + + server, req, asset := splitPath(serverPath), splitPath(reqPath), splitPath(assetPath) + + // Remove common prefix of request path with server path + _, req = stripCommonParts(server, req) + + // Remove common prefix of request path with asset path + asset, req = stripCommonParts(asset, req) + + // For each part of the request remaining (minus one) -> go up one level (..) + // For each part of the asset remaining -> append it + var relativeURL string + for i := 0; i < len(req)-1; i++ { + relativeURL = path.Join("..", relativeURL) + } + relativeURL = path.Join(relativeURL, path.Join(asset...)) + + return relativeURL } var scopeDescriptions = map[string]string{ diff --git a/server/templates_test.go b/server/templates_test.go new file mode 100644 index 00000000..defb2e5e --- /dev/null +++ b/server/templates_test.go @@ -0,0 +1,51 @@ +package server + +import "testing" + +func TestRelativeURL(t *testing.T) { + tests := []struct { + name string + serverPath string + reqPath string + assetPath string + expected string + }{ + { + name: "server-root-req-one-level-asset-two-level", + serverPath: "/", + reqPath: "/auth", + assetPath: "/theme/main.css", + expected: "theme/main.css", + }, + { + name: "server-one-level-req-one-level-asset-two-level", + serverPath: "/dex", + reqPath: "/dex/auth", + assetPath: "/theme/main.css", + expected: "theme/main.css", + }, + { + name: "server-root-req-two-level-asset-three-level", + serverPath: "/dex", + reqPath: "/dex/auth/connector", + assetPath: "assets/css/main.css", + expected: "../assets/css/main.css", + }, + { + name: "external-url", + serverPath: "/dex", + reqPath: "/dex/auth/connector", + assetPath: "https://kubernetes.io/images/favicon.png", + expected: "https://kubernetes.io/images/favicon.png", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := relativeURL(test.serverPath, test.reqPath, test.assetPath) + if actual != test.expected { + t.Fatalf("Got '%s'. Expected '%s'", actual, test.expected) + } + }) + } +} diff --git a/web/templates/header.html b/web/templates/header.html index bbd1c13f..8cf744e5 100644 --- a/web/templates/header.html +++ b/web/templates/header.html @@ -5,17 +5,16 @@ {{ issuer }} - - - + + +
- +
- From 2f28fc745120241096b0560add0743a800a6e931 Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Thu, 4 Mar 2021 10:17:05 -0500 Subject: [PATCH 5/5] default to ./web when Dir and WebFS are not set update WebFS doc Signed-off-by: Rui Yang Co-authored-by: Aidan Oldershaw --- server/server.go | 6 ++++-- server/templates.go | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index 46b23771..17d72af3 100644 --- a/server/server.go +++ b/server/server.go @@ -111,8 +111,10 @@ type WebConfig struct { // Dir string - // A file system includes web static. Will be overwritten by Dir - // It is expected to contain the directories as Dir. + // Alternative way to configure web static filesystem. Dir overrides this. + // It's expected to contain the same files and directories as mentioned + // above in Dir doc. + // WebFS fs.FS // Defaults to "( issuer URL )/theme/logo.png" diff --git a/server/templates.go b/server/templates.go index 0d191644..ca5e4d24 100644 --- a/server/templates.go +++ b/server/templates.go @@ -79,6 +79,8 @@ func loadWebConfig(c webConfig) (http.Handler, http.Handler, *templates, error) } if c.dir != "" { c.webFS = os.DirFS(c.dir) + } else if c.webFS == nil { + c.webFS = os.DirFS("./web") } if c.logoURL == "" { c.logoURL = "theme/logo.png"