1
Fork 0

Implement keys handling in API.

This commit is contained in:
Juliusz Chroboczek 2024-04-11 13:25:59 +02:00
parent 10cab468a8
commit 31a18bcf44
4 changed files with 88 additions and 1 deletions

View File

@ -55,6 +55,14 @@ on-disk format but without any user definitions or cryptographic keys.
Allowed methods are HEAD, GET, PUT and DELETE. The only accepted Allowed methods are HEAD, GET, PUT and DELETE. The only accepted
content-type is `application/json`. content-type is `application/json`.
### Authentication keys
/galene-api/0/.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
accepted content-type is `application/jwk-set+json`.
### List of users ### List of users
/galene-api/0/.groups/groupname/.users/ /galene-api/0/.groups/groupname/.users/

View File

@ -540,6 +540,18 @@ func GetDescriptionNames() ([]string, error) {
return names, err return names, err
} }
func SetKeys(group string, keys []map[string]any) error {
groups.mu.Lock()
defer groups.mu.Unlock()
desc, err := readDescription(group, false)
if err != nil {
return err
}
desc.AuthKeys = keys
return rewriteDescriptionFile(desc.FileName, desc)
}
func GetUsers(group string) ([]string, string, error) { func GetUsers(group string) ([]string, string, error) {
desc, err := GetDescription(group) desc, err := GetDescription(group)
if err != nil { if err != nil {

View File

@ -128,6 +128,9 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
if kind == ".users" { if kind == ".users" {
apiUserHandler(w, r, g, rest) apiUserHandler(w, r, g, rest)
return return
} else if kind == ".keys" && rest == "" {
keysHandler(w, r, g)
return
} else if kind != "" { } else if kind != "" {
if !checkAdmin(w, r) { if !checkAdmin(w, r) {
return return
@ -257,7 +260,7 @@ func apiUserHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
} }
first2, kind2, rest2 := splitPath(pth) first2, kind2, rest2 := splitPath(pth)
if kind2 == ".password" && first2 != "" && rest2 == "" { if first2 != "" && kind2 == ".password" && rest2 == "" {
passwordHandler(w, r, g, first2[1:]) passwordHandler(w, r, g, first2[1:])
return return
} else if kind2 != "" || first2 == "" { } else if kind2 != "" || first2 == "" {
@ -429,3 +432,47 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string) {
methodNotAllowed(w, "PUT", "POST", "DELETE") methodNotAllowed(w, "PUT", "POST", "DELETE")
return return
} }
type jwkset = struct {
Keys []map[string]any `json:"keys"`
}
func keysHandler(w http.ResponseWriter, r *http.Request, g string) {
if !checkAdmin(w, r) {
return
}
if r.Method == "PUT" {
ctype := parseContentType(r.Header.Get("Content-Type"))
if !strings.EqualFold(ctype, "application/jwk-set+json") {
http.Error(w, "unsupported content type",
http.StatusUnsupportedMediaType)
return
}
d := json.NewDecoder(r.Body)
var keys jwkset
err := d.Decode(&keys)
if err != nil {
httpError(w, err)
return
}
err = group.SetKeys(g, keys.Keys)
if err != nil {
httpError(w, err)
return
}
w.WriteHeader(http.StatusNoContent)
return
} else if r.Method == "DELETE" {
err := group.SetKeys(g, nil)
if err != nil {
httpError(w, err)
return
}
w.WriteHeader(http.StatusNoContent)
return
}
methodNotAllowed(w, "PUT", "DELETE")
return
}

View File

@ -168,6 +168,16 @@ func TestApi(t *testing.T) {
t.Errorf("Get groups: %v %#v", err, s) t.Errorf("Get groups: %v %#v", err, s)
} }
resp, err = do("PUT", "/galene-api/0/.groups/test/.keys",
"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)
}
s, err = getString("/galene-api/0/.groups/test/.users/") s, err = getString("/galene-api/0/.groups/test/.users/")
if err != nil || s != "" { if err != nil || s != "" {
t.Errorf("Get users: %v", err) t.Errorf("Get users: %v", err)
@ -251,6 +261,16 @@ func TestApi(t *testing.T) {
t.Errorf("Users (after delete): %#v", desc.Users) t.Errorf("Users (after delete): %#v", desc.Users)
} }
if len(desc.AuthKeys) != 1 {
t.Errorf("Keys: %v", len(desc.AuthKeys))
}
resp, err = do("DELETE", "/galene-api/0/.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/0/.groups/test/",
"", "", "", "") "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent { if err != nil || resp.StatusCode != http.StatusNoContent {