// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package google import ( "bytes" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "strings" "sync" "testing" "time" "golang.org/x/oauth2/jws" ) var ( privateKey *rsa.PrivateKey jsonKey []byte once sync.Once ) func TestJWTAccessTokenSourceFromJSON(t *testing.T) { setupDummyKey(t) ts, err := JWTAccessTokenSourceFromJSON(jsonKey, "audience") if err != nil { t.Fatalf("JWTAccessTokenSourceFromJSON: %v\nJSON: %s", err, string(jsonKey)) } tok, err := ts.Token() if err != nil { t.Fatalf("Token: %v", err) } if got, want := tok.TokenType, "Bearer"; got != want { t.Errorf("TokenType = %q, want %q", got, want) } if got := tok.Expiry; tok.Expiry.Before(time.Now()) { t.Errorf("Expiry = %v, should not be expired", got) } err = jws.Verify(tok.AccessToken, &privateKey.PublicKey) if err != nil { t.Errorf("jws.Verify on AccessToken: %v", err) } claim, err := jws.Decode(tok.AccessToken) if err != nil { t.Fatalf("jws.Decode on AccessToken: %v", err) } if got, want := claim.Iss, "gopher@developer.gserviceaccount.com"; got != want { t.Errorf("Iss = %q, want %q", got, want) } if got, want := claim.Sub, "gopher@developer.gserviceaccount.com"; got != want { t.Errorf("Sub = %q, want %q", got, want) } if got, want := claim.Aud, "audience"; got != want { t.Errorf("Aud = %q, want %q", got, want) } // Finally, check the header private key. parts := strings.Split(tok.AccessToken, ".") hdrJSON, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { t.Fatalf("base64 DecodeString: %v\nString: %q", err, parts[0]) } var hdr jws.Header if err := json.Unmarshal(hdrJSON, &hdr); err != nil { t.Fatalf("json.Unmarshal: %v (%q)", err, hdrJSON) } if got, want := hdr.KeyID, "268f54e43a1af97cfc71731688434f45aca15c8b"; got != want { t.Errorf("Header KeyID = %q, want %q", got, want) } } func TestJWTAccessTokenSourceWithScope(t *testing.T) { setupDummyKey(t) ts, err := JWTAccessTokenSourceWithScope(jsonKey, "scope1", "scope2") if err != nil { t.Fatalf("JWTAccessTokenSourceWithScope: %v\nJSON: %s", err, string(jsonKey)) } tok, err := ts.Token() if err != nil { t.Fatalf("Token: %v", err) } if got, want := tok.TokenType, "Bearer"; got != want { t.Errorf("TokenType = %q, want %q", got, want) } if got := tok.Expiry; tok.Expiry.Before(time.Now()) { t.Errorf("Expiry = %v, should not be expired", got) } err = jws.Verify(tok.AccessToken, &privateKey.PublicKey) if err != nil { t.Errorf("jws.Verify on AccessToken: %v", err) } claim, err := jws.Decode(tok.AccessToken) if err != nil { t.Fatalf("jws.Decode on AccessToken: %v", err) } if got, want := claim.Iss, "gopher@developer.gserviceaccount.com"; got != want { t.Errorf("Iss = %q, want %q", got, want) } if got, want := claim.Sub, "gopher@developer.gserviceaccount.com"; got != want { t.Errorf("Sub = %q, want %q", got, want) } if got, want := claim.Scope, "scope1 scope2"; got != want { t.Errorf("Aud = %q, want %q", got, want) } // Finally, check the header private key. parts := strings.Split(tok.AccessToken, ".") hdrJSON, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { t.Fatalf("base64 DecodeString: %v\nString: %q", err, parts[0]) } var hdr jws.Header if err := json.Unmarshal(hdrJSON, &hdr); err != nil { t.Fatalf("json.Unmarshal: %v (%q)", err, hdrJSON) } if got, want := hdr.KeyID, "268f54e43a1af97cfc71731688434f45aca15c8b"; got != want { t.Errorf("Header KeyID = %q, want %q", got, want) } } func setupDummyKey(t *testing.T) { once.Do(func() { // Generate a key we can use in the test data. pk, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } privateKey = pk // Encode the key and substitute into our example JSON. enc := pem.EncodeToMemory(&pem.Block{ Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey), }) enc, err = json.Marshal(string(enc)) if err != nil { t.Fatalf("json.Marshal: %v", err) } jsonKey = bytes.Replace(jwtJSONKey, []byte(`"super secret key"`), enc, 1) }) }