Add include-subgroups capabilities for JWTs

Implements #285
This commit is contained in:
izeau
2025-10-19 17:20:00 +02:00
committed by Juliusz Chroboczek
parent f9b86db9fb
commit e3d161e056
3 changed files with 35 additions and 8 deletions
+4
View File
@@ -587,6 +587,10 @@ the same format as in the `joined` message. Since the client will only
use the token once, at the very beginning of the session, the tokens use the token once, at the very beginning of the session, the tokens
issued may have a short lifetime (on the order of 30s). issued may have a short lifetime (on the order of 30s).
A boolean `include-subgroups` claim may be included in the JWT payload. It
has the same effect as the `include-subgroups` parameter used when generating
a stateful token.
## Authentication portal ## Authentication portal
If a group's status dictionary has a non-empty `authPortal` field, Galene If a group's status dictionary has a non-empty `authPortal` field, Galene
+15 -8
View File
@@ -9,7 +9,6 @@ import (
"errors" "errors"
"math/big" "math/big"
"net/url" "net/url"
"path"
"strings" "strings"
"time" "time"
@@ -201,7 +200,14 @@ func (token *JWT) Check(host, group string, username *string) (string, []string,
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
ok := false
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return "", nil, errors.New("unexpected type for token")
}
incSubgroups, _ := claims["include-subgroups"].(bool)
ok = false
for _, u := range aud { for _, u := range aud {
url, err := url.Parse(u) url, err := url.Parse(u)
if err != nil { if err != nil {
@@ -215,7 +221,13 @@ func (token *JWT) Check(host, group string, username *string) (string, []string,
continue continue
} }
} }
if url.Path == path.Join("/group", group)+"/" { // aud path takes the form /group/<groupname>/
if !strings.HasPrefix(url.Path, "/group/") || !strings.HasSuffix(url.Path, "/") {
continue
}
tokenGroup := url.Path[len("/group/") : len(url.Path)-1]
if group == tokenGroup ||
incSubgroups && (tokenGroup == "" || strings.HasPrefix(group, tokenGroup+"/")) {
ok = true ok = true
break break
} }
@@ -224,11 +236,6 @@ func (token *JWT) Check(host, group string, username *string) (string, []string,
return "", nil, errors.New("token for wrong group") return "", nil, errors.New("token for wrong group")
} }
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return "", nil, errors.New("unexpected type for token")
}
var perms []string var perms []string
if p, ok := claims["permissions"]; ok && p != nil { if p, ok := claims["permissions"]; ok && p != nil {
perms, ok = toStringArray(p) perms, ok = toStringArray(p)
+16
View File
@@ -127,6 +127,11 @@ func TestJWT(t *testing.T) {
t.Errorf("goodToken is valid for wrong group") t.Errorf("goodToken is valid for wrong group")
} }
_, _, err = tok.Check("galene.org:8443", "auth/subgroup", &john)
if err == nil {
t.Errorf("goodToken is valid for subgroup")
}
emptySubToken := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.xwpHIRzKAIgiHKG1pVQyZlXcolmvRwNvBm6FN2gTwZw" emptySubToken := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.xwpHIRzKAIgiHKG1pVQyZlXcolmvRwNvBm6FN2gTwZw"
tok, err = Parse(emptySubToken, keys) tok, err = Parse(emptySubToken, keys)
@@ -174,4 +179,15 @@ func TestJWT(t *testing.T) {
if err == nil { if err == nil {
t.Errorf("noneToken is good") t.Errorf("noneToken is good")
} }
subgroupsToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJpbmNsdWRlLXN1Ymdyb3VwcyI6dHJ1ZSwicGVybWlzc2lvbnMiOlsicHJlc2VudCJdLCJpYXQiOjE2NDUzMTAyOTQsImV4cCI6MjkwNjc1MDI5NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDoxMjM0LyJ9.ty4B7cJPCGJPMadwOCNMGQirIByj1L9AxWiDFnBm5wA"
tok, err = Parse(subgroupsToken, keys)
if err != nil {
t.Errorf("subgroupsToken is not valid: %v", err)
}
_, _, err = tok.Check("galene.org:8443", "auth/subgroup", &john)
if err != nil {
t.Errorf("subgroupsToken is not valid for subgroup: %v", err)
}
} }