Codebase list ruby-omniauth-facebook / db0d393
Refactor SignedRequest class. Josef Šimánek 9 years ago
8 changed file(s) with 570 addition(s) and 511 deletion(s). Raw diff Collapse all Expand all
22
33 Rake::TestTask.new do |task|
44 task.libs << 'test'
5 task.test_files = FileList['test/*_test.rb']
56 end
67
78 task :default => :test
0 require 'base64'
10 require 'openssl'
21
32 module OmniAuth
43 module Facebook
54 class SignedRequest
65 class UnknownSignatureAlgorithmError < NotImplementedError; end
7
86 SUPPORTED_ALGORITHM = 'HMAC-SHA256'
97
10 def self.parse_signed_request(value, secret)
8 attr_reader :value, :secret
9
10 def self.parse(value, secret)
11 new(value, secret).payload
12 end
13
14 def initialize(value, secret)
15 @value = value
16 @secret = secret
17 end
18
19 def payload
20 @payload ||= parse_signed_request
21 end
22
23 private
24
25 def parse_signed_request
1126 signature, encoded_payload = value.split('.')
1227 return if signature.nil?
1328
1833 raise UnknownSignatureAlgorithmError, "unknown algorithm: #{decoded_payload['algorithm']}"
1934 end
2035
21 if valid_signature?(secret, decoded_hex_signature, encoded_payload)
36 if valid_signature?(decoded_hex_signature, encoded_payload)
2237 decoded_payload
2338 end
2439 end
2540
26 def self.valid_signature?(secret, signature, payload, algorithm = OpenSSL::Digest::SHA256.new)
41 def valid_signature?(signature, payload, algorithm = OpenSSL::Digest::SHA256.new)
2742 OpenSSL::HMAC.digest(algorithm, secret, payload) == signature
2843 end
2944
30 def self.base64_decode_url(value)
45 def base64_decode_url(value)
3146 value += '=' * (4 - value.size.modulo(4))
3247 Base64.decode64(value.tr('-_', '+/'))
3348 end
117117 private
118118
119119 def signed_request_from_cookie
120 @signed_request_from_cookie ||= raw_signed_request_from_cookie && OmniAuth::Facebook::SignedRequest.parse_signed_request(raw_signed_request_from_cookie, client.secret)
120 @signed_request_from_cookie ||= raw_signed_request_from_cookie && OmniAuth::Facebook::SignedRequest.parse(raw_signed_request_from_cookie, client.secret)
121121 end
122122
123123 def raw_signed_request_from_cookie
0 {
1 "algorithm": "HMAC-SHA256",
2 "expires": 1308988800,
3 "issued_at": 1308985018,
4 "oauth_token": "111111111111111|2.AQBAttRlLVnwqNPZ.3600.1111111111.1-111111111111111|T49w3BqoZUegypru51Gra70hED8",
5 "user":
6 {
7 "country": "de",
8 "locale": "en_US",
9 "age":
10 {
11 "min": 21
12 }
13 },
14 "user_id": "111111111111111"
15 }
0 53umfudisP7mKhsi9nZboBg15yMZKhfQAARL9UoZtSE.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOjEzMDg5ODg4MDAsImlzc3VlZF9hdCI6MTMwODk4NTAxOCwib2F1dGhfdG9rZW4iOiIxMTExMTExMTExMTExMTF8Mi5BUUJBdHRSbExWbndxTlBaLjM2MDAuMTExMTExMTExMS4xLTExMTExMTExMTExMTExMXxUNDl3M0Jxb1pVZWd5cHJ1NTFHcmE3MGhFRDgiLCJ1c2VyIjp7ImNvdW50cnkiOiJkZSIsImxvY2FsZSI6ImVuX1VTIiwiYWdlIjp7Im1pbiI6MjF9fSwidXNlcl9pZCI6IjExMTExMTExMTExMTExMSJ9
0 require 'helper'
1 require 'omniauth/facebook/signed_request'
2
3 class SignedRequestTest < Minitest::Test
4 def setup
5 @value = fixture('signed_request.txt').strip
6 @secret = "897z956a2z7zzzzz5783z458zz3z7556"
7 @expected_payload = MultiJson.decode(fixture('payload.json'))
8 end
9
10 def test_signed_request_payload
11 signed_request = OmniAuth::Facebook::SignedRequest.new(@value, @secret)
12 assert_equal @expected_payload, signed_request.payload
13 end
14
15 def test_signed_request_parse
16 payload = OmniAuth::Facebook::SignedRequest.parse(@value, @secret)
17 assert_equal @expected_payload, payload
18 end
19
20 private
21
22 def fixture(name)
23 File.read(File.expand_path("fixtures/#{name}", File.dirname(__FILE__)))
24 end
25 end
0 require 'helper'
1 require 'omniauth-facebook'
2 require 'openssl'
3 require 'base64'
4
5 class StrategyTest < StrategyTestCase
6 include OAuth2StrategyTests
7 end
8
9 class ClientTest < StrategyTestCase
10 test 'has correct Facebook site' do
11 assert_equal 'https://graph.facebook.com', strategy.client.site
12 end
13
14 test 'has correct authorize url' do
15 assert_equal 'https://www.facebook.com/dialog/oauth', strategy.client.options[:authorize_url]
16 end
17
18 test 'has correct token url with versioning' do
19 @options = {:client_options => {:site => 'https://graph.facebook.net/v2.2'}}
20 assert_equal 'oauth/access_token', strategy.client.options[:token_url]
21 assert_equal 'https://graph.facebook.net/v2.2/oauth/access_token', strategy.client.token_url
22 end
23 end
24
25 class CallbackUrlTest < StrategyTestCase
26 test "returns the default callback url" do
27 url_base = 'http://auth.request.com'
28 @request.stubs(:url).returns("#{url_base}/some/page")
29 strategy.stubs(:script_name).returns('') # as not to depend on Rack env
30 assert_equal "#{url_base}/auth/facebook/callback", strategy.callback_url
31 end
32
33 test "returns path from callback_path option" do
34 @options = { :callback_path => "/auth/FB/done"}
35 url_base = 'http://auth.request.com'
36 @request.stubs(:url).returns("#{url_base}/page/path")
37 strategy.stubs(:script_name).returns('') # as not to depend on Rack env
38 assert_equal "#{url_base}/auth/FB/done", strategy.callback_url
39 end
40
41 test "returns url from callback_url option" do
42 url = 'https://auth.myapp.com/auth/fb/callback'
43 @options = { :callback_url => url }
44 assert_equal url, strategy.callback_url
45 end
46 end
47
48 class AuthorizeParamsTest < StrategyTestCase
49 test 'includes default scope for email' do
50 assert strategy.authorize_params.is_a?(Hash)
51 assert_equal 'email', strategy.authorize_params[:scope]
52 end
53
54 test 'includes display parameter from request when present' do
55 @request.stubs(:params).returns({ 'display' => 'touch' })
56 assert strategy.authorize_params.is_a?(Hash)
57 assert_equal 'touch', strategy.authorize_params[:display]
58 end
59
60 test 'includes auth_type parameter from request when present' do
61 @request.stubs(:params).returns({ 'auth_type' => 'reauthenticate' })
62 assert strategy.authorize_params.is_a?(Hash)
63 assert_equal 'reauthenticate', strategy.authorize_params[:auth_type]
64 end
65
66 test 'overrides default scope with parameter passed from request' do
67 @request.stubs(:params).returns({ 'scope' => 'email' })
68 assert strategy.authorize_params.is_a?(Hash)
69 assert_equal 'email', strategy.authorize_params[:scope]
70 end
71 end
72
73 class TokeParamsTest < StrategyTestCase
74 test 'has correct parse strategy' do
75 assert_equal :query, strategy.token_params[:parse]
76 end
77 end
78
79 class AccessTokenOptionsTest < StrategyTestCase
80 test 'has correct param name by default' do
81 assert_equal 'access_token', strategy.access_token_options[:param_name]
82 end
83
84 test 'has correct header format by default' do
85 assert_equal 'OAuth %s', strategy.access_token_options[:header_format]
86 end
87 end
88
89 class UidTest < StrategyTestCase
90 def setup
91 super
92 strategy.stubs(:raw_info).returns({ 'id' => '123' })
93 end
94
95 test 'returns the id from raw_info' do
96 assert_equal '123', strategy.uid
97 end
98 end
99
100 class InfoTest < StrategyTestCase
101 test 'returns the secure facebook avatar url when `secure_image_url` option is specified' do
102 @options = { :secure_image_url => true }
103 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
104 strategy.stubs(:raw_info).returns(raw_info)
105 assert_equal 'https://graph.facebook.com/321/picture', strategy.info['image']
106 end
107
108 test 'returns the image_url based of the client site' do
109 @options = { :secure_image_url => true, :client_options => {:site => "https://blah.facebook.com/v2.2"}}
110 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
111 strategy.stubs(:raw_info).returns(raw_info)
112 assert_equal 'https://blah.facebook.com/v2.2/321/picture', strategy.info['image']
113 end
114
115 test 'returns the image with size specified in the `image_size` option' do
116 @options = { :image_size => 'normal' }
117 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
118 strategy.stubs(:raw_info).returns(raw_info)
119 assert_equal 'http://graph.facebook.com/321/picture?type=normal', strategy.info['image']
120 end
121
122 test 'returns the image with size specified as a symbol in the `image_size` option' do
123 @options = { :image_size => :normal }
124 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
125 strategy.stubs(:raw_info).returns(raw_info)
126 assert_equal 'http://graph.facebook.com/321/picture?type=normal', strategy.info['image']
127 end
128
129 test 'returns the image with width and height specified in the `image_size` option' do
130 @options = { :image_size => { :width => 123, :height => 987 } }
131 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
132 strategy.stubs(:raw_info).returns(raw_info)
133 assert_match 'width=123', strategy.info['image']
134 assert_match 'height=987', strategy.info['image']
135 assert_match 'http://graph.facebook.com/321/picture?', strategy.info['image']
136 end
137 end
138
139 class InfoTestOptionalDataPresent < StrategyTestCase
140 def setup
141 super
142 @raw_info ||= { 'name' => 'Fred Smith' }
143 strategy.stubs(:raw_info).returns(@raw_info)
144 end
145
146 test 'returns the name' do
147 assert_equal 'Fred Smith', strategy.info['name']
148 end
149
150 test 'returns the email' do
151 @raw_info['email'] = 'fred@smith.com'
152 assert_equal 'fred@smith.com', strategy.info['email']
153 end
154
155 test 'returns the username as nickname' do
156 @raw_info['username'] = 'fredsmith'
157 assert_equal 'fredsmith', strategy.info['nickname']
158 end
159
160 test 'returns the first name' do
161 @raw_info['first_name'] = 'Fred'
162 assert_equal 'Fred', strategy.info['first_name']
163 end
164
165 test 'returns the last name' do
166 @raw_info['last_name'] = 'Smith'
167 assert_equal 'Smith', strategy.info['last_name']
168 end
169
170 test 'returns the location name as location' do
171 @raw_info['location'] = { 'id' => '104022926303756', 'name' => 'Palo Alto, California' }
172 assert_equal 'Palo Alto, California', strategy.info['location']
173 end
174
175 test 'returns bio as description' do
176 @raw_info['bio'] = 'I am great'
177 assert_equal 'I am great', strategy.info['description']
178 end
179
180 test 'returns the facebook avatar url' do
181 @raw_info['id'] = '321'
182 assert_equal 'http://graph.facebook.com/321/picture', strategy.info['image']
183 end
184
185 test 'returns the Facebook link as the Facebook url' do
186 @raw_info['link'] = 'http://www.facebook.com/fredsmith'
187 assert_kind_of Hash, strategy.info['urls']
188 assert_equal 'http://www.facebook.com/fredsmith', strategy.info['urls']['Facebook']
189 end
190
191 test 'returns website url' do
192 @raw_info['website'] = 'https://my-wonderful-site.com'
193 assert_kind_of Hash, strategy.info['urls']
194 assert_equal 'https://my-wonderful-site.com', strategy.info['urls']['Website']
195 end
196
197 test 'return both Facebook link and website urls' do
198 @raw_info['link'] = 'http://www.facebook.com/fredsmith'
199 @raw_info['website'] = 'https://my-wonderful-site.com'
200 assert_kind_of Hash, strategy.info['urls']
201 assert_equal 'http://www.facebook.com/fredsmith', strategy.info['urls']['Facebook']
202 assert_equal 'https://my-wonderful-site.com', strategy.info['urls']['Website']
203 end
204
205 test 'returns the positive verified status' do
206 @raw_info['verified'] = true
207 assert strategy.info['verified']
208 end
209
210 test 'returns the negative verified status' do
211 @raw_info['verified'] = false
212 refute strategy.info['verified']
213 end
214 end
215
216 class InfoTestOptionalDataNotPresent < StrategyTestCase
217 def setup
218 super
219 @raw_info ||= { 'name' => 'Fred Smith' }
220 strategy.stubs(:raw_info).returns(@raw_info)
221 end
222
223 test 'has no email key' do
224 refute_has_key 'email', strategy.info
225 end
226
227 test 'has no nickname key' do
228 refute_has_key 'nickname', strategy.info
229 end
230
231 test 'has no first name key' do
232 refute_has_key 'first_name', strategy.info
233 end
234
235 test 'has no last name key' do
236 refute_has_key 'last_name', strategy.info
237 end
238
239 test 'has no location key' do
240 refute_has_key 'location', strategy.info
241 end
242
243 test 'has no description key' do
244 refute_has_key 'description', strategy.info
245 end
246
247 test 'has no urls' do
248 refute_has_key 'urls', strategy.info
249 end
250
251 test 'has no verified key' do
252 refute_has_key 'verified', strategy.info
253 end
254 end
255
256 class RawInfoTest < StrategyTestCase
257 def setup
258 super
259 @access_token = stub('OAuth2::AccessToken')
260 @appsecret_proof = 'appsecret_proof'
261 @options = {:appsecret_proof => @appsecret_proof}
262 end
263
264 test 'performs a GET to https://graph.facebook.com/me' do
265 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
266 strategy.stubs(:access_token).returns(@access_token)
267 params = {:params => @options}
268 @access_token.expects(:get).with('me', params).returns(stub_everything('OAuth2::Response'))
269 strategy.raw_info
270 end
271
272 test 'performs a GET to https://graph.facebook.com/me with locale' do
273 @options.merge!({ :locale => 'cs_CZ' })
274 strategy.stubs(:access_token).returns(@access_token)
275 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
276 params = {:params => @options}
277 @access_token.expects(:get).with('me', params).returns(stub_everything('OAuth2::Response'))
278 strategy.raw_info
279 end
280
281 test 'performs a GET to https://graph.facebook.com/me with info_fields' do
282 @options.merge!({:info_fields => 'about'})
283 strategy.stubs(:access_token).returns(@access_token)
284 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
285 params = {:params => {:appsecret_proof => @appsecret_proof, :fields => 'about'}}
286 @access_token.expects(:get).with('me', params).returns(stub_everything('OAuth2::Response'))
287 strategy.raw_info
288 end
289
290 test 'returns a Hash' do
291 strategy.stubs(:access_token).returns(@access_token)
292 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
293 raw_response = stub('Faraday::Response')
294 raw_response.stubs(:body).returns('{ "ohai": "thar" }')
295 raw_response.stubs(:status).returns(200)
296 raw_response.stubs(:headers).returns({'Content-Type' => 'application/json' })
297 oauth2_response = OAuth2::Response.new(raw_response)
298 params = {:params => @options}
299 @access_token.stubs(:get).with('me', params).returns(oauth2_response)
300 assert_kind_of Hash, strategy.raw_info
301 assert_equal 'thar', strategy.raw_info['ohai']
302 end
303
304 test 'returns an empty hash when the response is false' do
305 strategy.stubs(:access_token).returns(@access_token)
306 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
307 oauth2_response = stub('OAuth2::Response', :parsed => false)
308 params = {:params => @options}
309 @access_token.stubs(:get).with('me', params).returns(oauth2_response)
310 assert_kind_of Hash, strategy.raw_info
311 assert_equal({}, strategy.raw_info)
312 end
313
314 test 'should not include raw_info in extras hash when skip_info is specified' do
315 @options = { :skip_info => true }
316 strategy.stubs(:raw_info).returns({:foo => 'bar' })
317 refute_has_key 'raw_info', strategy.extra
318 end
319 end
320
321 class CredentialsTest < StrategyTestCase
322 def setup
323 super
324 @access_token = stub('OAuth2::AccessToken')
325 @access_token.stubs(:token)
326 @access_token.stubs(:expires?)
327 @access_token.stubs(:expires_at)
328 @access_token.stubs(:refresh_token)
329 strategy.stubs(:access_token).returns(@access_token)
330 end
331
332 test 'returns a Hash' do
333 assert_kind_of Hash, strategy.credentials
334 end
335
336 test 'returns the token' do
337 @access_token.stubs(:token).returns('123')
338 assert_equal '123', strategy.credentials['token']
339 end
340
341 test 'returns the expiry status' do
342 @access_token.stubs(:expires?).returns(true)
343 assert strategy.credentials['expires']
344
345 @access_token.stubs(:expires?).returns(false)
346 refute strategy.credentials['expires']
347 end
348
349 test 'returns the refresh token and expiry time when expiring' do
350 ten_mins_from_now = (Time.now + 600).to_i
351 @access_token.stubs(:expires?).returns(true)
352 @access_token.stubs(:refresh_token).returns('321')
353 @access_token.stubs(:expires_at).returns(ten_mins_from_now)
354 assert_equal '321', strategy.credentials['refresh_token']
355 assert_equal ten_mins_from_now, strategy.credentials['expires_at']
356 end
357
358 test 'does not return the refresh token when test is nil and expiring' do
359 @access_token.stubs(:expires?).returns(true)
360 @access_token.stubs(:refresh_token).returns(nil)
361 assert_nil strategy.credentials['refresh_token']
362 refute_has_key 'refresh_token', strategy.credentials
363 end
364
365 test 'does not return the refresh token when not expiring' do
366 @access_token.stubs(:expires?).returns(false)
367 @access_token.stubs(:refresh_token).returns('XXX')
368 assert_nil strategy.credentials['refresh_token']
369 refute_has_key 'refresh_token', strategy.credentials
370 end
371 end
372
373 class ExtraTest < StrategyTestCase
374 def setup
375 super
376 @raw_info = { 'name' => 'Fred Smith' }
377 strategy.stubs(:raw_info).returns(@raw_info)
378 end
379
380 test 'returns a Hash' do
381 assert_kind_of Hash, strategy.extra
382 end
383
384 test 'contains raw info' do
385 assert_equal({ 'raw_info' => @raw_info }, strategy.extra)
386 end
387 end
388
389 module SignedRequestHelpers
390 def signed_request(payload, secret)
391 encoded_payload = base64_encode_url(MultiJson.encode(payload))
392 encoded_signature = base64_encode_url(signature(encoded_payload, secret))
393 [encoded_signature, encoded_payload].join('.')
394 end
395
396 def base64_encode_url(value)
397 Base64.encode64(value).tr('+/', '-_').gsub(/\n/, '')
398 end
399
400 def signature(payload, secret, algorithm = OpenSSL::Digest::SHA256.new)
401 OpenSSL::HMAC.digest(algorithm, secret, payload)
402 end
403 end
404
405 module SignedRequestTests
406 class TestCase < StrategyTestCase
407 include SignedRequestHelpers
408 end
409
410 class CookieAndParamNotPresentTest < TestCase
411 test 'is nil' do
412 assert_nil strategy.send(:signed_request_from_cookie)
413 end
414
415 test 'throws an error on calling build_access_token' do
416 assert_raises(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError) { strategy.send(:with_authorization_code!) {} }
417 end
418 end
419
420 class CookiePresentTest < TestCase
421 def setup(algo = nil)
422 super()
423 @payload = {
424 'algorithm' => algo || 'HMAC-SHA256',
425 'code' => 'm4c0d3z',
426 'issued_at' => Time.now.to_i,
427 'user_id' => '123456'
428 }
429
430 @request.stubs(:cookies).returns({"fbsr_#{@client_id}" => signed_request(@payload, @client_secret)})
431 end
432
433 test 'parses the access code out from the cookie' do
434 assert_equal @payload, strategy.send(:signed_request_from_cookie)
435 end
436
437 test 'throws an error if the algorithm is unknown' do
438 setup('UNKNOWN-ALGO')
439 assert_equal "unknown algorithm: UNKNOWN-ALGO", assert_raises(OmniAuth::Facebook::SignedRequest::UnknownSignatureAlgorithmError) { strategy.send(:signed_request_from_cookie) }.message
440 end
441 end
442
443 class EmptySignedRequestTest < TestCase
444 def setup
445 super
446 @request.stubs(:params).returns({'signed_request' => ''})
447 end
448
449 test 'empty param' do
450 assert_equal nil, strategy.send(:signed_request_from_cookie)
451 end
452 end
453
454 class MissingCodeInParamsRequestTest < TestCase
455 def setup
456 super
457 @request.stubs(:params).returns({})
458 end
459
460 test 'calls fail! when a code is not included in the params' do
461 strategy.expects(:fail!).times(1).with(:no_authorization_code, kind_of(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError))
462 strategy.callback_phase
463 end
464 end
465
466 class MissingCodeInCookieRequestTest < TestCase
467 def setup(algo = nil)
468 super()
469 @payload = {
470 'algorithm' => algo || 'HMAC-SHA256',
471 'code' => nil,
472 'issued_at' => Time.now.to_i,
473 'user_id' => '123456'
474 }
475
476 @request.stubs(:cookies).returns({"fbsr_#{@client_id}" => signed_request(@payload, @client_secret)})
477 end
478
479 test 'calls fail! when a code is not included in the cookie' do
480 strategy.expects(:fail!).times(1).with(:no_authorization_code, kind_of(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError))
481 strategy.callback_phase
482 end
483 end
484
485 class UnknownAlgorithmInCookieRequestTest < TestCase
486 def setup
487 super()
488 @payload = {
489 'algorithm' => 'UNKNOWN-ALGO',
490 'code' => nil,
491 'issued_at' => Time.now.to_i,
492 'user_id' => '123456'
493 }
494
495 @request.stubs(:cookies).returns({"fbsr_#{@client_id}" => signed_request(@payload, @client_secret)})
496 end
497
498 test 'calls fail! when an algorithm is unknown' do
499 strategy.expects(:fail!).times(1).with(:unknown_signature_algorithm, kind_of(OmniAuth::Facebook::SignedRequest::UnknownSignatureAlgorithmError))
500 strategy.callback_phase
501 end
502 end
503 end
+0
-504
test/test.rb less more
0 require 'helper'
1 require 'omniauth-facebook'
2 require 'openssl'
3 require 'base64'
4
5 class StrategyTest < StrategyTestCase
6 include OAuth2StrategyTests
7 end
8
9 class ClientTest < StrategyTestCase
10 test 'has correct Facebook site' do
11 assert_equal 'https://graph.facebook.com', strategy.client.site
12 end
13
14 test 'has correct authorize url' do
15 assert_equal 'https://www.facebook.com/dialog/oauth', strategy.client.options[:authorize_url]
16 end
17
18 test 'has correct token url with versioning' do
19 @options = {:client_options => {:site => 'https://graph.facebook.net/v2.2'}}
20 assert_equal 'oauth/access_token', strategy.client.options[:token_url]
21 assert_equal 'https://graph.facebook.net/v2.2/oauth/access_token', strategy.client.token_url
22 end
23 end
24
25 class CallbackUrlTest < StrategyTestCase
26 test "returns the default callback url" do
27 url_base = 'http://auth.request.com'
28 @request.stubs(:url).returns("#{url_base}/some/page")
29 strategy.stubs(:script_name).returns('') # as not to depend on Rack env
30 assert_equal "#{url_base}/auth/facebook/callback", strategy.callback_url
31 end
32
33 test "returns path from callback_path option" do
34 @options = { :callback_path => "/auth/FB/done"}
35 url_base = 'http://auth.request.com'
36 @request.stubs(:url).returns("#{url_base}/page/path")
37 strategy.stubs(:script_name).returns('') # as not to depend on Rack env
38 assert_equal "#{url_base}/auth/FB/done", strategy.callback_url
39 end
40
41 test "returns url from callback_url option" do
42 url = 'https://auth.myapp.com/auth/fb/callback'
43 @options = { :callback_url => url }
44 assert_equal url, strategy.callback_url
45 end
46 end
47
48 class AuthorizeParamsTest < StrategyTestCase
49 test 'includes default scope for email' do
50 assert strategy.authorize_params.is_a?(Hash)
51 assert_equal 'email', strategy.authorize_params[:scope]
52 end
53
54 test 'includes display parameter from request when present' do
55 @request.stubs(:params).returns({ 'display' => 'touch' })
56 assert strategy.authorize_params.is_a?(Hash)
57 assert_equal 'touch', strategy.authorize_params[:display]
58 end
59
60 test 'includes auth_type parameter from request when present' do
61 @request.stubs(:params).returns({ 'auth_type' => 'reauthenticate' })
62 assert strategy.authorize_params.is_a?(Hash)
63 assert_equal 'reauthenticate', strategy.authorize_params[:auth_type]
64 end
65
66 test 'overrides default scope with parameter passed from request' do
67 @request.stubs(:params).returns({ 'scope' => 'email' })
68 assert strategy.authorize_params.is_a?(Hash)
69 assert_equal 'email', strategy.authorize_params[:scope]
70 end
71 end
72
73 class TokeParamsTest < StrategyTestCase
74 test 'has correct parse strategy' do
75 assert_equal :query, strategy.token_params[:parse]
76 end
77 end
78
79 class AccessTokenOptionsTest < StrategyTestCase
80 test 'has correct param name by default' do
81 assert_equal 'access_token', strategy.access_token_options[:param_name]
82 end
83
84 test 'has correct header format by default' do
85 assert_equal 'OAuth %s', strategy.access_token_options[:header_format]
86 end
87 end
88
89 class UidTest < StrategyTestCase
90 def setup
91 super
92 strategy.stubs(:raw_info).returns({ 'id' => '123' })
93 end
94
95 test 'returns the id from raw_info' do
96 assert_equal '123', strategy.uid
97 end
98 end
99
100 class InfoTest < StrategyTestCase
101 test 'returns the secure facebook avatar url when `secure_image_url` option is specified' do
102 @options = { :secure_image_url => true }
103 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
104 strategy.stubs(:raw_info).returns(raw_info)
105 assert_equal 'https://graph.facebook.com/321/picture', strategy.info['image']
106 end
107
108 test 'returns the image_url based of the client site' do
109 @options = { :secure_image_url => true, :client_options => {:site => "https://blah.facebook.com/v2.2"}}
110 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
111 strategy.stubs(:raw_info).returns(raw_info)
112 assert_equal 'https://blah.facebook.com/v2.2/321/picture', strategy.info['image']
113 end
114
115 test 'returns the image with size specified in the `image_size` option' do
116 @options = { :image_size => 'normal' }
117 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
118 strategy.stubs(:raw_info).returns(raw_info)
119 assert_equal 'http://graph.facebook.com/321/picture?type=normal', strategy.info['image']
120 end
121
122 test 'returns the image with size specified as a symbol in the `image_size` option' do
123 @options = { :image_size => :normal }
124 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
125 strategy.stubs(:raw_info).returns(raw_info)
126 assert_equal 'http://graph.facebook.com/321/picture?type=normal', strategy.info['image']
127 end
128
129 test 'returns the image with width and height specified in the `image_size` option' do
130 @options = { :image_size => { :width => 123, :height => 987 } }
131 raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
132 strategy.stubs(:raw_info).returns(raw_info)
133 assert_match 'width=123', strategy.info['image']
134 assert_match 'height=987', strategy.info['image']
135 assert_match 'http://graph.facebook.com/321/picture?', strategy.info['image']
136 end
137 end
138
139 class InfoTestOptionalDataPresent < StrategyTestCase
140 def setup
141 super
142 @raw_info ||= { 'name' => 'Fred Smith' }
143 strategy.stubs(:raw_info).returns(@raw_info)
144 end
145
146 test 'returns the name' do
147 assert_equal 'Fred Smith', strategy.info['name']
148 end
149
150 test 'returns the email' do
151 @raw_info['email'] = 'fred@smith.com'
152 assert_equal 'fred@smith.com', strategy.info['email']
153 end
154
155 test 'returns the username as nickname' do
156 @raw_info['username'] = 'fredsmith'
157 assert_equal 'fredsmith', strategy.info['nickname']
158 end
159
160 test 'returns the first name' do
161 @raw_info['first_name'] = 'Fred'
162 assert_equal 'Fred', strategy.info['first_name']
163 end
164
165 test 'returns the last name' do
166 @raw_info['last_name'] = 'Smith'
167 assert_equal 'Smith', strategy.info['last_name']
168 end
169
170 test 'returns the location name as location' do
171 @raw_info['location'] = { 'id' => '104022926303756', 'name' => 'Palo Alto, California' }
172 assert_equal 'Palo Alto, California', strategy.info['location']
173 end
174
175 test 'returns bio as description' do
176 @raw_info['bio'] = 'I am great'
177 assert_equal 'I am great', strategy.info['description']
178 end
179
180 test 'returns the facebook avatar url' do
181 @raw_info['id'] = '321'
182 assert_equal 'http://graph.facebook.com/321/picture', strategy.info['image']
183 end
184
185 test 'returns the Facebook link as the Facebook url' do
186 @raw_info['link'] = 'http://www.facebook.com/fredsmith'
187 assert_kind_of Hash, strategy.info['urls']
188 assert_equal 'http://www.facebook.com/fredsmith', strategy.info['urls']['Facebook']
189 end
190
191 test 'returns website url' do
192 @raw_info['website'] = 'https://my-wonderful-site.com'
193 assert_kind_of Hash, strategy.info['urls']
194 assert_equal 'https://my-wonderful-site.com', strategy.info['urls']['Website']
195 end
196
197 test 'return both Facebook link and website urls' do
198 @raw_info['link'] = 'http://www.facebook.com/fredsmith'
199 @raw_info['website'] = 'https://my-wonderful-site.com'
200 assert_kind_of Hash, strategy.info['urls']
201 assert_equal 'http://www.facebook.com/fredsmith', strategy.info['urls']['Facebook']
202 assert_equal 'https://my-wonderful-site.com', strategy.info['urls']['Website']
203 end
204
205 test 'returns the positive verified status' do
206 @raw_info['verified'] = true
207 assert strategy.info['verified']
208 end
209
210 test 'returns the negative verified status' do
211 @raw_info['verified'] = false
212 refute strategy.info['verified']
213 end
214 end
215
216 class InfoTestOptionalDataNotPresent < StrategyTestCase
217 def setup
218 super
219 @raw_info ||= { 'name' => 'Fred Smith' }
220 strategy.stubs(:raw_info).returns(@raw_info)
221 end
222
223 test 'has no email key' do
224 refute_has_key 'email', strategy.info
225 end
226
227 test 'has no nickname key' do
228 refute_has_key 'nickname', strategy.info
229 end
230
231 test 'has no first name key' do
232 refute_has_key 'first_name', strategy.info
233 end
234
235 test 'has no last name key' do
236 refute_has_key 'last_name', strategy.info
237 end
238
239 test 'has no location key' do
240 refute_has_key 'location', strategy.info
241 end
242
243 test 'has no description key' do
244 refute_has_key 'description', strategy.info
245 end
246
247 test 'has no urls' do
248 refute_has_key 'urls', strategy.info
249 end
250
251 test 'has no verified key' do
252 refute_has_key 'verified', strategy.info
253 end
254 end
255
256 class RawInfoTest < StrategyTestCase
257 def setup
258 super
259 @access_token = stub('OAuth2::AccessToken')
260 @appsecret_proof = 'appsecret_proof'
261 @options = {:appsecret_proof => @appsecret_proof}
262 end
263
264 test 'performs a GET to https://graph.facebook.com/me' do
265 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
266 strategy.stubs(:access_token).returns(@access_token)
267 params = {:params => @options}
268 @access_token.expects(:get).with('me', params).returns(stub_everything('OAuth2::Response'))
269 strategy.raw_info
270 end
271
272 test 'performs a GET to https://graph.facebook.com/me with locale' do
273 @options.merge!({ :locale => 'cs_CZ' })
274 strategy.stubs(:access_token).returns(@access_token)
275 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
276 params = {:params => @options}
277 @access_token.expects(:get).with('me', params).returns(stub_everything('OAuth2::Response'))
278 strategy.raw_info
279 end
280
281 test 'performs a GET to https://graph.facebook.com/me with info_fields' do
282 @options.merge!({:info_fields => 'about'})
283 strategy.stubs(:access_token).returns(@access_token)
284 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
285 params = {:params => {:appsecret_proof => @appsecret_proof, :fields => 'about'}}
286 @access_token.expects(:get).with('me', params).returns(stub_everything('OAuth2::Response'))
287 strategy.raw_info
288 end
289
290 test 'returns a Hash' do
291 strategy.stubs(:access_token).returns(@access_token)
292 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
293 raw_response = stub('Faraday::Response')
294 raw_response.stubs(:body).returns('{ "ohai": "thar" }')
295 raw_response.stubs(:status).returns(200)
296 raw_response.stubs(:headers).returns({'Content-Type' => 'application/json' })
297 oauth2_response = OAuth2::Response.new(raw_response)
298 params = {:params => @options}
299 @access_token.stubs(:get).with('me', params).returns(oauth2_response)
300 assert_kind_of Hash, strategy.raw_info
301 assert_equal 'thar', strategy.raw_info['ohai']
302 end
303
304 test 'returns an empty hash when the response is false' do
305 strategy.stubs(:access_token).returns(@access_token)
306 strategy.stubs(:appsecret_proof).returns(@appsecret_proof)
307 oauth2_response = stub('OAuth2::Response', :parsed => false)
308 params = {:params => @options}
309 @access_token.stubs(:get).with('me', params).returns(oauth2_response)
310 assert_kind_of Hash, strategy.raw_info
311 assert_equal({}, strategy.raw_info)
312 end
313
314 test 'should not include raw_info in extras hash when skip_info is specified' do
315 @options = { :skip_info => true }
316 strategy.stubs(:raw_info).returns({:foo => 'bar' })
317 refute_has_key 'raw_info', strategy.extra
318 end
319 end
320
321 class CredentialsTest < StrategyTestCase
322 def setup
323 super
324 @access_token = stub('OAuth2::AccessToken')
325 @access_token.stubs(:token)
326 @access_token.stubs(:expires?)
327 @access_token.stubs(:expires_at)
328 @access_token.stubs(:refresh_token)
329 strategy.stubs(:access_token).returns(@access_token)
330 end
331
332 test 'returns a Hash' do
333 assert_kind_of Hash, strategy.credentials
334 end
335
336 test 'returns the token' do
337 @access_token.stubs(:token).returns('123')
338 assert_equal '123', strategy.credentials['token']
339 end
340
341 test 'returns the expiry status' do
342 @access_token.stubs(:expires?).returns(true)
343 assert strategy.credentials['expires']
344
345 @access_token.stubs(:expires?).returns(false)
346 refute strategy.credentials['expires']
347 end
348
349 test 'returns the refresh token and expiry time when expiring' do
350 ten_mins_from_now = (Time.now + 600).to_i
351 @access_token.stubs(:expires?).returns(true)
352 @access_token.stubs(:refresh_token).returns('321')
353 @access_token.stubs(:expires_at).returns(ten_mins_from_now)
354 assert_equal '321', strategy.credentials['refresh_token']
355 assert_equal ten_mins_from_now, strategy.credentials['expires_at']
356 end
357
358 test 'does not return the refresh token when test is nil and expiring' do
359 @access_token.stubs(:expires?).returns(true)
360 @access_token.stubs(:refresh_token).returns(nil)
361 assert_nil strategy.credentials['refresh_token']
362 refute_has_key 'refresh_token', strategy.credentials
363 end
364
365 test 'does not return the refresh token when not expiring' do
366 @access_token.stubs(:expires?).returns(false)
367 @access_token.stubs(:refresh_token).returns('XXX')
368 assert_nil strategy.credentials['refresh_token']
369 refute_has_key 'refresh_token', strategy.credentials
370 end
371 end
372
373 class ExtraTest < StrategyTestCase
374 def setup
375 super
376 @raw_info = { 'name' => 'Fred Smith' }
377 strategy.stubs(:raw_info).returns(@raw_info)
378 end
379
380 test 'returns a Hash' do
381 assert_kind_of Hash, strategy.extra
382 end
383
384 test 'contains raw info' do
385 assert_equal({ 'raw_info' => @raw_info }, strategy.extra)
386 end
387 end
388
389 module SignedRequestHelpers
390 def signed_request(payload, secret)
391 encoded_payload = base64_encode_url(MultiJson.encode(payload))
392 encoded_signature = base64_encode_url(signature(encoded_payload, secret))
393 [encoded_signature, encoded_payload].join('.')
394 end
395
396 def base64_encode_url(value)
397 Base64.encode64(value).tr('+/', '-_').gsub(/\n/, '')
398 end
399
400 def signature(payload, secret, algorithm = OpenSSL::Digest::SHA256.new)
401 OpenSSL::HMAC.digest(algorithm, secret, payload)
402 end
403 end
404
405 module SignedRequestTests
406 class TestCase < StrategyTestCase
407 include SignedRequestHelpers
408 end
409
410 class CookieAndParamNotPresentTest < TestCase
411 test 'is nil' do
412 assert_nil strategy.send(:signed_request_from_cookie)
413 end
414
415 test 'throws an error on calling build_access_token' do
416 assert_raises(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError) { strategy.send(:with_authorization_code!) {} }
417 end
418 end
419
420 class CookiePresentTest < TestCase
421 def setup(algo = nil)
422 super()
423 @payload = {
424 'algorithm' => algo || 'HMAC-SHA256',
425 'code' => 'm4c0d3z',
426 'issued_at' => Time.now.to_i,
427 'user_id' => '123456'
428 }
429
430 @request.stubs(:cookies).returns({"fbsr_#{@client_id}" => signed_request(@payload, @client_secret)})
431 end
432
433 test 'parses the access code out from the cookie' do
434 assert_equal @payload, strategy.send(:signed_request_from_cookie)
435 end
436
437 test 'throws an error if the algorithm is unknown' do
438 setup('UNKNOWN-ALGO')
439 assert_equal "unknown algorithm: UNKNOWN-ALGO", assert_raises(OmniAuth::Facebook::SignedRequest::UnknownSignatureAlgorithmError) { strategy.send(:signed_request_from_cookie) }.message
440 end
441 end
442
443 class EmptySignedRequestTest < TestCase
444 def setup
445 super
446 @request.stubs(:params).returns({'signed_request' => ''})
447 end
448
449 test 'empty param' do
450 assert_equal nil, strategy.send(:signed_request_from_cookie)
451 end
452 end
453
454 class MissingCodeInParamsRequestTest < TestCase
455 def setup
456 super
457 @request.stubs(:params).returns({})
458 end
459
460 test 'calls fail! when a code is not included in the params' do
461 strategy.expects(:fail!).times(1).with(:no_authorization_code, kind_of(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError))
462 strategy.callback_phase
463 end
464 end
465
466 class MissingCodeInCookieRequestTest < TestCase
467 def setup(algo = nil)
468 super()
469 @payload = {
470 'algorithm' => algo || 'HMAC-SHA256',
471 'code' => nil,
472 'issued_at' => Time.now.to_i,
473 'user_id' => '123456'
474 }
475
476 @request.stubs(:cookies).returns({"fbsr_#{@client_id}" => signed_request(@payload, @client_secret)})
477 end
478
479 test 'calls fail! when a code is not included in the cookie' do
480 strategy.expects(:fail!).times(1).with(:no_authorization_code, kind_of(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError))
481 strategy.callback_phase
482 end
483 end
484
485 class UnknownAlgorithmInCookieRequestTest < TestCase
486 def setup
487 super()
488 @payload = {
489 'algorithm' => 'UNKNOWN-ALGO',
490 'code' => nil,
491 'issued_at' => Time.now.to_i,
492 'user_id' => '123456'
493 }
494
495 @request.stubs(:cookies).returns({"fbsr_#{@client_id}" => signed_request(@payload, @client_secret)})
496 end
497
498 test 'calls fail! when an algorithm is unknown' do
499 strategy.expects(:fail!).times(1).with(:unknown_signature_algorithm, kind_of(OmniAuth::Facebook::SignedRequest::UnknownSignatureAlgorithmError))
500 strategy.callback_phase
501 end
502 end
503 end