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  }