github.com/nats-io/jwt/v2@v2.5.6/v1compat/account_claims.go (about)

     1  /*
     2   * Copyright 2018-2023 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package jwt
    17  
    18  import (
    19  	"errors"
    20  	"sort"
    21  	"time"
    22  
    23  	"github.com/nats-io/nkeys"
    24  )
    25  
    26  // NoLimit is used to indicate a limit field is unlimited in value.
    27  const NoLimit = -1
    28  
    29  // OperatorLimits are used to limit access by an account
    30  type OperatorLimits struct {
    31  	Subs            int64 `json:"subs,omitempty"`      // Max number of subscriptions
    32  	Conn            int64 `json:"conn,omitempty"`      // Max number of active connections
    33  	LeafNodeConn    int64 `json:"leaf,omitempty"`      // Max number of active leaf node connections
    34  	Imports         int64 `json:"imports,omitempty"`   // Max number of imports
    35  	Exports         int64 `json:"exports,omitempty"`   // Max number of exports
    36  	Data            int64 `json:"data,omitempty"`      // Max number of bytes
    37  	Payload         int64 `json:"payload,omitempty"`   // Max message payload
    38  	WildcardExports bool  `json:"wildcards,omitempty"` // Are wildcards allowed in exports
    39  }
    40  
    41  // IsEmpty returns true if all of the limits are 0/false.
    42  func (o *OperatorLimits) IsEmpty() bool {
    43  	return *o == OperatorLimits{}
    44  }
    45  
    46  // IsUnlimited returns true if all limits are
    47  func (o *OperatorLimits) IsUnlimited() bool {
    48  	return *o == OperatorLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, true}
    49  }
    50  
    51  // Validate checks that the operator limits contain valid values
    52  func (o *OperatorLimits) Validate(vr *ValidationResults) {
    53  	// negative values mean unlimited, so all numbers are valid
    54  }
    55  
    56  // Account holds account specific claims data
    57  type Account struct {
    58  	Imports     Imports        `json:"imports,omitempty"`
    59  	Exports     Exports        `json:"exports,omitempty"`
    60  	Identities  []Identity     `json:"identity,omitempty"`
    61  	Limits      OperatorLimits `json:"limits,omitempty"`
    62  	SigningKeys StringList     `json:"signing_keys,omitempty"`
    63  	Revocations RevocationList `json:"revocations,omitempty"`
    64  }
    65  
    66  // Validate checks if the account is valid, based on the wrapper
    67  func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) {
    68  	a.Imports.Validate(acct.Subject, vr)
    69  	a.Exports.Validate(vr)
    70  	a.Limits.Validate(vr)
    71  
    72  	for _, i := range a.Identities {
    73  		i.Validate(vr)
    74  	}
    75  
    76  	if !a.Limits.IsEmpty() && a.Limits.Imports >= 0 && int64(len(a.Imports)) > a.Limits.Imports {
    77  		vr.AddError("the account contains more imports than allowed by the operator")
    78  	}
    79  
    80  	// Check Imports and Exports for limit violations.
    81  	if a.Limits.Imports != NoLimit {
    82  		if int64(len(a.Imports)) > a.Limits.Imports {
    83  			vr.AddError("the account contains more imports than allowed by the operator")
    84  		}
    85  	}
    86  	if a.Limits.Exports != NoLimit {
    87  		if int64(len(a.Exports)) > a.Limits.Exports {
    88  			vr.AddError("the account contains more exports than allowed by the operator")
    89  		}
    90  		// Check for wildcard restrictions
    91  		if !a.Limits.WildcardExports {
    92  			for _, ex := range a.Exports {
    93  				if ex.Subject.HasWildCards() {
    94  					vr.AddError("the account contains wildcard exports that are not allowed by the operator")
    95  				}
    96  			}
    97  		}
    98  	}
    99  
   100  	for _, k := range a.SigningKeys {
   101  		if !nkeys.IsValidPublicAccountKey(k) {
   102  			vr.AddError("%s is not an account public key", k)
   103  		}
   104  	}
   105  }
   106  
   107  // AccountClaims defines the body of an account JWT
   108  type AccountClaims struct {
   109  	ClaimsData
   110  	Account `json:"nats,omitempty"`
   111  }
   112  
   113  // NewAccountClaims creates a new account JWT
   114  func NewAccountClaims(subject string) *AccountClaims {
   115  	if subject == "" {
   116  		return nil
   117  	}
   118  	c := &AccountClaims{}
   119  	// Set to unlimited to start. We do it this way so we get compiler
   120  	// errors if we add to the OperatorLimits.
   121  	c.Limits = OperatorLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, true}
   122  	c.Subject = subject
   123  	return c
   124  }
   125  
   126  // Encode converts account claims into a JWT string
   127  func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) {
   128  	if !nkeys.IsValidPublicAccountKey(a.Subject) {
   129  		return "", errors.New("expected subject to be account public key")
   130  	}
   131  	sort.Sort(a.Exports)
   132  	sort.Sort(a.Imports)
   133  	a.ClaimsData.Type = AccountClaim
   134  	return a.ClaimsData.Encode(pair, a)
   135  }
   136  
   137  // DecodeAccountClaims decodes account claims from a JWT string
   138  func DecodeAccountClaims(token string) (*AccountClaims, error) {
   139  	v := AccountClaims{}
   140  	if err := Decode(token, &v); err != nil {
   141  		return nil, err
   142  	}
   143  	return &v, nil
   144  }
   145  
   146  func (a *AccountClaims) String() string {
   147  	return a.ClaimsData.String(a)
   148  }
   149  
   150  // Payload pulls the accounts specific payload out of the claims
   151  func (a *AccountClaims) Payload() interface{} {
   152  	return &a.Account
   153  }
   154  
   155  // Validate checks the accounts contents
   156  func (a *AccountClaims) Validate(vr *ValidationResults) {
   157  	a.ClaimsData.Validate(vr)
   158  	a.Account.Validate(a, vr)
   159  
   160  	if nkeys.IsValidPublicAccountKey(a.ClaimsData.Issuer) {
   161  		if len(a.Identities) > 0 {
   162  			vr.AddWarning("self-signed account JWTs shouldn't contain identity proofs")
   163  		}
   164  		if !a.Limits.IsEmpty() {
   165  			vr.AddWarning("self-signed account JWTs shouldn't contain operator limits")
   166  		}
   167  	}
   168  }
   169  
   170  // ExpectedPrefixes defines the types that can encode an account jwt, account and operator
   171  func (a *AccountClaims) ExpectedPrefixes() []nkeys.PrefixByte {
   172  	return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator}
   173  }
   174  
   175  // Claims returns the accounts claims data
   176  func (a *AccountClaims) Claims() *ClaimsData {
   177  	return &a.ClaimsData
   178  }
   179  
   180  // DidSign checks the claims against the account's public key and its signing keys
   181  func (a *AccountClaims) DidSign(c Claims) bool {
   182  	if c != nil {
   183  		issuer := c.Claims().Issuer
   184  		if issuer == a.Subject {
   185  			return true
   186  		}
   187  		uc, ok := c.(*UserClaims)
   188  		if ok && uc.IssuerAccount == a.Subject {
   189  			return a.SigningKeys.Contains(issuer)
   190  		}
   191  		at, ok := c.(*ActivationClaims)
   192  		if ok && at.IssuerAccount == a.Subject {
   193  			return a.SigningKeys.Contains(issuer)
   194  		}
   195  	}
   196  	return false
   197  }
   198  
   199  // Revoke enters a revocation by publickey using time.Now().
   200  func (a *AccountClaims) Revoke(pubKey string) {
   201  	a.RevokeAt(pubKey, time.Now())
   202  }
   203  
   204  // RevokeAt enters a revocation by public key and timestamp into this account
   205  // This will revoke all jwt issued for pubKey, prior to timestamp
   206  // If there is already a revocation for this public key that is newer, it is kept.
   207  func (a *AccountClaims) RevokeAt(pubKey string, timestamp time.Time) {
   208  	if a.Revocations == nil {
   209  		a.Revocations = RevocationList{}
   210  	}
   211  
   212  	a.Revocations.Revoke(pubKey, timestamp)
   213  }
   214  
   215  // ClearRevocation removes any revocation for the public key
   216  func (a *AccountClaims) ClearRevocation(pubKey string) {
   217  	a.Revocations.ClearRevocation(pubKey)
   218  }
   219  
   220  // IsRevokedAt checks if the public key is in the revoked list with a timestamp later than the one passed in.
   221  // Generally this method is called with the subject and issue time of the jwt to be tested.
   222  // DO NOT pass time.Now(), it will not produce a stable/expected response.
   223  // The value is expected to be a public key or "*" (means all public keys)
   224  func (a *AccountClaims) IsRevokedAt(pubKey string, timestamp time.Time) bool {
   225  	return a.Revocations.IsRevoked(pubKey, timestamp)
   226  }
   227  
   228  // IsRevoked does not perform a valid check. Use IsRevokedAt instead.
   229  func (a *AccountClaims) IsRevoked(_ string) bool {
   230  	return true
   231  }
   232  
   233  // IsClaimRevoked checks if the account revoked the claim passed in.
   234  // Invalid claims (nil, no Subject or IssuedAt) will return true.
   235  func (a *AccountClaims) IsClaimRevoked(claim *UserClaims) bool {
   236  	if claim == nil || claim.IssuedAt == 0 || claim.Subject == "" {
   237  		return true
   238  	}
   239  	return a.Revocations.IsRevoked(claim.Subject, time.Unix(claim.IssuedAt, 0))
   240  }