From 52b586d82fee25c61edebcd3d0203a83025a2f49 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Sun, 5 Dec 2021 21:20:27 +0100 Subject: [PATCH] Added JSON endpoints. Made Web UI optional. --- cmd/csaf_provider/actions.go | 14 +++-- cmd/csaf_provider/config.go | 1 + cmd/csaf_provider/controller.go | 83 +++++++++++++++++++++++------- cmd/csaf_provider/tmpl/create.html | 2 +- 4 files changed, 76 insertions(+), 24 deletions(-) diff --git a/cmd/csaf_provider/actions.go b/cmd/csaf_provider/actions.go index 13ba7be..8c5c786 100644 --- a/cmd/csaf_provider/actions.go +++ b/cmd/csaf_provider/actions.go @@ -115,11 +115,19 @@ func (c *controller) tlpParam(r *http.Request) (tlp, error) { return "", fmt.Errorf("unsupported TLP type '%s'", t) } -func (c *controller) create(http.ResponseWriter, *http.Request) error { - return ensureFolders(c.cfg) +func (c *controller) create(*http.Request) (interface{}, error) { + if err := ensureFolders(c.cfg); err != nil { + return nil, err + } + return &struct { + Message string `json:"message"` + Error bool `json:"-"` + }{ + Message: "Everything is setup fine now.", + }, nil } -func (c *controller) upload(rw http.ResponseWriter, r *http.Request) (interface{}, error) { +func (c *controller) upload(r *http.Request) (interface{}, error) { newCSAF, data, err := loadCSAF(r) if err != nil { diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go index a7f3e5d..38be445 100644 --- a/cmd/csaf_provider/config.go +++ b/cmd/csaf_provider/config.go @@ -28,6 +28,7 @@ type config struct { Domain string `toml:"domain"` NoPassphrase bool `toml:"no_passphrase"` NoValidation bool `toml:"no_validation"` + NoWebUI bool `toml:"no_web_ui"` DynamicProviderMetaData bool `toml:"dynamic_provider_metadata"` Publisher *csaf.Publisher `toml:"publisher"` } diff --git a/cmd/csaf_provider/controller.go b/cmd/csaf_provider/controller.go index e90b09c..31e29b9 100644 --- a/cmd/csaf_provider/controller.go +++ b/cmd/csaf_provider/controller.go @@ -2,6 +2,7 @@ package main import ( "embed" + "encoding/json" "html/template" "log" "net/http" @@ -17,6 +18,17 @@ func (me multiError) Error() string { return strings.Join([]string(me), ", ") } +func asMultiError(err error) multiError { + if err == nil { + return nil + } + e, ok := err.(multiError) + if ok { + return e + } + return multiError([]string{err.Error()}) +} + type controller struct { cfg *config tmpl *template.Template @@ -27,31 +39,35 @@ func newController(cfg *config) (*controller, error) { c := controller{cfg: cfg} var err error - if c.tmpl, err = template.ParseFS(tmplFS, "tmpl/*.html"); err != nil { - return nil, err + if !cfg.NoWebUI { + if c.tmpl, err = template.ParseFS(tmplFS, "tmpl/*.html"); err != nil { + return nil, err + } } return &c, nil } func (c *controller) bind(pim *pathInfoMux) { - pim.handleFunc("/", c.index) - pim.handleFunc("/upload", c.uploadWeb) - pim.handleFunc("/create", c.createWeb) + if !c.cfg.NoWebUI { + pim.handleFunc("/", c.index) + pim.handleFunc("/upload", c.web(c.upload, "upload.html")) + pim.handleFunc("/create", c.web(c.create, "create.html")) + } + pim.handleFunc("/api/upload", api(c.upload)) + pim.handleFunc("/api/create", api(c.create)) } func (c *controller) render(rw http.ResponseWriter, tmpl string, arg interface{}) { rw.Header().Set("Content-type", "text/html; charset=utf-8") + rw.Header().Set("X-Content-Type-Options", "nosniff") if err := c.tmpl.ExecuteTemplate(rw, tmpl, arg); err != nil { log.Printf("warn: %v\n", err) } } func (c *controller) failed(rw http.ResponseWriter, tmpl string, err error) { - if _, ok := err.(multiError); err != nil && !ok { - err = multiError([]string{err.Error()}) - } - result := map[string]interface{}{"Error": err} + result := map[string]interface{}{"Error": asMultiError(err)} c.render(rw, tmpl, result) } @@ -61,19 +77,46 @@ func (c *controller) index(rw http.ResponseWriter, r *http.Request) { }) } -func (c *controller) createWeb(rw http.ResponseWriter, r *http.Request) { - if err := c.create(rw, r); err != nil { - c.failed(rw, "create.html", err) - return +func (c *controller) web( + fn func(*http.Request) (interface{}, error), + tmpl string, +) func(http.ResponseWriter, *http.Request) { + + return func(rw http.ResponseWriter, r *http.Request) { + if content, err := fn(r); err != nil { + c.failed(rw, tmpl, err) + } else { + c.render(rw, tmpl, content) + } } - c.render(rw, "create.html", nil) } -func (c *controller) uploadWeb(rw http.ResponseWriter, r *http.Request) { - result, err := c.upload(rw, r) - if err != nil { - c.failed(rw, "upload.html", err) - return +func writeJSON(rw http.ResponseWriter, content interface{}, code int) { + rw.Header().Set("Content-type", "application/json; charset=utf-8") + rw.Header().Set("X-Content-Type-Options", "nosniff") + rw.WriteHeader(code) + if err := json.NewEncoder(rw).Encode(content); err != nil { + log.Printf("error: %v\n", err) + } +} + +func errorToContent(err error) interface{} { + return &struct { + Errors multiError `json:"errors"` + }{ + Errors: asMultiError(err), + } +} + +func api( + fn func(*http.Request) (interface{}, error), +) func(http.ResponseWriter, *http.Request) { + + return func(rw http.ResponseWriter, r *http.Request) { + if content, err := fn(r); err != nil { + writeJSON(rw, errorToContent(err), http.StatusBadRequest) + } else { + writeJSON(rw, content, http.StatusOK) + } } - c.render(rw, "upload.html", result) } diff --git a/cmd/csaf_provider/tmpl/create.html b/cmd/csaf_provider/tmpl/create.html index f2de646..cdbedc2 100644 --- a/cmd/csaf_provider/tmpl/create.html +++ b/cmd/csaf_provider/tmpl/create.html @@ -21,7 +21,7 @@

{{ end }} {{ else }} - Everything is setup fine now. + {{ .Message }} {{ end }}