1
2
3
4
5
6
7
8
9
10
11
12
13
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
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