github.com/lestrrat-go/jwx/v2@v2.0.21/jwt/openid/openid_test.go (about) 1 package openid_test 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "testing" 8 "time" 9 10 "github.com/lestrrat-go/jwx/v2/internal/json" 11 "github.com/lestrrat-go/jwx/v2/internal/jwxtest" 12 13 "github.com/lestrrat-go/jwx/v2/jwa" 14 "github.com/lestrrat-go/jwx/v2/jwt" 15 "github.com/lestrrat-go/jwx/v2/jwt/internal/types" 16 "github.com/lestrrat-go/jwx/v2/jwt/openid" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 const aLongLongTimeAgo = 233431200 22 const aLongLongTimeAgoString = "233431200" 23 const ( 24 tokenTime = 233431200 25 ) 26 27 var expectedTokenTime = time.Unix(tokenTime, 0).UTC() 28 29 func testStockAddressClaim(t *testing.T, x *openid.AddressClaim) { 30 t.Helper() 31 if !assert.NotNil(t, x) { 32 return 33 } 34 35 tests := []struct { 36 Accessor func() string 37 KeyName string 38 Value string 39 }{ 40 { 41 Accessor: x.Formatted, 42 KeyName: openid.AddressFormattedKey, 43 Value: "〒105-0011 東京都港区芝公園4丁目2−8", 44 }, 45 { 46 Accessor: x.Country, 47 KeyName: openid.AddressCountryKey, 48 Value: "日本", 49 }, 50 { 51 Accessor: x.Region, 52 KeyName: openid.AddressRegionKey, 53 Value: "東京都", 54 }, 55 { 56 Accessor: x.Locality, 57 KeyName: openid.AddressLocalityKey, 58 Value: "港区", 59 }, 60 { 61 Accessor: x.StreetAddress, 62 KeyName: openid.AddressStreetAddressKey, 63 Value: "芝公園4丁目2−8", 64 }, 65 { 66 Accessor: x.PostalCode, 67 KeyName: openid.AddressPostalCodeKey, 68 Value: "105-0011", 69 }, 70 } 71 72 for _, tc := range tests { 73 tc := tc 74 t.Run(tc.KeyName, func(t *testing.T) { 75 t.Run("Accessor", func(t *testing.T) { 76 if !assert.Equal(t, tc.Value, tc.Accessor(), "values should match") { 77 return 78 } 79 }) 80 t.Run("Get", func(t *testing.T) { 81 v, ok := x.Get(tc.KeyName) 82 if !assert.True(t, ok, `x.Get should succeed`) { 83 return 84 } 85 if !assert.Equal(t, tc.Value, v, `values should match`) { 86 return 87 } 88 }) 89 }) 90 } 91 } 92 93 func TestAdressClaim(t *testing.T) { 94 const src = `{ 95 "formatted": "〒105-0011 東京都港区芝公園4丁目2−8", 96 "street_address": "芝公園4丁目2−8", 97 "locality": "港区", 98 "region": "東京都", 99 "postal_code": "105-0011", 100 "country": "日本" 101 }` 102 103 var address openid.AddressClaim 104 if !assert.NoError(t, json.Unmarshal([]byte(src), &address), "json.Unmarshal should succeed") { 105 return 106 } 107 108 var roundtrip openid.AddressClaim 109 buf, err := json.Marshal(address) 110 if !assert.NoError(t, err, `json.Marshal(address) should succeed`) { 111 return 112 } 113 114 if !assert.NoError(t, json.Unmarshal(buf, &roundtrip), "json.Unmarshal should succeed") { 115 return 116 } 117 118 for _, x := range []*openid.AddressClaim{&address, &roundtrip} { 119 testStockAddressClaim(t, x) 120 } 121 } 122 123 func TestOpenIDClaims(t *testing.T) { 124 getVerify := func(token openid.Token, key string, expected interface{}) bool { 125 v, ok := token.Get(key) 126 if !assert.True(t, ok, `token.Get %#v should succeed`, key) { 127 return false 128 } 129 return assert.Equal(t, v, expected) 130 } 131 132 var base = []struct { 133 Value interface{} 134 Expected func(interface{}) interface{} 135 Check func(openid.Token) 136 Key string 137 }{ 138 { 139 Key: openid.AudienceKey, 140 Value: []string{"developers", "secops", "tac"}, 141 Check: func(token openid.Token) { 142 assert.Equal(t, token.Audience(), []string{"developers", "secops", "tac"}) 143 }, 144 }, 145 { 146 Key: openid.ExpirationKey, 147 Value: tokenTime, 148 Expected: func(v interface{}) interface{} { 149 var n types.NumericDate 150 if err := n.Accept(v); err != nil { 151 panic(err) 152 } 153 return n.Get() 154 }, 155 Check: func(token openid.Token) { 156 assert.Equal(t, token.Expiration(), expectedTokenTime) 157 }, 158 }, 159 { 160 Key: openid.IssuedAtKey, 161 Value: tokenTime, 162 Expected: func(v interface{}) interface{} { 163 var n types.NumericDate 164 if err := n.Accept(v); err != nil { 165 panic(err) 166 } 167 return n.Get() 168 }, 169 Check: func(token openid.Token) { 170 assert.Equal(t, token.Expiration(), expectedTokenTime) 171 }, 172 }, 173 { 174 Key: openid.IssuerKey, 175 Value: "http://www.example.com", 176 Check: func(token openid.Token) { 177 assert.Equal(t, token.Issuer(), "http://www.example.com") 178 }, 179 }, 180 { 181 Key: openid.JwtIDKey, 182 Value: "e9bc097a-ce51-4036-9562-d2ade882db0d", 183 Check: func(token openid.Token) { 184 assert.Equal(t, token.JwtID(), "e9bc097a-ce51-4036-9562-d2ade882db0d") 185 }, 186 }, 187 { 188 Key: openid.NotBeforeKey, 189 Value: tokenTime, 190 Expected: func(v interface{}) interface{} { 191 var n types.NumericDate 192 if err := n.Accept(v); err != nil { 193 panic(err) 194 } 195 return n.Get() 196 }, 197 Check: func(token openid.Token) { 198 assert.Equal(t, token.NotBefore(), expectedTokenTime) 199 }, 200 }, 201 { 202 Key: openid.SubjectKey, 203 Value: "unit test", 204 Check: func(token openid.Token) { 205 assert.Equal(t, token.Subject(), "unit test") 206 }, 207 }, 208 { 209 Value: "jwx", 210 Key: openid.NameKey, 211 Check: func(token openid.Token) { 212 assert.Equal(t, token.Name(), "jwx") 213 }, 214 }, 215 { 216 Value: "jay", 217 Key: openid.GivenNameKey, 218 Check: func(token openid.Token) { 219 assert.Equal(t, token.GivenName(), "jay") 220 }, 221 }, 222 { 223 Value: "weee", 224 Key: openid.MiddleNameKey, 225 Check: func(token openid.Token) { 226 assert.Equal(t, token.MiddleName(), "weee") 227 }, 228 }, 229 { 230 Value: "xi", 231 Key: openid.FamilyNameKey, 232 Check: func(token openid.Token) { 233 assert.Equal(t, token.FamilyName(), "xi") 234 }, 235 }, 236 { 237 Value: "jayweexi", 238 Key: openid.NicknameKey, 239 Check: func(token openid.Token) { 240 assert.Equal(t, token.Nickname(), "jayweexi") 241 }, 242 }, 243 { 244 Value: "jwx", 245 Key: openid.PreferredUsernameKey, 246 Check: func(token openid.Token) { 247 assert.Equal(t, token.PreferredUsername(), "jwx") 248 }, 249 }, 250 { 251 Value: "https://github.com/lestrrat-go/jwx/v2", 252 Key: openid.ProfileKey, 253 Check: func(token openid.Token) { 254 assert.Equal(t, token.Profile(), "https://github.com/lestrrat-go/jwx/v2") 255 }, 256 }, 257 { 258 Value: "https://avatars1.githubusercontent.com/u/36653903?s=400&v=4", 259 Key: openid.PictureKey, 260 Check: func(token openid.Token) { 261 assert.Equal(t, token.Picture(), "https://avatars1.githubusercontent.com/u/36653903?s=400&v=4") 262 }, 263 }, 264 { 265 Value: "https://github.com/lestrrat-go/jwx/v2", 266 Key: openid.WebsiteKey, 267 Check: func(token openid.Token) { 268 assert.Equal(t, token.Website(), "https://github.com/lestrrat-go/jwx/v2") 269 }, 270 }, 271 { 272 Value: "lestrrat+github@gmail.com", 273 Key: openid.EmailKey, 274 Check: func(token openid.Token) { 275 assert.Equal(t, token.Email(), "lestrrat+github@gmail.com") 276 }, 277 }, 278 { 279 Value: true, 280 Key: openid.EmailVerifiedKey, 281 Check: func(token openid.Token) { 282 assert.True(t, token.EmailVerified()) 283 }, 284 }, 285 { 286 Value: "n/a", 287 Key: openid.GenderKey, 288 Check: func(token openid.Token) { 289 assert.Equal(t, token.Gender(), "n/a") 290 }, 291 }, 292 { 293 Value: "2015-11-04", 294 Key: openid.BirthdateKey, 295 Expected: func(v interface{}) interface{} { 296 var b openid.BirthdateClaim 297 if err := b.Accept(v); err != nil { 298 panic(err) 299 } 300 return &b 301 }, 302 Check: func(token openid.Token) { 303 var b openid.BirthdateClaim 304 b.Accept("2015-11-04") 305 assert.Equal(t, token.Birthdate(), &b) 306 }, 307 }, 308 { 309 Value: "Asia/Tokyo", 310 Key: openid.ZoneinfoKey, 311 Check: func(token openid.Token) { 312 assert.Equal(t, token.Zoneinfo(), "Asia/Tokyo") 313 }, 314 }, 315 { 316 Value: "ja_JP", 317 Key: openid.LocaleKey, 318 Check: func(token openid.Token) { 319 assert.Equal(t, token.Locale(), "ja_JP") 320 }, 321 }, 322 { 323 Value: "819012345678", 324 Key: openid.PhoneNumberKey, 325 Check: func(token openid.Token) { 326 assert.Equal(t, token.PhoneNumber(), "819012345678") 327 }, 328 }, 329 { 330 Value: true, 331 Key: openid.PhoneNumberVerifiedKey, 332 Check: func(token openid.Token) { 333 assert.True(t, token.PhoneNumberVerified()) 334 }, 335 }, 336 { 337 Value: map[string]interface{}{ 338 "formatted": "〒105-0011 東京都港区芝公園4丁目2−8", 339 "street_address": "芝公園4丁目2−8", 340 "locality": "港区", 341 "region": "東京都", 342 "country": "日本", 343 "postal_code": "105-0011", 344 }, 345 Key: openid.AddressKey, 346 Expected: func(v interface{}) interface{} { 347 address := openid.NewAddress() 348 m, ok := v.(map[string]interface{}) 349 if !ok { 350 panic(fmt.Sprintf("expected map[string]interface{}, got %T", v)) 351 } 352 for name, val := range m { 353 if !assert.NoError(t, address.Set(name, val), `address.Set should succeed`) { 354 return nil 355 } 356 } 357 return address 358 }, 359 Check: func(token openid.Token) { 360 testStockAddressClaim(t, token.Address()) 361 }, 362 }, 363 { 364 Value: aLongLongTimeAgoString, 365 Key: openid.UpdatedAtKey, 366 Expected: func(v interface{}) interface{} { 367 var n types.NumericDate 368 if err := n.Accept(v); err != nil { 369 panic(err) 370 } 371 return n.Get() 372 }, 373 Check: func(token openid.Token) { 374 assert.Equal(t, time.Unix(aLongLongTimeAgo, 0).UTC(), token.UpdatedAt()) 375 }, 376 }, 377 { 378 Value: `dummy`, 379 Key: `dummy`, 380 Check: func(token openid.Token) { 381 v, ok := token.Get(`dummy`) 382 if !assert.True(t, ok, `token.Get should return valid value`) { 383 return 384 } 385 if !assert.Equal(t, `dummy`, v, `values should match`) { 386 return 387 } 388 }, 389 }, 390 } 391 392 var data = map[string]interface{}{} 393 var expected = map[string]interface{}{} 394 for _, value := range base { 395 data[value.Key] = value.Value 396 if expf := value.Expected; expf != nil { 397 expected[value.Key] = expf(value.Value) 398 } else { 399 expected[value.Key] = value.Value 400 } 401 } 402 403 type openidTokTestCase struct { 404 Token openid.Token 405 Name string 406 } 407 var tokens []openidTokTestCase 408 409 { // one with Set() 410 b := openid.NewBuilder() 411 for name, value := range data { 412 b.Claim(name, value) 413 } 414 token, err := b.Build() 415 if !assert.NoError(t, err, `b.Build() should succeed`) { 416 return 417 } 418 tokens = append(tokens, openidTokTestCase{Name: `token constructed by calling Set()`, Token: token}) 419 } 420 421 { // two with json.Marshal / json.Unmarshal 422 src, err := json.MarshalIndent(data, "", " ") 423 if !assert.NoError(t, err, `failed to marshal base map`) { 424 return 425 } 426 427 t.Logf("Using source JSON: %s", src) 428 429 token := openid.New() 430 if !assert.NoError(t, json.Unmarshal(src, &token), `json.Unmarshal should succeed`) { 431 return 432 } 433 tokens = append(tokens, openidTokTestCase{Name: `token constructed by Marshal(map)+Unmashal`, Token: token}) 434 435 // One more... Marshal the token, _and_ re-unmarshal 436 buf, err := json.Marshal(token) 437 if !assert.NoError(t, err, `json.Marshal should succeed`) { 438 return 439 } 440 441 token2 := openid.New() 442 if !assert.NoError(t, json.Unmarshal(buf, &token2), `json.Unmarshal should succeed`) { 443 return 444 } 445 tokens = append(tokens, openidTokTestCase{Name: `token constructed by Marshal(openid.Token)+Unmashal`, Token: token2}) 446 447 // Sign it, and use jwt.Parse 448 449 var token3 openid.Token 450 { 451 alg := jwa.RS256 452 key, err := jwxtest.GenerateRsaKey() 453 if !assert.NoError(t, err, `rsa.GeneraKey should succeed`) { 454 return 455 } 456 signed, err := jwt.Sign(token, jwt.WithKey(alg, key)) 457 if !assert.NoError(t, err, `jwt.Sign should succeed`) { 458 return 459 } 460 461 tokenTmp, err := jwt.Parse(signed, jwt.WithToken(openid.New()), jwt.WithKey(alg, &key.PublicKey), jwt.WithValidate(false)) 462 if !assert.NoError(t, err, `parsing the token via jwt.Parse should succeed`) { 463 return 464 } 465 466 // Check if token is an OpenID token 467 if _, ok := tokenTmp.(openid.Token); !assert.True(t, ok, `token should be a openid.Token (%T)`, tokenTmp) { 468 return 469 } 470 token3 = tokenTmp.(openid.Token) 471 } 472 473 tokens = append(tokens, openidTokTestCase{Name: `token constructed by jwt.Parse`, Token: token3}) 474 } 475 476 for _, token := range tokens { 477 token := token 478 t.Run(token.Name, func(t *testing.T) { 479 for _, value := range base { 480 value := value 481 t.Run(value.Key, func(t *testing.T) { 482 value.Check(token.Token) 483 }) 484 t.Run(value.Key+" via Get()", func(t *testing.T) { 485 expected := value.Value 486 if expf := value.Expected; expf != nil { 487 expected = expf(value.Value) 488 } 489 getVerify(token.Token, value.Key, expected) 490 }) 491 } 492 }) 493 } 494 495 t.Run("Iterator", func(t *testing.T) { 496 v := tokens[0].Token 497 t.Run("Iterate", func(t *testing.T) { 498 seen := make(map[string]interface{}) 499 for iter := v.Iterate(context.TODO()); iter.Next(context.TODO()); { 500 pair := iter.Pair() 501 seen[pair.Key.(string)] = pair.Value 502 503 getV, ok := v.Get(pair.Key.(string)) 504 if !assert.True(t, ok, `v.Get should succeed for key %#v`, pair.Key) { 505 return 506 } 507 if !assert.Equal(t, pair.Value, getV, `pair.Value should match value from v.Get()`) { 508 return 509 } 510 } 511 if !assert.Equal(t, expected, seen, `values should match`) { 512 return 513 } 514 }) 515 t.Run("Walk", func(t *testing.T) { 516 seen := make(map[string]interface{}) 517 v.Walk(context.TODO(), openid.VisitorFunc(func(key string, value interface{}) error { 518 seen[key] = value 519 return nil 520 })) 521 if !assert.Equal(t, expected, seen, `values should match`) { 522 return 523 } 524 }) 525 t.Run("AsMap", func(t *testing.T) { 526 seen, err := v.AsMap(context.TODO()) 527 if !assert.NoError(t, err, `v.AsMap should succeed`) { 528 return 529 } 530 if !assert.Equal(t, expected, seen, `values should match`) { 531 return 532 } 533 }) 534 t.Run("Clone", func(t *testing.T) { 535 cloned, err := v.Clone() 536 if !assert.NoError(t, err, `v.Clone should succeed`) { 537 return 538 } 539 540 if !assert.True(t, jwt.Equal(v, cloned), `values should match`) { 541 return 542 } 543 }) 544 }) 545 } 546 547 func TestBirthdateClaim(t *testing.T) { 548 t.Parallel() 549 t.Run("regular date", func(t *testing.T) { 550 t.Parallel() 551 testcases := []struct { 552 Source string 553 Year int 554 Month int 555 Day int 556 Error bool 557 }{ 558 { 559 Source: `"2015-11-04"`, 560 Year: 2015, 561 Month: 11, 562 Day: 4, 563 }, 564 { 565 Source: `"0009-09-09"`, 566 Year: 9, 567 Month: 9, 568 Day: 9, 569 }, 570 { 571 Source: `{}`, 572 Error: true, 573 }, 574 { 575 Source: `"202X-01-01"`, 576 Error: true, 577 }, 578 { 579 Source: `"0000-01-01"`, 580 Error: true, 581 }, 582 { 583 Source: `"0001-00-01"`, 584 Error: true, 585 }, 586 { 587 Source: `"0001-01-00"`, 588 Error: true, 589 }, 590 } 591 592 for _, tc := range testcases { 593 tc := tc 594 t.Run(tc.Source, func(t *testing.T) { 595 var b openid.BirthdateClaim 596 if tc.Error { 597 assert.Error(t, json.Unmarshal([]byte(tc.Source), &b), `json.Unmarshal should fail`) 598 return 599 } 600 601 if !assert.NoError(t, json.Unmarshal([]byte(tc.Source), &b), `json.Unmarshal should succeed`) { 602 return 603 } 604 605 if !assert.Equal(t, b.Year(), tc.Year, "year should match") { 606 return 607 } 608 if !assert.Equal(t, b.Month(), tc.Month, "month should match") { 609 return 610 } 611 if !assert.Equal(t, b.Day(), tc.Day, "day should match") { 612 return 613 } 614 serialized, err := json.Marshal(b) 615 if !assert.NoError(t, err, `json.Marshal should succeed`) { 616 return 617 } 618 if !assert.Equal(t, string(serialized), tc.Source, `serialized format should be the same`) { 619 return 620 } 621 stringified := b.String() 622 expectedString, _ := strconv.Unquote(tc.Source) 623 if !assert.Equal(t, stringified, expectedString, `stringified format should be the same`) { 624 return 625 } 626 }) 627 } 628 }) 629 t.Run("empty date", func(t *testing.T) { 630 t.Parallel() 631 var b openid.BirthdateClaim 632 if !assert.Equal(t, b.Year(), 0, "year should match") { 633 return 634 } 635 if !assert.Equal(t, b.Month(), 0, "month should match") { 636 return 637 } 638 if !assert.Equal(t, b.Day(), 0, "day should match") { 639 return 640 } 641 }) 642 t.Run("invalid accept", func(t *testing.T) { 643 t.Parallel() 644 var b openid.BirthdateClaim 645 if !assert.Error(t, b.Accept(nil)) { 646 return 647 } 648 }) 649 } 650 651 func TestKeys(t *testing.T) { 652 at := assert.New(t) 653 at.Equal(`address`, openid.AddressKey) 654 at.Equal(`aud`, openid.AudienceKey) 655 at.Equal(`birthdate`, openid.BirthdateKey) 656 at.Equal(`email`, openid.EmailKey) 657 at.Equal(`email_verified`, openid.EmailVerifiedKey) 658 at.Equal(`exp`, openid.ExpirationKey) 659 at.Equal(`family_name`, openid.FamilyNameKey) 660 at.Equal(`gender`, openid.GenderKey) 661 at.Equal(`given_name`, openid.GivenNameKey) 662 at.Equal(`iat`, openid.IssuedAtKey) 663 at.Equal(`iss`, openid.IssuerKey) 664 at.Equal(`jti`, openid.JwtIDKey) 665 at.Equal(`locale`, openid.LocaleKey) 666 at.Equal(`middle_name`, openid.MiddleNameKey) 667 at.Equal(`name`, openid.NameKey) 668 at.Equal(`nickname`, openid.NicknameKey) 669 at.Equal(`nbf`, openid.NotBeforeKey) 670 at.Equal(`phone_number`, openid.PhoneNumberKey) 671 at.Equal(`phone_number_verified`, openid.PhoneNumberVerifiedKey) 672 at.Equal(`picture`, openid.PictureKey) 673 at.Equal(`preferred_username`, openid.PreferredUsernameKey) 674 at.Equal(`profile`, openid.ProfileKey) 675 at.Equal(`sub`, openid.SubjectKey) 676 at.Equal(`updated_at`, openid.UpdatedAtKey) 677 at.Equal(`website`, openid.WebsiteKey) 678 at.Equal(`zoneinfo`, openid.ZoneinfoKey) 679 } 680 681 func TestGH734(t *testing.T) { 682 const src = `{ 683 "nickname": "miniscruff", 684 "updated_at": "2022-05-06T04:57:24.367Z", 685 "email_verified": true 686 }` 687 688 expected, _ := time.Parse(time.RFC3339, "2022-05-06T04:57:24.367Z") 689 for _, pedantic := range []bool{true, false} { 690 t.Run(fmt.Sprintf("pedantic=%t", pedantic), func(t *testing.T) { 691 jwt.Settings(jwt.WithNumericDateParsePedantic(pedantic)) 692 tok := openid.New() 693 _, err := jwt.Parse( 694 []byte(src), 695 jwt.WithToken(tok), 696 jwt.WithVerify(false), 697 jwt.WithValidate(false), 698 ) 699 if pedantic { 700 require.Error(t, err, `jwt.Parse should fail for pedantic parser`) 701 } else { 702 require.NoError(t, err, `jwt.Parse should succeed`) 703 require.Equal(t, expected, tok.UpdatedAt(), `updated_at should match`) 704 } 705 }) 706 } 707 jwt.Settings(jwt.WithNumericDateParsePedantic(false)) 708 }