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 if desc.AllowRecording {
p = []string{"op", "present"} return []string{"op", "present", "record"}, nil
if desc.AllowRecording {
p = append(p, "record")
}
return creds.Username, p, nil
} }
return "", nil, ErrNotAuthorised return []string{"op", "present"}, nil
} }
if found, good := matchClient(creds, desc.Presenter); found { return nil, ErrNotAuthorised
if good { }
return creds.Username, []string{"present"}, nil if found, good := matchClient(creds, desc.Presenter); found {
} if good {
return "", nil, ErrNotAuthorised return []string{"present"}, nil
} }
if found, good := matchClient(creds, desc.Other); found { return nil, ErrNotAuthorised
if good { }
return creds.Username, nil, nil if found, good := matchClient(creds, desc.Other); found {
} if good {
return "", nil, ErrNotAuthorised return nil, nil
} }
return "", nil, ErrNotAuthorised return nil, ErrNotAuthorised
} }
return nil, ErrNotAuthorised
}
sub, aud, perms, err := token.Valid(creds.Token, desc.AuthKeys) // called locked
if err != nil { func (g *Group) getPermission(creds ClientCredentials) (string, []string, error) {
log.Printf("Token authentication: %v", err) desc := g.description
return "", nil, ErrNotAuthorised var username string
} var perms []string
if sub == nil { if creds.Token != "" {
log.Printf("Token authentication: token has no sub") tok, err := token.Parse(creds.Token, desc.AuthKeys)
return "", nil, ErrNotAuthorised
}
username := *sub
if !desc.AllowAnonymous && username == "" {
return "", nil, ErrAnonymousNotAuthorised
}
conf, err := GetConfiguration()
if err != nil {
log.Printf("Read config.json: %v", err)
return "", nil, err
}
ok := false
for _, u := range aud {
url, err := url.Parse(u)
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 conf, err := GetConfiguration()
// servers use distinct keys. if err != nil {
if conf.CanonicalHost != "" { return "", nil, err
if !strings.EqualFold(
url.Host, conf.CanonicalHost,
) {
continue
}
} }
if url.Path == path.Join("/group", g.name)+"/" {
ok = true username, perms, err =
break tok.Check(conf.CanonicalHost, g.name, &creds.Username)
if err != nil {
return "", nil, err
}
} 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 {
t.Errorf("Couldn't parse goodToken: %v", err)
}
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)
}
username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
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)
}
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")
}
_, _, err = tok.Check("galene.org:8443", "not-auth", &john)
if err == nil {
t.Errorf("goodToken is valid for wrong group")
}
emptySubToken := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.xwpHIRzKAIgiHKG1pVQyZlXcolmvRwNvBm6FN2gTwZw"
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 || *sub != "john" { username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
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)
}
}
anonymousToken := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.xwpHIRzKAIgiHKG1pVQyZlXcolmvRwNvBm6FN2gTwZw"
sub, aud, perms, err = Valid(anonymousToken, keys)
if err != nil { if err != nil {
t.Errorf("Token invalid: %v", err) t.Errorf("anonymousToken is not valid: %v", err)
} else { }
if sub == nil || *sub != "" { if username != "" || !reflect.DeepEqual(perms, []string{"present"}) {
t.Errorf("Unexpected sub: %v", sub) t.Errorf("Expected \"\", [present], got %v %v", username, perms)
}
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)
}
} }
noSubToken := "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.7LvoZEKPNVvsRe8SjLxmKa1TgjTA4ZQo2LMPJSXl-ro" noSubToken := "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.7LvoZEKPNVvsRe8SjLxmKa1TgjTA4ZQo2LMPJSXl-ro"
sub, aud, perms, err = Valid(noSubToken, keys) tok, err = Parse(noSubToken, keys)
if err != nil { if err != nil {
t.Errorf("Token invalid: %v", err) t.Errorf("Couldn't parse noSubToken: %v", err)
} else { }
if sub != nil { username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
t.Errorf("Unexpected sub: %v", sub) if err == nil {
} t.Errorf("noSubToken is valid")
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)
}
} }
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