From a12331ee09074a9e0a03f8c38ca0ad401c9ab10e Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Fri, 3 May 2024 18:54:22 +0200 Subject: [PATCH] Use JSON arrays instead of plain text in API. Suggested by Dianne Skoll. --- README.API | 14 ++++++------ static/management.js | 11 +++++----- webserver/api.go | 27 ++++++++++------------- webserver/api_test.go | 51 +++++++++++++++---------------------------- 4 files changed, 42 insertions(+), 61 deletions(-) diff --git a/README.API b/README.API index 241e144..9c7db8e 100644 --- a/README.API +++ b/README.API @@ -43,8 +43,8 @@ allowed methods are HEAD and GET. /galene-api/v0/.groups/ -Returns a list of groups, as plain text, one per line. The only allowed -methods are HEAD and GET. +Returns a list of groups, as a JSON array. The only allowed methods are +HEAD and GET. ### Group definition @@ -75,8 +75,8 @@ accepted content-type is `application/jwk-set+json`. /galene-api/v0/.groups/groupname/.users/ -Returns a list of users, as plain text, one per line. The only allowed -methods are HEAD and GET. +Returns a list of users, as a JSON array. The only allowed methods are +HEAD and GET. ### User definition @@ -101,9 +101,9 @@ POST. /galene-api/v0/.groups/groupname/.users/username/.tokens/ -GET returns the list of stateful tokens, as plain text, one token per -line. POST creates a new token, and returns its name in the `Location` -header. Allowed methods are HEAD, GET and POST. +GET returns the list of stateful tokens, as a JSON array. POST creates +a new token, and returns its name in the `Location` header. Allowed +methods are HEAD, GET and POST. ### Stateful token diff --git a/static/management.js b/static/management.js index 9c4b004..2c3b006 100644 --- a/static/management.js +++ b/static/management.js @@ -49,12 +49,11 @@ async function listObjects(url) { let r = await fetch(url); if(!r.ok) throw httpError(r); - let strings = (await r.text()).split('\n'); - if(strings[strings.length - 1] === '') { - strings.pop(); - } - return strings; -} + let data = await r.json(); + if(!(data instanceof Array)) + throw new Error("Server didn't return array"); + return data; + } /** * createObject makes a PUT request to url with JSON data. diff --git a/webserver/api.go b/webserver/api.go index dfc913f..179f369 100644 --- a/webserver/api.go +++ b/webserver/api.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "fmt" "io" "net/http" "os" @@ -118,13 +117,12 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) { httpError(w, err) return } - w.Header().Set("content-type", "text/plain; charset=utf-8") + w.Header().Set("content-type", "application/json") if r.Method == "HEAD" { return } - for _, g := range groups { - fmt.Fprintln(w, g) - } + e := json.NewEncoder(w) + e.Encode(groups) return } @@ -257,7 +255,7 @@ func usersHandler(w http.ResponseWriter, r *http.Request, g, pth string) { httpError(w, err) return } - w.Header().Set("content-type", "text/plain; charset=utf-8") + w.Header().Set("content-type", "application/json") w.Header().Set("etag", etag) done := checkPreconditions(w, r, etag) if done { @@ -266,9 +264,8 @@ func usersHandler(w http.ResponseWriter, r *http.Request, g, pth string) { if r.Method == "HEAD" { return } - for _, u := range users { - fmt.Fprintln(w, u) - } + e := json.NewEncoder(w) + e.Encode(users) return } @@ -545,17 +542,19 @@ func tokensHandler(w http.ResponseWriter, r *http.Request, g, pth string) { httpError(w, err) return } - w.Header().Set("content-type", - "text/plain; charset=utf-8") + w.Header().Set("content-type", "application/json") if etag != "" { w.Header().Set("etag", etag) } if r.Method == "HEAD" { return } - for _, t := range tokens { - fmt.Fprintln(w, t.Token) + toknames := make([]string, len(tokens)) + for i, t := range tokens { + toknames[i] = t.Token } + e := json.NewEncoder(w) + e.Encode(toknames) return } else if r.Method == "POST" { ctype := parseContentType(r.Header.Get("Content-Type")) @@ -586,8 +585,6 @@ func tokensHandler(w http.ResponseWriter, r *http.Request, g, pth string) { httpError(w, err) return } - w.Header().Set("content-type", - "text/plain; charset=utf-8") w.Header().Set("location", t.Token) w.WriteHeader(http.StatusCreated) return diff --git a/webserver/api_test.go b/webserver/api_test.go index bb8de15..1e5418b 100644 --- a/webserver/api_test.go +++ b/webserver/api_test.go @@ -3,7 +3,6 @@ package webserver import ( "errors" "fmt" - "io" "os" "strings" "sync" @@ -91,23 +90,6 @@ func TestApi(t *testing.T) { return client.Do(req) } - getString := func(path string) (string, error) { - resp, err := do("GET", path, "", "", "", "") - if err != nil { - return "", err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("Status is %v", resp.StatusCode) - } - ctype := parseContentType(resp.Header.Get("Content-Type")) - if !strings.EqualFold(ctype, "text/plain") { - return "", errors.New("Unexpected Content-Type") - } - b, err := io.ReadAll(resp.Body) - return string(b), err - } - getJSON := func(path string, value any) error { resp, err := do("GET", path, "", "", "", "") if err != nil { @@ -119,14 +101,15 @@ func TestApi(t *testing.T) { } ctype := parseContentType(resp.Header.Get("Content-Type")) if !strings.EqualFold(ctype, "application/json") { - return errors.New("Unexpected") + return errors.New("Unexpected content-type") } d := json.NewDecoder(resp.Body) return d.Decode(value) } - s, err := getString("/galene-api/v0/.groups/") - if err != nil || s != "" { + var groups []string + err = getJSON("/galene-api/v0/.groups/", &groups) + if err != nil || len(groups) != 0 { t.Errorf("Get groups: %v", err) } @@ -171,9 +154,9 @@ func TestApi(t *testing.T) { t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode) } - s, err = getString("/galene-api/v0/.groups/") - if err != nil || s != "test\n" { - t.Errorf("Get groups: %v %#v", err, s) + err = getJSON("/galene-api/v0/.groups/", &groups) + if err != nil || len(groups) != 1 || groups[0] != "test" { + t.Errorf("Get groups: %v %v", err, groups) } resp, err = do("PUT", "/galene-api/v0/.groups/test/.fallback-users", @@ -193,8 +176,8 @@ func TestApi(t *testing.T) { t.Errorf("Set key: %v %v", err, resp.StatusCode) } - s, err = getString("/galene-api/v0/.groups/test/.users/") - if err != nil || s != "" { + err = getJSON("/galene-api/v0/.groups/test/.users/", &groups) + if err != nil || len(groups) != 0 { t.Errorf("Get users: %v", err) } @@ -213,9 +196,10 @@ func TestApi(t *testing.T) { t.Errorf("Create user: %v %v", err, resp.StatusCode) } - s, err = getString("/galene-api/v0/.groups/test/.users/") - if err != nil || s != "jch\n" { - t.Errorf("Get users: %v", err) + var users []string + err = getJSON("/galene-api/v0/.groups/test/.users/", &users) + if err != nil || len(users) != 1 || users[0] != "jch" { + t.Errorf("Get users: %v %v", err, users) } resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch", @@ -296,11 +280,12 @@ func TestApi(t *testing.T) { t.Errorf("Create token: %v %v", err, resp.StatusCode) } - tokname, err := getString("/galene-api/v0/.groups/test/.tokens/") - if err != nil { - t.Errorf("Get tokens: %v", err) + var toknames []string + err = getJSON("/galene-api/v0/.groups/test/.tokens/", &toknames) + if err != nil || len(toknames) != 1 { + t.Errorf("Get tokens: %v %v", err, toknames) } - tokname = tokname[:len(tokname)-1] + tokname := toknames[0] tokens, etag, err := token.List("test") if err != nil || len(tokens) != 1 || tokens[0].Token != tokname {