Commit c58064d9 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Move token handling into the separate module.

Tokens are now an interface, and all the token logic is encapsulated
in the token module.
parent 59ff2531
...@@ -1111,81 +1111,66 @@ func readDescription(name string) (*Description, error) { ...@@ -1111,81 +1111,66 @@ func readDescription(name string) (*Description, error) {
} }
// called locked // called locked
func (g *Group) getPermission(creds ClientCredentials) (string, []string, error) { func (g *Group) getPasswordPermission(creds ClientCredentials) ([]string, error) {
desc := g.description desc := g.description
if creds.Token == "" {
if !desc.AllowAnonymous && creds.Username == "" { if !desc.AllowAnonymous && creds.Username == "" {
return "", nil, ErrAnonymousNotAuthorised return nil, ErrAnonymousNotAuthorised
} }
if found, good := matchClient(creds, desc.Op); found { if found, good := matchClient(creds, desc.Op); found {
if good { if good {
var p []string
p = []string{"op", "present"}
if desc.AllowRecording { if desc.AllowRecording {
p = append(p, "record") return []string{"op", "present", "record"}, nil
} }
return creds.Username, p, nil return []string{"op", "present"}, nil
} }
return "", nil, ErrNotAuthorised return nil, ErrNotAuthorised
} }
if found, good := matchClient(creds, desc.Presenter); found { if found, good := matchClient(creds, desc.Presenter); found {
if good { if good {
return creds.Username, []string{"present"}, nil return []string{"present"}, nil
} }
return "", nil, ErrNotAuthorised return nil, ErrNotAuthorised
} }
if found, good := matchClient(creds, desc.Other); found { if found, good := matchClient(creds, desc.Other); found {
if good { if good {
return creds.Username, nil, nil return nil, nil
} }
return "", nil, ErrNotAuthorised return nil, ErrNotAuthorised
}
return "", nil, ErrNotAuthorised
} }
return nil, ErrNotAuthorised
}
sub, aud, perms, err := token.Valid(creds.Token, desc.AuthKeys) // called locked
func (g *Group) getPermission(creds ClientCredentials) (string, []string, error) {
desc := g.description
var username string
var perms []string
if creds.Token != "" {
tok, err := token.Parse(creds.Token, desc.AuthKeys)
if err != nil { if err != nil {
log.Printf("Token authentication: %v", err) return "", nil, err
return "", nil, ErrNotAuthorised
}
if sub == nil {
log.Printf("Token authentication: token has no sub")
return "", nil, ErrNotAuthorised
}
username := *sub
if !desc.AllowAnonymous && username == "" {
return "", nil, ErrAnonymousNotAuthorised
} }
conf, err := GetConfiguration() conf, err := GetConfiguration()
if err != nil { if err != nil {
log.Printf("Read config.json: %v", err)
return "", nil, err return "", nil, err
} }
ok := false
for _, u := range aud { username, perms, err =
url, err := url.Parse(u) tok.Check(conf.CanonicalHost, g.name, &creds.Username)
if err != nil { if err != nil {
log.Printf("Token URL: %v", err) return "", nil, err
continue
}
// if canonicalHost is not set, we allow tokens
// for any domain name. Hopefully different
// servers use distinct keys.
if conf.CanonicalHost != "" {
if !strings.EqualFold(
url.Host, conf.CanonicalHost,
) {
continue
}
}
if url.Path == path.Join("/group", g.name)+"/" {
ok = true
break
} }
} else {
var err error
username = creds.Username
perms, err = g.getPasswordPermission(creds)
if err != nil {
return "", nil, err
} }
if !ok {
return "", nil, ErrNotAuthorised
} }
return username, perms, nil return username, perms, nil
} }
......
package token
import (
"crypto/ecdsa"
"crypto/elliptic"
"encoding/base64"
"errors"
"math/big"
"net/url"
"path"
"strings"
"github.com/golang-jwt/jwt/v4"
)
type JWT jwt.Token
func parseBase64(k string, d map[string]interface{}) ([]byte, error) {
v, ok := d[k].(string)
if !ok {
return nil, errors.New("key " + k + " not found")
}
vv, err := base64.RawURLEncoding.DecodeString(v)
if err != nil {
return nil, err
}
return vv, nil
}
func ParseKey(key map[string]interface{}) (interface{}, error) {
kty, ok := key["kty"].(string)
if !ok {
return nil, errors.New("kty not found")
}
alg, ok := key["alg"].(string)
if !ok {
return nil, errors.New("alg not found")
}
switch kty {
case "oct":
var length int
switch alg {
case "HS256":
length = 32
case "HS384":
length = 48
case "HS512":
length = 64
default:
return nil, errors.New("unknown alg")
}
k, err := parseBase64("k", key)
if err != nil {
return nil, err
}
if len(k) != length {
return nil, errors.New("bad length for key")
}
return k, nil
case "EC":
if alg != "ES256" {
return nil, errors.New("uknown alg")
}
crv, ok := key["crv"].(string)
if !ok {
return nil, errors.New("crv not found")
}
if crv != "P-256" {
return nil, errors.New("unknown crv")
}
curve := elliptic.P256()
xbytes, err := parseBase64("x", key)
if err != nil {
return nil, err
}
var x big.Int
x.SetBytes(xbytes)
ybytes, err := parseBase64("y", key)
if err != nil {
return nil, err
}
var y big.Int
y.SetBytes(ybytes)
if !curve.IsOnCurve(&x, &y) {
return nil, errors.New("key is not on curve")
}
return &ecdsa.PublicKey{
Curve: curve,
X: &x,
Y: &y,
}, nil
default:
return nil, errors.New("unknown key type")
}
}
func getKey(header map[string]interface{}, keys []map[string]interface{}) (interface{}, error) {
alg, _ := header["alg"].(string)
kid, _ := header["kid"].(string)
for _, k := range keys {
kid2, _ := k["kid"].(string)
alg2, _ := k["alg"].(string)
if (kid == "" || kid == kid2) && alg == alg2 {
return ParseKey(k)
}
}
return nil, errors.New("key not found")
}
func toStringArray(a []interface{}) ([]string, bool) {
b := make([]string, len(a))
for i, v := range a {
w, ok := v.(string)
if !ok {
return nil, false
}
b[i] = w
}
return b, true
}
func parseJWT(token string, keys []map[string]interface{}) (Token, error) {
t, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
return getKey(t.Header, keys)
})
if err != nil {
return nil, err
}
return (*JWT)(t), nil
}
func (token *JWT) Check(host, group string, username *string) (string, []string, error) {
claims := token.Claims.(jwt.MapClaims)
s, ok := claims["sub"]
if !ok {
return "", nil, errors.New("token has no 'sub' field")
}
sub, ok := s.(string)
if !ok {
return "", nil, errors.New("invalid 'sub' field")
}
// we accept tokens with a different username from the one provided,
// and use the token's 'sub' field to override the username
var aud []string
if a, ok := claims["aud"]; ok && a != nil {
switch a := a.(type) {
case string:
aud = []string{a}
case []interface{}:
aud, ok = toStringArray(a)
if !ok {
return "", nil, errors.New("invalid 'aud' field")
}
default:
return "", nil, errors.New("invalid 'aud' field")
}
}
ok = false
for _, u := range aud {
url, err := url.Parse(u)
if err != nil {
continue
}
// if canonicalHost is not set, we allow tokens
// for any domain name. Hopefully different
// servers use distinct keys.
if host != "" {
if !strings.EqualFold(url.Host, host) {
continue
}
}
if url.Path == path.Join("/group", group)+"/" {
ok = true
break
}
}
if !ok {
return "", nil, errors.New("token for wrong group")
}
var perms []string
if p, ok := claims["permissions"]; ok && p != nil {
pp, ok := p.([]interface{})
if !ok {
return "", nil, errors.New("invalid 'permissions' field")
}
perms, ok = toStringArray(pp)
if !ok {
return "", nil, errors.New("invalid 'permissions' field")
}
}
return sub, perms, nil
}
...@@ -3,14 +3,11 @@ package token ...@@ -3,14 +3,11 @@ package token
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/json" "encoding/json"
"errors"
"reflect" "reflect"
"testing" "testing"
"github.com/golang-jwt/jwt/v4"
) )
func TestHS256(t *testing.T) { func TestJWKHS256(t *testing.T) {
key := `{ key := `{
"kty":"oct", "kty":"oct",
"alg":"HS256", "alg":"HS256",
...@@ -31,7 +28,7 @@ func TestHS256(t *testing.T) { ...@@ -31,7 +28,7 @@ func TestHS256(t *testing.T) {
} }
} }
func TestES256(t *testing.T) { func TestJWKES256(t *testing.T) {
key := `{ key := `{
"kty":"EC", "kty":"EC",
"alg":"ES256", "alg":"ES256",
...@@ -57,7 +54,7 @@ func TestES256(t *testing.T) { ...@@ -57,7 +54,7 @@ func TestES256(t *testing.T) {
} }
} }
func TestValid(t *testing.T) { func TestJWT(t *testing.T) {
key := `{"alg":"HS256","k":"H7pCkktUl5KyPCZ7CKw09y1j460tfIv4dRcS1XstUKY","key_ops":["sign","verify"],"kty":"oct"}` key := `{"alg":"HS256","k":"H7pCkktUl5KyPCZ7CKw09y1j460tfIv4dRcS1XstUKY","key_ops":["sign","verify"],"kty":"oct"}`
var k map[string]interface{} var k map[string]interface{}
err := json.Unmarshal([]byte(key), &k) err := json.Unmarshal([]byte(key), &k)
...@@ -66,76 +63,89 @@ func TestValid(t *testing.T) { ...@@ -66,76 +63,89 @@ func TestValid(t *testing.T) {
} }
keys := []map[string]interface{}{k} keys := []map[string]interface{}{k}
john := "john"
jack := "jack"
goodToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDI5NCwiZXhwIjoyOTA2NzUwMjk0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0.6xXpgBkBMn4PSBpnwYHb-gRn_Q97Yq9DoKkAf2_6iwc" goodToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDI5NCwiZXhwIjoyOTA2NzUwMjk0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0.6xXpgBkBMn4PSBpnwYHb-gRn_Q97Yq9DoKkAf2_6iwc"
sub, aud, perms, err := Valid(goodToken, keys) tok, err := Parse(goodToken, keys)
if err != nil { if err != nil {
t.Errorf("Token invalid: %v", err) t.Errorf("Couldn't parse goodToken: %v", err)
} else {
if sub == nil || *sub != "john" {
t.Errorf("Unexpected sub: %v", sub)
}
if !reflect.DeepEqual(aud, []string{"https://galene.org:8443/group/auth/"}) {
t.Errorf("Unexpected aud: %v", aud)
} }
if !reflect.DeepEqual(perms, []string{"present"}) {
t.Errorf("Unexpected perms: %v", perms) username, perms, err := tok.Check("galene.org:8443", "auth", &john)
if err != nil {
t.Errorf("goodToken is not valid: %v", err)
} }
if username != "john" || !reflect.DeepEqual(perms, []string{"present"}) {
t.Errorf("Expected john, [present], got %v %v", username, perms)
} }
anonymousToken := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.xwpHIRzKAIgiHKG1pVQyZlXcolmvRwNvBm6FN2gTwZw" username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
sub, aud, perms, err = Valid(anonymousToken, keys)
if err != nil { if err != nil {
t.Errorf("Token invalid: %v", err) t.Errorf("goodToken is not valid: %v", err)
} else {
if sub == nil || *sub != "" {
t.Errorf("Unexpected sub: %v", sub)
} }
if !reflect.DeepEqual(aud, []string{"https://galene.org:8443/group/auth/"}) { if username != "john" || !reflect.DeepEqual(perms, []string{"present"}) {
t.Errorf("Unexpected aud: %v", aud) t.Errorf("Expected john, [present], got %v %v", username, perms)
} }
if !reflect.DeepEqual(perms, []string{"present"}) {
t.Errorf("Unexpected perms: %v", perms) username, perms, err = tok.Check("", "auth", &john)
if err != nil {
t.Errorf("goodToken is not valid: %v", err)
} }
_, _, err = tok.Check("galene.org", "auth", &john)
if err == nil {
t.Errorf("goodToken is valid for wrong hostname")
} }
noSubToken := "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.7LvoZEKPNVvsRe8SjLxmKa1TgjTA4ZQo2LMPJSXl-ro" _, _, err = tok.Check("galene.org:8443", "not-auth", &john)
if err == nil {
t.Errorf("goodToken is valid for wrong group")
}
emptySubToken := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.xwpHIRzKAIgiHKG1pVQyZlXcolmvRwNvBm6FN2gTwZw"
sub, aud, perms, err = Valid(noSubToken, keys) tok, err = Parse(emptySubToken, keys)
if err != nil { if err != nil {
t.Errorf("Token invalid: %v", err) t.Errorf("Couldn't parse emptySubToken: %v", err)
} else {
if sub != nil {
t.Errorf("Unexpected sub: %v", sub)
} }
if !reflect.DeepEqual(aud, []string{"https://galene.org:8443/group/auth/"}) { username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
t.Errorf("Unexpected aud: %v", aud) if err != nil {
t.Errorf("anonymousToken is not valid: %v", err)
} }
if !reflect.DeepEqual(perms, []string{"present"}) { if username != "" || !reflect.DeepEqual(perms, []string{"present"}) {
t.Errorf("Unexpected perms: %v", perms) t.Errorf("Expected \"\", [present], got %v %v", username, perms)
} }
noSubToken := "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.7LvoZEKPNVvsRe8SjLxmKa1TgjTA4ZQo2LMPJSXl-ro"
tok, err = Parse(noSubToken, keys)
if err != nil {
t.Errorf("Couldn't parse noSubToken: %v", err)
}
username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
if err == nil {
t.Errorf("noSubToken is valid")
} }
badToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDQ2OSwiZXhwIjoyOTA2NzUwNDY5LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0." badToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDQ2OSwiZXhwIjoyOTA2NzUwNDY5LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0."
_, _, _, err = Valid(badToken, keys) _, err = Parse(badToken, keys)
var verr *jwt.ValidationError if err == nil {
if !errors.As(err, &verr) { t.Errorf("badToken is good")
t.Errorf("Token should fail")
} }
expiredToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDMyMiwiZXhwIjoxNjQ1MzEwMzUyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0.jyqRhoV6iK54SvlP33Fy630aDo-sLNmKKi1kcfqs378" expiredToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDMyMiwiZXhwIjoxNjQ1MzEwMzUyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0.jyqRhoV6iK54SvlP33Fy630aDo-sLNmKKi1kcfqs378"
_, _, _, err = Valid(expiredToken, keys) _, err = Parse(expiredToken, keys)
if !errors.As(err, &verr) { if err == nil {
t.Errorf("Token should be expired") t.Errorf("expiredToken is good")
} }
noneToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDQwMSwiZXhwIjoxNjQ1MzEwNDMxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0." noneToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDQwMSwiZXhwIjoxNjQ1MzEwNDMxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0."
_, _, _, err = Valid(noneToken, keys) _, err = Parse(noneToken, keys)
if err == nil { if err == nil {
t.Errorf("Unsigned token should fail") t.Errorf("noneToken is good")
} }
} }
package token package token
import ( type Token interface {
"crypto/ecdsa" Check(host, group string, username *string) (string, []string, error)
"crypto/elliptic"
"encoding/base64"
"errors"
"math/big"
"github.com/golang-jwt/jwt/v4"
)
func parseBase64(k string, d map[string]interface{}) ([]byte, error) {
v, ok := d[k].(string)
if !ok {
return nil, errors.New("key " + k + " not found")
}
vv, err := base64.RawURLEncoding.DecodeString(v)
if err != nil {
return nil, err
}
return vv, nil
}
func ParseKey(key map[string]interface{}) (interface{}, error) {
kty, ok := key["kty"].(string)
if !ok {
return nil, errors.New("kty not found")
}
alg, ok := key["alg"].(string)
if !ok {
return nil, errors.New("alg not found")
}
switch kty {
case "oct":
var length int
switch alg {
case "HS256":
length = 32
case "HS384":
length = 48
case "HS512":
length = 64
default:
return nil, errors.New("unknown alg")
}
k, err := parseBase64("k", key)
if err != nil {
return nil, err
}
if len(k) != length {
return nil, errors.New("bad length for key")
}
return k, nil
case "EC":
if alg != "ES256" {
return nil, errors.New("uknown alg")
}
crv, ok := key["crv"].(string)
if !ok {
return nil, errors.New("crv not found")
}
if crv != "P-256" {
return nil, errors.New("unknown crv")
}
curve := elliptic.P256()
xbytes, err := parseBase64("x", key)
if err != nil {
return nil, err
}
var x big.Int
x.SetBytes(xbytes)
ybytes, err := parseBase64("y", key)
if err != nil {
return nil, err
}
var y big.Int
y.SetBytes(ybytes)
if !curve.IsOnCurve(&x, &y) {
return nil, errors.New("key is not on curve")
}
return &ecdsa.PublicKey{
Curve: curve,
X: &x,
Y: &y,
}, nil
default:
return nil, errors.New("unknown key type")
}
}
func getKey(header map[string]interface{}, keys []map[string]interface{}) (interface{}, error) {
alg, _ := header["alg"].(string)
kid, _ := header["kid"].(string)
for _, k := range keys {
kid2, _ := k["kid"].(string)
alg2, _ := k["alg"].(string)
if (kid == "" || kid == kid2) && alg == alg2 {
return ParseKey(k)
}
}
return nil, errors.New("key not found")
}
func toStringArray(a []interface{}) ([]string, bool) {
b := make([]string, len(a))
for i, v := range a {
w, ok := v.(string)
if !ok {
return nil, false
}
b[i] = w
}
return b, true
} }
func Valid(token string, keys []map[string]interface{}) (*string, []string, []string, error) { func Parse(token string, keys []map[string]interface{}) (Token, error) {
tok, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { return parseJWT(token, keys)
return getKey(t.Header, keys)
})
if err != nil {
return nil, nil, nil, err
}
claims := tok.Claims.(jwt.MapClaims)
var sub *string
if s, ok := claims["sub"]; ok && s != nil {
ss, ok := s.(string)
if !ok {
return nil, nil, nil,
errors.New("invalid 'sub' field")
}
sub = &ss
}
var aud []string
if a, ok := claims["aud"]; ok && a != nil {
switch a := a.(type) {
case string:
aud = []string{a}
case []interface{}:
aud, ok = toStringArray(a)
if !ok {
return nil, nil, nil,
errors.New("invalid 'aud' field")
}
default:
return nil, nil, nil,
errors.New("invalid 'aud' field")
}
}
var perms []string
if p, ok := claims["permissions"]; ok && p != nil {
pp, ok := p.([]interface{})
if !ok {
return nil, nil, nil,
errors.New("invalid 'permissions' field")
}
perms, ok = toStringArray(pp)
if !ok {
return nil, nil, nil,
errors.New("invalid 'permissions' field")
}
}
return sub, aud, perms, nil
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment