...

Source file src/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange_test.go

Documentation: cloud.google.com/go/auth/credentials/internal/stsexchange

     1  // Copyright 2023 Google LLC
     2  //
     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  package stsexchange
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"io"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"net/url"
    24  	"testing"
    25  
    26  	"cloud.google.com/go/auth"
    27  	"cloud.google.com/go/auth/internal"
    28  	"github.com/google/go-cmp/cmp"
    29  )
    30  
    31  var (
    32  	clientAuth = ClientAuthentication{
    33  		AuthStyle:    auth.StyleInHeader,
    34  		ClientID:     clientID,
    35  		ClientSecret: clientSecret,
    36  	}
    37  	tokReq = TokenRequest{
    38  		ActingParty: struct {
    39  			ActorToken     string
    40  			ActorTokenType string
    41  		}{},
    42  		GrantType:          GrantType,
    43  		Resource:           "",
    44  		Audience:           "32555940559.apps.googleusercontent.com",
    45  		Scope:              []string{"https://www.googleapis.com/auth/devstorage.full_control"},
    46  		RequestedTokenType: TokenType,
    47  		SubjectToken:       "Sample.Subject.Token",
    48  		SubjectTokenType:   jwtTokenType,
    49  	}
    50  
    51  	responseBody = `{"access_token":"Sample.Access.Token","issued_token_type":"urn:ietf:params:oauth:token-type:access_token","token_type":"Bearer","expires_in":3600,"scope":"https://www.googleapis.com/auth/cloud-platform"}`
    52  )
    53  
    54  func TestExchangeToken(t *testing.T) {
    55  	requestbody := "audience=32555940559.apps.googleusercontent.com&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdevstorage.full_control&subject_token=Sample.Subject.Token&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Ajwt"
    56  	wantToken := TokenResponse{
    57  		AccessToken:     "Sample.Access.Token",
    58  		IssuedTokenType: TokenType,
    59  		TokenType:       internal.TokenTypeBearer,
    60  		ExpiresIn:       3600,
    61  		Scope:           "https://www.googleapis.com/auth/cloud-platform",
    62  	}
    63  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    64  		if r.Method != http.MethodPost {
    65  			t.Errorf("got %v, want %v", r.Method, http.MethodPost)
    66  		}
    67  		if got, want := r.Header.Get("Authorization"), "Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ="; got != want {
    68  			t.Errorf("got %v, want %v", got, want)
    69  		}
    70  		if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want {
    71  			t.Errorf("got %v, want %v", got, want)
    72  		}
    73  		body, err := io.ReadAll(r.Body)
    74  		if err != nil {
    75  			t.Error(err)
    76  		}
    77  		if got, want := string(body), requestbody; got != want {
    78  			t.Errorf("got %v, want %v", got, want)
    79  		}
    80  		w.Header().Set("Content-Type", "application/json")
    81  		w.Write([]byte(responseBody))
    82  	}))
    83  	defer ts.Close()
    84  
    85  	headers := http.Header{}
    86  	headers.Set("Content-Type", "application/x-www-form-urlencoded")
    87  
    88  	resp, err := ExchangeToken(context.Background(), &Options{
    89  		Client:         internal.CloneDefaultClient(),
    90  		Endpoint:       ts.URL,
    91  		Request:        &tokReq,
    92  		Authentication: clientAuth,
    93  		Headers:        headers,
    94  		ExtraOpts:      nil,
    95  	})
    96  	if err != nil {
    97  		t.Fatalf("exchangeToken() = %v", err)
    98  	}
    99  
   100  	if *resp != wantToken {
   101  		t.Errorf("got %v, want %v", *resp, wantToken)
   102  	}
   103  }
   104  
   105  func TestExchangeToken_Err(t *testing.T) {
   106  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   107  		w.Header().Set("Content-Type", "application/json")
   108  		w.Write([]byte("what's wrong with this response?"))
   109  	}))
   110  	defer ts.Close()
   111  
   112  	headers := http.Header{}
   113  	headers.Set("Content-Type", "application/x-www-form-urlencoded")
   114  	if _, err := ExchangeToken(context.Background(), &Options{
   115  		Client:         internal.CloneDefaultClient(),
   116  		Endpoint:       ts.URL,
   117  		Request:        &tokReq,
   118  		Authentication: clientAuth,
   119  		Headers:        headers,
   120  		ExtraOpts:      nil,
   121  	}); err == nil {
   122  		t.Errorf("got nil, want an error")
   123  	}
   124  }
   125  
   126  func TestExchangeToken_Opts(t *testing.T) {
   127  	optsValues := [][]string{{"foo", "bar"}, {"cat", "pan"}}
   128  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   129  		body, err := io.ReadAll(r.Body)
   130  		if err != nil {
   131  			t.Fatalf("io.ReadAll() = %v", err)
   132  		}
   133  		data, err := url.ParseQuery(string(body))
   134  		if err != nil {
   135  			t.Fatalf("url.ParseQuery() = %v", err)
   136  		}
   137  		strOpts, ok := data["options"]
   138  		if !ok {
   139  			t.Errorf(`server didn't receive an "options" field`)
   140  		} else if len(strOpts) < 1 {
   141  			t.Errorf(`"options" field has length 0`)
   142  		}
   143  		var opts map[string]interface{}
   144  		err = json.Unmarshal([]byte(strOpts[0]), &opts)
   145  		if err != nil {
   146  			t.Fatalf(`couldn't parse received "options" field`)
   147  		}
   148  		if len(opts) < 2 {
   149  			t.Errorf("too few options received")
   150  		}
   151  
   152  		val, ok := opts["one"]
   153  		if !ok {
   154  			t.Errorf("couldn't find first option parameter")
   155  		} else {
   156  			tOpts1, ok := val.(map[string]interface{})
   157  			if !ok {
   158  				t.Errorf("failed to assert the first option parameter as type testOpts")
   159  			} else {
   160  				if got, want := tOpts1["first"].(string), optsValues[0][0]; got != want {
   161  					t.Errorf("got %v, want %v", got, want)
   162  				}
   163  				if got, want := tOpts1["second"].(string), optsValues[0][1]; got != want {
   164  					t.Errorf("got %v, want %v", got, want)
   165  				}
   166  			}
   167  		}
   168  
   169  		val2, ok := opts["two"]
   170  		if !ok {
   171  			t.Errorf("couldn't find second option parameter")
   172  		} else {
   173  			tOpts2, ok := val2.(map[string]interface{})
   174  			if !ok {
   175  				t.Errorf("Failed to assert the second option parameter as type testOpts.")
   176  			} else {
   177  				if got, want := tOpts2["first"].(string), optsValues[1][0]; got != want {
   178  					t.Errorf("got %v, want %v", got, want)
   179  				}
   180  				if got, want := tOpts2["second"].(string), optsValues[1][1]; got != want {
   181  					t.Errorf("got %v, want %v", got, want)
   182  				}
   183  			}
   184  		}
   185  		// Send a proper reply so that no other errors crop up.
   186  		w.Header().Set("Content-Type", "application/json")
   187  		w.Write([]byte(responseBody))
   188  
   189  	}))
   190  	defer ts.Close()
   191  	headers := http.Header{}
   192  	headers.Set("Content-Type", "application/x-www-form-urlencoded")
   193  
   194  	type testOpts struct {
   195  		First  string `json:"first"`
   196  		Second string `json:"second"`
   197  	}
   198  	firstOption := testOpts{optsValues[0][0], optsValues[0][1]}
   199  	secondOption := testOpts{optsValues[1][0], optsValues[1][1]}
   200  	inputOpts := make(map[string]interface{})
   201  	inputOpts["one"] = firstOption
   202  	inputOpts["two"] = secondOption
   203  
   204  	ExchangeToken(context.Background(), &Options{
   205  		Client:         internal.CloneDefaultClient(),
   206  		Endpoint:       ts.URL,
   207  		Request:        &tokReq,
   208  		Authentication: clientAuth,
   209  		Headers:        headers,
   210  		ExtraOpts:      inputOpts,
   211  	})
   212  }
   213  
   214  var (
   215  	clientID           = "rbrgnognrhongo3bi4gb9ghg9g"
   216  	clientSecret       = "notsosecret"
   217  	audience           = []string{"32555940559.apps.googleusercontent.com"}
   218  	grantType          = []string{GrantType}
   219  	requestedTokenType = []string{TokenType}
   220  	subjectTokenType   = []string{jwtTokenType}
   221  	subjectToken       = []string{"eyJhbGciOiJSUzI1NiIsImtpZCI6IjJjNmZhNmY1OTUwYTdjZTQ2NWZjZjI0N2FhMGIwOTQ4MjhhYzk1MmMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIzMjU1NTk0MDU1OS5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImF1ZCI6IjMyNTU1OTQwNTU5LmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEzMzE4NTQxMDA5MDU3Mzc4MzI4IiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJpdGh1cmllbEBnb29nbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiI5OVJVYVFrRHJsVDFZOUV0SzdiYXJnIiwiaWF0IjoxNjAxNTgxMzQ5LCJleHAiOjE2MDE1ODQ5NDl9.SZ-4DyDcogDh_CDUKHqPCiT8AKLg4zLMpPhGQzmcmHQ6cJiV0WRVMf5Lq911qsvuekgxfQpIdKNXlD6yk3FqvC2rjBbuEztMF-OD_2B8CEIYFlMLGuTQimJlUQksLKM-3B2ITRDCxnyEdaZik0OVssiy1CBTsllS5MgTFqic7w8w0Cd6diqNkfPFZRWyRYsrRDRlHHbH5_TUnv2wnLVHBHlNvU4wU2yyjDIoqOvTRp8jtXdq7K31CDhXd47-hXsVFQn2ZgzuUEAkH2Q6NIXACcVyZOrjBcZiOQI9IRWz-g03LzbzPSecO7I8dDrhqUSqMrdNUz_f8Kr8JFhuVMfVug"}
   222  	scope              = []string{"https://www.googleapis.com/auth/devstorage.full_control"}
   223  	ContentType        = []string{"application/x-www-form-urlencoded"}
   224  )
   225  
   226  func TestClientAuthentication_InjectHeaderAuthentication(t *testing.T) {
   227  	valuesH := url.Values{
   228  		"audience":             audience,
   229  		"grant_type":           grantType,
   230  		"requested_token_type": requestedTokenType,
   231  		"subject_token_type":   subjectTokenType,
   232  		"subject_token":        subjectToken,
   233  		"scope":                scope,
   234  	}
   235  	headerH := http.Header{
   236  		"Content-Type": ContentType,
   237  	}
   238  
   239  	headerAuthentication := ClientAuthentication{
   240  		AuthStyle:    auth.StyleInHeader,
   241  		ClientID:     clientID,
   242  		ClientSecret: clientSecret,
   243  	}
   244  	headerAuthentication.InjectAuthentication(valuesH, headerH)
   245  
   246  	if got, want := valuesH["audience"], audience; !cmp.Equal(got, want) {
   247  		t.Errorf("audience = %q, want %q", got, want)
   248  	}
   249  	if got, want := valuesH["grant_type"], grantType; !cmp.Equal(got, want) {
   250  		t.Errorf("grant_type = %q, want %q", got, want)
   251  	}
   252  	if got, want := valuesH["requested_token_type"], requestedTokenType; !cmp.Equal(got, want) {
   253  		t.Errorf("requested_token_type = %q, want %q", got, want)
   254  	}
   255  	if got, want := valuesH["subject_token_type"], subjectTokenType; !cmp.Equal(got, want) {
   256  		t.Errorf("subject_token_type = %q, want %q", got, want)
   257  	}
   258  	if got, want := valuesH["subject_token"], subjectToken; !cmp.Equal(got, want) {
   259  		t.Errorf("subject_token = %q, want %q", got, want)
   260  	}
   261  	if got, want := valuesH["scope"], scope; !cmp.Equal(got, want) {
   262  		t.Errorf("scope = %q, want %q", got, want)
   263  	}
   264  	if got, want := headerH["Authorization"], []string{"Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ="}; !cmp.Equal(got, want) {
   265  		t.Errorf("Authorization in header = %q, want %q", got, want)
   266  	}
   267  }
   268  
   269  func TestClientAuthentication_ParamsAuthentication(t *testing.T) {
   270  	valuesP := url.Values{
   271  		"audience":             audience,
   272  		"grant_type":           grantType,
   273  		"requested_token_type": requestedTokenType,
   274  		"subject_token_type":   subjectTokenType,
   275  		"subject_token":        subjectToken,
   276  		"scope":                scope,
   277  	}
   278  	headerP := http.Header{
   279  		"Content-Type": ContentType,
   280  	}
   281  	paramsAuthentication := ClientAuthentication{
   282  		AuthStyle:    auth.StyleInParams,
   283  		ClientID:     clientID,
   284  		ClientSecret: clientSecret,
   285  	}
   286  	paramsAuthentication.InjectAuthentication(valuesP, headerP)
   287  
   288  	if got, want := valuesP["audience"], audience; !cmp.Equal(got, want) {
   289  		t.Errorf("audience = %q, want %q", got, want)
   290  	}
   291  	if got, want := valuesP["grant_type"], grantType; !cmp.Equal(got, want) {
   292  		t.Errorf("grant_type = %q, want %q", got, want)
   293  	}
   294  	if got, want := valuesP["requested_token_type"], requestedTokenType; !cmp.Equal(got, want) {
   295  		t.Errorf("requested_token_type = %q, want %q", got, want)
   296  	}
   297  	if got, want := valuesP["subject_token_type"], subjectTokenType; !cmp.Equal(got, want) {
   298  		t.Errorf("subject_token_type = %q, want %q", got, want)
   299  	}
   300  	if got, want := valuesP["subject_token"], subjectToken; !cmp.Equal(got, want) {
   301  		t.Errorf("subject_token = %q, want %q", got, want)
   302  	}
   303  	if got, want := valuesP["scope"], scope; !cmp.Equal(got, want) {
   304  		t.Errorf("scope = %q, want %q", got, want)
   305  	}
   306  	if got, want := valuesP["client_id"], []string{clientID}; !cmp.Equal(got, want) {
   307  		t.Errorf("client_id = %q, want %q", got, want)
   308  	}
   309  	if got, want := valuesP["client_secret"], []string{clientSecret}; !cmp.Equal(got, want) {
   310  		t.Errorf("client_secret = %q, want %q", got, want)
   311  	}
   312  }
   313  

View as plain text