1// Copyright 2020 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package externalaccount 6 7import ( 8 "context" 9 "encoding/json" 10 "golang.org/x/oauth2" 11 "io/ioutil" 12 "net/http" 13 "net/http/httptest" 14 "net/url" 15 "testing" 16) 17 18var auth = clientAuthentication{ 19 AuthStyle: oauth2.AuthStyleInHeader, 20 ClientID: clientID, 21 ClientSecret: clientSecret, 22} 23 24var tokenRequest = stsTokenExchangeRequest{ 25 ActingParty: struct { 26 ActorToken string 27 ActorTokenType string 28 }{}, 29 GrantType: "urn:ietf:params:oauth:grant-type:token-exchange", 30 Resource: "", 31 Audience: "32555940559.apps.googleusercontent.com", //TODO: Make sure audience is correct in this test (might be mismatched) 32 Scope: []string{"https://www.googleapis.com/auth/devstorage.full_control"}, 33 RequestedTokenType: "urn:ietf:params:oauth:token-type:access_token", 34 SubjectToken: "Sample.Subject.Token", 35 SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt", 36} 37 38var 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" 39var 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"}` 40var expectedToken = stsTokenExchangeResponse{ 41 AccessToken: "Sample.Access.Token", 42 IssuedTokenType: "urn:ietf:params:oauth:token-type:access_token", 43 TokenType: "Bearer", 44 ExpiresIn: 3600, 45 Scope: "https://www.googleapis.com/auth/cloud-platform", 46 RefreshToken: "", 47} 48 49func TestExchangeToken(t *testing.T) { 50 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 51 if r.Method != "POST" { 52 t.Errorf("Unexpected request method, %v is found", r.Method) 53 } 54 if r.URL.String() != "/" { 55 t.Errorf("Unexpected request URL, %v is found", r.URL) 56 } 57 if got, want := r.Header.Get("Authorization"), "Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ="; got != want { 58 t.Errorf("Unexpected authorization header, got %v, want %v", got, want) 59 } 60 if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want { 61 t.Errorf("Unexpected Content-Type header, got %v, want %v", got, want) 62 } 63 body, err := ioutil.ReadAll(r.Body) 64 if err != nil { 65 t.Errorf("Failed reading request body: %v.", err) 66 } 67 if got, want := string(body), requestbody; got != want { 68 t.Errorf("Unexpected exchange payload, got %v but want %v", got, want) 69 } 70 w.Header().Set("Content-Type", "application/json") 71 w.Write([]byte(responseBody)) 72 })) 73 defer ts.Close() 74 75 headers := http.Header{} 76 headers.Add("Content-Type", "application/x-www-form-urlencoded") 77 78 resp, err := exchangeToken(context.Background(), ts.URL, &tokenRequest, auth, headers, nil) 79 if err != nil { 80 t.Fatalf("exchangeToken failed with error: %v", err) 81 } 82 83 if expectedToken != *resp { 84 t.Errorf("mismatched messages received by mock server. \nWant: \n%v\n\nGot:\n%v", expectedToken, *resp) 85 } 86 87} 88 89func TestExchangeToken_Err(t *testing.T) { 90 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 91 w.Header().Set("Content-Type", "application/json") 92 w.Write([]byte("what's wrong with this response?")) 93 })) 94 defer ts.Close() 95 96 headers := http.Header{} 97 headers.Add("Content-Type", "application/x-www-form-urlencoded") 98 _, err := exchangeToken(context.Background(), ts.URL, &tokenRequest, auth, headers, nil) 99 if err == nil { 100 t.Errorf("Expected handled error; instead got nil.") 101 } 102} 103 104/* Lean test specifically for options, as the other features are tested earlier. */ 105type testOpts struct { 106 First string `json:"first"` 107 Second string `json:"second"` 108} 109 110var optsValues = [][]string{{"foo", "bar"}, {"cat", "pan"}} 111 112func TestExchangeToken_Opts(t *testing.T) { 113 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 114 body, err := ioutil.ReadAll(r.Body) 115 if err != nil { 116 t.Fatalf("Failed reading request body: %v.", err) 117 } 118 data, err := url.ParseQuery(string(body)) 119 if err != nil { 120 t.Fatalf("Failed to parse request body: %v", err) 121 } 122 strOpts, ok := data["options"] 123 if !ok { 124 t.Errorf("Server didn't recieve an \"options\" field.") 125 } else if len(strOpts) < 1 { 126 t.Errorf("\"options\" field has length 0.") 127 } 128 var opts map[string]interface{} 129 err = json.Unmarshal([]byte(strOpts[0]), &opts) 130 if len(opts) < 2 { 131 t.Errorf("Too few options received.") 132 } 133 134 val, ok := opts["one"] 135 if !ok { 136 t.Errorf("Couldn't find first option parameter.") 137 } else { 138 tOpts1, ok := val.(map[string]interface{}) 139 if !ok { 140 t.Errorf("Failed to assert the first option parameter as type testOpts.") 141 } else { 142 if got, want := tOpts1["first"].(string), optsValues[0][0]; got != want { 143 t.Errorf("First value in first options field is incorrect; got %v but want %v", got, want) 144 } 145 if got, want := tOpts1["second"].(string), optsValues[0][1]; got != want { 146 t.Errorf("Second value in first options field is incorrect; got %v but want %v", got, want) 147 } 148 } 149 } 150 151 val2, ok := opts["two"] 152 if !ok { 153 t.Errorf("Couldn't find second option parameter.") 154 } else { 155 tOpts2, ok := val2.(map[string]interface{}) 156 if !ok { 157 t.Errorf("Failed to assert the second option parameter as type testOpts.") 158 } else { 159 if got, want := tOpts2["first"].(string), optsValues[1][0]; got != want { 160 t.Errorf("First value in second options field is incorrect; got %v but want %v", got, want) 161 } 162 if got, want := tOpts2["second"].(string), optsValues[1][1]; got != want { 163 t.Errorf("Second value in second options field is incorrect; got %v but want %v", got, want) 164 } 165 } 166 } 167 168 // Send a proper reply so that no other errors crop up. 169 w.Header().Set("Content-Type", "application/json") 170 w.Write([]byte(responseBody)) 171 172 })) 173 defer ts.Close() 174 headers := http.Header{} 175 headers.Add("Content-Type", "application/x-www-form-urlencoded") 176 177 firstOption := testOpts{optsValues[0][0], optsValues[0][1]} 178 secondOption := testOpts{optsValues[1][0], optsValues[1][1]} 179 inputOpts := make(map[string]interface{}) 180 inputOpts["one"] = firstOption 181 inputOpts["two"] = secondOption 182 exchangeToken(context.Background(), ts.URL, &tokenRequest, auth, headers, inputOpts) 183} 184