From aa35408dba0dd7f64d7a51941dc4f417487a7ae4 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Fri, 3 May 2024 18:33:37 +0200 Subject: [PATCH] Rename /galene-api/0/ to /v0/. --- README.API | 28 +++++++------- static/management.js | 30 +++++++-------- static/stats.js | 2 +- webserver/api.go | 4 +- webserver/api_test.go | 90 +++++++++++++++++++++---------------------- 5 files changed, 77 insertions(+), 77 deletions(-) diff --git a/README.API b/README.API index df4fe87..241e144 100644 --- a/README.API +++ b/README.API @@ -3,7 +3,7 @@ Galene provides an HTTP-based API that can be used to create groups and users. For example, in order to create a group, a client may do - PUT /galene-api/0/.groups/groupname/ + PUT /galene-api/v0/.groups/groupname/ Content-Type: application/json If-None-Match: * @@ -11,12 +11,12 @@ The `If-None-Match` header avoids overwriting an existing group. In order to edit a group definition, a client first does - GET /galene-api/0/.groups/groupname/ + GET /galene-api/v0/.groups/groupname/ This yields the group definition and an entity tag (in the ETag header). The client then modifies the group defintion, and does - PUT /galene-api/0/.groups/groupname/ + PUT /galene-api/v0/.groups/groupname/ If-Match: "abcd" where "abcd" is the entity tag returned by the GET request. If the group @@ -27,13 +27,13 @@ in the case of a concurrent modification. ## Endpoints -The API is located under `/galene-api/0/`. The `/0/` is a version number, +The API is located under `/galene-api/v0/`. The `/v0/` is a version number, and will be incremented if we ever find out that the current API cannot be extended in a backwards compatible manner. ### Statistics - /galene-api/0/.stats + /galene-api/v0/.stats Provides a number of statistics about the running server, in JSON. The exact format is undocumented, and may change between versions. The only @@ -41,14 +41,14 @@ allowed methods are HEAD and GET. ### List of groups - /galene-api/0/.groups/ + /galene-api/v0/.groups/ Returns a list of groups, as plain text, one per line. The only allowed methods are HEAD and GET. ### Group definition - /galene-api/0/.groups/groupname + /galene-api/v0/.groups/groupname Contains a "sanitised" group definition in JSON format, analogous to the on-disk format but without any user definitions or cryptographic keys. @@ -57,7 +57,7 @@ content-type is `application/json`. ### Fallback users - /galene-api/0/.groups/groupname/.fallback-users + /galene-api/v0/.groups/groupname/.fallback-users Contains fallback user descriptions, in the same format as the `fallbackUsers` field of the on-disk format. Allowed methods are PUT and @@ -65,7 +65,7 @@ DELETE. The only accepted content-type is `application/json`. ### Authentication keys - /galene-api/0/.groups/groupname/.keys + /galene-api/v0/.groups/groupname/.keys Contains the keys used for validation of stateless tokens, encoded as a JSON key set (RFC 7517). Allowed methods are PUT and DELETE. The only @@ -73,14 +73,14 @@ accepted content-type is `application/jwk-set+json`. ### List of users - /galene-api/0/.groups/groupname/.users/ + /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. ### User definition - /galene-api/0/.groups/groupname/.users/username + /galene-api/v0/.groups/groupname/.users/username Contains a "sanitised" user definition (without any passwords), a JSON object with a single field `permissions`. Allowed methods are HEAD, GET, @@ -88,7 +88,7 @@ PUT and DELETE. The only accepted content-type is `application/json`. ### Passwords - /galene-api/0/.groups/groupname/.users/username/.password + /galene-api/v0/.groups/groupname/.users/username/.password Contains the password of a given user. The PUT method takes a full password definition, identical to what can appear in the `"password"` @@ -99,7 +99,7 @@ POST. ### List of stateful tokens - /galene-api/0/.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 line. POST creates a new token, and returns its name in the `Location` @@ -107,7 +107,7 @@ header. Allowed methods are HEAD, GET and POST. ### Stateful token - /galene-api/0/.groups/groupname/.users/username/.tokens/token + /galene-api/v0/.groups/groupname/.users/username/.tokens/token The full contents of a single token, in JSON. The exact format may change between versions, so a client should first GET a token, update one or more diff --git a/static/management.js b/static/management.js index ff0845c..9c4b004 100644 --- a/static/management.js +++ b/static/management.js @@ -160,7 +160,7 @@ async function updateObject(url, values, etag) { * @returns {Promise>} */ async function listGroups() { - return await listObjects('/galene-api/0/.groups/'); + return await listObjects('/galene-api/v0/.groups/'); } /** @@ -171,7 +171,7 @@ async function listGroups() { * @returns {Promise} */ async function getGroup(group, etag) { - return await getObject(`/galene-api/0/.groups/${group}`, etag); + return await getObject(`/galene-api/v0/.groups/${group}`, etag); } /** @@ -181,7 +181,7 @@ async function getGroup(group, etag) { * @param {Object} [values] */ async function createGroup(group, values) { - return await createObject(`/galene-api/0/.groups/${group}`, values); + return await createObject(`/galene-api/v0/.groups/${group}`, values); } /** @@ -191,7 +191,7 @@ async function createGroup(group, values) { * @param {string} [etag] */ async function deleteGroup(group, etag) { - return await deleteObject(`/galene-api/0/.groups/${group}`, etag); + return await deleteObject(`/galene-api/v0/.groups/${group}`, etag); } /** @@ -204,7 +204,7 @@ async function deleteGroup(group, etag) { * @param {string} [etag] */ async function updateGroup(group, values, etag) { - return await updateObject(`/galene-api/0/.groups/${group}`, values); + return await updateObject(`/galene-api/v0/.groups/${group}`, values); } /** @@ -214,7 +214,7 @@ async function updateGroup(group, values, etag) { * @returns {Promise>} */ async function listUsers(group) { - return await listObjects(`/galene-api/0/.groups/${group}/.users/`); + return await listObjects(`/galene-api/v0/.groups/${group}/.users/`); } /** @@ -226,7 +226,7 @@ async function listUsers(group) { * @returns {Promise} */ async function getUser(group, user, etag) { - return await getObject(`/galene-api/0/.groups/${group}/.users/${user}`, + return await getObject(`/galene-api/v0/.groups/${group}/.users/${user}`, etag); } @@ -239,7 +239,7 @@ async function getUser(group, user, etag) { * @param {Object} values */ async function createUser(group, user, values) { - return await createObject(`/galene-api/0/.groups/${group}/.users/${user}`, + return await createObject(`/galene-api/v0/.groups/${group}/.users/${user}`, values); } @@ -252,7 +252,7 @@ async function createUser(group, user, values) { */ async function deleteUser(group, user, etag) { return await deleteObject( - `/galene-api/0/.groups/${group}/.users/${user}/`, etag, + `/galene-api/v0/.groups/${group}/.users/${user}/`, etag, ); } @@ -265,7 +265,7 @@ async function deleteUser(group, user, etag) { * @param {string} [etag] */ async function updateUser(group, user, values, etag) { - return await updateObject(`/galene-api/0/.groups/${group}/.users/${user}`, + return await updateObject(`/galene-api/v0/.groups/${group}/.users/${user}`, values, etag); } @@ -294,7 +294,7 @@ async function setPassword(group, user, password, oldpassword) { } let r = await fetch( - `/galene-api/0/.groups/${group}/.users/${user}/.password`, + `/galene-api/v0/.groups/${group}/.users/${user}/.password`, options); if(!r.ok) throw httpError(r); @@ -307,7 +307,7 @@ async function setPassword(group, user, password, oldpassword) { * @returns {Promise>} */ async function listTokens(group) { - return await listObjects(`/galene-api/0/.groups/${group}/.tokens/`); + return await listObjects(`/galene-api/v0/.groups/${group}/.tokens/`); } /** @@ -319,7 +319,7 @@ async function listTokens(group) { * @returns {Promise} */ async function getToken(group, token, etag) { - return await getObject(`/galene-api/0/.groups/${group}/.tokens/${token}`, + return await getObject(`/galene-api/v0/.groups/${group}/.tokens/${token}`, etag); } @@ -340,7 +340,7 @@ async function createToken(group, template) { } let r = await fetch( - `/galene-api/0/.groups/${group}/.tokens/`, + `/galene-api/v0/.groups/${group}/.tokens/`, options); if(!r.ok) throw httpError(r); @@ -360,6 +360,6 @@ async function updateToken(group, token, etag) { if(!token.token) throw new Error("Unnamed token"); return await updateObject( - `/galene-api/0/.groups/${group}/.tokens/${token.token}`, + `/galene-api/v0/.groups/${group}/.tokens/${token.token}`, token, etag); } diff --git a/static/stats.js b/static/stats.js index 7be63a9..2de274f 100644 --- a/static/stats.js +++ b/static/stats.js @@ -25,7 +25,7 @@ async function listStats() { let l; try { - let r = await fetch('/galene-api/0/.stats'); + let r = await fetch('/galene-api/v0/.stats'); if(!r.ok) throw new Error(`${r.status} ${r.statusText}`); l = await r.json(); diff --git a/webserver/api.go b/webserver/api.go index 7b42b83..dfc913f 100644 --- a/webserver/api.go +++ b/webserver/api.go @@ -71,7 +71,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) { } first, kind, rest := splitPath(r.URL.Path[len("/galene-api"):]) - if first == "/0" && kind == ".stats" && rest == "" { + if first == "/v0" && kind == ".stats" && rest == "" { if !checkAdmin(w, r) { return } @@ -89,7 +89,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) { e := json.NewEncoder(w) e.Encode(ss) return - } else if first == "/0" && kind == ".groups" { + } else if first == "/v0" && kind == ".groups" { apiGroupHandler(w, r, rest) return } diff --git a/webserver/api_test.go b/webserver/api_test.go index 352a9a9..bb8de15 100644 --- a/webserver/api_test.go +++ b/webserver/api_test.go @@ -125,19 +125,19 @@ func TestApi(t *testing.T) { return d.Decode(value) } - s, err := getString("/galene-api/0/.groups/") + s, err := getString("/galene-api/v0/.groups/") if err != nil || s != "" { t.Errorf("Get groups: %v", err) } - resp, err := do("PUT", "/galene-api/0/.groups/test/", + resp, err := do("PUT", "/galene-api/v0/.groups/test/", "application/json", "\"foo\"", "", "{}") if err != nil || resp.StatusCode != http.StatusPreconditionFailed { t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode) } - resp, err = do("PUT", "/galene-api/0/.groups/test/", + resp, err = do("PUT", "/galene-api/v0/.groups/test/", "text/plain", "", "", "Hello, world!") if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType { @@ -145,7 +145,7 @@ func TestApi(t *testing.T) { err, resp.StatusCode) } - resp, err = do("PUT", "/galene-api/0/.groups/test/", + resp, err = do("PUT", "/galene-api/v0/.groups/test/", "application/json", "", "*", "{}") if err != nil || resp.StatusCode != http.StatusCreated { @@ -153,37 +153,37 @@ func TestApi(t *testing.T) { } var desc *group.Description - err = getJSON("/galene-api/0/.groups/test/", &desc) + err = getJSON("/galene-api/v0/.groups/test/", &desc) if err != nil || len(desc.Users) != 0 { t.Errorf("Get group: %v", err) } - resp, err = do("PUT", "/galene-api/0/.groups/test/", + resp, err = do("PUT", "/galene-api/v0/.groups/test/", "application/json", "", "*", "{}") if err != nil || resp.StatusCode != http.StatusPreconditionFailed { t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode) } - resp, err = do("DELETE", "/galene-api/0/.groups/test/", + resp, err = do("DELETE", "/galene-api/v0/.groups/test/", "", "", "*", "") if err != nil || resp.StatusCode != http.StatusPreconditionFailed { t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode) } - s, err = getString("/galene-api/0/.groups/") + s, err = getString("/galene-api/v0/.groups/") if err != nil || s != "test\n" { t.Errorf("Get groups: %v %#v", err, s) } - resp, err = do("PUT", "/galene-api/0/.groups/test/.fallback-users", + resp, err = do("PUT", "/galene-api/v0/.groups/test/.fallback-users", "application/json", "", "", `[{"password": "topsecret"}]`) if err != nil || resp.StatusCode != http.StatusNoContent { t.Errorf("Set fallback users: %v %v", err, resp.StatusCode) } - resp, err = do("PUT", "/galene-api/0/.groups/test/.keys", + resp, err = do("PUT", "/galene-api/v0/.groups/test/.keys", "application/jwk-set+json", "", "", `{"keys": [{ "kty": "oct", "alg": "HS256", @@ -193,12 +193,12 @@ func TestApi(t *testing.T) { t.Errorf("Set key: %v %v", err, resp.StatusCode) } - s, err = getString("/galene-api/0/.groups/test/.users/") + s, err = getString("/galene-api/v0/.groups/test/.users/") if err != nil || s != "" { t.Errorf("Get users: %v", err) } - resp, err = do("PUT", "/galene-api/0/.groups/test/.users/jch", + resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch", "text/plain", "", "*", `hello, world!`) if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType { @@ -206,33 +206,33 @@ func TestApi(t *testing.T) { err, resp.StatusCode) } - resp, err = do("PUT", "/galene-api/0/.groups/test/.users/jch", + resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch", "application/json", "", "*", `{"permissions": "present"}`) if err != nil || resp.StatusCode != http.StatusCreated { t.Errorf("Create user: %v %v", err, resp.StatusCode) } - s, err = getString("/galene-api/0/.groups/test/.users/") + s, err = getString("/galene-api/v0/.groups/test/.users/") if err != nil || s != "jch\n" { t.Errorf("Get users: %v", err) } - resp, err = do("PUT", "/galene-api/0/.groups/test/.users/jch", + resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch", "application/json", "", "*", `{"permissions": "present"}`) if err != nil || resp.StatusCode != http.StatusPreconditionFailed { t.Errorf("Create user (bad ETag): %v %v", err, resp.StatusCode) } - resp, err = do("PUT", "/galene-api/0/.groups/test/.users/jch/.password", + resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch/.password", "application/json", "", "", `"toto"`) if err != nil || resp.StatusCode != http.StatusNoContent { t.Errorf("Set password (PUT): %v %v", err, resp.StatusCode) } - resp, err = do("POST", "/galene-api/0/.groups/test/.users/jch/.password", + resp, err = do("POST", "/galene-api/v0/.groups/test/.users/jch/.password", "text/plain", "", "", `toto`) if err != nil || resp.StatusCode != http.StatusNoContent { @@ -240,7 +240,7 @@ func TestApi(t *testing.T) { } var user group.UserDescription - err = getJSON("/galene-api/0/.groups/test/.users/jch", &user) + err = getJSON("/galene-api/v0/.groups/test/.users/jch", &user) if err != nil { t.Errorf("Get user: %v", err) } @@ -261,7 +261,7 @@ func TestApi(t *testing.T) { t.Errorf("Password.Type: %v", desc.Users["jch"].Password.Type) } - resp, err = do("DELETE", "/galene-api/0/.groups/test/.users/jch", + resp, err = do("DELETE", "/galene-api/v0/.groups/test/.users/jch", "", "", "", "") if err != nil || resp.StatusCode != http.StatusNoContent { t.Errorf("Delete group: %v %v", err, resp.StatusCode) @@ -284,19 +284,19 @@ func TestApi(t *testing.T) { t.Errorf("Keys: %v", len(desc.AuthKeys)) } - resp, err = do("POST", "/galene-api/0/.groups/test/.tokens/", + resp, err = do("POST", "/galene-api/v0/.groups/test/.tokens/", "application/json", "", "", `{"group":"bad"}`) if err != nil || resp.StatusCode != http.StatusBadRequest { t.Errorf("Create token (bad group): %v %v", err, resp.StatusCode) } - resp, err = do("POST", "/galene-api/0/.groups/test/.tokens/", + resp, err = do("POST", "/galene-api/v0/.groups/test/.tokens/", "application/json", "", "", "{}") if err != nil || resp.StatusCode != http.StatusCreated { t.Errorf("Create token: %v %v", err, resp.StatusCode) } - tokname, err := getString("/galene-api/0/.groups/test/.tokens/") + tokname, err := getString("/galene-api/v0/.groups/test/.tokens/") if err != nil { t.Errorf("Get tokens: %v", err) } @@ -307,7 +307,7 @@ func TestApi(t *testing.T) { t.Errorf("token.List: %v %v", tokens, err) } - tokenpath := "/galene-api/0/.groups/test/.tokens/" + tokname + tokenpath := "/galene-api/v0/.groups/test/.tokens/" + tokname resp, err = do("GET", tokenpath, "", "", "", "") if err != nil || resp.StatusCode != http.StatusOK { @@ -359,19 +359,19 @@ func TestApi(t *testing.T) { t.Errorf("Token list: %v %v", tokens, err) } - resp, err = do("DELETE", "/galene-api/0/.groups/test/.fallback-users", + resp, err = do("DELETE", "/galene-api/v0/.groups/test/.fallback-users", "", "", "", "") if err != nil || resp.StatusCode != http.StatusNoContent { t.Errorf("Delete fallback users: %v %v", err, resp.StatusCode) } - resp, err = do("DELETE", "/galene-api/0/.groups/test/.keys", + resp, err = do("DELETE", "/galene-api/v0/.groups/test/.keys", "", "", "", "") if err != nil || resp.StatusCode != http.StatusNoContent { t.Errorf("Delete keys: %v %v", err, resp.StatusCode) } - resp, err = do("DELETE", "/galene-api/0/.groups/test/", + resp, err = do("DELETE", "/galene-api/v0/.groups/test/", "", "", "", "") if err != nil || resp.StatusCode != http.StatusNoContent { t.Errorf("Delete group: %v %v", err, resp.StatusCode) @@ -410,9 +410,9 @@ func TestApiBadAuth(t *testing.T) { } } - do("GET", "/galene-api/0/.stats") - do("GET", "/galene-api/0/.groups/") - do("PUT", "/galene-api/0/.groups/test/") + do("GET", "/galene-api/v0/.stats") + do("GET", "/galene-api/v0/.groups/") + do("PUT", "/galene-api/v0/.groups/test/") f, err := os.Create(filepath.Join(group.Directory, "test.json")) if err != nil { @@ -423,20 +423,20 @@ func TestApiBadAuth(t *testing.T) { }\n`) f.Close() - do("PUT", "/galene-api/0/.groups/test/") - do("DELETE", "/galene-api/0/.groups/test/") - do("GET", "/galene-api/0/.groups/test/.users/") - do("GET", "/galene-api/0/.groups/test/.users/jch") - do("GET", "/galene-api/0/.groups/test/.users/jch") - do("PUT", "/galene-api/0/.groups/test/.users/jch") - do("DELETE", "/galene-api/0/.groups/test/.users/jch") - do("GET", "/galene-api/0/.groups/test/.users/not-jch") - do("PUT", "/galene-api/0/.groups/test/.users/not-jch") - do("PUT", "/galene-api/0/.groups/test/.users/jch/.password") - do("POST", "/galene-api/0/.groups/test/.users/jch/.password") - do("GET", "/galene-api/0/.groups/test/.tokens/") - do("POST", "/galene-api/0/.groups/test/.tokens/") - do("GET", "/galene-api/0/.groups/test/.tokens/token") - do("PUT", "/galene-api/0/.groups/test/.tokens/token") - do("DELETE", "/galene-api/0/.groups/test/.tokens/token") + do("PUT", "/galene-api/v0/.groups/test/") + do("DELETE", "/galene-api/v0/.groups/test/") + do("GET", "/galene-api/v0/.groups/test/.users/") + do("GET", "/galene-api/v0/.groups/test/.users/jch") + do("GET", "/galene-api/v0/.groups/test/.users/jch") + do("PUT", "/galene-api/v0/.groups/test/.users/jch") + do("DELETE", "/galene-api/v0/.groups/test/.users/jch") + do("GET", "/galene-api/v0/.groups/test/.users/not-jch") + do("PUT", "/galene-api/v0/.groups/test/.users/not-jch") + do("PUT", "/galene-api/v0/.groups/test/.users/jch/.password") + do("POST", "/galene-api/v0/.groups/test/.users/jch/.password") + do("GET", "/galene-api/v0/.groups/test/.tokens/") + do("POST", "/galene-api/v0/.groups/test/.tokens/") + do("GET", "/galene-api/v0/.groups/test/.tokens/token") + do("PUT", "/galene-api/v0/.groups/test/.tokens/token") + do("DELETE", "/galene-api/v0/.groups/test/.tokens/token") }