1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-11-22 08:35:57 +01:00

Use JSON arrays instead of plain text in API.

Suggested by Dianne Skoll.
This commit is contained in:
Juliusz Chroboczek 2024-05-03 18:54:22 +02:00
parent aa35408dba
commit a12331ee09
4 changed files with 42 additions and 61 deletions

View file

@ -43,8 +43,8 @@ allowed methods are HEAD and GET.
/galene-api/v0/.groups/ /galene-api/v0/.groups/
Returns a list of groups, as plain text, one per line. The only allowed Returns a list of groups, as a JSON array. The only allowed methods are
methods are HEAD and GET. HEAD and GET.
### Group definition ### Group definition
@ -75,8 +75,8 @@ accepted content-type is `application/jwk-set+json`.
/galene-api/v0/.groups/groupname/.users/ /galene-api/v0/.groups/groupname/.users/
Returns a list of users, as plain text, one per line. The only allowed Returns a list of users, as a JSON array. The only allowed methods are
methods are HEAD and GET. HEAD and GET.
### User definition ### User definition
@ -101,9 +101,9 @@ POST.
/galene-api/v0/.groups/groupname/.users/username/.tokens/ /galene-api/v0/.groups/groupname/.users/username/.tokens/
GET returns the list of stateful tokens, as plain text, one token per GET returns the list of stateful tokens, as a JSON array. POST creates
line. POST creates a new token, and returns its name in the `Location` a new token, and returns its name in the `Location` header. Allowed
header. Allowed methods are HEAD, GET and POST. methods are HEAD, GET and POST.
### Stateful token ### Stateful token

View file

@ -49,11 +49,10 @@ async function listObjects(url) {
let r = await fetch(url); let r = await fetch(url);
if(!r.ok) if(!r.ok)
throw httpError(r); throw httpError(r);
let strings = (await r.text()).split('\n'); let data = await r.json();
if(strings[strings.length - 1] === '') { if(!(data instanceof Array))
strings.pop(); throw new Error("Server didn't return array");
} return data;
return strings;
} }
/** /**

View file

@ -7,7 +7,6 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"os" "os"
@ -118,13 +117,12 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
httpError(w, err) httpError(w, err)
return return
} }
w.Header().Set("content-type", "text/plain; charset=utf-8") w.Header().Set("content-type", "application/json")
if r.Method == "HEAD" { if r.Method == "HEAD" {
return return
} }
for _, g := range groups { e := json.NewEncoder(w)
fmt.Fprintln(w, g) e.Encode(groups)
}
return return
} }
@ -257,7 +255,7 @@ func usersHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
httpError(w, err) httpError(w, err)
return return
} }
w.Header().Set("content-type", "text/plain; charset=utf-8") w.Header().Set("content-type", "application/json")
w.Header().Set("etag", etag) w.Header().Set("etag", etag)
done := checkPreconditions(w, r, etag) done := checkPreconditions(w, r, etag)
if done { if done {
@ -266,9 +264,8 @@ func usersHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
if r.Method == "HEAD" { if r.Method == "HEAD" {
return return
} }
for _, u := range users { e := json.NewEncoder(w)
fmt.Fprintln(w, u) e.Encode(users)
}
return return
} }
@ -545,17 +542,19 @@ func tokensHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
httpError(w, err) httpError(w, err)
return return
} }
w.Header().Set("content-type", w.Header().Set("content-type", "application/json")
"text/plain; charset=utf-8")
if etag != "" { if etag != "" {
w.Header().Set("etag", etag) w.Header().Set("etag", etag)
} }
if r.Method == "HEAD" { if r.Method == "HEAD" {
return return
} }
for _, t := range tokens { toknames := make([]string, len(tokens))
fmt.Fprintln(w, t.Token) for i, t := range tokens {
toknames[i] = t.Token
} }
e := json.NewEncoder(w)
e.Encode(toknames)
return return
} else if r.Method == "POST" { } else if r.Method == "POST" {
ctype := parseContentType(r.Header.Get("Content-Type")) 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) httpError(w, err)
return return
} }
w.Header().Set("content-type",
"text/plain; charset=utf-8")
w.Header().Set("location", t.Token) w.Header().Set("location", t.Token)
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
return return

View file

@ -3,7 +3,6 @@ package webserver
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"strings" "strings"
"sync" "sync"
@ -91,23 +90,6 @@ func TestApi(t *testing.T) {
return client.Do(req) 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 { getJSON := func(path string, value any) error {
resp, err := do("GET", path, "", "", "", "") resp, err := do("GET", path, "", "", "", "")
if err != nil { if err != nil {
@ -119,14 +101,15 @@ func TestApi(t *testing.T) {
} }
ctype := parseContentType(resp.Header.Get("Content-Type")) ctype := parseContentType(resp.Header.Get("Content-Type"))
if !strings.EqualFold(ctype, "application/json") { if !strings.EqualFold(ctype, "application/json") {
return errors.New("Unexpected") return errors.New("Unexpected content-type")
} }
d := json.NewDecoder(resp.Body) d := json.NewDecoder(resp.Body)
return d.Decode(value) return d.Decode(value)
} }
s, err := getString("/galene-api/v0/.groups/") var groups []string
if err != nil || s != "" { err = getJSON("/galene-api/v0/.groups/", &groups)
if err != nil || len(groups) != 0 {
t.Errorf("Get groups: %v", err) 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) t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode)
} }
s, err = getString("/galene-api/v0/.groups/") err = getJSON("/galene-api/v0/.groups/", &groups)
if err != nil || s != "test\n" { if err != nil || len(groups) != 1 || groups[0] != "test" {
t.Errorf("Get groups: %v %#v", err, s) t.Errorf("Get groups: %v %v", err, groups)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/.fallback-users", 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) t.Errorf("Set key: %v %v", err, resp.StatusCode)
} }
s, err = getString("/galene-api/v0/.groups/test/.users/") err = getJSON("/galene-api/v0/.groups/test/.users/", &groups)
if err != nil || s != "" { if err != nil || len(groups) != 0 {
t.Errorf("Get users: %v", err) t.Errorf("Get users: %v", err)
} }
@ -213,9 +196,10 @@ func TestApi(t *testing.T) {
t.Errorf("Create user: %v %v", err, resp.StatusCode) t.Errorf("Create user: %v %v", err, resp.StatusCode)
} }
s, err = getString("/galene-api/v0/.groups/test/.users/") var users []string
if err != nil || s != "jch\n" { err = getJSON("/galene-api/v0/.groups/test/.users/", &users)
t.Errorf("Get users: %v", err) 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", 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) t.Errorf("Create token: %v %v", err, resp.StatusCode)
} }
tokname, err := getString("/galene-api/v0/.groups/test/.tokens/") var toknames []string
if err != nil { err = getJSON("/galene-api/v0/.groups/test/.tokens/", &toknames)
t.Errorf("Get tokens: %v", err) 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") tokens, etag, err := token.List("test")
if err != nil || len(tokens) != 1 || tokens[0].Token != tokname { if err != nil || len(tokens) != 1 || tokens[0].Token != tokname {