Skip to content

Commit

Permalink
Address requested changes
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianajg committed Jan 3, 2023
1 parent 5267088 commit 9ba99d9
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 93 deletions.
42 changes: 30 additions & 12 deletions idtoken/idtoken.go
Expand Up @@ -29,6 +29,14 @@ import (
// ClientOption is for configuring a Google API client or transport.
type ClientOption = option.ClientOption

type credentialsType int

const (
unknownCredType credentialsType = iota
serviceAccount
impersonatedServiceAccount
)

// NewClient creates a HTTP Client that automatically adds an ID token to each
// request via an Authorization header. The token will have the audience
// provided and be configured with the supplied options. The parameter audience
Expand Down Expand Up @@ -71,7 +79,6 @@ func NewClient(ctx context.Context, audience string, opts ...ClientOption) (*htt
// provided and configured with the supplied options. The parameter audience may
// not be empty.
func NewTokenSource(ctx context.Context, audience string, opts ...ClientOption) (oauth2.TokenSource, error) {
option.WithScopes()
if audience == "" {
return nil, fmt.Errorf("idtoken: must supply a non-empty audience")
}
Expand Down Expand Up @@ -112,7 +119,8 @@ func tokenSourceFromBytes(ctx context.Context, data []byte, audience string, ds
if err != nil {
return nil, err
}
if allowedType == "service_account" {
switch allowedType {
case serviceAccount:
cfg, err := google.JWTConfigFromJSON(data, ds.GetScopes()...)
if err != nil {
return nil, err
Expand All @@ -132,8 +140,7 @@ func tokenSourceFromBytes(ctx context.Context, data []byte, audience string, ds
return nil, err
}
return oauth2.ReuseTokenSource(tok, ts), nil
} else {
// if allowedType is "impersonated_service_account":
case impersonatedServiceAccount:
type url struct {
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
}
Expand All @@ -154,26 +161,37 @@ func tokenSourceFromBytes(ctx context.Context, data []byte, audience string, ds
log.Println(err)
}
return ts, nil
default:
return nil, fmt.Errorf("idtoken: unsupported credentials type")
}
}

// isOfAllowedType returns the credentials type as a string, and an error.
// getAllowedType returns the credentials type of type credentialsType, and an error.
// allowed types are "service_account" and "impersonated_service_account"
func getAllowedType(data []byte) (string, error) {
func getAllowedType(data []byte) (credentialsType, error) {
var t credentialsType
if len(data) == 0 {
return "", fmt.Errorf("idtoken: credential provided is 0 bytes")
return t, fmt.Errorf("idtoken: credential provided is 0 bytes")
}
var f struct {
Type string `json:"type"`
}
// if not service account return an error
if err := json.Unmarshal(data, &f); err != nil {
return "", err
return t, err
}
if f.Type != "service_account" && f.Type != "impersonated_service_account" {
return "", fmt.Errorf("idtoken: credential must be service_account or impersonated_service_account, found %q", f.Type)
t = parseCredType(f.Type)
return t, nil
}

func parseCredType(typeString string) credentialsType {
switch typeString {
case "service_account":
return serviceAccount
case "impersonated_service_account":
return impersonatedServiceAccount
default:
return unknownCredType
}
return f.Type, nil
}

// WithCustomClaims optionally specifies custom private claims for an ID token.
Expand Down
80 changes: 0 additions & 80 deletions idtoken/idtoken_test.go

This file was deleted.

55 changes: 54 additions & 1 deletion idtoken/integration_test.go
@@ -1,11 +1,12 @@
// Copyright 2020 Google LLC.
// Copyright 2023 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package idtoken_test

import (
"context"
"encoding/json"
"net/http"
"os"
"strings"
Expand Down Expand Up @@ -49,6 +50,58 @@ func TestNewTokenSource(t *testing.T) {
}
}

func TestNewTokenSource_WithImpersonatedServiceAccountCreds(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
type SourceCreds struct {
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RefreshToken string `json:"refresh_token"`
Type string `json:"type"`
}
type Creds struct {
Type string `json:"type"`
ImpersonationUrl string `json:"service_account_impersonation_url"`
SourceCredentials SourceCreds `json:"source_credentials"`
}
sc := SourceCreds{
"764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com",
"d-FL95Q19q7MQmFpd7hHD0Ty",
"1//013HO0uxdpTsACgYIARAAGAESNwF-L9IrnetMwzfoPKYTW1axwThb67BXquvFgOSXPX557y7AMkWLB2kcNWW54K34LLxow6_FU7s",
"authorized_user",
}
c := Creds{
"impersonated_service_account",
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/kyoshi@adriana-testing.iam.gserviceaccount.com:generateAccessToken",
sc,
}
creds, err := json.Marshal(c)
if err != nil {
t.Fatalf("unable to marshal credentials: %v", err)
}
ts, err := idtoken.NewTokenSource(context.Background(), "http://example.com", option.WithCredentialsJSON(creds))
if err != nil {
t.Fatalf("unable to create TokenSource: %v", err)
}
tok, err := ts.Token()
if err != nil {
t.Fatalf("unable to retrieve Token: %v", err)
}
req := &http.Request{Header: make(http.Header)}
tok.SetAuthHeader(req)
if !strings.HasPrefix(req.Header.Get("Authorization"), "Bearer ") {
t.Fatalf("token should sign requests with Bearer Authorization header")
}
validTok, err := idtoken.Validate(context.Background(), tok.AccessToken, aud)
if err != nil {
t.Fatalf("token validation failed: %v", err)
}
if validTok.Audience != aud {
t.Fatalf("got %q, want %q", validTok.Audience, aud)
}
}

func TestNewClient_WithCredentialFile(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
Expand Down

0 comments on commit 9ba99d9

Please sign in to comment.