1/* 2Copyright 2015 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package oidc 18 19import ( 20 "bytes" 21 "context" 22 "crypto" 23 "crypto/x509" 24 "encoding/hex" 25 "encoding/json" 26 "encoding/pem" 27 "fmt" 28 "io/ioutil" 29 "net/http" 30 "net/http/httptest" 31 "os" 32 "reflect" 33 "strings" 34 "testing" 35 "text/template" 36 "time" 37 38 oidc "github.com/coreos/go-oidc" 39 jose "gopkg.in/square/go-jose.v2" 40 "k8s.io/apiserver/pkg/authentication/user" 41 "k8s.io/klog/v2" 42) 43 44// utilities for loading JOSE keys. 45 46func loadRSAKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey { 47 return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) { 48 key, err := x509.ParsePKCS1PrivateKey(b) 49 if err != nil { 50 return nil, err 51 } 52 return key.Public(), nil 53 }) 54} 55 56func loadRSAPrivKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey { 57 return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) { 58 return x509.ParsePKCS1PrivateKey(b) 59 }) 60} 61 62func loadECDSAKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey { 63 return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) { 64 key, err := x509.ParseECPrivateKey(b) 65 if err != nil { 66 return nil, err 67 } 68 return key.Public(), nil 69 }) 70} 71 72func loadECDSAPrivKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey { 73 return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) { 74 return x509.ParseECPrivateKey(b) 75 }) 76} 77 78func loadKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm, unmarshal func([]byte) (interface{}, error)) *jose.JSONWebKey { 79 data, err := ioutil.ReadFile(filepath) 80 if err != nil { 81 t.Fatalf("load file: %v", err) 82 } 83 block, _ := pem.Decode(data) 84 if block == nil { 85 t.Fatalf("file contained no PEM encoded data: %s", filepath) 86 } 87 priv, err := unmarshal(block.Bytes) 88 if err != nil { 89 t.Fatalf("unmarshal key: %v", err) 90 } 91 key := &jose.JSONWebKey{Key: priv, Use: "sig", Algorithm: string(alg)} 92 thumbprint, err := key.Thumbprint(crypto.SHA256) 93 if err != nil { 94 t.Fatalf("computing thumbprint: %v", err) 95 } 96 key.KeyID = hex.EncodeToString(thumbprint) 97 return key 98} 99 100// staticKeySet implements oidc.KeySet. 101type staticKeySet struct { 102 keys []*jose.JSONWebKey 103} 104 105func (s *staticKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) { 106 jws, err := jose.ParseSigned(jwt) 107 if err != nil { 108 return nil, err 109 } 110 if len(jws.Signatures) == 0 { 111 return nil, fmt.Errorf("jwt contained no signatures") 112 } 113 kid := jws.Signatures[0].Header.KeyID 114 115 for _, key := range s.keys { 116 if key.KeyID == kid { 117 return jws.Verify(key) 118 } 119 } 120 121 return nil, fmt.Errorf("no keys matches jwk keyid") 122} 123 124var ( 125 expired, _ = time.Parse(time.RFC3339Nano, "2009-11-10T22:00:00Z") 126 now, _ = time.Parse(time.RFC3339Nano, "2009-11-10T23:00:00Z") 127 valid, _ = time.Parse(time.RFC3339Nano, "2009-11-11T00:00:00Z") 128) 129 130type claimsTest struct { 131 name string 132 options Options 133 signingKey *jose.JSONWebKey 134 pubKeys []*jose.JSONWebKey 135 claims string 136 want *user.DefaultInfo 137 wantSkip bool 138 wantErr bool 139 wantInitErr bool 140 claimToResponseMap map[string]string 141 openIDConfig string 142} 143 144// Replace formats the contents of v into the provided template. 145func replace(tmpl string, v interface{}) string { 146 t := template.Must(template.New("test").Parse(tmpl)) 147 buf := bytes.NewBuffer(nil) 148 t.Execute(buf, &v) 149 ret := buf.String() 150 klog.V(4).Infof("Replaced: %v into: %v", tmpl, ret) 151 return ret 152} 153 154// newClaimServer returns a new test HTTPS server, which is rigged to return 155// OIDC responses to requests that resolve distributed claims. signer is the 156// signer used for the served JWT tokens. claimToResponseMap is a map of 157// responses that the server will return for each claim it is given. 158func newClaimServer(t *testing.T, keys jose.JSONWebKeySet, signer jose.Signer, claimToResponseMap map[string]string, openIDConfig *string) *httptest.Server { 159 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 160 klog.V(5).Infof("request: %+v", *r) 161 switch r.URL.Path { 162 case "/.testing/keys": 163 w.Header().Set("Content-Type", "application/json") 164 keyBytes, err := json.Marshal(keys) 165 if err != nil { 166 t.Fatalf("unexpected error while marshaling keys: %v", err) 167 } 168 klog.V(5).Infof("%v: returning: %+v", r.URL, string(keyBytes)) 169 w.Write(keyBytes) 170 171 case "/.well-known/openid-configuration": 172 w.Header().Set("Content-Type", "application/json") 173 klog.V(5).Infof("%v: returning: %+v", r.URL, *openIDConfig) 174 w.Write([]byte(*openIDConfig)) 175 // These claims are tested in the unit tests. 176 case "/groups": 177 fallthrough 178 case "/rabbits": 179 if claimToResponseMap == nil { 180 t.Errorf("no claims specified in response") 181 } 182 claim := r.URL.Path[1:] // "/groups" -> "groups" 183 expectedAuth := fmt.Sprintf("Bearer %v_token", claim) 184 auth := r.Header.Get("Authorization") 185 if auth != expectedAuth { 186 t.Errorf("bearer token expected: %q, was %q", expectedAuth, auth) 187 } 188 jws, err := signer.Sign([]byte(claimToResponseMap[claim])) 189 if err != nil { 190 t.Errorf("while signing response token: %v", err) 191 } 192 token, err := jws.CompactSerialize() 193 if err != nil { 194 t.Errorf("while serializing response token: %v", err) 195 } 196 w.Write([]byte(token)) 197 default: 198 w.WriteHeader(http.StatusNotFound) 199 fmt.Fprintf(w, "unexpected URL: %v", r.URL) 200 } 201 })) 202 klog.V(4).Infof("Serving OIDC at: %v", ts.URL) 203 return ts 204} 205 206// writeTempCert writes out the supplied certificate into a temporary file in 207// PEM-encoded format. Returns the name of the temporary file used. The caller 208// is responsible for cleaning the file up. 209func writeTempCert(t *testing.T, cert []byte) string { 210 tempFile, err := ioutil.TempFile("", "ca.crt") 211 if err != nil { 212 t.Fatalf("could not open temp file: %v", err) 213 } 214 block := &pem.Block{ 215 Type: "CERTIFICATE", 216 Bytes: cert, 217 } 218 if err := pem.Encode(tempFile, block); err != nil { 219 t.Fatalf("could not write to temp file %v: %v", tempFile.Name(), err) 220 } 221 tempFile.Close() 222 return tempFile.Name() 223} 224 225func toKeySet(keys []*jose.JSONWebKey) jose.JSONWebKeySet { 226 ret := jose.JSONWebKeySet{} 227 for _, k := range keys { 228 ret.Keys = append(ret.Keys, *k) 229 } 230 return ret 231} 232 233func (c *claimsTest) run(t *testing.T) { 234 var ( 235 signer jose.Signer 236 err error 237 ) 238 if c.signingKey != nil { 239 // Initialize the signer only in the tests that make use of it. We can 240 // not defer this initialization because the test server uses it too. 241 signer, err = jose.NewSigner(jose.SigningKey{ 242 Algorithm: jose.SignatureAlgorithm(c.signingKey.Algorithm), 243 Key: c.signingKey, 244 }, nil) 245 if err != nil { 246 t.Fatalf("initialize signer: %v", err) 247 } 248 } 249 // The HTTPS server used for requesting distributed groups claims. 250 ts := newClaimServer(t, toKeySet(c.pubKeys), signer, c.claimToResponseMap, &c.openIDConfig) 251 defer ts.Close() 252 253 // Make the certificate of the helper server available to the authenticator 254 // by writing its root CA certificate into a temporary file. 255 tempFileName := writeTempCert(t, ts.TLS.Certificates[0].Certificate[0]) 256 defer os.Remove(tempFileName) 257 c.options.CAFile = tempFileName 258 259 // Allow claims to refer to the serving URL of the test server. For this, 260 // substitute all references to {{.URL}} in appropriate places. 261 v := struct{ URL string }{URL: ts.URL} 262 c.claims = replace(c.claims, &v) 263 c.openIDConfig = replace(c.openIDConfig, &v) 264 c.options.IssuerURL = replace(c.options.IssuerURL, &v) 265 for claim, response := range c.claimToResponseMap { 266 c.claimToResponseMap[claim] = replace(response, &v) 267 } 268 269 // Initialize the authenticator. 270 a, err := newAuthenticator(c.options, func(ctx context.Context, a *Authenticator, config *oidc.Config) { 271 // Set the verifier to use the public key set instead of reading from a remote. 272 a.setVerifier(oidc.NewVerifier( 273 c.options.IssuerURL, 274 &staticKeySet{keys: c.pubKeys}, 275 config, 276 )) 277 }) 278 if err != nil { 279 if !c.wantInitErr { 280 t.Fatalf("initialize authenticator: %v", err) 281 } 282 return 283 } 284 if c.wantInitErr { 285 t.Fatalf("wanted initialization error") 286 } 287 288 // Sign and serialize the claims in a JWT. 289 jws, err := signer.Sign([]byte(c.claims)) 290 if err != nil { 291 t.Fatalf("sign claims: %v", err) 292 } 293 token, err := jws.CompactSerialize() 294 if err != nil { 295 t.Fatalf("serialize token: %v", err) 296 } 297 298 got, ok, err := a.AuthenticateToken(context.Background(), token) 299 300 if err != nil { 301 if !c.wantErr { 302 t.Fatalf("authenticate token: %v", err) 303 } 304 return 305 } 306 307 if c.wantErr { 308 t.Fatalf("expected error authenticating token") 309 } 310 if !ok { 311 if !c.wantSkip { 312 // We don't have any cases where we return (nil, false, nil) 313 t.Fatalf("no error but token not authenticated") 314 } 315 return 316 } 317 if c.wantSkip { 318 t.Fatalf("expected authenticator to skip token") 319 } 320 321 gotUser := got.User.(*user.DefaultInfo) 322 if !reflect.DeepEqual(gotUser, c.want) { 323 t.Fatalf("wanted user=%#v, got=%#v", c.want, gotUser) 324 } 325} 326 327func TestToken(t *testing.T) { 328 synchronizeTokenIDVerifierForTest = true 329 tests := []claimsTest{ 330 { 331 name: "token", 332 options: Options{ 333 IssuerURL: "https://auth.example.com", 334 ClientID: "my-client", 335 UsernameClaim: "username", 336 now: func() time.Time { return now }, 337 }, 338 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 339 pubKeys: []*jose.JSONWebKey{ 340 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 341 }, 342 claims: fmt.Sprintf(`{ 343 "iss": "https://auth.example.com", 344 "aud": "my-client", 345 "username": "jane", 346 "exp": %d 347 }`, valid.Unix()), 348 want: &user.DefaultInfo{ 349 Name: "jane", 350 }, 351 }, 352 { 353 name: "no-username", 354 options: Options{ 355 IssuerURL: "https://auth.example.com", 356 ClientID: "my-client", 357 UsernameClaim: "username", 358 now: func() time.Time { return now }, 359 }, 360 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 361 pubKeys: []*jose.JSONWebKey{ 362 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 363 }, 364 claims: fmt.Sprintf(`{ 365 "iss": "https://auth.example.com", 366 "aud": "my-client", 367 "exp": %d 368 }`, valid.Unix()), 369 wantErr: true, 370 }, 371 { 372 name: "email", 373 options: Options{ 374 IssuerURL: "https://auth.example.com", 375 ClientID: "my-client", 376 UsernameClaim: "email", 377 now: func() time.Time { return now }, 378 }, 379 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 380 pubKeys: []*jose.JSONWebKey{ 381 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 382 }, 383 claims: fmt.Sprintf(`{ 384 "iss": "https://auth.example.com", 385 "aud": "my-client", 386 "email": "jane@example.com", 387 "email_verified": true, 388 "exp": %d 389 }`, valid.Unix()), 390 want: &user.DefaultInfo{ 391 Name: "jane@example.com", 392 }, 393 }, 394 { 395 name: "email-not-verified", 396 options: Options{ 397 IssuerURL: "https://auth.example.com", 398 ClientID: "my-client", 399 UsernameClaim: "email", 400 now: func() time.Time { return now }, 401 }, 402 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 403 pubKeys: []*jose.JSONWebKey{ 404 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 405 }, 406 claims: fmt.Sprintf(`{ 407 "iss": "https://auth.example.com", 408 "aud": "my-client", 409 "email": "jane@example.com", 410 "email_verified": false, 411 "exp": %d 412 }`, valid.Unix()), 413 wantErr: true, 414 }, 415 { 416 // If "email_verified" isn't present, assume true 417 name: "no-email-verified-claim", 418 options: Options{ 419 IssuerURL: "https://auth.example.com", 420 ClientID: "my-client", 421 UsernameClaim: "email", 422 now: func() time.Time { return now }, 423 }, 424 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 425 pubKeys: []*jose.JSONWebKey{ 426 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 427 }, 428 claims: fmt.Sprintf(`{ 429 "iss": "https://auth.example.com", 430 "aud": "my-client", 431 "email": "jane@example.com", 432 "exp": %d 433 }`, valid.Unix()), 434 want: &user.DefaultInfo{ 435 Name: "jane@example.com", 436 }, 437 }, 438 { 439 name: "invalid-email-verified-claim", 440 options: Options{ 441 IssuerURL: "https://auth.example.com", 442 ClientID: "my-client", 443 UsernameClaim: "email", 444 now: func() time.Time { return now }, 445 }, 446 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 447 pubKeys: []*jose.JSONWebKey{ 448 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 449 }, 450 // string value for "email_verified" 451 claims: fmt.Sprintf(`{ 452 "iss": "https://auth.example.com", 453 "aud": "my-client", 454 "email": "jane@example.com", 455 "email_verified": "false", 456 "exp": %d 457 }`, valid.Unix()), 458 wantErr: true, 459 }, 460 { 461 name: "groups", 462 options: Options{ 463 IssuerURL: "https://auth.example.com", 464 ClientID: "my-client", 465 UsernameClaim: "username", 466 GroupsClaim: "groups", 467 now: func() time.Time { return now }, 468 }, 469 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 470 pubKeys: []*jose.JSONWebKey{ 471 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 472 }, 473 claims: fmt.Sprintf(`{ 474 "iss": "https://auth.example.com", 475 "aud": "my-client", 476 "username": "jane", 477 "groups": ["team1", "team2"], 478 "exp": %d 479 }`, valid.Unix()), 480 want: &user.DefaultInfo{ 481 Name: "jane", 482 Groups: []string{"team1", "team2"}, 483 }, 484 }, 485 { 486 name: "groups-distributed", 487 options: Options{ 488 IssuerURL: "{{.URL}}", 489 ClientID: "my-client", 490 UsernameClaim: "username", 491 GroupsClaim: "groups", 492 now: func() time.Time { return now }, 493 }, 494 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 495 pubKeys: []*jose.JSONWebKey{ 496 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 497 }, 498 claims: fmt.Sprintf(`{ 499 "iss": "{{.URL}}", 500 "aud": "my-client", 501 "username": "jane", 502 "_claim_names": { 503 "groups": "src1" 504 }, 505 "_claim_sources": { 506 "src1": { 507 "endpoint": "{{.URL}}/groups", 508 "access_token": "groups_token" 509 } 510 }, 511 "exp": %d 512 }`, valid.Unix()), 513 claimToResponseMap: map[string]string{ 514 "groups": fmt.Sprintf(`{ 515 "iss": "{{.URL}}", 516 "aud": "my-client", 517 "groups": ["team1", "team2"], 518 "exp": %d 519 }`, valid.Unix()), 520 }, 521 openIDConfig: `{ 522 "issuer": "{{.URL}}", 523 "jwks_uri": "{{.URL}}/.testing/keys" 524 }`, 525 want: &user.DefaultInfo{ 526 Name: "jane", 527 Groups: []string{"team1", "team2"}, 528 }, 529 }, 530 { 531 name: "groups-distributed-malformed-claim-names", 532 options: Options{ 533 IssuerURL: "{{.URL}}", 534 ClientID: "my-client", 535 UsernameClaim: "username", 536 GroupsClaim: "groups", 537 now: func() time.Time { return now }, 538 }, 539 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 540 pubKeys: []*jose.JSONWebKey{ 541 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 542 }, 543 claims: fmt.Sprintf(`{ 544 "iss": "{{.URL}}", 545 "aud": "my-client", 546 "username": "jane", 547 "_claim_names": { 548 "groups": "nonexistent-claim-source" 549 }, 550 "_claim_sources": { 551 "src1": { 552 "endpoint": "{{.URL}}/groups", 553 "access_token": "groups_token" 554 } 555 }, 556 "exp": %d 557 }`, valid.Unix()), 558 claimToResponseMap: map[string]string{ 559 "groups": fmt.Sprintf(`{ 560 "iss": "{{.URL}}", 561 "aud": "my-client", 562 "groups": ["team1", "team2"], 563 "exp": %d 564 }`, valid.Unix()), 565 }, 566 openIDConfig: `{ 567 "issuer": "{{.URL}}", 568 "jwks_uri": "{{.URL}}/.testing/keys" 569 }`, 570 wantErr: true, 571 }, 572 { 573 name: "groups-distributed-malformed-names-and-sources", 574 options: Options{ 575 IssuerURL: "{{.URL}}", 576 ClientID: "my-client", 577 UsernameClaim: "username", 578 GroupsClaim: "groups", 579 now: func() time.Time { return now }, 580 }, 581 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 582 pubKeys: []*jose.JSONWebKey{ 583 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 584 }, 585 claims: fmt.Sprintf(`{ 586 "iss": "{{.URL}}", 587 "aud": "my-client", 588 "username": "jane", 589 "_claim_names": { 590 "groups": "src1" 591 }, 592 "exp": %d 593 }`, valid.Unix()), 594 claimToResponseMap: map[string]string{ 595 "groups": fmt.Sprintf(`{ 596 "iss": "{{.URL}}", 597 "aud": "my-client", 598 "groups": ["team1", "team2"], 599 "exp": %d 600 }`, valid.Unix()), 601 }, 602 openIDConfig: `{ 603 "issuer": "{{.URL}}", 604 "jwks_uri": "{{.URL}}/.testing/keys" 605 }`, 606 wantErr: true, 607 }, 608 { 609 name: "groups-distributed-malformed-distributed-claim", 610 options: Options{ 611 IssuerURL: "{{.URL}}", 612 ClientID: "my-client", 613 UsernameClaim: "username", 614 GroupsClaim: "groups", 615 now: func() time.Time { return now }, 616 }, 617 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 618 pubKeys: []*jose.JSONWebKey{ 619 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 620 }, 621 claims: fmt.Sprintf(`{ 622 "iss": "{{.URL}}", 623 "aud": "my-client", 624 "username": "jane", 625 "_claim_names": { 626 "groups": "src1" 627 }, 628 "_claim_sources": { 629 "src1": { 630 "endpoint": "{{.URL}}/groups", 631 "access_token": "groups_token" 632 } 633 }, 634 "exp": %d 635 }`, valid.Unix()), 636 claimToResponseMap: map[string]string{ 637 // Doesn't contain the "groups" claim as it promises. 638 "groups": fmt.Sprintf(`{ 639 "iss": "{{.URL}}", 640 "aud": "my-client", 641 "exp": %d 642 }`, valid.Unix()), 643 }, 644 openIDConfig: `{ 645 "issuer": "{{.URL}}", 646 "jwks_uri": "{{.URL}}/.testing/keys" 647 }`, 648 wantErr: true, 649 }, 650 { 651 name: "groups-distributed-unusual-name", 652 options: Options{ 653 IssuerURL: "{{.URL}}", 654 ClientID: "my-client", 655 UsernameClaim: "username", 656 GroupsClaim: "rabbits", 657 now: func() time.Time { return now }, 658 }, 659 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 660 pubKeys: []*jose.JSONWebKey{ 661 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 662 }, 663 claims: fmt.Sprintf(`{ 664 "iss": "{{.URL}}", 665 "aud": "my-client", 666 "username": "jane", 667 "_claim_names": { 668 "rabbits": "src1" 669 }, 670 "_claim_sources": { 671 "src1": { 672 "endpoint": "{{.URL}}/rabbits", 673 "access_token": "rabbits_token" 674 } 675 }, 676 "exp": %d 677 }`, valid.Unix()), 678 claimToResponseMap: map[string]string{ 679 "rabbits": fmt.Sprintf(`{ 680 "iss": "{{.URL}}", 681 "aud": "my-client", 682 "rabbits": ["team1", "team2"], 683 "exp": %d 684 }`, valid.Unix()), 685 }, 686 openIDConfig: `{ 687 "issuer": "{{.URL}}", 688 "jwks_uri": "{{.URL}}/.testing/keys" 689 }`, 690 want: &user.DefaultInfo{ 691 Name: "jane", 692 Groups: []string{"team1", "team2"}, 693 }, 694 }, 695 { 696 name: "groups-distributed-wrong-audience", 697 options: Options{ 698 IssuerURL: "{{.URL}}", 699 ClientID: "my-client", 700 UsernameClaim: "username", 701 GroupsClaim: "groups", 702 now: func() time.Time { return now }, 703 }, 704 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 705 pubKeys: []*jose.JSONWebKey{ 706 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 707 }, 708 claims: fmt.Sprintf(`{ 709 "iss": "{{.URL}}", 710 "aud": "my-client", 711 "username": "jane", 712 "_claim_names": { 713 "groups": "src1" 714 }, 715 "_claim_sources": { 716 "src1": { 717 "endpoint": "{{.URL}}/groups", 718 "access_token": "groups_token" 719 } 720 }, 721 "exp": %d 722 }`, valid.Unix()), 723 claimToResponseMap: map[string]string{ 724 // Note mismatching "aud" 725 "groups": fmt.Sprintf(`{ 726 "iss": "{{.URL}}", 727 "aud": "your-client", 728 "groups": ["team1", "team2"], 729 "exp": %d 730 }`, valid.Unix()), 731 }, 732 openIDConfig: `{ 733 "issuer": "{{.URL}}", 734 "jwks_uri": "{{.URL}}/.testing/keys" 735 }`, 736 // "aud" was "your-client", not "my-client" 737 wantErr: true, 738 }, 739 { 740 name: "groups-distributed-wrong-audience", 741 options: Options{ 742 IssuerURL: "{{.URL}}", 743 ClientID: "my-client", 744 UsernameClaim: "username", 745 GroupsClaim: "groups", 746 now: func() time.Time { return now }, 747 }, 748 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 749 pubKeys: []*jose.JSONWebKey{ 750 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 751 }, 752 claims: fmt.Sprintf(`{ 753 "iss": "{{.URL}}", 754 "aud": "my-client", 755 "username": "jane", 756 "_claim_names": { 757 "groups": "src1" 758 }, 759 "_claim_sources": { 760 "src1": { 761 "endpoint": "{{.URL}}/groups", 762 "access_token": "groups_token" 763 } 764 }, 765 "exp": %d 766 }`, valid.Unix()), 767 claimToResponseMap: map[string]string{ 768 // Note expired timestamp. 769 "groups": fmt.Sprintf(`{ 770 "iss": "{{.URL}}", 771 "aud": "my-client", 772 "groups": ["team1", "team2"], 773 "exp": %d 774 }`, expired.Unix()), 775 }, 776 openIDConfig: `{ 777 "issuer": "{{.URL}}", 778 "jwks_uri": "{{.URL}}/.testing/keys" 779 }`, 780 // The distributed token is expired. 781 wantErr: true, 782 }, 783 { 784 // Specs are unclear about this behavior. We adopt a behavior where 785 // normal claim wins over a distributed claim by the same name. 786 name: "groups-distributed-normal-claim-wins", 787 options: Options{ 788 IssuerURL: "{{.URL}}", 789 ClientID: "my-client", 790 UsernameClaim: "username", 791 GroupsClaim: "groups", 792 now: func() time.Time { return now }, 793 }, 794 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 795 pubKeys: []*jose.JSONWebKey{ 796 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 797 }, 798 claims: fmt.Sprintf(`{ 799 "iss": "{{.URL}}", 800 "aud": "my-client", 801 "username": "jane", 802 "groups": "team1", 803 "_claim_names": { 804 "groups": "src1" 805 }, 806 "_claim_sources": { 807 "src1": { 808 "endpoint": "{{.URL}}/groups", 809 "access_token": "groups_token" 810 } 811 }, 812 "exp": %d 813 }`, valid.Unix()), 814 claimToResponseMap: map[string]string{ 815 "groups": fmt.Sprintf(`{ 816 "iss": "{{.URL}}", 817 "aud": "my-client", 818 "groups": ["team2"], 819 "exp": %d 820 }`, valid.Unix()), 821 }, 822 openIDConfig: `{ 823 "issuer": "{{.URL}}", 824 "jwks_uri": "{{.URL}}/.testing/keys" 825 }`, 826 want: &user.DefaultInfo{ 827 Name: "jane", 828 // "team1" is from the normal "groups" claim. 829 Groups: []string{"team1"}, 830 }, 831 }, 832 { 833 // Groups should be able to be a single string, not just a slice. 834 name: "group-string-claim", 835 options: Options{ 836 IssuerURL: "https://auth.example.com", 837 ClientID: "my-client", 838 UsernameClaim: "username", 839 GroupsClaim: "groups", 840 now: func() time.Time { return now }, 841 }, 842 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 843 pubKeys: []*jose.JSONWebKey{ 844 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 845 }, 846 claims: fmt.Sprintf(`{ 847 "iss": "https://auth.example.com", 848 "aud": "my-client", 849 "username": "jane", 850 "groups": "team1", 851 "exp": %d 852 }`, valid.Unix()), 853 want: &user.DefaultInfo{ 854 Name: "jane", 855 Groups: []string{"team1"}, 856 }, 857 }, 858 { 859 // Groups should be able to be a single string, not just a slice. 860 name: "group-string-claim-distributed", 861 options: Options{ 862 IssuerURL: "{{.URL}}", 863 ClientID: "my-client", 864 UsernameClaim: "username", 865 GroupsClaim: "groups", 866 now: func() time.Time { return now }, 867 }, 868 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 869 pubKeys: []*jose.JSONWebKey{ 870 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 871 }, 872 claims: fmt.Sprintf(`{ 873 "iss": "{{.URL}}", 874 "aud": "my-client", 875 "username": "jane", 876 "_claim_names": { 877 "groups": "src1" 878 }, 879 "_claim_sources": { 880 "src1": { 881 "endpoint": "{{.URL}}/groups", 882 "access_token": "groups_token" 883 } 884 }, 885 "exp": %d 886 }`, valid.Unix()), 887 claimToResponseMap: map[string]string{ 888 "groups": fmt.Sprintf(`{ 889 "iss": "{{.URL}}", 890 "aud": "my-client", 891 "groups": "team1", 892 "exp": %d 893 }`, valid.Unix()), 894 }, 895 openIDConfig: `{ 896 "issuer": "{{.URL}}", 897 "jwks_uri": "{{.URL}}/.testing/keys" 898 }`, 899 want: &user.DefaultInfo{ 900 Name: "jane", 901 Groups: []string{"team1"}, 902 }, 903 }, 904 { 905 name: "group-string-claim-aggregated-not-supported", 906 options: Options{ 907 IssuerURL: "https://auth.example.com", 908 ClientID: "my-client", 909 UsernameClaim: "username", 910 GroupsClaim: "groups", 911 now: func() time.Time { return now }, 912 }, 913 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 914 pubKeys: []*jose.JSONWebKey{ 915 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 916 }, 917 claims: fmt.Sprintf(`{ 918 "iss": "https://auth.example.com", 919 "aud": "my-client", 920 "username": "jane", 921 "_claim_names": { 922 "groups": "src1" 923 }, 924 "_claim_sources": { 925 "src1": { 926 "JWT": "some.jwt.token" 927 } 928 }, 929 "exp": %d 930 }`, valid.Unix()), 931 want: &user.DefaultInfo{ 932 Name: "jane", 933 }, 934 }, 935 { 936 // if the groups claim isn't provided, this shouldn't error out 937 name: "no-groups-claim", 938 options: Options{ 939 IssuerURL: "https://auth.example.com", 940 ClientID: "my-client", 941 UsernameClaim: "username", 942 GroupsClaim: "groups", 943 now: func() time.Time { return now }, 944 }, 945 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 946 pubKeys: []*jose.JSONWebKey{ 947 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 948 }, 949 claims: fmt.Sprintf(`{ 950 "iss": "https://auth.example.com", 951 "aud": "my-client", 952 "username": "jane", 953 "exp": %d 954 }`, valid.Unix()), 955 want: &user.DefaultInfo{ 956 Name: "jane", 957 }, 958 }, 959 { 960 name: "invalid-groups-claim", 961 options: Options{ 962 IssuerURL: "https://auth.example.com", 963 ClientID: "my-client", 964 UsernameClaim: "username", 965 GroupsClaim: "groups", 966 now: func() time.Time { return now }, 967 }, 968 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 969 pubKeys: []*jose.JSONWebKey{ 970 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 971 }, 972 claims: fmt.Sprintf(`{ 973 "iss": "https://auth.example.com", 974 "aud": "my-client", 975 "username": "jane", 976 "groups": 42, 977 "exp": %d 978 }`, valid.Unix()), 979 wantErr: true, 980 }, 981 { 982 name: "required-claim", 983 options: Options{ 984 IssuerURL: "https://auth.example.com", 985 ClientID: "my-client", 986 UsernameClaim: "username", 987 GroupsClaim: "groups", 988 RequiredClaims: map[string]string{ 989 "hd": "example.com", 990 "sub": "test", 991 }, 992 now: func() time.Time { return now }, 993 }, 994 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 995 pubKeys: []*jose.JSONWebKey{ 996 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 997 }, 998 claims: fmt.Sprintf(`{ 999 "iss": "https://auth.example.com", 1000 "aud": "my-client", 1001 "username": "jane", 1002 "hd": "example.com", 1003 "sub": "test", 1004 "exp": %d 1005 }`, valid.Unix()), 1006 want: &user.DefaultInfo{ 1007 Name: "jane", 1008 }, 1009 }, 1010 { 1011 name: "no-required-claim", 1012 options: Options{ 1013 IssuerURL: "https://auth.example.com", 1014 ClientID: "my-client", 1015 UsernameClaim: "username", 1016 GroupsClaim: "groups", 1017 RequiredClaims: map[string]string{ 1018 "hd": "example.com", 1019 }, 1020 now: func() time.Time { return now }, 1021 }, 1022 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1023 pubKeys: []*jose.JSONWebKey{ 1024 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1025 }, 1026 claims: fmt.Sprintf(`{ 1027 "iss": "https://auth.example.com", 1028 "aud": "my-client", 1029 "username": "jane", 1030 "exp": %d 1031 }`, valid.Unix()), 1032 wantErr: true, 1033 }, 1034 { 1035 name: "invalid-required-claim", 1036 options: Options{ 1037 IssuerURL: "https://auth.example.com", 1038 ClientID: "my-client", 1039 UsernameClaim: "username", 1040 GroupsClaim: "groups", 1041 RequiredClaims: map[string]string{ 1042 "hd": "example.com", 1043 }, 1044 now: func() time.Time { return now }, 1045 }, 1046 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1047 pubKeys: []*jose.JSONWebKey{ 1048 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1049 }, 1050 claims: fmt.Sprintf(`{ 1051 "iss": "https://auth.example.com", 1052 "aud": "my-client", 1053 "username": "jane", 1054 "hd": "example.org", 1055 "exp": %d 1056 }`, valid.Unix()), 1057 wantErr: true, 1058 }, 1059 { 1060 name: "invalid-signature", 1061 options: Options{ 1062 IssuerURL: "https://auth.example.com", 1063 ClientID: "my-client", 1064 UsernameClaim: "username", 1065 now: func() time.Time { return now }, 1066 }, 1067 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1068 pubKeys: []*jose.JSONWebKey{ 1069 loadRSAKey(t, "testdata/rsa_2.pem", jose.RS256), 1070 }, 1071 claims: fmt.Sprintf(`{ 1072 "iss": "https://auth.example.com", 1073 "aud": "my-client", 1074 "username": "jane", 1075 "exp": %d 1076 }`, valid.Unix()), 1077 wantErr: true, 1078 }, 1079 { 1080 name: "expired", 1081 options: Options{ 1082 IssuerURL: "https://auth.example.com", 1083 ClientID: "my-client", 1084 UsernameClaim: "username", 1085 now: func() time.Time { return now }, 1086 }, 1087 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1088 pubKeys: []*jose.JSONWebKey{ 1089 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1090 }, 1091 claims: fmt.Sprintf(`{ 1092 "iss": "https://auth.example.com", 1093 "aud": "my-client", 1094 "username": "jane", 1095 "exp": %d 1096 }`, expired.Unix()), 1097 wantErr: true, 1098 }, 1099 { 1100 name: "invalid-aud", 1101 options: Options{ 1102 IssuerURL: "https://auth.example.com", 1103 ClientID: "my-client", 1104 UsernameClaim: "username", 1105 now: func() time.Time { return now }, 1106 }, 1107 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1108 pubKeys: []*jose.JSONWebKey{ 1109 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1110 }, 1111 claims: fmt.Sprintf(`{ 1112 "iss": "https://auth.example.com", 1113 "aud": "not-my-client", 1114 "username": "jane", 1115 "exp": %d 1116 }`, valid.Unix()), 1117 wantErr: true, 1118 }, 1119 { 1120 // ID tokens may contain multiple audiences: 1121 // https://openid.net/specs/openid-connect-core-1_0.html#IDToken 1122 name: "multiple-audiences", 1123 options: Options{ 1124 IssuerURL: "https://auth.example.com", 1125 ClientID: "my-client", 1126 UsernameClaim: "username", 1127 now: func() time.Time { return now }, 1128 }, 1129 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1130 pubKeys: []*jose.JSONWebKey{ 1131 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1132 }, 1133 claims: fmt.Sprintf(`{ 1134 "iss": "https://auth.example.com", 1135 "aud": ["not-my-client", "my-client"], 1136 "azp": "not-my-client", 1137 "username": "jane", 1138 "exp": %d 1139 }`, valid.Unix()), 1140 want: &user.DefaultInfo{ 1141 Name: "jane", 1142 }, 1143 }, 1144 { 1145 name: "invalid-issuer", 1146 options: Options{ 1147 IssuerURL: "https://auth.example.com", 1148 ClientID: "my-client", 1149 UsernameClaim: "username", 1150 now: func() time.Time { return now }, 1151 }, 1152 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1153 pubKeys: []*jose.JSONWebKey{ 1154 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1155 }, 1156 claims: fmt.Sprintf(`{ 1157 "iss": "https://example.com", 1158 "aud": "my-client", 1159 "username": "jane", 1160 "exp": %d 1161 }`, valid.Unix()), 1162 wantSkip: true, 1163 }, 1164 { 1165 name: "username-prefix", 1166 options: Options{ 1167 IssuerURL: "https://auth.example.com", 1168 ClientID: "my-client", 1169 UsernameClaim: "username", 1170 UsernamePrefix: "oidc:", 1171 now: func() time.Time { return now }, 1172 }, 1173 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1174 pubKeys: []*jose.JSONWebKey{ 1175 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1176 }, 1177 claims: fmt.Sprintf(`{ 1178 "iss": "https://auth.example.com", 1179 "aud": "my-client", 1180 "username": "jane", 1181 "exp": %d 1182 }`, valid.Unix()), 1183 want: &user.DefaultInfo{ 1184 Name: "oidc:jane", 1185 }, 1186 }, 1187 { 1188 name: "groups-prefix", 1189 options: Options{ 1190 IssuerURL: "https://auth.example.com", 1191 ClientID: "my-client", 1192 UsernameClaim: "username", 1193 UsernamePrefix: "oidc:", 1194 GroupsClaim: "groups", 1195 GroupsPrefix: "groups:", 1196 now: func() time.Time { return now }, 1197 }, 1198 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1199 pubKeys: []*jose.JSONWebKey{ 1200 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1201 }, 1202 claims: fmt.Sprintf(`{ 1203 "iss": "https://auth.example.com", 1204 "aud": "my-client", 1205 "username": "jane", 1206 "groups": ["team1", "team2"], 1207 "exp": %d 1208 }`, valid.Unix()), 1209 want: &user.DefaultInfo{ 1210 Name: "oidc:jane", 1211 Groups: []string{"groups:team1", "groups:team2"}, 1212 }, 1213 }, 1214 { 1215 name: "groups-prefix-distributed", 1216 options: Options{ 1217 IssuerURL: "{{.URL}}", 1218 ClientID: "my-client", 1219 UsernameClaim: "username", 1220 UsernamePrefix: "oidc:", 1221 GroupsClaim: "groups", 1222 GroupsPrefix: "groups:", 1223 now: func() time.Time { return now }, 1224 }, 1225 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1226 pubKeys: []*jose.JSONWebKey{ 1227 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1228 }, 1229 claims: fmt.Sprintf(`{ 1230 "iss": "{{.URL}}", 1231 "aud": "my-client", 1232 "username": "jane", 1233 "_claim_names": { 1234 "groups": "src1" 1235 }, 1236 "_claim_sources": { 1237 "src1": { 1238 "endpoint": "{{.URL}}/groups", 1239 "access_token": "groups_token" 1240 } 1241 }, 1242 "exp": %d 1243 }`, valid.Unix()), 1244 claimToResponseMap: map[string]string{ 1245 "groups": fmt.Sprintf(`{ 1246 "iss": "{{.URL}}", 1247 "aud": "my-client", 1248 "groups": ["team1", "team2"], 1249 "exp": %d 1250 }`, valid.Unix()), 1251 }, 1252 openIDConfig: `{ 1253 "issuer": "{{.URL}}", 1254 "jwks_uri": "{{.URL}}/.testing/keys" 1255 }`, 1256 want: &user.DefaultInfo{ 1257 Name: "oidc:jane", 1258 Groups: []string{"groups:team1", "groups:team2"}, 1259 }, 1260 }, 1261 { 1262 name: "invalid-signing-alg", 1263 options: Options{ 1264 IssuerURL: "https://auth.example.com", 1265 ClientID: "my-client", 1266 UsernameClaim: "username", 1267 now: func() time.Time { return now }, 1268 }, 1269 // Correct key but invalid signature algorithm "PS256" 1270 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.PS256), 1271 pubKeys: []*jose.JSONWebKey{ 1272 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1273 }, 1274 claims: fmt.Sprintf(`{ 1275 "iss": "https://auth.example.com", 1276 "aud": "my-client", 1277 "username": "jane", 1278 "exp": %d 1279 }`, valid.Unix()), 1280 wantErr: true, 1281 }, 1282 { 1283 name: "ps256", 1284 options: Options{ 1285 IssuerURL: "https://auth.example.com", 1286 ClientID: "my-client", 1287 UsernameClaim: "username", 1288 SupportedSigningAlgs: []string{"PS256"}, 1289 now: func() time.Time { return now }, 1290 }, 1291 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.PS256), 1292 pubKeys: []*jose.JSONWebKey{ 1293 loadRSAKey(t, "testdata/rsa_1.pem", jose.PS256), 1294 }, 1295 claims: fmt.Sprintf(`{ 1296 "iss": "https://auth.example.com", 1297 "aud": "my-client", 1298 "username": "jane", 1299 "exp": %d 1300 }`, valid.Unix()), 1301 want: &user.DefaultInfo{ 1302 Name: "jane", 1303 }, 1304 }, 1305 { 1306 name: "es512", 1307 options: Options{ 1308 IssuerURL: "https://auth.example.com", 1309 ClientID: "my-client", 1310 UsernameClaim: "username", 1311 SupportedSigningAlgs: []string{"ES512"}, 1312 now: func() time.Time { return now }, 1313 }, 1314 signingKey: loadECDSAPrivKey(t, "testdata/ecdsa_2.pem", jose.ES512), 1315 pubKeys: []*jose.JSONWebKey{ 1316 loadECDSAKey(t, "testdata/ecdsa_1.pem", jose.ES512), 1317 loadECDSAKey(t, "testdata/ecdsa_2.pem", jose.ES512), 1318 }, 1319 claims: fmt.Sprintf(`{ 1320 "iss": "https://auth.example.com", 1321 "aud": "my-client", 1322 "username": "jane", 1323 "exp": %d 1324 }`, valid.Unix()), 1325 want: &user.DefaultInfo{ 1326 Name: "jane", 1327 }, 1328 }, 1329 { 1330 name: "not-https", 1331 options: Options{ 1332 IssuerURL: "http://auth.example.com", 1333 ClientID: "my-client", 1334 UsernameClaim: "username", 1335 now: func() time.Time { return now }, 1336 }, 1337 pubKeys: []*jose.JSONWebKey{ 1338 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1339 }, 1340 wantInitErr: true, 1341 }, 1342 { 1343 name: "no-username-claim", 1344 options: Options{ 1345 IssuerURL: "https://auth.example.com", 1346 ClientID: "my-client", 1347 now: func() time.Time { return now }, 1348 }, 1349 pubKeys: []*jose.JSONWebKey{ 1350 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1351 }, 1352 wantInitErr: true, 1353 }, 1354 { 1355 name: "invalid-sig-alg", 1356 options: Options{ 1357 IssuerURL: "https://auth.example.com", 1358 ClientID: "my-client", 1359 UsernameClaim: "username", 1360 SupportedSigningAlgs: []string{"HS256"}, 1361 now: func() time.Time { return now }, 1362 }, 1363 pubKeys: []*jose.JSONWebKey{ 1364 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1365 }, 1366 wantInitErr: true, 1367 }, 1368 { 1369 name: "accounts.google.com issuer", 1370 options: Options{ 1371 IssuerURL: "https://accounts.google.com", 1372 ClientID: "my-client", 1373 UsernameClaim: "email", 1374 now: func() time.Time { return now }, 1375 }, 1376 claims: fmt.Sprintf(`{ 1377 "iss": "accounts.google.com", 1378 "email": "thomas.jefferson@gmail.com", 1379 "aud": "my-client", 1380 "exp": %d 1381 }`, valid.Unix()), 1382 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1383 pubKeys: []*jose.JSONWebKey{ 1384 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1385 }, 1386 want: &user.DefaultInfo{ 1387 Name: "thomas.jefferson@gmail.com", 1388 }, 1389 }, 1390 { 1391 name: "good token with bad client id", 1392 options: Options{ 1393 IssuerURL: "https://auth.example.com", 1394 ClientID: "my-client", 1395 UsernameClaim: "username", 1396 now: func() time.Time { return now }, 1397 }, 1398 signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), 1399 pubKeys: []*jose.JSONWebKey{ 1400 loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), 1401 }, 1402 claims: fmt.Sprintf(`{ 1403 "iss": "https://auth.example.com", 1404 "aud": "my-wrong-client", 1405 "username": "jane", 1406 "exp": %d 1407 }`, valid.Unix()), 1408 wantErr: true, 1409 }, 1410 } 1411 for _, test := range tests { 1412 t.Run(test.name, test.run) 1413 } 1414} 1415 1416func TestUnmarshalClaimError(t *testing.T) { 1417 // Ensure error strings returned by unmarshaling claims don't include the claim. 1418 const token = "96bb299a-02e9-11e8-8673-54ee7553240e" // Fake token for testing. 1419 payload := fmt.Sprintf(`{ 1420 "token": "%s" 1421 }`, token) 1422 1423 var c claims 1424 if err := json.Unmarshal([]byte(payload), &c); err != nil { 1425 t.Fatal(err) 1426 } 1427 var n int 1428 err := c.unmarshalClaim("token", &n) 1429 if err == nil { 1430 t.Fatal("expected error") 1431 } 1432 1433 if strings.Contains(err.Error(), token) { 1434 t.Fatalf("unmarshal error included token") 1435 } 1436} 1437 1438func TestUnmarshalClaim(t *testing.T) { 1439 tests := []struct { 1440 name string 1441 claims string 1442 do func(claims) (interface{}, error) 1443 want interface{} 1444 wantErr bool 1445 }{ 1446 { 1447 name: "string claim", 1448 claims: `{"aud":"foo"}`, 1449 do: func(c claims) (interface{}, error) { 1450 var s string 1451 err := c.unmarshalClaim("aud", &s) 1452 return s, err 1453 }, 1454 want: "foo", 1455 }, 1456 { 1457 name: "mismatched types", 1458 claims: `{"aud":"foo"}`, 1459 do: func(c claims) (interface{}, error) { 1460 var n int 1461 err := c.unmarshalClaim("aud", &n) 1462 return n, err 1463 1464 }, 1465 wantErr: true, 1466 }, 1467 { 1468 name: "bool claim", 1469 claims: `{"email":"foo@coreos.com","email_verified":true}`, 1470 do: func(c claims) (interface{}, error) { 1471 var verified bool 1472 err := c.unmarshalClaim("email_verified", &verified) 1473 return verified, err 1474 }, 1475 want: true, 1476 }, 1477 { 1478 name: "strings claim", 1479 claims: `{"groups":["a","b","c"]}`, 1480 do: func(c claims) (interface{}, error) { 1481 var groups []string 1482 err := c.unmarshalClaim("groups", &groups) 1483 return groups, err 1484 }, 1485 want: []string{"a", "b", "c"}, 1486 }, 1487 } 1488 1489 for _, test := range tests { 1490 t.Run(test.name, func(t *testing.T) { 1491 var c claims 1492 if err := json.Unmarshal([]byte(test.claims), &c); err != nil { 1493 t.Fatal(err) 1494 } 1495 1496 got, err := test.do(c) 1497 if err != nil { 1498 if test.wantErr { 1499 return 1500 } 1501 t.Fatalf("unexpected error: %v", err) 1502 } 1503 if test.wantErr { 1504 t.Fatalf("expected error") 1505 } 1506 1507 if !reflect.DeepEqual(got, test.want) { 1508 t.Errorf("wanted=%#v, got=%#v", test.want, got) 1509 } 1510 }) 1511 } 1512} 1513