1
Fork 0

Rename /galene-api/0/ to /v0/.

This commit is contained in:
Juliusz Chroboczek 2024-05-03 18:33:37 +02:00
parent 0090ae71dd
commit aa35408dba
5 changed files with 77 additions and 77 deletions

View File

@ -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

View File

@ -160,7 +160,7 @@ async function updateObject(url, values, etag) {
* @returns {Promise<Array<string>>}
*/
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<Object>}
*/
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<Array<string>>}
*/
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<Object>}
*/
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<Array<string>>}
*/
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<Object>}
*/
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);
}

View File

@ -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();

View File

@ -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
}

View File

@ -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")
}