github.com/nats-io/jwt/v2@v2.5.6/v1compat/exports.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  	"fmt"
    20  	"time"
    21  )
    22  
    23  // ResponseType is used to store an export response type
    24  type ResponseType string
    25  
    26  const (
    27  	// ResponseTypeSingleton is used for a service that sends a single response only
    28  	ResponseTypeSingleton = "Singleton"
    29  
    30  	// ResponseTypeStream is used for a service that will send multiple responses
    31  	ResponseTypeStream = "Stream"
    32  
    33  	// ResponseTypeChunked is used for a service that sends a single response in chunks (so not quite a stream)
    34  	ResponseTypeChunked = "Chunked"
    35  )
    36  
    37  // ServiceLatency is used when observing and exported service for
    38  // latency measurements.
    39  // Sampling 1-100, represents sampling rate, defaults to 100.
    40  // Results is the subject where the latency metrics are published.
    41  // A metric will be defined by the nats-server's ServiceLatency. Time durations
    42  // are in nanoseconds.
    43  // see https://github.com/nats-io/nats-server/blob/main/server/accounts.go#L524
    44  // e.g.
    45  //
    46  //	{
    47  //	 "app": "dlc22",
    48  //	 "start": "2019-09-16T21:46:23.636869585-07:00",
    49  //	 "svc": 219732,
    50  //	 "nats": {
    51  //	   "req": 320415,
    52  //	   "resp": 228268,
    53  //	   "sys": 0
    54  //	 },
    55  //	 "total": 768415
    56  //	}
    57  type ServiceLatency struct {
    58  	Sampling int     `json:"sampling,omitempty"`
    59  	Results  Subject `json:"results"`
    60  }
    61  
    62  func (sl *ServiceLatency) Validate(vr *ValidationResults) {
    63  	if sl.Sampling < 1 || sl.Sampling > 100 {
    64  		vr.AddError("sampling percentage needs to be between 1-100")
    65  	}
    66  	sl.Results.Validate(vr)
    67  	if sl.Results.HasWildCards() {
    68  		vr.AddError("results subject can not contain wildcards")
    69  	}
    70  }
    71  
    72  // Export represents a single export
    73  type Export struct {
    74  	Name                 string          `json:"name,omitempty"`
    75  	Subject              Subject         `json:"subject,omitempty"`
    76  	Type                 ExportType      `json:"type,omitempty"`
    77  	TokenReq             bool            `json:"token_req,omitempty"`
    78  	Revocations          RevocationList  `json:"revocations,omitempty"`
    79  	ResponseType         ResponseType    `json:"response_type,omitempty"`
    80  	Latency              *ServiceLatency `json:"service_latency,omitempty"`
    81  	AccountTokenPosition uint            `json:"account_token_position,omitempty"`
    82  }
    83  
    84  // IsService returns true if an export is for a service
    85  func (e *Export) IsService() bool {
    86  	return e.Type == Service
    87  }
    88  
    89  // IsStream returns true if an export is for a stream
    90  func (e *Export) IsStream() bool {
    91  	return e.Type == Stream
    92  }
    93  
    94  // IsSingleResponse returns true if an export has a single response
    95  // or no resopnse type is set, also checks that the type is service
    96  func (e *Export) IsSingleResponse() bool {
    97  	return e.Type == Service && (e.ResponseType == ResponseTypeSingleton || e.ResponseType == "")
    98  }
    99  
   100  // IsChunkedResponse returns true if an export has a chunked response
   101  func (e *Export) IsChunkedResponse() bool {
   102  	return e.Type == Service && e.ResponseType == ResponseTypeChunked
   103  }
   104  
   105  // IsStreamResponse returns true if an export has a chunked response
   106  func (e *Export) IsStreamResponse() bool {
   107  	return e.Type == Service && e.ResponseType == ResponseTypeStream
   108  }
   109  
   110  // Validate appends validation issues to the passed in results list
   111  func (e *Export) Validate(vr *ValidationResults) {
   112  	if e == nil {
   113  		vr.AddError("null export is not allowed")
   114  		return
   115  	}
   116  	if !e.IsService() && !e.IsStream() {
   117  		vr.AddError("invalid export type: %q", e.Type)
   118  	}
   119  	if e.IsService() && !e.IsSingleResponse() && !e.IsChunkedResponse() && !e.IsStreamResponse() {
   120  		vr.AddError("invalid response type for service: %q", e.ResponseType)
   121  	}
   122  	if e.IsStream() && e.ResponseType != "" {
   123  		vr.AddError("invalid response type for stream: %q", e.ResponseType)
   124  	}
   125  	if e.Latency != nil {
   126  		if !e.IsService() {
   127  			vr.AddError("latency tracking only permitted for services")
   128  		}
   129  		e.Latency.Validate(vr)
   130  	}
   131  	e.Subject.Validate(vr)
   132  }
   133  
   134  // Revoke enters a revocation by publickey using time.Now().
   135  func (e *Export) Revoke(pubKey string) {
   136  	e.RevokeAt(pubKey, time.Now())
   137  }
   138  
   139  // RevokeAt enters a revocation by publickey and timestamp into this export
   140  // If there is already a revocation for this public key that is newer, it is kept.
   141  func (e *Export) RevokeAt(pubKey string, timestamp time.Time) {
   142  	if e.Revocations == nil {
   143  		e.Revocations = RevocationList{}
   144  	}
   145  
   146  	e.Revocations.Revoke(pubKey, timestamp)
   147  }
   148  
   149  // ClearRevocation removes any revocation for the public key
   150  func (e *Export) ClearRevocation(pubKey string) {
   151  	e.Revocations.ClearRevocation(pubKey)
   152  }
   153  
   154  // IsRevokedAt checks if the public key is in the revoked list with a timestamp later than the one passed in.
   155  // Generally this method is called with the subject and issue time of the jwt to be tested.
   156  // DO NOT pass time.Now(), it will not produce a stable/expected response.
   157  func (e *Export) IsRevokedAt(pubKey string, timestamp time.Time) bool {
   158  	return e.Revocations.IsRevoked(pubKey, timestamp)
   159  }
   160  
   161  // IsRevoked does not perform a valid check. Use IsRevokedAt instead.
   162  func (e *Export) IsRevoked(_ string) bool {
   163  	return true
   164  }
   165  
   166  // Exports is a slice of exports
   167  type Exports []*Export
   168  
   169  // Add appends exports to the list
   170  func (e *Exports) Add(i ...*Export) {
   171  	*e = append(*e, i...)
   172  }
   173  
   174  func isContainedIn(kind ExportType, subjects []Subject, vr *ValidationResults) {
   175  	m := make(map[string]string)
   176  	for i, ns := range subjects {
   177  		for j, s := range subjects {
   178  			if i == j {
   179  				continue
   180  			}
   181  			if ns.IsContainedIn(s) {
   182  				str := string(s)
   183  				_, ok := m[str]
   184  				if !ok {
   185  					m[str] = string(ns)
   186  				}
   187  			}
   188  		}
   189  	}
   190  
   191  	if len(m) != 0 {
   192  		for k, v := range m {
   193  			var vi ValidationIssue
   194  			vi.Blocking = true
   195  			vi.Description = fmt.Sprintf("%s export subject %q already exports %q", kind, k, v)
   196  			vr.Add(&vi)
   197  		}
   198  	}
   199  }
   200  
   201  // Validate calls validate on all of the exports
   202  func (e *Exports) Validate(vr *ValidationResults) error {
   203  	var serviceSubjects []Subject
   204  	var streamSubjects []Subject
   205  
   206  	for _, v := range *e {
   207  		if v == nil {
   208  			vr.AddError("null export is not allowed")
   209  			continue
   210  		}
   211  		if v.IsService() {
   212  			serviceSubjects = append(serviceSubjects, v.Subject)
   213  		} else {
   214  			streamSubjects = append(streamSubjects, v.Subject)
   215  		}
   216  		v.Validate(vr)
   217  	}
   218  
   219  	isContainedIn(Service, serviceSubjects, vr)
   220  	isContainedIn(Stream, streamSubjects, vr)
   221  
   222  	return nil
   223  }
   224  
   225  // HasExportContainingSubject checks if the export list has an export with the provided subject
   226  func (e *Exports) HasExportContainingSubject(subject Subject) bool {
   227  	for _, s := range *e {
   228  		if subject.IsContainedIn(s.Subject) {
   229  			return true
   230  		}
   231  	}
   232  	return false
   233  }
   234  
   235  func (e Exports) Len() int {
   236  	return len(e)
   237  }
   238  
   239  func (e Exports) Swap(i, j int) {
   240  	e[i], e[j] = e[j], e[i]
   241  }
   242  
   243  func (e Exports) Less(i, j int) bool {
   244  	return e[i].Subject < e[j].Subject
   245  }