github.com/nats-io/jwt/v2@v2.5.6/types.go (about) 1 /* 2 * Copyright 2018-2019 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 "encoding/json" 20 "fmt" 21 "net" 22 "net/url" 23 "reflect" 24 "strconv" 25 "strings" 26 "time" 27 ) 28 29 const MaxInfoLength = 8 * 1024 30 31 type Info struct { 32 Description string `json:"description,omitempty"` 33 InfoURL string `json:"info_url,omitempty"` 34 } 35 36 func (s Info) Validate(vr *ValidationResults) { 37 if len(s.Description) > MaxInfoLength { 38 vr.AddError("Description is too long") 39 } 40 if s.InfoURL != "" { 41 if len(s.InfoURL) > MaxInfoLength { 42 vr.AddError("Info URL is too long") 43 } 44 u, err := url.Parse(s.InfoURL) 45 if err == nil && (u.Hostname() == "" || u.Scheme == "") { 46 err = fmt.Errorf("no hostname or scheme") 47 } 48 if err != nil { 49 vr.AddError("error parsing info url: %v", err) 50 } 51 } 52 } 53 54 // ExportType defines the type of import/export. 55 type ExportType int 56 57 const ( 58 // Unknown is used if we don't know the type 59 Unknown ExportType = iota 60 // Stream defines the type field value for a stream "stream" 61 Stream 62 // Service defines the type field value for a service "service" 63 Service 64 ) 65 66 func (t ExportType) String() string { 67 switch t { 68 case Stream: 69 return "stream" 70 case Service: 71 return "service" 72 } 73 return "unknown" 74 } 75 76 // MarshalJSON marshals the enum as a quoted json string 77 func (t *ExportType) MarshalJSON() ([]byte, error) { 78 switch *t { 79 case Stream: 80 return []byte("\"stream\""), nil 81 case Service: 82 return []byte("\"service\""), nil 83 } 84 return nil, fmt.Errorf("unknown export type") 85 } 86 87 // UnmarshalJSON unmashals a quoted json string to the enum value 88 func (t *ExportType) UnmarshalJSON(b []byte) error { 89 var j string 90 err := json.Unmarshal(b, &j) 91 if err != nil { 92 return err 93 } 94 switch j { 95 case "stream": 96 *t = Stream 97 return nil 98 case "service": 99 *t = Service 100 return nil 101 } 102 return fmt.Errorf("unknown export type %q", j) 103 } 104 105 type RenamingSubject Subject 106 107 func (s RenamingSubject) Validate(from Subject, vr *ValidationResults) { 108 v := Subject(s) 109 v.Validate(vr) 110 if from == "" { 111 vr.AddError("subject cannot be empty") 112 } 113 if strings.Contains(string(s), " ") { 114 vr.AddError("subject %q cannot have spaces", v) 115 } 116 matchesSuffix := func(s Subject) bool { 117 return s == ">" || strings.HasSuffix(string(s), ".>") 118 } 119 if matchesSuffix(v) != matchesSuffix(from) { 120 vr.AddError("both, renaming subject and subject, need to end or not end in >") 121 } 122 fromCnt := from.countTokenWildcards() 123 refCnt := 0 124 for _, tk := range strings.Split(string(v), ".") { 125 if tk == "*" { 126 refCnt++ 127 } 128 if len(tk) < 2 { 129 continue 130 } 131 if tk[0] == '$' { 132 if idx, err := strconv.Atoi(tk[1:]); err == nil { 133 if idx > fromCnt { 134 vr.AddError("Reference $%d in %q reference * in %q that do not exist", idx, s, from) 135 } else { 136 refCnt++ 137 } 138 } 139 } 140 } 141 if refCnt != fromCnt { 142 vr.AddError("subject does not contain enough * or reference wildcards $[0-9]") 143 } 144 } 145 146 // Replaces reference tokens with * 147 func (s RenamingSubject) ToSubject() Subject { 148 if !strings.Contains(string(s), "$") { 149 return Subject(s) 150 } 151 bldr := strings.Builder{} 152 tokens := strings.Split(string(s), ".") 153 for i, tk := range tokens { 154 convert := false 155 if len(tk) > 1 && tk[0] == '$' { 156 if _, err := strconv.Atoi(tk[1:]); err == nil { 157 convert = true 158 } 159 } 160 if convert { 161 bldr.WriteString("*") 162 } else { 163 bldr.WriteString(tk) 164 } 165 if i != len(tokens)-1 { 166 bldr.WriteString(".") 167 } 168 } 169 return Subject(bldr.String()) 170 } 171 172 // Subject is a string that represents a NATS subject 173 type Subject string 174 175 // Validate checks that a subject string is valid, ie not empty and without spaces 176 func (s Subject) Validate(vr *ValidationResults) { 177 v := string(s) 178 if v == "" { 179 vr.AddError("subject cannot be empty") 180 // No other checks after that make sense 181 return 182 } 183 if strings.Contains(v, " ") { 184 vr.AddError("subject %q cannot have spaces", v) 185 } 186 if v[0] == '.' || v[len(v)-1] == '.' { 187 vr.AddError("subject %q cannot start or end with a `.`", v) 188 } 189 if strings.Contains(v, "..") { 190 vr.AddError("subject %q cannot contain consecutive `.`", v) 191 } 192 } 193 194 func (s Subject) countTokenWildcards() int { 195 v := string(s) 196 if v == "*" { 197 return 1 198 } 199 cnt := 0 200 for _, t := range strings.Split(v, ".") { 201 if t == "*" { 202 cnt++ 203 } 204 } 205 return cnt 206 } 207 208 // HasWildCards is used to check if a subject contains a > or * 209 func (s Subject) HasWildCards() bool { 210 v := string(s) 211 return strings.HasSuffix(v, ".>") || 212 strings.Contains(v, ".*.") || 213 strings.HasSuffix(v, ".*") || 214 strings.HasPrefix(v, "*.") || 215 v == "*" || 216 v == ">" 217 } 218 219 // IsContainedIn does a simple test to see if the subject is contained in another subject 220 func (s Subject) IsContainedIn(other Subject) bool { 221 otherArray := strings.Split(string(other), ".") 222 myArray := strings.Split(string(s), ".") 223 224 if len(myArray) > len(otherArray) && otherArray[len(otherArray)-1] != ">" { 225 return false 226 } 227 228 if len(myArray) < len(otherArray) { 229 return false 230 } 231 232 for ind, tok := range otherArray { 233 myTok := myArray[ind] 234 235 if ind == len(otherArray)-1 && tok == ">" { 236 return true 237 } 238 239 if tok != myTok && tok != "*" { 240 return false 241 } 242 } 243 244 return true 245 } 246 247 // TimeRange is used to represent a start and end time 248 type TimeRange struct { 249 Start string `json:"start,omitempty"` 250 End string `json:"end,omitempty"` 251 } 252 253 // Validate checks the values in a time range struct 254 func (tr *TimeRange) Validate(vr *ValidationResults) { 255 format := "15:04:05" 256 257 if tr.Start == "" { 258 vr.AddError("time ranges start must contain a start") 259 } else { 260 _, err := time.Parse(format, tr.Start) 261 if err != nil { 262 vr.AddError("start in time range is invalid %q", tr.Start) 263 } 264 } 265 266 if tr.End == "" { 267 vr.AddError("time ranges end must contain an end") 268 } else { 269 _, err := time.Parse(format, tr.End) 270 if err != nil { 271 vr.AddError("end in time range is invalid %q", tr.End) 272 } 273 } 274 } 275 276 // Src is a comma separated list of CIDR specifications 277 type UserLimits struct { 278 Src CIDRList `json:"src,omitempty"` 279 Times []TimeRange `json:"times,omitempty"` 280 Locale string `json:"times_location,omitempty"` 281 } 282 283 func (u *UserLimits) Empty() bool { 284 return reflect.DeepEqual(*u, UserLimits{}) 285 } 286 287 func (u *UserLimits) IsUnlimited() bool { 288 return len(u.Src) == 0 && len(u.Times) == 0 289 } 290 291 // Limits are used to control acccess for users and importing accounts 292 type Limits struct { 293 UserLimits 294 NatsLimits 295 } 296 297 func (l *Limits) IsUnlimited() bool { 298 return l.UserLimits.IsUnlimited() && l.NatsLimits.IsUnlimited() 299 } 300 301 // Validate checks the values in a limit struct 302 func (l *Limits) Validate(vr *ValidationResults) { 303 if len(l.Src) != 0 { 304 for _, cidr := range l.Src { 305 _, ipNet, err := net.ParseCIDR(cidr) 306 if err != nil || ipNet == nil { 307 vr.AddError("invalid cidr %q in user src limits", cidr) 308 } 309 } 310 } 311 312 if l.Times != nil && len(l.Times) > 0 { 313 for _, t := range l.Times { 314 t.Validate(vr) 315 } 316 } 317 318 if l.Locale != "" { 319 if _, err := time.LoadLocation(l.Locale); err != nil { 320 vr.AddError("could not parse iana time zone by name: %v", err) 321 } 322 } 323 } 324 325 // Permission defines allow/deny subjects 326 type Permission struct { 327 Allow StringList `json:"allow,omitempty"` 328 Deny StringList `json:"deny,omitempty"` 329 } 330 331 func (p *Permission) Empty() bool { 332 return len(p.Allow) == 0 && len(p.Deny) == 0 333 } 334 335 func checkPermission(vr *ValidationResults, subj string, permitQueue bool) { 336 tk := strings.Split(subj, " ") 337 switch len(tk) { 338 case 1: 339 Subject(tk[0]).Validate(vr) 340 case 2: 341 Subject(tk[0]).Validate(vr) 342 Subject(tk[1]).Validate(vr) 343 if !permitQueue { 344 vr.AddError(`Permission Subject "%s" is not allowed to contain queue`, subj) 345 } 346 default: 347 vr.AddError(`Permission Subject "%s" contains too many spaces`, subj) 348 } 349 } 350 351 // Validate the allow, deny elements of a permission 352 func (p *Permission) Validate(vr *ValidationResults, permitQueue bool) { 353 for _, subj := range p.Allow { 354 checkPermission(vr, subj, permitQueue) 355 } 356 for _, subj := range p.Deny { 357 checkPermission(vr, subj, permitQueue) 358 } 359 } 360 361 // ResponsePermission can be used to allow responses to any reply subject 362 // that is received on a valid subscription. 363 type ResponsePermission struct { 364 MaxMsgs int `json:"max"` 365 Expires time.Duration `json:"ttl"` 366 } 367 368 // Validate the response permission. 369 func (p *ResponsePermission) Validate(_ *ValidationResults) { 370 // Any values can be valid for now. 371 } 372 373 // Permissions are used to restrict subject access, either on a user or for everyone on a server by default 374 type Permissions struct { 375 Pub Permission `json:"pub,omitempty"` 376 Sub Permission `json:"sub,omitempty"` 377 Resp *ResponsePermission `json:"resp,omitempty"` 378 } 379 380 // Validate the pub and sub fields in the permissions list 381 func (p *Permissions) Validate(vr *ValidationResults) { 382 if p.Resp != nil { 383 p.Resp.Validate(vr) 384 } 385 p.Sub.Validate(vr, true) 386 p.Pub.Validate(vr, false) 387 } 388 389 // StringList is a wrapper for an array of strings 390 type StringList []string 391 392 // Contains returns true if the list contains the string 393 func (u *StringList) Contains(p string) bool { 394 for _, t := range *u { 395 if t == p { 396 return true 397 } 398 } 399 return false 400 } 401 402 // Add appends 1 or more strings to a list 403 func (u *StringList) Add(p ...string) { 404 for _, v := range p { 405 if !u.Contains(v) && v != "" { 406 *u = append(*u, v) 407 } 408 } 409 } 410 411 // Remove removes 1 or more strings from a list 412 func (u *StringList) Remove(p ...string) { 413 for _, v := range p { 414 for i, t := range *u { 415 if t == v { 416 a := *u 417 *u = append(a[:i], a[i+1:]...) 418 break 419 } 420 } 421 } 422 } 423 424 // TagList is a unique array of lower case strings 425 // All tag list methods lower case the strings in the arguments 426 type TagList []string 427 428 // Contains returns true if the list contains the tags 429 func (u *TagList) Contains(p string) bool { 430 p = strings.ToLower(strings.TrimSpace(p)) 431 for _, t := range *u { 432 if t == p { 433 return true 434 } 435 } 436 return false 437 } 438 439 // Add appends 1 or more tags to a list 440 func (u *TagList) Add(p ...string) { 441 for _, v := range p { 442 v = strings.ToLower(strings.TrimSpace(v)) 443 if !u.Contains(v) && v != "" { 444 *u = append(*u, v) 445 } 446 } 447 } 448 449 // Remove removes 1 or more tags from a list 450 func (u *TagList) Remove(p ...string) { 451 for _, v := range p { 452 v = strings.ToLower(strings.TrimSpace(v)) 453 for i, t := range *u { 454 if t == v { 455 a := *u 456 *u = append(a[:i], a[i+1:]...) 457 break 458 } 459 } 460 } 461 } 462 463 type CIDRList TagList 464 465 func (c *CIDRList) Contains(p string) bool { 466 return (*TagList)(c).Contains(p) 467 } 468 469 func (c *CIDRList) Add(p ...string) { 470 (*TagList)(c).Add(p...) 471 } 472 473 func (c *CIDRList) Remove(p ...string) { 474 (*TagList)(c).Remove(p...) 475 } 476 477 func (c *CIDRList) Set(values string) { 478 *c = CIDRList{} 479 c.Add(strings.Split(strings.ToLower(values), ",")...) 480 } 481 482 func (c *CIDRList) UnmarshalJSON(body []byte) (err error) { 483 // parse either as array of strings or comma separate list 484 var request []string 485 var list string 486 if err := json.Unmarshal(body, &request); err == nil { 487 *c = request 488 return nil 489 } else if err := json.Unmarshal(body, &list); err == nil { 490 c.Set(list) 491 return nil 492 } else { 493 return err 494 } 495 }