remove support for canvas app flow
Mark Dodwell authored 10 years ago
Josef Šimánek committed 9 years ago
67 | 67 | end |
68 | 68 | |
69 | 69 | def callback_phase |
70 | super | |
71 | rescue NoAuthorizationCodeError => e | |
72 | fail!(:no_authorization_code, e) | |
73 | rescue UnknownSignatureAlgorithmError => e | |
74 | fail!(:unknown_signature_algoruthm, e) | |
75 | end | |
76 | ||
77 | def request_phase | |
78 | if signed_request_contains_access_token? | |
79 | # If we already have an access token, we can just hit the callback URL directly and pass the signed request. | |
80 | params = { :signed_request => raw_signed_request } | |
81 | query = Rack::Utils.build_query(params) | |
82 | ||
83 | url = callback_url | |
84 | url << "?" unless url.match(/\?/) | |
85 | url << "&" unless url.match(/[\&\?]$/) | |
86 | url << query | |
87 | ||
88 | redirect url | |
89 | else | |
90 | super | |
70 | with_authorization_code! do | |
71 | begin | |
72 | super | |
73 | rescue NoAuthorizationCodeError => e | |
74 | fail!(:no_authorization_code, e) | |
75 | rescue UnknownSignatureAlgorithmError => e | |
76 | fail!(:unknown_signature_algoruthm, e) | |
77 | end | |
91 | 78 | end |
92 | 79 | end |
93 | 80 | |
94 | 81 | # NOTE If we're using code from the signed request then FB sets the redirect_uri to '' during the authorize |
95 | 82 | # phase and it must match during the access_token phase: |
96 | # https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php#L348 | |
83 | # https://github.com/facebook/facebook-php-sdk/blob/master/src/base_facebook.php#L477 | |
97 | 84 | def callback_url |
98 | if @authorization_code_from_signed_request | |
85 | if @authorization_code_from_signed_request_in_cookie | |
99 | 86 | '' |
100 | 87 | else |
101 | 88 | options[:callback_url] || super |
109 | 96 | # You can pass +display+, +scope+, or +auth_type+ params to the auth request, if you need to set them dynamically. |
110 | 97 | # You can also set these options in the OmniAuth config :authorize_params option. |
111 | 98 | # |
112 | # /auth/facebook?display=popup | |
99 | # For example: /auth/facebook?display=popup | |
113 | 100 | def authorize_params |
114 | 101 | super.tap do |params| |
115 | 102 | %w[display scope auth_type].each do |v| |
122 | 109 | end |
123 | 110 | end |
124 | 111 | |
125 | # Parse signed request in order, from: | |
126 | # | |
127 | # 1. The request 'signed_request' param (server-side flow from canvas pages) or | |
128 | # 2. A cookie (client-side flow via JS SDK) | |
129 | def signed_request | |
130 | @signed_request ||= raw_signed_request && parse_signed_request(raw_signed_request) | |
131 | end | |
132 | ||
133 | 112 | protected |
134 | 113 | |
135 | 114 | def build_access_token |
136 | if signed_request_contains_access_token? | |
137 | hash = signed_request.clone | |
138 | ::OAuth2::AccessToken.new( | |
139 | client, | |
140 | hash.delete('oauth_token'), | |
141 | hash.merge!(access_token_options.merge(:expires_at => hash.delete('expires'))) | |
142 | ) | |
143 | else | |
144 | with_authorization_code! { super }.tap do |token| | |
145 | token.options.merge!(access_token_options) | |
146 | end | |
115 | super.tap do |token| | |
116 | token.options.merge!(access_token_options) | |
147 | 117 | end |
148 | 118 | end |
149 | 119 | |
150 | 120 | private |
151 | 121 | |
152 | def raw_signed_request | |
153 | request.params['signed_request'] || request.cookies["fbsr_#{client.id}"] | |
154 | end | |
155 | ||
156 | # If the signed_request comes from a FB canvas page and the user has already authorized your application, the JSON | |
157 | # object will be contain the access token. | |
158 | # | |
159 | # https://developers.facebook.com/docs/authentication/canvas/ | |
160 | def signed_request_contains_access_token? | |
161 | signed_request && signed_request['oauth_token'] | |
122 | def signed_request_from_cookie | |
123 | @signed_request_from_cookie ||= raw_signed_request_from_cookie && parse_signed_request(raw_signed_request_from_cookie) | |
124 | end | |
125 | ||
126 | def raw_signed_request_from_cookie | |
127 | request.cookies["fbsr_#{client.id}"] | |
162 | 128 | end |
163 | 129 | |
164 | 130 | # Picks the authorization code in order, from: |
165 | 131 | # |
166 | 132 | # 1. The request 'code' param (manual callback from standard server-side flow) |
167 | # 2. A signed request (see #signed_request for more) | |
133 | # 2. A signed request from cookie (passed from the client during the client-side flow) | |
168 | 134 | def with_authorization_code! |
169 | 135 | if request.params.key?('code') |
170 | 136 | yield |
171 | elsif code_from_signed_request = signed_request && signed_request['code'] | |
137 | elsif code_from_signed_request = signed_request_from_cookie && signed_request_from_cookie['code'] | |
172 | 138 | request.params['code'] = code_from_signed_request |
173 | @authorization_code_from_signed_request = true | |
139 | @authorization_code_from_signed_request_in_cookie = true | |
140 | original_provider_ignores_state = options.provider_ignores_state | |
141 | options.provider_ignores_state = true | |
174 | 142 | begin |
175 | 143 | yield |
176 | 144 | ensure |
177 | 145 | request.params.delete('code') |
178 | @authorization_code_from_signed_request = false | |
146 | @authorization_code_from_signed_request_in_cookie = false | |
147 | options.provider_ignores_state = original_provider_ignores_state | |
179 | 148 | end |
180 | 149 | else |
181 | raise NoAuthorizationCodeError, 'must pass either a `code` parameter or a signed request (via `signed_request` parameter or a `fbsr_XXX` cookie)' | |
150 | raise NoAuthorizationCodeError, 'must pass either a `code` (via URL or by an `fbsr_XXX` signed request cookie)' | |
182 | 151 | end |
183 | 152 | end |
184 | 153 |
393 | 393 | |
394 | 394 | class CookieAndParamNotPresentTest < TestCase |
395 | 395 | test 'is nil' do |
396 | assert_nil strategy.send(:signed_request) | |
396 | assert_nil strategy.send(:signed_request_from_cookie) | |
397 | 397 | end |
398 | 398 | |
399 | 399 | test 'throws an error on calling build_access_token' do |
400 | assert_equal 'must pass either a `code` parameter or a signed request (via `signed_request` parameter or a `fbsr_XXX` cookie)', | |
401 | assert_raises(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError) { strategy.send(:build_access_token) }.message | |
400 | assert_raises(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError) { strategy.send(:with_authorization_code!) {} } | |
402 | 401 | end |
403 | 402 | end |
404 | 403 | |
416 | 415 | end |
417 | 416 | |
418 | 417 | test 'parses the access code out from the cookie' do |
419 | assert_equal @payload, strategy.send(:signed_request) | |
418 | assert_equal @payload, strategy.send(:signed_request_from_cookie) | |
420 | 419 | end |
421 | 420 | |
422 | 421 | test 'throws an error if the algorithm is unknown' do |
423 | 422 | setup('UNKNOWN-ALGO') |
424 | assert_equal "unknown algorithm: UNKNOWN-ALGO", assert_raises(OmniAuth::Strategies::Facebook::UnknownSignatureAlgorithmError) { strategy.send(:signed_request) }.message | |
425 | end | |
426 | end | |
427 | ||
428 | class ParamPresentTest < TestCase | |
429 | def setup(algo = nil) | |
430 | super() | |
431 | @payload = { | |
432 | 'algorithm' => algo || 'HMAC-SHA256', | |
433 | 'oauth_token' => 'XXX', | |
434 | 'issued_at' => Time.now.to_i, | |
435 | 'user_id' => '123456' | |
436 | } | |
437 | ||
438 | @request.stubs(:params).returns({'signed_request' => signed_request(@payload, @client_secret)}) | |
439 | end | |
440 | ||
441 | test 'parses the access code out from the param' do | |
442 | assert_equal @payload, strategy.send(:signed_request) | |
443 | end | |
444 | ||
445 | test 'throws an error if the algorithm is unknown' do | |
446 | setup('UNKNOWN-ALGO') | |
447 | assert_equal "unknown algorithm: UNKNOWN-ALGO", assert_raises(OmniAuth::Strategies::Facebook::UnknownSignatureAlgorithmError) { strategy.send(:signed_request) }.message | |
448 | end | |
449 | end | |
450 | ||
451 | class CookieAndParamPresentTest < TestCase | |
452 | def setup | |
453 | super | |
454 | @payload_from_cookie = { | |
455 | 'algorithm' => 'HMAC-SHA256', | |
456 | 'from' => 'cookie' | |
457 | } | |
458 | ||
459 | @request.stubs(:cookies).returns({"fbsr_#{@client_id}" => signed_request(@payload_from_cookie, @client_secret)}) | |
460 | ||
461 | @payload_from_param = { | |
462 | 'algorithm' => 'HMAC-SHA256', | |
463 | 'from' => 'param' | |
464 | } | |
465 | ||
466 | @request.stubs(:params).returns({'signed_request' => signed_request(@payload_from_param, @client_secret)}) | |
467 | end | |
468 | ||
469 | test 'picks param over cookie' do | |
470 | assert_equal @payload_from_param, strategy.send(:signed_request) | |
423 | assert_equal "unknown algorithm: UNKNOWN-ALGO", assert_raises(OmniAuth::Strategies::Facebook::UnknownSignatureAlgorithmError) { strategy.send(:signed_request_from_cookie) }.message | |
471 | 424 | end |
472 | 425 | end |
473 | 426 | |
478 | 431 | end |
479 | 432 | |
480 | 433 | test 'empty param' do |
481 | assert_equal nil, strategy.send(:signed_request) | |
482 | end | |
483 | end | |
484 | ||
485 | end | |
486 | ||
487 | class RequestPhaseWithSignedRequestTest < StrategyTestCase | |
488 | include SignedRequestHelpers | |
489 | ||
490 | def setup | |
491 | super | |
492 | ||
493 | payload = { | |
494 | 'algorithm' => 'HMAC-SHA256', | |
495 | 'oauth_token' => 'm4c0d3z' | |
496 | } | |
497 | @raw_signed_request = signed_request(payload, @client_secret) | |
498 | @request.stubs(:params).returns("signed_request" => @raw_signed_request) | |
499 | ||
500 | strategy.stubs(:callback_url).returns('/') | |
501 | end | |
502 | ||
503 | test 'redirects to callback passing along signed request' do | |
504 | strategy.expects(:redirect).with("/?signed_request=#{Rack::Utils.escape(@raw_signed_request)}").once | |
505 | strategy.request_phase | |
506 | end | |
507 | end | |
508 | ||
509 | module BuildAccessTokenTests | |
510 | class TestCase < StrategyTestCase | |
511 | include SignedRequestHelpers | |
512 | end | |
513 | ||
514 | class ParamsContainSignedRequestWithAccessTokenTest < TestCase | |
515 | def setup | |
516 | super | |
517 | ||
518 | @payload = { | |
519 | 'algorithm' => 'HMAC-SHA256', | |
520 | 'oauth_token' => 'm4c0d3z', | |
521 | 'expires' => Time.now.to_i | |
522 | } | |
523 | @raw_signed_request = signed_request(@payload, @client_secret) | |
524 | @request.stubs(:params).returns({"signed_request" => @raw_signed_request}) | |
525 | ||
526 | strategy.stubs(:callback_url).returns('/') | |
527 | end | |
528 | ||
529 | test 'returns a new access token from the signed request' do | |
530 | result = strategy.send(:build_access_token) | |
531 | assert_kind_of ::OAuth2::AccessToken, result | |
532 | assert_equal @payload['oauth_token'], result.token | |
533 | end | |
534 | ||
535 | test 'returns an access token with the correct expiry time' do | |
536 | result = strategy.send(:build_access_token) | |
537 | assert_equal @payload['expires'], result.expires_at | |
538 | end | |
539 | end | |
540 | end | |
434 | assert_equal nil, strategy.send(:signed_request_from_cookie) | |
435 | end | |
436 | end | |
437 | end |