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
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
/galene-api/0/.groups/groupname/.users/

View File

@ -540,6 +540,18 @@ func GetDescriptionNames() ([]string, error) {
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) {
desc, err := GetDescription(group)
if err != nil {

View File

@ -128,6 +128,9 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
if kind == ".users" {
apiUserHandler(w, r, g, rest)
return
} else if kind == ".keys" && rest == "" {
keysHandler(w, r, g)
return
} else if kind != "" {
if !checkAdmin(w, r) {
return
@ -257,7 +260,7 @@ func apiUserHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
}
first2, kind2, rest2 := splitPath(pth)
if kind2 == ".password" && first2 != "" && rest2 == "" {
if first2 != "" && kind2 == ".password" && rest2 == "" {
passwordHandler(w, r, g, first2[1:])
return
} else if kind2 != "" || first2 == "" {
@ -429,3 +432,47 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string) {
methodNotAllowed(w, "PUT", "POST", "DELETE")
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)
}
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/")
if err != nil || s != "" {
t.Errorf("Get users: %v", err)
@ -251,6 +261,16 @@ func TestApi(t *testing.T) {
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/",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {