diff --git a/account_claims.go b/account_claims.go index 945bd98..dd75188 100644 --- a/account_claims.go +++ b/account_claims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -61,6 +61,7 @@ type Account struct { Limits OperatorLimits `json:"limits,omitempty"` SigningKeys StringList `json:"signing_keys,omitempty"` Revocations RevocationList `json:"revocations,omitempty"` + NatsStandard } // Validate checks if the account is valid, based on the wrapper @@ -130,7 +131,7 @@ func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) { } sort.Sort(a.Exports) sort.Sort(a.Imports) - a.ClaimsData.Type = AccountClaim + a.Type = AccountClaim return a.ClaimsData.Encode(pair, a) } diff --git a/activation_claims.go b/activation_claims.go index 99228a7..a171942 100644 --- a/activation_claims.go +++ b/activation_claims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -30,6 +30,7 @@ type Activation struct { ImportSubject Subject `json:"subject,omitempty"` ImportType ExportType `json:"type,omitempty"` Limits + NatsStandard } // IsService returns true if an Activation is for a service @@ -82,7 +83,7 @@ func (a *ActivationClaims) Encode(pair nkeys.KeyPair) (string, error) { if !nkeys.IsValidPublicAccountKey(a.ClaimsData.Subject) { return "", errors.New("expected subject to be an account") } - a.ClaimsData.Type = ActivationClaim + a.Type = ActivationClaim return a.ClaimsData.Encode(pair, a) } diff --git a/claims.go b/claims.go index d402bcc..7238f5c 100644 --- a/claims.go +++ b/claims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -59,16 +59,20 @@ type Claims interface { // ClaimsData is the base struct for all claims type ClaimsData struct { - Audience string `json:"aud,omitempty"` - Expires int64 `json:"exp,omitempty"` - ID string `json:"jti,omitempty"` - IssuedAt int64 `json:"iat,omitempty"` - Issuer string `json:"iss,omitempty"` - Name string `json:"name,omitempty"` - NotBefore int64 `json:"nbf,omitempty"` - Subject string `json:"sub,omitempty"` - Tags TagList `json:"tags,omitempty"` - Type ClaimType `json:"type,omitempty"` + Audience string `json:"aud,omitempty"` + Expires int64 `json:"exp,omitempty"` + ID string `json:"jti,omitempty"` + IssuedAt int64 `json:"iat,omitempty"` + Issuer string `json:"iss,omitempty"` + Name string `json:"name,omitempty"` + NotBefore int64 `json:"nbf,omitempty"` + Subject string `json:"sub,omitempty"` +} + +// NatsStandard contains fields shared by all NATS JWTs +type NatsStandard struct { + Tags TagList `json:"tags,omitempty"` + Type ClaimType `json:"type,omitempty"` } // Prefix holds the prefix byte for an NKey diff --git a/cluster_claims.go b/cluster_claims.go index bbfcf06..0706185 100644 --- a/cluster_claims.go +++ b/cluster_claims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -27,6 +27,7 @@ type Cluster struct { Accounts []string `json:"accts,omitempty"` AccountURL string `json:"accturl,omitempty"` OperatorURL string `json:"opurl,omitempty"` + NatsStandard } // Validate checks the cluster and permissions for a cluster JWT @@ -55,7 +56,7 @@ func (c *ClusterClaims) Encode(pair nkeys.KeyPair) (string, error) { if !nkeys.IsValidPublicClusterKey(c.Subject) { return "", errors.New("expected subject to be a cluster public key") } - c.ClaimsData.Type = ClusterClaim + c.Type = ClusterClaim return c.ClaimsData.Encode(pair, c) } diff --git a/genericlaims.go b/genericlaims.go index 94cd86e..0d1295f 100644 --- a/genericlaims.go +++ b/genericlaims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -20,6 +20,7 @@ import "github.com/nats-io/nkeys" // GenericClaims can be used to read a JWT as a map for any non-generic fields type GenericClaims struct { ClaimsData + NatsStandard Data map[string]interface{} `json:"nats,omitempty"` } @@ -40,6 +41,15 @@ func DecodeGeneric(token string) (*GenericClaims, error) { if err := Decode(token, &v); err != nil { return nil, err } + ct, ok := v.Data["type"].(string) + if ok { + v.Type = ClaimType(ct) + } + tl, ok := v.Data["tags"].(TagList) + if ok { + v.Tags = tl + } + return &v, nil } diff --git a/operator_claims.go b/operator_claims.go index 6a99597..e54f26f 100644 --- a/operator_claims.go +++ b/operator_claims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -26,6 +26,7 @@ import ( // Operator specific claims type Operator struct { + NatsStandard // Slice of real identies (like websites) that can be used to identify the operator. Identities []Identity `json:"identity,omitempty"` // Slice of other operator NKeys that can be used to sign on behalf of the main @@ -112,15 +113,15 @@ func ValidateOperatorServiceURL(v string) error { } func (o *Operator) validateOperatorServiceURLs() []error { - var errors []error + var errs []error for _, v := range o.OperatorServiceURLs { if v != "" { if err := ValidateOperatorServiceURL(v); err != nil { - errors = append(errors, err) + errs = append(errs, err) } } } - return errors + return errs } // OperatorClaims define the data for an operator JWT @@ -165,7 +166,7 @@ func (oc *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) { if err != nil { return "", err } - oc.ClaimsData.Type = OperatorClaim + oc.Type = OperatorClaim return oc.ClaimsData.Encode(pair, oc) } diff --git a/operator_claims_test.go b/operator_claims_test.go index 73cae23..034e7cf 100644 --- a/operator_claims_test.go +++ b/operator_claims_test.go @@ -134,7 +134,7 @@ func TestSigningKeyValidation(t *testing.T) { uc := NewOperatorClaims(publicKey(ckp, t)) uc.Expires = time.Now().Add(time.Duration(time.Hour)).Unix() - uc.AddSigningKey(publicKey(ckp2, t)) + uc.SigningKeys.Add(publicKey(ckp2, t)) uJwt := encode(uc, ckp, t) uc2, err := DecodeOperatorClaims(uJwt) @@ -152,7 +152,7 @@ func TestSigningKeyValidation(t *testing.T) { t.Fatal("valid operator key should have no validation issues") } - uc.AddSigningKey("") // add an invalid one + uc.SigningKeys.Add("") // add an invalid one vr = &ValidationResults{} uc.Validate(vr) @@ -193,7 +193,7 @@ func TestSignedBy(t *testing.T) { AssertEquals(uc.DidSign(ac), false, t) // no signing key AssertEquals(uc2.DidSign(ac), true, t) // actual key - uc.AddSigningKey(publicKey(ckp2, t)) + uc.SigningKeys.Add(publicKey(ckp2, t)) AssertEquals(uc.DidSign(ac), true, t) // signing key clusterKey := createClusterNKey(t) @@ -352,3 +352,18 @@ func Test_OperatorServiceURL(t *testing.T) { errs := vr.Errors() AssertEquals(len(errs), shouldFail, t) } + +func Test_OperatorCompatibility(t *testing.T) { + token := "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJhdWQiOiJOR1MiLCJleHAiOjE2MjkwNDEwNTEsImp0aSI6IldWRFpYQ0dMQlVOWjYyWUI1MjRWTVdDNVRBRzJGS0lTWUNDUExLWTJKT0JURkdNMzM0V0EiLCJpYXQiOjE1NjU4ODI2NTEsImlzcyI6Ik9ETEMyMk5JUVE1VTRKNlpEVFZPRktURVg0Rjc3RTdUVk0yUkhXU0c3TjI2NllPVktUUkk0RVdYIiwibmFtZSI6IlN5bmFkaWEgQ29tbXVuaWNhdGlvbnMgSW5jLiIsIm5iZiI6MTU2NTg4MjcxMSwic3ViIjoiT0RMQzIyTklRUTVVNEo2WkRUVk9GS1RFWDRGNzdFN1RWTTJSSFdTRzdOMjY2WU9WS1RSSTRFV1giLCJ0eXBlIjoib3BlcmF0b3IiLCJuYXRzIjp7InNpZ25pbmdfa2V5cyI6WyJPRFNLQVlVNUpSTVhMRlhGSlU3WTczUE5OMjJaVFhDVlRWQTI2VlZMVkgyQ05NNlFWMlZCSk1JTyIsIk9EU0tCTkRJVDNMVFpXRlNSQVdPQlhTQlo3VlpDRFFWVTZUQkpYM1RRR1lYVVdSVTQ2QU5KSlM0IiwiT0RTS0NOSTVNTFNXTlBTNEdJQ0pHMktISk9DTEhaV1BPQlROVUIyNDVBTkNPQlg3VklRNk1BWkQiXSwiYWNjb3VudF9zZXJ2ZXJfdXJsIjoiaHR0cHM6Ly9hcGkuc3luYWRpYS5pby9qd3QvdjEiLCJvcGVyYXRvcl9zZXJ2aWNlX3VybHMiOlsidGxzOi8vY29ubmVjdC5uZ3MuZ2xvYmFsIl19fQ.aMRYSQJ8NEcH0j_7iLLgrtycMA5n1aiUftBe0kO9Ed6A1Jm8cxItDOMqV2qO3EixDDukHuFIhvgMg-fmsd44Ag" + _, err := DecodeOperatorClaims(token) + if err != nil { + t.Fatal(err) + } + gc, err := DecodeGeneric(token) + if err != nil { + t.Fatal(err) + } + if gc.Type != "operator" { + t.Fatalf("expected %q to be 'operator'", gc.Type) + } +} diff --git a/server_claims.go b/server_claims.go index c18f167..9e1eb24 100644 --- a/server_claims.go +++ b/server_claims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -25,6 +25,7 @@ import ( type Server struct { Permissions Cluster string `json:"cluster,omitempty"` + NatsStandard } // Validate checks the cluster and permissions for a server JWT @@ -55,7 +56,7 @@ func (s *ServerClaims) Encode(pair nkeys.KeyPair) (string, error) { if !nkeys.IsValidPublicServerKey(s.Subject) { return "", errors.New("expected subject to be a server public key") } - s.ClaimsData.Type = ServerClaim + s.Type = ServerClaim return s.ClaimsData.Encode(pair, s) } diff --git a/user_claims.go b/user_claims.go index 78fe6a9..a889667 100644 --- a/user_claims.go +++ b/user_claims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -26,6 +26,7 @@ type User struct { Permissions Limits BearerToken bool `json:"bearer_token,omitempty"` + NatsStandard } // Validate checks the permissions and limits in a User jwt @@ -59,7 +60,7 @@ func (u *UserClaims) Encode(pair nkeys.KeyPair) (string, error) { if !nkeys.IsValidPublicUserKey(u.Subject) { return "", errors.New("expected subject to be user public key") } - u.ClaimsData.Type = UserClaim + u.Type = UserClaim return u.ClaimsData.Encode(pair, u) }