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  }