Source file
src/github.com/ory/fosite/authorize_request_handler_test.go
1
21
22 package fosite_test
23
24 import (
25 "context"
26 "fmt"
27 "net/http"
28 "net/url"
29 "testing"
30
31 "github.com/golang/mock/gomock"
32 "github.com/pkg/errors"
33 "github.com/stretchr/testify/assert"
34 "github.com/stretchr/testify/require"
35
36 . "github.com/ory/fosite"
37 . "github.com/ory/fosite/internal"
38 )
39
40
41
42
43
44
45
46 func TestNewAuthorizeRequest(t *testing.T) {
47 var store *MockStorage
48
49 redir, _ := url.Parse("https://foo.bar/cb")
50 specialCharRedir, _ := url.Parse("web+application://callback")
51 for k, c := range []struct {
52 desc string
53 conf *Fosite
54 r *http.Request
55 query url.Values
56 expectedError error
57 mock func()
58 expect *AuthorizeRequest
59 }{
60
61 {
62 desc: "empty request fails",
63 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
64 r: &http.Request{},
65 expectedError: ErrInvalidClient,
66 mock: func() {
67 store.EXPECT().GetClient(gomock.Any(), gomock.Any()).Return(nil, errors.New("foo"))
68 },
69 },
70
71 {
72 desc: "invalid redirect uri fails",
73 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
74 query: url.Values{"redirect_uri": []string{"invalid"}},
75 expectedError: ErrInvalidClient,
76 mock: func() {
77 store.EXPECT().GetClient(gomock.Any(), gomock.Any()).Return(nil, errors.New("foo"))
78 },
79 },
80
81 {
82 desc: "invalid client fails",
83 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
84 query: url.Values{"redirect_uri": []string{"https://foo.bar/cb"}},
85 expectedError: ErrInvalidClient,
86 mock: func() {
87 store.EXPECT().GetClient(gomock.Any(), gomock.Any()).Return(nil, errors.New("foo"))
88 },
89 },
90
91 {
92 desc: "client and request redirects mismatch",
93 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
94 query: url.Values{
95 "client_id": []string{"1234"},
96 },
97 expectedError: ErrInvalidRequest,
98 mock: func() {
99 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"invalid"}, Scopes: []string{}}, nil)
100 },
101 },
102
103 {
104 desc: "client and request redirects mismatch",
105 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
106 query: url.Values{
107 "redirect_uri": []string{""},
108 "client_id": []string{"1234"},
109 },
110 expectedError: ErrInvalidRequest,
111 mock: func() {
112 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"invalid"}, Scopes: []string{}}, nil)
113 },
114 },
115
116 {
117 desc: "client and request redirects mismatch",
118 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
119 query: url.Values{
120 "redirect_uri": []string{"https://foo.bar/cb"},
121 "client_id": []string{"1234"},
122 },
123 expectedError: ErrInvalidRequest,
124 mock: func() {
125 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"invalid"}, Scopes: []string{}}, nil)
126 },
127 },
128
129 {
130 desc: "no state",
131 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
132 query: url.Values{
133 "redirect_uri": []string{"https://foo.bar/cb"},
134 "client_id": []string{"1234"},
135 "response_type": []string{"code"},
136 },
137 expectedError: ErrInvalidState,
138 mock: func() {
139 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}, Scopes: []string{}}, nil)
140 },
141 },
142
143 {
144 desc: "short state",
145 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
146 query: url.Values{
147 "redirect_uri": {"https://foo.bar/cb"},
148 "client_id": {"1234"},
149 "response_type": {"code"},
150 "state": {"short"},
151 },
152 expectedError: ErrInvalidState,
153 mock: func() {
154 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}, Scopes: []string{}}, nil)
155 },
156 },
157
158 {
159 desc: "should fail because client does not have scope baz",
160 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
161 query: url.Values{
162 "redirect_uri": {"https://foo.bar/cb"},
163 "client_id": {"1234"},
164 "response_type": {"code token"},
165 "state": {"strong-state"},
166 "scope": {"foo bar baz"},
167 },
168 mock: func() {
169 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}, Scopes: []string{"foo", "bar"}}, nil)
170 },
171 expectedError: ErrInvalidScope,
172 },
173
174 {
175 desc: "should fail because client does not have scope baz",
176 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
177 query: url.Values{
178 "redirect_uri": {"https://foo.bar/cb"},
179 "client_id": {"1234"},
180 "response_type": {"code token"},
181 "state": {"strong-state"},
182 "scope": {"foo bar"},
183 "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"},
184 },
185 mock: func() {
186 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{
187 RedirectURIs: []string{"https://foo.bar/cb"}, Scopes: []string{"foo", "bar"},
188 Audience: []string{"https://cloud.ory.sh/api"},
189 }, nil)
190 },
191 expectedError: ErrInvalidRequest,
192 },
193
194 {
195 desc: "should pass",
196 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
197 query: url.Values{
198 "redirect_uri": {"https://foo.bar/cb"},
199 "client_id": {"1234"},
200 "response_type": {"code token"},
201 "state": {"strong-state"},
202 "scope": {"foo bar"},
203 "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"},
204 },
205 mock: func() {
206 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{
207 ResponseTypes: []string{"code token"},
208 RedirectURIs: []string{"https://foo.bar/cb"},
209 Scopes: []string{"foo", "bar"},
210 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
211 }, nil)
212 },
213 expect: &AuthorizeRequest{
214 RedirectURI: redir,
215 ResponseTypes: []string{"code", "token"},
216 State: "strong-state",
217 Request: Request{
218 Client: &DefaultClient{
219 ResponseTypes: []string{"code token"}, RedirectURIs: []string{"https://foo.bar/cb"},
220 Scopes: []string{"foo", "bar"},
221 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
222 },
223 RequestedScope: []string{"foo", "bar"},
224 RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
225 },
226 },
227 },
228
229 {
230 desc: "repeated audience parameter",
231 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
232 query: url.Values{
233 "redirect_uri": {"https://foo.bar/cb"},
234 "client_id": {"1234"},
235 "response_type": {"code token"},
236 "state": {"strong-state"},
237 "scope": {"foo bar"},
238 "audience": {"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
239 },
240 mock: func() {
241 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{
242 ResponseTypes: []string{"code token"},
243 RedirectURIs: []string{"https://foo.bar/cb"},
244 Scopes: []string{"foo", "bar"},
245 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
246 }, nil)
247 },
248 expect: &AuthorizeRequest{
249 RedirectURI: redir,
250 ResponseTypes: []string{"code", "token"},
251 State: "strong-state",
252 Request: Request{
253 Client: &DefaultClient{
254 ResponseTypes: []string{"code token"}, RedirectURIs: []string{"https://foo.bar/cb"},
255 Scopes: []string{"foo", "bar"},
256 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
257 },
258 RequestedScope: []string{"foo", "bar"},
259 RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
260 },
261 },
262 },
263
264 {
265 desc: "repeated audience parameter with tricky values",
266 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: ExactAudienceMatchingStrategy},
267 query: url.Values{
268 "redirect_uri": {"https://foo.bar/cb"},
269 "client_id": {"1234"},
270 "response_type": {"code token"},
271 "state": {"strong-state"},
272 "scope": {"foo bar"},
273 "audience": {"test value", ""},
274 },
275 mock: func() {
276 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{
277 ResponseTypes: []string{"code token"},
278 RedirectURIs: []string{"https://foo.bar/cb"},
279 Scopes: []string{"foo", "bar"},
280 Audience: []string{"test value"},
281 }, nil)
282 },
283 expect: &AuthorizeRequest{
284 RedirectURI: redir,
285 ResponseTypes: []string{"code", "token"},
286 State: "strong-state",
287 Request: Request{
288 Client: &DefaultClient{
289 ResponseTypes: []string{"code token"}, RedirectURIs: []string{"https://foo.bar/cb"},
290 Scopes: []string{"foo", "bar"},
291 Audience: []string{"test value"},
292 },
293 RequestedScope: []string{"foo", "bar"},
294 RequestedAudience: []string{"test value"},
295 },
296 },
297 },
298
299 {
300 desc: "redirect_uri with special character",
301 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
302 query: url.Values{
303 "redirect_uri": {"web+application://callback"},
304 "client_id": {"1234"},
305 "response_type": {"code token"},
306 "state": {"strong-state"},
307 "scope": {"foo bar"},
308 "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"},
309 },
310 mock: func() {
311 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{
312 ResponseTypes: []string{"code token"},
313 RedirectURIs: []string{"web+application://callback"},
314 Scopes: []string{"foo", "bar"},
315 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
316 }, nil)
317 },
318 expect: &AuthorizeRequest{
319 RedirectURI: specialCharRedir,
320 ResponseTypes: []string{"code", "token"},
321 State: "strong-state",
322 Request: Request{
323 Client: &DefaultClient{
324 ResponseTypes: []string{"code token"}, RedirectURIs: []string{"web+application://callback"},
325 Scopes: []string{"foo", "bar"},
326 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
327 },
328 RequestedScope: []string{"foo", "bar"},
329 RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
330 },
331 },
332 },
333
334 {
335 desc: "audience with double spaces between values",
336 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
337 query: url.Values{
338 "redirect_uri": {"https://foo.bar/cb"},
339 "client_id": {"1234"},
340 "response_type": {"code token"},
341 "state": {"strong-state"},
342 "scope": {"foo bar"},
343 "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"},
344 },
345 mock: func() {
346 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{
347 ResponseTypes: []string{"code token"},
348 RedirectURIs: []string{"https://foo.bar/cb"},
349 Scopes: []string{"foo", "bar"},
350 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
351 }, nil)
352 },
353 expect: &AuthorizeRequest{
354 RedirectURI: redir,
355 ResponseTypes: []string{"code", "token"},
356 State: "strong-state",
357 Request: Request{
358 Client: &DefaultClient{
359 ResponseTypes: []string{"code token"}, RedirectURIs: []string{"https://foo.bar/cb"},
360 Scopes: []string{"foo", "bar"},
361 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
362 },
363 RequestedScope: []string{"foo", "bar"},
364 RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
365 },
366 },
367 },
368
369 {
370 desc: "should fail because unknown response_mode",
371 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
372 query: url.Values{
373 "redirect_uri": {"https://foo.bar/cb"},
374 "client_id": {"1234"},
375 "response_type": {"code token"},
376 "state": {"strong-state"},
377 "scope": {"foo bar"},
378 "response_mode": {"unknown"},
379 },
380 mock: func() {
381 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}, Scopes: []string{"foo", "bar"}, ResponseTypes: []string{"code token"}}, nil)
382 },
383 expectedError: ErrUnsupportedResponseMode,
384 },
385
386 {
387 desc: "should fail because response_mode is requested but the OAuth 2.0 client doesn't support response mode",
388 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
389 query: url.Values{
390 "redirect_uri": {"https://foo.bar/cb"},
391 "client_id": {"1234"},
392 "response_type": {"code token"},
393 "state": {"strong-state"},
394 "scope": {"foo bar"},
395 "response_mode": {"form_post"},
396 },
397 mock: func() {
398 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}, Scopes: []string{"foo", "bar"}, ResponseTypes: []string{"code token"}}, nil)
399 },
400 expectedError: ErrUnsupportedResponseMode,
401 },
402
403 {
404 desc: "should fail because requested response mode is not allowed",
405 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
406 query: url.Values{
407 "redirect_uri": {"https://foo.bar/cb"},
408 "client_id": {"1234"},
409 "response_type": {"code token"},
410 "state": {"strong-state"},
411 "scope": {"foo bar"},
412 "response_mode": {"form_post"},
413 },
414 mock: func() {
415 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultResponseModeClient{
416 DefaultClient: &DefaultClient{
417 RedirectURIs: []string{"https://foo.bar/cb"},
418 Scopes: []string{"foo", "bar"},
419 ResponseTypes: []string{"code token"},
420 },
421 ResponseModes: []ResponseModeType{ResponseModeQuery},
422 }, nil)
423 },
424 expectedError: ErrUnsupportedResponseMode,
425 },
426
427 {
428 desc: "success with response mode",
429 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
430 query: url.Values{
431 "redirect_uri": {"https://foo.bar/cb"},
432 "client_id": {"1234"},
433 "response_type": {"code token"},
434 "state": {"strong-state"},
435 "scope": {"foo bar"},
436 "response_mode": {"form_post"},
437 "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"},
438 },
439 mock: func() {
440 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultResponseModeClient{
441 DefaultClient: &DefaultClient{
442 RedirectURIs: []string{"https://foo.bar/cb"},
443 Scopes: []string{"foo", "bar"},
444 ResponseTypes: []string{"code token"},
445 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
446 },
447 ResponseModes: []ResponseModeType{ResponseModeFormPost},
448 }, nil)
449 },
450 expect: &AuthorizeRequest{
451 RedirectURI: redir,
452 ResponseTypes: []string{"code", "token"},
453 State: "strong-state",
454 Request: Request{
455 Client: &DefaultResponseModeClient{
456 DefaultClient: &DefaultClient{
457 RedirectURIs: []string{"https://foo.bar/cb"},
458 Scopes: []string{"foo", "bar"},
459 ResponseTypes: []string{"code token"},
460 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
461 },
462 ResponseModes: []ResponseModeType{ResponseModeFormPost},
463 },
464 RequestedScope: []string{"foo", "bar"},
465 RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
466 },
467 },
468 },
469
470 {
471 desc: "success with response mode",
472 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
473 query: url.Values{
474 "redirect_uri": {"https://foo.bar/cb"},
475 "client_id": {"1234"},
476 "response_type": {"code"},
477 "state": {"strong-state"},
478 "scope": {"foo bar"},
479 "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"},
480 },
481 mock: func() {
482 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultResponseModeClient{
483 DefaultClient: &DefaultClient{
484 RedirectURIs: []string{"https://foo.bar/cb"},
485 Scopes: []string{"foo", "bar"},
486 ResponseTypes: []string{"code"},
487 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
488 },
489 ResponseModes: []ResponseModeType{ResponseModeQuery},
490 }, nil)
491 },
492 expect: &AuthorizeRequest{
493 RedirectURI: redir,
494 ResponseTypes: []string{"code"},
495 State: "strong-state",
496 Request: Request{
497 Client: &DefaultResponseModeClient{
498 DefaultClient: &DefaultClient{
499 RedirectURIs: []string{"https://foo.bar/cb"},
500 Scopes: []string{"foo", "bar"},
501 ResponseTypes: []string{"code"},
502 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
503 },
504 ResponseModes: []ResponseModeType{ResponseModeQuery},
505 },
506 RequestedScope: []string{"foo", "bar"},
507 RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
508 },
509 },
510 },
511
512 {
513 desc: "success with response mode",
514 conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy},
515 query: url.Values{
516 "redirect_uri": {"https://foo.bar/cb"},
517 "client_id": {"1234"},
518 "response_type": {"code token"},
519 "state": {"strong-state"},
520 "scope": {"foo bar"},
521 "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"},
522 },
523 mock: func() {
524 store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultResponseModeClient{
525 DefaultClient: &DefaultClient{
526 RedirectURIs: []string{"https://foo.bar/cb"},
527 Scopes: []string{"foo", "bar"},
528 ResponseTypes: []string{"code token"},
529 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
530 },
531 ResponseModes: []ResponseModeType{ResponseModeFragment},
532 }, nil)
533 },
534 expect: &AuthorizeRequest{
535 RedirectURI: redir,
536 ResponseTypes: []string{"code", "token"},
537 State: "strong-state",
538 Request: Request{
539 Client: &DefaultResponseModeClient{
540 DefaultClient: &DefaultClient{
541 RedirectURIs: []string{"https://foo.bar/cb"},
542 Scopes: []string{"foo", "bar"},
543 ResponseTypes: []string{"code token"},
544 Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
545 },
546 ResponseModes: []ResponseModeType{ResponseModeFragment},
547 },
548 RequestedScope: []string{"foo", "bar"},
549 RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"},
550 },
551 },
552 },
553 } {
554 t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
555 ctrl := gomock.NewController(t)
556 store = NewMockStorage(ctrl)
557 defer ctrl.Finish()
558
559 c.mock()
560 if c.r == nil {
561 c.r = &http.Request{Header: http.Header{}}
562 if c.query != nil {
563 c.r.URL = &url.URL{RawQuery: c.query.Encode()}
564 }
565 }
566
567 c.conf.Store = store
568 ar, err := c.conf.NewAuthorizeRequest(context.Background(), c.r)
569 if c.expectedError != nil {
570 assert.EqualError(t, err, c.expectedError.Error())
571
572 AssertObjectKeysEqual(t, &AuthorizeRequest{State: c.query.Get("state")}, ar, "State")
573 } else {
574 require.NoError(t, err)
575 AssertObjectKeysEqual(t, c.expect, ar, "ResponseTypes", "RequestedAudience", "RequestedScope", "Client", "RedirectURI", "State")
576 assert.NotNil(t, ar.GetRequestedAt())
577 }
578 })
579 }
580 }
581
View as plain text