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 }