From 8b0aef41bf7101eeaeafd1b7345bb963b6547d0f Mon Sep 17 00:00:00 2001 From: Joshua Winters Date: Wed, 21 Feb 2018 11:20:46 -0500 Subject: [PATCH] Use http.Filesystem for web config --- server/server.go | 32 +++---- server/server_test.go | 2 +- server/templates.go | 176 ++++++++++++++------------------------ web/templates/header.html | 6 +- 4 files changed, 81 insertions(+), 135 deletions(-) diff --git a/server/server.go b/server/server.go index adf872eb7d..a1bfcfe192 100644 --- a/server/server.go +++ b/server/server.go @@ -96,7 +96,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 @@ -173,33 +173,24 @@ 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, + if c.Now == nil { + c.Now = time.Now } - static, theme, tmpls, err := loadWebConfig(web) + templates, err := loadTemplates(c.Web, c.Issuer) if err != nil { - return nil, fmt.Errorf("server: failed to load web static: %v", err) - } - - now := c.Now - if now == nil { - now = time.Now + return nil, fmt.Errorf("server: failed to templates: %v", err) } s := &Server{ issuerURL: *issuerURL, connectors: make(map[string]Connector), - storage: newKeyCacher(c.Storage, now), + storage: newKeyCacher(c.Storage, c.Now), supportedResponseTypes: supported, idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour), skipApproval: c.SkipApprovalScreen, - now: now, - templates: tmpls, + now: c.Now, + templates: templates, logger: c.Logger, } @@ -281,12 +272,11 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) handleFunc("/callback/{connector}", s.handleConnectorCallback) handleFunc("/approval", s.handleApproval) handleFunc("/healthz", s.handleHealth) - handlePrefix("/static", static) - handlePrefix("/theme", theme) + handlePrefix("/", http.FileServer(c.Web.Dir)) s.mux = r - s.startKeyRotation(ctx, rotationStrategy, now) - s.startGarbageCollection(ctx, value(c.GCFrequency, 5*time.Minute), now) + s.startKeyRotation(ctx, rotationStrategy, c.Now) + s.startGarbageCollection(ctx, value(c.GCFrequency, 5*time.Minute), c.Now) return s, nil } diff --git a/server/server_test.go b/server/server_test.go index 536387c40d..74f3715593 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -90,7 +90,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(), diff --git a/server/templates.go b/server/templates.go index ab9e580205..b0fb330691 100644 --- a/server/templates.go +++ b/server/templates.go @@ -1,12 +1,13 @@ package server import ( + "bytes" "fmt" "html/template" "io" - "io/ioutil" "net/http" - "os" + "net/url" + "path" "path/filepath" "sort" "strings" @@ -20,14 +21,6 @@ const ( tmplError = "error.html" ) -var requiredTmpls = []string{ - tmplApproval, - tmplLogin, - tmplPassword, - tmplOOB, - tmplError, -} - type templates struct { loginTmpl *template.Template approvalTmpl *template.Template @@ -37,133 +30,96 @@ type templates struct { } type webConfig struct { - dir string - logoURL string - issuer string - theme string - issuerURL string + themeDir http.FileSystem + staticDir http.FileSystem + templatesDir http.FileSystem + logoURL string + issuer string + theme string + issuerURL string } -func join(base, path string) string { - b := strings.HasSuffix(base, "/") - p := strings.HasPrefix(path, "/") - switch { - case b && p: - return base + path[1:] - case b || p: - return base + path - default: - return base + "/" + path - } +func join(basepath string, paths ...string) string { + u, _ := url.Parse(basepath) + u.Path = path.Join(append([]string{u.Path}, paths...)...) + return u.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 -} +// loadTemplates parses the expected templates from the provided directory. +func loadTemplates(c WebConfig, issuerURL string) (*templates, error) { -// 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) (static, theme http.Handler, templates *templates, err error) { - if c.theme == "" { - c.theme = "coreos" - } - if c.issuer == "" { - c.issuer = "dex" - } - if c.dir == "" { - c.dir = "./web" - } - if c.logoURL == "" { - c.logoURL = join(c.issuerURL, "theme/logo.png") + if c.Theme == "" { + c.Theme = "coreos" } - if err := dirExists(c.dir); err != nil { - return nil, nil, nil, fmt.Errorf("load web dir: %v", err) + if c.Issuer == "" { + c.Issuer = "dex" } - 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) - } + if c.LogoURL == "" { + c.LogoURL = join(issuerURL, "themes", c.Theme, "logo.png") } - static = http.FileServer(http.Dir(staticDir)) - theme = http.FileServer(http.Dir(themeDir)) + funcs := template.FuncMap{ + "issuer": func() string { return c.Issuer }, + "logo": func() string { return c.LogoURL }, + "static": func(s string) string { return join(issuerURL, "static", s) }, + "theme": func(s string) string { return join(issuerURL, "themes", c.Theme, s) }, + "lower": strings.ToLower, + } - templates, err = loadTemplates(c, templatesDir) - return -} + group := template.New("") -// loadTemplates parses the expected templates from the provided directory. -func loadTemplates(c webConfig, templatesDir string) (*templates, error) { - files, err := ioutil.ReadDir(templatesDir) + 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) + approvalTemplate, err := loadTemplate(c.Dir, tmplApproval, funcs, group) + if err != nil { + return nil, err } - funcs := map[string]interface{}{ - "issuer": func() string { return c.issuer }, - "logo": func() string { return c.logoURL }, - "url": func(s string) string { return join(c.issuerURL, s) }, - "lower": strings.ToLower, + passwordTemplate, err := loadTemplate(c.Dir, tmplPassword, funcs, group) + if err != nil { + return nil, err } - tmpls, err := template.New("").Funcs(funcs).ParseFiles(filenames...) + oobTemplate, err := loadTemplate(c.Dir, tmplOOB, funcs, group) if err != nil { - return nil, fmt.Errorf("parse files: %v", err) - } - missingTmpls := []string{} - for _, tmplName := range requiredTmpls { - if tmpls.Lookup(tmplName) == nil { - missingTmpls = append(missingTmpls, tmplName) - } + 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 } + + loadTemplate(c.Dir, "header.html", funcs, group) + loadTemplate(c.Dir, "footer.html", funcs, group) + return &templates{ - loginTmpl: tmpls.Lookup(tmplLogin), - approvalTmpl: tmpls.Lookup(tmplApproval), - passwordTmpl: tmpls.Lookup(tmplPassword), - oobTmpl: tmpls.Lookup(tmplOOB), - errorTmpl: tmpls.Lookup(tmplError), + loginTmpl: loginTemplate, + approvalTmpl: approvalTemplate, + passwordTmpl: passwordTemplate, + oobTmpl: oobTemplate, + errorTmpl: errorTemplate, }, nil } +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 + } + + 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 edd6289a2b..bbd1c13fa6 100644 --- a/web/templates/header.html +++ b/web/templates/header.html @@ -5,9 +5,9 @@ {{ issuer }} - - - + + +