| // Copyright 2020 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 externalaccount |
| |
| import ( |
| "context" |
| "encoding/json" |
| "io/ioutil" |
| "net/http" |
| "net/http/httptest" |
| "net/url" |
| "testing" |
| |
| "golang.org/x/oauth2" |
| ) |
| |
| var auth = clientAuthentication{ |
| AuthStyle: oauth2.AuthStyleInHeader, |
| ClientID: clientID, |
| ClientSecret: clientSecret, |
| } |
| |
| var tokenRequest = stsTokenExchangeRequest{ |
| ActingParty: struct { |
| ActorToken string |
| ActorTokenType string |
| }{}, |
| GrantType: "urn:ietf:params:oauth:grant-type:token-exchange", |
| Resource: "", |
| Audience: "32555940559.apps.googleusercontent.com", //TODO: Make sure audience is correct in this test (might be mismatched) |
| Scope: []string{"https://www.googleapis.com/auth/devstorage.full_control"}, |
| RequestedTokenType: "urn:ietf:params:oauth:token-type:access_token", |
| SubjectToken: "Sample.Subject.Token", |
| SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt", |
| } |
| |
| var 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" |
| var 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"}` |
| var expectedToken = stsTokenExchangeResponse{ |
| AccessToken: "Sample.Access.Token", |
| IssuedTokenType: "urn:ietf:params:oauth:token-type:access_token", |
| TokenType: "Bearer", |
| ExpiresIn: 3600, |
| Scope: "https://www.googleapis.com/auth/cloud-platform", |
| RefreshToken: "", |
| } |
| |
| func TestExchangeToken(t *testing.T) { |
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| if r.Method != "POST" { |
| t.Errorf("Unexpected request method, %v is found", r.Method) |
| } |
| if r.URL.String() != "/" { |
| t.Errorf("Unexpected request URL, %v is found", r.URL) |
| } |
| if got, want := r.Header.Get("Authorization"), "Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ="; got != want { |
| t.Errorf("Unexpected authorization header, got %v, want %v", got, want) |
| } |
| if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want { |
| t.Errorf("Unexpected Content-Type header, got %v, want %v", got, want) |
| } |
| body, err := ioutil.ReadAll(r.Body) |
| if err != nil { |
| t.Errorf("Failed reading request body: %v.", err) |
| } |
| if got, want := string(body), requestbody; got != want { |
| t.Errorf("Unexpected exchange payload, got %v but want %v", got, want) |
| } |
| w.Header().Set("Content-Type", "application/json") |
| w.Write([]byte(responseBody)) |
| })) |
| defer ts.Close() |
| |
| headers := http.Header{} |
| headers.Add("Content-Type", "application/x-www-form-urlencoded") |
| |
| resp, err := exchangeToken(context.Background(), ts.URL, &tokenRequest, auth, headers, nil) |
| if err != nil { |
| t.Fatalf("exchangeToken failed with error: %v", err) |
| } |
| |
| if expectedToken != *resp { |
| t.Errorf("mismatched messages received by mock server. \nWant: \n%v\n\nGot:\n%v", expectedToken, *resp) |
| } |
| |
| } |
| |
| func TestExchangeToken_Err(t *testing.T) { |
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| w.Header().Set("Content-Type", "application/json") |
| w.Write([]byte("what's wrong with this response?")) |
| })) |
| defer ts.Close() |
| |
| headers := http.Header{} |
| headers.Add("Content-Type", "application/x-www-form-urlencoded") |
| _, err := exchangeToken(context.Background(), ts.URL, &tokenRequest, auth, headers, nil) |
| if err == nil { |
| t.Errorf("Expected handled error; instead got nil.") |
| } |
| } |
| |
| /* Lean test specifically for options, as the other features are tested earlier. */ |
| type testOpts struct { |
| First string `json:"first"` |
| Second string `json:"second"` |
| } |
| |
| var optsValues = [][]string{{"foo", "bar"}, {"cat", "pan"}} |
| |
| func TestExchangeToken_Opts(t *testing.T) { |
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| body, err := ioutil.ReadAll(r.Body) |
| if err != nil { |
| t.Fatalf("Failed reading request body: %v.", err) |
| } |
| data, err := url.ParseQuery(string(body)) |
| if err != nil { |
| t.Fatalf("Failed to parse request body: %v", err) |
| } |
| strOpts, ok := data["options"] |
| if !ok { |
| t.Errorf("Server didn't recieve an \"options\" field.") |
| } else if len(strOpts) < 1 { |
| t.Errorf("\"options\" field has length 0.") |
| } |
| var opts map[string]interface{} |
| err = json.Unmarshal([]byte(strOpts[0]), &opts) |
| if err != nil { |
| t.Fatalf("Couldn't parse received \"options\" field.") |
| } |
| if len(opts) < 2 { |
| t.Errorf("Too few options received.") |
| } |
| |
| val, ok := opts["one"] |
| if !ok { |
| t.Errorf("Couldn't find first option parameter.") |
| } else { |
| tOpts1, ok := val.(map[string]interface{}) |
| if !ok { |
| t.Errorf("Failed to assert the first option parameter as type testOpts.") |
| } else { |
| if got, want := tOpts1["first"].(string), optsValues[0][0]; got != want { |
| t.Errorf("First value in first options field is incorrect; got %v but want %v", got, want) |
| } |
| if got, want := tOpts1["second"].(string), optsValues[0][1]; got != want { |
| t.Errorf("Second value in first options field is incorrect; got %v but want %v", got, want) |
| } |
| } |
| } |
| |
| val2, ok := opts["two"] |
| if !ok { |
| t.Errorf("Couldn't find second option parameter.") |
| } else { |
| tOpts2, ok := val2.(map[string]interface{}) |
| if !ok { |
| t.Errorf("Failed to assert the second option parameter as type testOpts.") |
| } else { |
| if got, want := tOpts2["first"].(string), optsValues[1][0]; got != want { |
| t.Errorf("First value in second options field is incorrect; got %v but want %v", got, want) |
| } |
| if got, want := tOpts2["second"].(string), optsValues[1][1]; got != want { |
| t.Errorf("Second value in second options field is incorrect; got %v but want %v", got, want) |
| } |
| } |
| } |
| |
| // Send a proper reply so that no other errors crop up. |
| w.Header().Set("Content-Type", "application/json") |
| w.Write([]byte(responseBody)) |
| |
| })) |
| defer ts.Close() |
| headers := http.Header{} |
| headers.Add("Content-Type", "application/x-www-form-urlencoded") |
| |
| firstOption := testOpts{optsValues[0][0], optsValues[0][1]} |
| secondOption := testOpts{optsValues[1][0], optsValues[1][1]} |
| inputOpts := make(map[string]interface{}) |
| inputOpts["one"] = firstOption |
| inputOpts["two"] = secondOption |
| exchangeToken(context.Background(), ts.URL, &tokenRequest, auth, headers, inputOpts) |
| } |