github.com/nats-io/jwt/v2@v2.5.6/exports_test.go (about) 1 /* 2 * Copyright 2018 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 "sort" 20 "strings" 21 "testing" 22 "time" 23 24 "github.com/nats-io/nkeys" 25 ) 26 27 func TestSimpleExportValidation(t *testing.T) { 28 e := &Export{Subject: "foo", Type: Stream, Info: Info{InfoURL: "http://localhost/foo/bar", Description: "description"}} 29 30 vr := CreateValidationResults() 31 e.Validate(vr) 32 33 if !vr.IsEmpty() { 34 t.Errorf("simple export should validate cleanly") 35 } 36 37 e.Type = Service 38 vr = CreateValidationResults() 39 e.Validate(vr) 40 41 if !vr.IsEmpty() { 42 t.Errorf("simple export should validate cleanly") 43 } 44 } 45 46 func TestResponseTypeValidation(t *testing.T) { 47 e := &Export{Subject: "foo", Type: Stream, ResponseType: ResponseTypeSingleton} 48 49 vr := CreateValidationResults() 50 e.Validate(vr) 51 52 if vr.IsEmpty() { 53 t.Errorf("response type on stream should have an validation issue") 54 } 55 if e.IsSingleResponse() { 56 t.Errorf("response type should always fail for stream") 57 } 58 59 e.Type = Service 60 vr = CreateValidationResults() 61 e.Validate(vr) 62 if !vr.IsEmpty() { 63 t.Errorf("response type on service should validate cleanly") 64 } 65 if !e.IsSingleResponse() || e.IsChunkedResponse() || e.IsStreamResponse() { 66 t.Errorf("response type should be single") 67 } 68 69 e.ResponseType = ResponseTypeChunked 70 vr = CreateValidationResults() 71 e.Validate(vr) 72 if !vr.IsEmpty() { 73 t.Errorf("response type on service should validate cleanly") 74 } 75 if e.IsSingleResponse() || !e.IsChunkedResponse() || e.IsStreamResponse() { 76 t.Errorf("response type should be chunk") 77 } 78 79 e.ResponseType = ResponseTypeStream 80 vr = CreateValidationResults() 81 e.Validate(vr) 82 if !vr.IsEmpty() { 83 t.Errorf("response type on service should validate cleanly") 84 } 85 if e.IsSingleResponse() || e.IsChunkedResponse() || !e.IsStreamResponse() { 86 t.Errorf("response type should be stream") 87 } 88 89 e.ResponseType = "" 90 vr = CreateValidationResults() 91 e.Validate(vr) 92 if !vr.IsEmpty() { 93 t.Errorf("response type on service should validate cleanly") 94 } 95 if !e.IsSingleResponse() || e.IsChunkedResponse() || e.IsStreamResponse() { 96 t.Errorf("response type should be single") 97 } 98 99 e.ResponseType = "bad" 100 vr = CreateValidationResults() 101 e.Validate(vr) 102 if vr.IsEmpty() { 103 t.Errorf("response type should match available options") 104 } 105 if e.IsSingleResponse() || e.IsChunkedResponse() || e.IsStreamResponse() { 106 t.Errorf("response type should be bad") 107 } 108 } 109 110 func TestInvalidExportType(t *testing.T) { 111 i := &Export{Subject: "foo", Type: Unknown} 112 113 vr := CreateValidationResults() 114 i.Validate(vr) 115 116 if vr.IsEmpty() { 117 t.Errorf("export with bad type should not validate cleanly") 118 } 119 120 if !vr.IsBlocking(true) { 121 t.Errorf("invalid type is blocking") 122 } 123 } 124 125 func TestInvalidExportInfo(t *testing.T) { 126 e := &Export{Subject: "foo", Type: Stream, Info: Info{InfoURL: "/bad"}} 127 vr := CreateValidationResults() 128 e.Validate(vr) 129 if vr.IsEmpty() { 130 t.Errorf("export info should not validate cleanly") 131 } 132 if !vr.IsBlocking(true) { 133 t.Errorf("invalid info needs to be blocking") 134 } 135 } 136 137 func TestOverlappingExports(t *testing.T) { 138 i := &Export{Subject: "bar.foo", Type: Stream} 139 i2 := &Export{Subject: "bar.*", Type: Stream} 140 141 exports := &Exports{} 142 exports.Add(i, i2) 143 144 vr := CreateValidationResults() 145 exports.Validate(vr) 146 147 if len(vr.Issues) != 1 { 148 t.Errorf("export has overlapping subjects") 149 } 150 } 151 152 func TestDifferentExportTypes_OverlapOK(t *testing.T) { 153 i := &Export{Subject: "bar.foo", Type: Service} 154 i2 := &Export{Subject: "bar.*", Type: Stream} 155 156 exports := &Exports{} 157 exports.Add(i, i2) 158 159 vr := CreateValidationResults() 160 exports.Validate(vr) 161 162 if len(vr.Issues) != 0 { 163 t.Errorf("should allow overlaps on different export kind") 164 } 165 } 166 167 func TestDifferentExportTypes_SameSubjectOK(t *testing.T) { 168 i := &Export{Subject: "bar", Type: Service} 169 i2 := &Export{Subject: "bar", Type: Stream} 170 171 exports := &Exports{} 172 exports.Add(i, i2) 173 174 vr := CreateValidationResults() 175 exports.Validate(vr) 176 177 if len(vr.Issues) != 0 { 178 t.Errorf("should allow overlaps on different export kind") 179 } 180 } 181 182 func TestSameExportType_SameSubject(t *testing.T) { 183 i := &Export{Subject: "bar", Type: Service} 184 i2 := &Export{Subject: "bar", Type: Service} 185 186 exports := &Exports{} 187 exports.Add(i, i2) 188 189 vr := CreateValidationResults() 190 exports.Validate(vr) 191 192 if len(vr.Issues) != 1 { 193 t.Errorf("should not allow same subject on same export kind") 194 } 195 } 196 197 func TestExportRevocation(t *testing.T) { 198 akp := createAccountNKey(t) 199 apk := publicKey(akp, t) 200 account := NewAccountClaims(apk) 201 e := &Export{Subject: "foo", Type: Stream} 202 203 account.Exports.Add(e) 204 205 ikp := createAccountNKey(t) 206 pubKey := publicKey(ikp, t) 207 208 ac := NewActivationClaims(pubKey) 209 ac.IssuerAccount = apk 210 ac.Name = "foo" 211 ac.Activation.ImportSubject = "foo" 212 ac.Activation.ImportType = Stream 213 aJwt, _ := ac.Encode(akp) 214 ac, err := DecodeActivationClaims(aJwt) 215 if err != nil { 216 t.Errorf("Failed to decode activation claim: %v", err) 217 } 218 219 now := time.Now() 220 221 // test that clear is safe before we add any 222 e.ClearRevocation(pubKey) 223 224 if e.isRevoked(pubKey, now) { 225 t.Errorf("no revocation was added so is revoked should be false") 226 } 227 228 e.RevokeAt(pubKey, now.Add(time.Second*100)) 229 230 if !e.isRevoked(pubKey, now) { 231 t.Errorf("revocation should hold when timestamp is in the future") 232 } 233 234 if e.isRevoked(pubKey, now.Add(time.Second*150)) { 235 t.Errorf("revocation should time out") 236 } 237 238 e.RevokeAt(pubKey, now.Add(time.Second*50)) // shouldn't change the revocation, you can't move it in 239 240 if !e.isRevoked(pubKey, now.Add(time.Second*60)) { 241 t.Errorf("revocation should hold, 100 > 50") 242 } 243 244 encoded, _ := account.Encode(akp) 245 decoded, _ := DecodeAccountClaims(encoded) 246 247 if !decoded.Exports[0].isRevoked(pubKey, now.Add(time.Second*60)) { 248 t.Errorf("revocation should last across encoding") 249 } 250 251 e.ClearRevocation(pubKey) 252 253 if e.IsClaimRevoked(ac) { 254 t.Errorf("revocations should be cleared") 255 } 256 257 e.RevokeAt(pubKey, now) 258 259 if !e.IsClaimRevoked(ac) { 260 t.Errorf("revocation be true we revoked in the future") 261 } 262 } 263 264 func TestExportTrackLatency(t *testing.T) { 265 e := &Export{Subject: "foo", Type: Service} 266 e.Latency = &ServiceLatency{Sampling: 100, Results: "results"} 267 vr := CreateValidationResults() 268 e.Validate(vr) 269 if !vr.IsEmpty() { 270 t.Errorf("Expected to validate with simple tracking") 271 } 272 273 e = &Export{Subject: "foo", Type: Service} 274 e.Latency = &ServiceLatency{Sampling: Headers, Results: "results"} 275 vr = CreateValidationResults() 276 e.Validate(vr) 277 if !vr.IsEmpty() { 278 t.Errorf("Headers must not need to ") 279 } 280 281 e = &Export{Subject: "foo", Type: Stream} 282 e.Latency = &ServiceLatency{Sampling: 100, Results: "results"} 283 vr = CreateValidationResults() 284 e.Validate(vr) 285 if vr.IsEmpty() { 286 t.Errorf("adding latency tracking to a stream should have an validation issue") 287 } 288 289 e = &Export{Subject: "foo", Type: Service} 290 e.Latency = &ServiceLatency{Sampling: -1, Results: "results"} 291 vr = CreateValidationResults() 292 e.Validate(vr) 293 if vr.IsEmpty() { 294 t.Errorf("Sampling <1 should have a validation issue") 295 } 296 297 e = &Export{Subject: "foo", Type: Service} 298 e.Latency = &ServiceLatency{Sampling: 122, Results: "results"} 299 vr = CreateValidationResults() 300 e.Validate(vr) 301 if vr.IsEmpty() { 302 t.Errorf("Sampling >100 should have a validation issue") 303 } 304 305 e = &Export{Subject: "foo", Type: Service} 306 e.Latency = &ServiceLatency{Sampling: 22, Results: "results.*"} 307 vr = CreateValidationResults() 308 e.Validate(vr) 309 if vr.IsEmpty() { 310 t.Errorf("Results subject needs to be valid publish subject") 311 } 312 } 313 314 func TestExportTrackHeader(t *testing.T) { 315 akp, err := nkeys.CreateAccount() 316 AssertNoError(err, t) 317 apk, err := akp.PublicKey() 318 AssertNoError(err, t) 319 ac := NewAccountClaims(apk) 320 e := &Export{Subject: "foo", Type: Service} 321 e.Latency = &ServiceLatency{Sampling: Headers, Results: "results"} 322 ac.Exports.Add(e) 323 theJWT, err := ac.Encode(akp) 324 AssertNoError(err, t) 325 ac2, err := DecodeAccountClaims(theJWT) 326 AssertNoError(err, t) 327 if *(ac2.Exports[0].Latency) != *e.Latency { 328 t.Errorf("Headers need to de serialize as headers") 329 } 330 } 331 332 func TestExport_Sorting(t *testing.T) { 333 var exports Exports 334 exports.Add(&Export{Subject: "x", Type: Service}) 335 exports.Add(&Export{Subject: "z", Type: Service}) 336 exports.Add(&Export{Subject: "y", Type: Service}) 337 338 if exports[0] == nil || exports[0].Subject != "x" { 339 t.Fatal("added export not in expected order") 340 } 341 sort.Sort(exports) 342 if exports[0].Subject != "x" && exports[1].Subject != "y" && exports[2].Subject != "z" { 343 t.Fatal("exports not sorted") 344 } 345 } 346 347 func TestExportAccountTokenPos(t *testing.T) { 348 okp := createOperatorNKey(t) 349 akp := createAccountNKey(t) 350 apk := publicKey(akp, t) 351 tbl := map[Subject]uint{ 352 "*": 1, 353 "foo.*": 2, 354 "foo.*.bar.*": 2, 355 "foo.*.bar.>": 2, 356 "*.*.*.>": 2, 357 "*.*.>": 1, 358 } 359 for k, v := range tbl { 360 t.Run(string(k), func(t *testing.T) { 361 account := NewAccountClaims(apk) 362 //account.Limits = OperatorLimits{} 363 account.Exports = append(account.Exports, 364 &Export{Type: Stream, Subject: k, AccountTokenPosition: v}) 365 actJwt := encode(account, okp, t) 366 account2, err := DecodeAccountClaims(actJwt) 367 if err != nil { 368 t.Fatal("error decoding account jwt", err) 369 } 370 AssertEquals(account.String(), account2.String(), t) 371 vr := &ValidationResults{} 372 account2.Validate(vr) 373 if len(vr.Issues) != 0 { 374 t.Fatal("validation issues", *vr) 375 } 376 }) 377 } 378 } 379 380 func TestExportAccountTokenPosFail(t *testing.T) { 381 okp := createOperatorNKey(t) 382 akp := createAccountNKey(t) 383 apk := publicKey(akp, t) 384 tbl := map[Subject]uint{ 385 ">": 5, 386 "foo.>": 2, 387 "bar.>": 1, 388 "*": 5, 389 "*.*": 5, 390 "bar": 1, 391 "foo.bar": 2, 392 "foo.*.bar": 3, 393 "*.>": 3, 394 "*.*.>": 3, 395 "foo.*x.bar": 2, 396 "foo.x*.bar": 2, 397 } 398 for k, v := range tbl { 399 t.Run(string(k), func(t *testing.T) { 400 account := NewAccountClaims(apk) 401 //account.Limits = OperatorLimits{} 402 account.Exports = append(account.Exports, 403 &Export{Type: Stream, Subject: k, AccountTokenPosition: v}) 404 actJwt := encode(account, okp, t) 405 account2, err := DecodeAccountClaims(actJwt) 406 if err != nil { 407 t.Fatal("error decoding account jwt", err) 408 } 409 AssertEquals(account.String(), account2.String(), t) 410 vr := &ValidationResults{} 411 account2.Validate(vr) 412 if len(vr.Issues) != 1 { 413 t.Fatal("validation issue expected", *vr) 414 } 415 }) 416 } 417 } 418 419 func TestExport_ResponseThreshold(t *testing.T) { 420 var exports Exports 421 exports.Add(&Export{Subject: "x", Type: Service, ResponseThreshold: time.Second}) 422 vr := ValidationResults{} 423 exports.Validate(&vr) 424 if !vr.IsEmpty() { 425 t.Fatal("expected this to pass") 426 } 427 428 exports = Exports{} 429 exports.Add(&Export{Subject: "x", Type: Stream, ResponseThreshold: time.Second}) 430 vr = ValidationResults{} 431 exports.Validate(&vr) 432 if vr.IsEmpty() { 433 t.Fatal("expected this to fail due to type") 434 } 435 436 exports = Exports{} 437 exports.Add(&Export{Subject: "x", Type: Service, ResponseThreshold: -1 * time.Second}) 438 vr = ValidationResults{} 439 exports.Validate(&vr) 440 if vr.IsEmpty() { 441 t.Fatal("expected this to fail due to negative duration") 442 } 443 } 444 445 func TestExportAllowTrace(t *testing.T) { 446 // AllowTrace is only applicable to ServiceExport 447 e := &Export{Subject: "foo", Type: Stream, AllowTrace: true} 448 vr := CreateValidationResults() 449 e.Validate(vr) 450 if vr.IsEmpty() { 451 t.Fatalf("AllowTrace on stream should have an validation issue") 452 } 453 issue := vr.Issues[0] 454 if !strings.Contains(issue.Description, "AllowTrace only valid for service export") { 455 t.Fatalf("AllowTrace should be valid only for service export, got %q", issue.Description) 456 } 457 458 e.Type = Service 459 vr = CreateValidationResults() 460 e.Validate(vr) 461 if !vr.IsEmpty() { 462 t.Fatalf("validation should have been ok, got %+v", vr.Issues) 463 } 464 }