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 }