1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-11-25 10:05:58 +01:00
galene/webserver/api_test.go

468 lines
13 KiB
Go
Raw Normal View History

2024-04-09 16:53:03 +02:00
package webserver
import (
"errors"
"fmt"
"mime"
2024-04-09 16:53:03 +02:00
"os"
2024-06-01 17:04:28 +02:00
"reflect"
2024-04-09 16:53:03 +02:00
"strings"
"sync"
2024-05-01 22:12:48 +02:00
"time"
2024-04-09 16:53:03 +02:00
"encoding/json"
"net/http"
"path/filepath"
"testing"
"github.com/jech/galene/group"
2024-05-01 22:12:48 +02:00
"github.com/jech/galene/token"
2024-04-09 16:53:03 +02:00
)
2024-08-13 16:26:08 +02:00
var setupOnce sync.Once
func setup() {
setupOnce.Do(func() {
Insecure = true
err := Serve("localhost:1234", "")
if err != nil {
panic("could not start server")
}
})
}
2024-04-09 16:53:03 +02:00
func setupTest(dir, datadir string) error {
2024-08-13 16:26:08 +02:00
setup()
2024-04-09 16:53:03 +02:00
group.Directory = dir
group.DataDirectory = datadir
config := `{
"writableGroups": true,
"users": {
"root": {
"password": "pw",
"permissions": "admin"
}
}
}`
f, err := os.Create(filepath.Join(group.DataDirectory, "config.json"))
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(config)
if err != nil {
return err
}
2024-05-01 22:12:48 +02:00
token.SetStatefulFilename(filepath.Join(datadir, "tokens.jsonl"))
2024-04-09 16:53:03 +02:00
return nil
}
2024-05-01 22:12:48 +02:00
func marshalToString(v any) string {
buf, err := json.Marshal(v)
if err != nil {
return ""
}
return string(buf)
}
2024-04-09 16:53:03 +02:00
func TestApi(t *testing.T) {
err := setupTest(t.TempDir(), t.TempDir())
if err != nil {
t.Fatal(err)
}
client := http.Client{}
do := func(method, path, ctype, im, inm, body string) (*http.Response, error) {
req, err := http.NewRequest(method,
"http://localhost:1234"+path,
strings.NewReader(body),
)
if err != nil {
return nil, err
}
if ctype != "" {
req.Header.Set("Content-Type", ctype)
}
if im != "" {
req.Header.Set("If-Match", im)
}
if inm != "" {
req.Header.Set("If-None-Match", inm)
}
req.SetBasicAuth("root", "pw")
return client.Do(req)
}
getJSON := func(path string, value any) 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, _, err :=
mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil || !strings.EqualFold(ctype, "application/json") {
return errors.New("Unexpected content-type")
2024-04-09 16:53:03 +02:00
}
d := json.NewDecoder(resp.Body)
return d.Decode(value)
}
var groups []string
err = getJSON("/galene-api/v0/.groups/", &groups)
if err != nil || len(groups) != 0 {
2024-04-09 16:53:03 +02:00
t.Errorf("Get groups: %v", err)
}
2024-05-03 18:33:37 +02:00
resp, err := do("PUT", "/galene-api/v0/.groups/test/",
2024-04-09 16:53:03 +02:00
"application/json", "\"foo\"", "",
"{}")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode)
}
2024-05-03 18:33:37 +02:00
resp, err = do("PUT", "/galene-api/v0/.groups/test/",
2024-04-09 16:53:03 +02:00
"text/plain", "", "",
"Hello, world!")
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
t.Errorf("Create group (bad content-type): %v %v",
err, resp.StatusCode)
}
2024-05-03 18:33:37 +02:00
resp, err = do("PUT", "/galene-api/v0/.groups/test/",
2024-04-09 16:53:03 +02:00
"application/json", "", "*",
"{}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create group: %v %v", err, resp.StatusCode)
}
var desc *group.Description
2024-05-03 18:33:37 +02:00
err = getJSON("/galene-api/v0/.groups/test/", &desc)
2024-04-09 16:53:03 +02:00
if err != nil || len(desc.Users) != 0 {
t.Errorf("Get group: %v", err)
}
2024-05-03 18:33:37 +02:00
resp, err = do("PUT", "/galene-api/v0/.groups/test/",
2024-04-09 16:53:03 +02:00
"application/json", "", "*",
"{}")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode)
}
2024-05-03 18:33:37 +02:00
resp, err = do("DELETE", "/galene-api/v0/.groups/test/",
2024-04-09 16:53:03 +02:00
"", "", "*", "")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode)
}
err = getJSON("/galene-api/v0/.groups/", &groups)
if err != nil || len(groups) != 1 || groups[0] != "test" {
t.Errorf("Get groups: %v %v", err, groups)
2024-04-09 16:53:03 +02:00
}
2024-05-03 18:33:37 +02:00
resp, err = do("PUT", "/galene-api/v0/.groups/test/.keys",
2024-04-11 13:25:59 +02:00
"application/jwk-set+json", "", "",
`{"keys": [{
"kty": "oct", "alg": "HS256",
"k": "4S9YZLHK1traIaXQooCnPfBw_yR8j9VEPaAMWAog_YQ"
}]}`)
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Set key: %v %v", err, resp.StatusCode)
}
err = getJSON("/galene-api/v0/.groups/test/.users/", &groups)
if err != nil || len(groups) != 0 {
2024-04-09 16:53:03 +02:00
t.Errorf("Get users: %v", err)
}
2024-05-03 18:33:37 +02:00
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch",
2024-04-09 16:53:03 +02:00
"text/plain", "", "*",
`hello, world!`)
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
t.Errorf("Create user (bad content-type): %v %v",
err, resp.StatusCode)
}
2024-05-03 18:33:37 +02:00
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch",
2024-04-09 16:53:03 +02:00
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create user: %v %v", err, resp.StatusCode)
}
var users []string
err = getJSON("/galene-api/v0/.groups/test/.users/", &users)
2024-06-01 17:04:28 +02:00
if err != nil || len(users) != 1 || users[0] != "jch" {
t.Errorf("Get users: %v %v", err, users)
2024-04-09 16:53:03 +02:00
}
2024-05-03 18:33:37 +02:00
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch",
2024-04-09 16:53:03 +02:00
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create user (bad ETag): %v %v", err, resp.StatusCode)
}
2024-05-03 18:33:37 +02:00
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch/.password",
2024-04-09 16:53:03 +02:00
"application/json", "", "",
`"toto"`)
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Set password (PUT): %v %v", err, resp.StatusCode)
}
2024-05-03 18:33:37 +02:00
resp, err = do("POST", "/galene-api/v0/.groups/test/.users/jch/.password",
2024-04-09 16:53:03 +02:00
"text/plain", "", "",
`toto`)
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Set password (POST): %v %v", err, resp.StatusCode)
}
var user group.UserDescription
2024-05-03 18:33:37 +02:00
err = getJSON("/galene-api/v0/.groups/test/.users/jch", &user)
2024-04-09 16:53:03 +02:00
if err != nil {
t.Errorf("Get user: %v", err)
}
if user.Password.Type != "" && user.Password.Key != nil {
2024-04-09 16:53:03 +02:00
t.Errorf("User not sanitised properly")
}
desc, err = group.GetDescription("test")
if err != nil {
t.Errorf("GetDescription: %v", err)
}
if len(desc.Users) != 1 {
t.Errorf("Users: %#v", desc.Users)
}
if desc.Users["jch"].Password.Type != "pbkdf2" {
t.Errorf("Password.Type: %v", desc.Users["jch"].Password.Type)
}
2024-05-03 18:33:37 +02:00
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.users/jch",
2024-04-09 16:53:03 +02:00
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete group: %v %v", err, resp.StatusCode)
}
desc, err = group.GetDescription("test")
if err != nil {
t.Errorf("GetDescription: %v", err)
}
if len(desc.Users) != 0 {
t.Errorf("Users (after delete): %#v", desc.Users)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.wildcard-user",
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create wildcard user: %v %v", err, resp.StatusCode)
}
err = getJSON("/galene-api/v0/.groups/test/.wildcard-user", &user)
if err != nil {
t.Errorf("Get wildcard user: %v", err)
}
2024-06-01 17:04:28 +02:00
desc, err = group.GetDescription("test")
if err != nil {
t.Errorf("GetDescription: %v", err)
}
if !reflect.DeepEqual(user, *desc.WildcardUser) {
t.Errorf("Got %v, expected %v", desc.WildcardUser, user)
}
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.wildcard-user",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete wildcard user: %v %v", err, resp.StatusCode)
}
2024-06-01 17:04:28 +02:00
desc, err = group.GetDescription("test")
if err != nil {
t.Errorf("GetDescription: %v", err)
}
if desc.WildcardUser != nil {
t.Errorf("Got %v, expected nil", desc.WildcardUser)
}
2024-04-11 13:25:59 +02:00
if len(desc.AuthKeys) != 1 {
t.Errorf("Keys: %v", len(desc.AuthKeys))
}
2024-05-03 18:33:37 +02:00
resp, err = do("POST", "/galene-api/v0/.groups/test/.tokens/",
2024-05-01 22:12:48 +02:00
"application/json", "", "", `{"group":"bad"}`)
if err != nil || resp.StatusCode != http.StatusBadRequest {
t.Errorf("Create token (bad group): %v %v", err, resp.StatusCode)
}
2024-05-03 18:33:37 +02:00
resp, err = do("POST", "/galene-api/v0/.groups/test/.tokens/",
2024-05-01 22:12:48 +02:00
"application/json", "", "", "{}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create token: %v %v", err, resp.StatusCode)
}
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)
2024-05-01 22:12:48 +02:00
}
tokname := toknames[0]
2024-05-01 22:12:48 +02:00
tokens, etag, err := token.List("test")
if err != nil || len(tokens) != 1 || tokens[0].Token != tokname {
t.Errorf("token.List: %v %v", tokens, err)
}
2024-05-03 18:33:37 +02:00
tokenpath := "/galene-api/v0/.groups/test/.tokens/" + tokname
var tok token.Stateful
err = getJSON(tokenpath, &tok)
if err != nil {
2024-05-01 22:12:48 +02:00
t.Errorf("Get token: %v %v", err, resp.StatusCode)
}
if tok.Token != "" || tok.Group != "" {
t.Errorf("Get token: %v %v", tok.Token, tok.Group)
}
2024-05-01 22:12:48 +02:00
e := time.Now().Add(time.Hour)
tok.Expires = &e
resp, err = do("PUT", tokenpath,
"application/json", etag, "", marshalToString(tok))
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Update token: %v %v", err, resp.StatusCode)
}
tok.Token = tokens[0].Token
2024-05-02 18:14:51 +02:00
resp, err = do("PUT", tokenpath,
"application/json", "", "", marshalToString(tok))
if err != nil || resp.StatusCode != http.StatusBadRequest {
t.Errorf("Update token with name: %v %v", err, resp.StatusCode)
2024-05-02 18:14:51 +02:00
}
tok.Token = ""
tok.Group = "test"
2024-05-01 22:12:48 +02:00
resp, err = do("PUT", tokenpath,
"application/json", "", "", marshalToString(tok))
if err != nil || resp.StatusCode != http.StatusBadRequest {
t.Errorf("Update token with group: %v %v", err, resp.StatusCode)
2024-05-01 22:12:48 +02:00
}
err = getJSON(tokenpath, &tok)
if err != nil || !tok.Expires.Equal(e) {
t.Errorf("Got %v, expected %v (%v)", tok.Expires, e, err)
2024-05-01 22:12:48 +02:00
}
resp, err = do("PUT", "/galene-api/v0/.groups/test2",
"application/json", "", "*", "{}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create test2: %v %v", err, resp.StatusCode)
2024-05-01 22:12:48 +02:00
}
tokenpath2 := "/galene-api/v0/.groups/test2/.tokens/" + tokname
resp, err = do("GET", tokenpath2, "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNotFound {
t.Errorf("Get token in bad group: %v %v", err, resp.StatusCode)
}
resp, err = do("PUT", tokenpath2,
"application/json", "", "", "{}")
if err != nil || resp.StatusCode != http.StatusConflict {
t.Errorf("Put token in bad group: %v %v", err, resp.StatusCode)
2024-05-01 22:12:48 +02:00
}
resp, err = do("DELETE", tokenpath, "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Update token: %v %v", err, resp.StatusCode)
}
tokens, etag, err = token.List("test")
if err != nil || len(tokens) != 0 {
t.Errorf("Token list: %v %v", tokens, err)
}
2024-05-03 18:33:37 +02:00
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.keys",
2024-04-11 13:25:59 +02:00
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete keys: %v %v", err, resp.StatusCode)
}
2024-05-03 18:33:37 +02:00
resp, err = do("DELETE", "/galene-api/v0/.groups/test/",
2024-04-09 16:53:03 +02:00
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete group: %v %v", err, resp.StatusCode)
}
_, err = group.GetDescription("test")
if !errors.Is(err, os.ErrNotExist) {
2024-04-09 16:53:03 +02:00
t.Errorf("Group exists after delete")
}
}
func TestApiBadAuth(t *testing.T) {
err := setupTest(t.TempDir(), t.TempDir())
if err != nil {
t.Fatal(err)
}
client := http.Client{}
do := func(method, path string) {
req, err := http.NewRequest(method,
"http://localhost:1234"+path,
nil)
if err != nil {
t.Errorf("New request: %v", err)
return
}
req.SetBasicAuth("root", "badpw")
resp, err := client.Do(req)
if err != nil {
t.Errorf("%v %v: %v", method, path, err)
return
}
if resp.StatusCode != http.StatusUnauthorized {
t.Errorf("%v %v: %v", method, path, resp.StatusCode)
}
}
2024-05-03 18:33:37 +02:00
do("GET", "/galene-api/v0/.stats")
do("GET", "/galene-api/v0/.groups/")
do("PUT", "/galene-api/v0/.groups/test/")
2024-04-09 16:53:03 +02:00
f, err := os.Create(filepath.Join(group.Directory, "test.json"))
if err != nil {
t.Fatalf("Create(test.json): %v", err)
}
f.WriteString(`{
"users": {"jch": {"permissions": "present", "password": "pw"}}
}\n`)
f.Close()
2024-05-03 18:33:37 +02:00
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")
2024-04-09 16:53:03 +02:00
}