omniauth-facebook 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of omniauth-facebook might be problematic. Click here for more details.

data/.gitignore CHANGED
@@ -5,3 +5,4 @@
5
5
  pkg/*
6
6
  .powenv
7
7
  tmp
8
+ bin
data/README.md CHANGED
@@ -26,28 +26,39 @@ Rails.application.config.middleware.use OmniAuth::Builder do
26
26
  end
27
27
  ```
28
28
 
29
- See a full example of both server and client-side flows in the example Sinatra app in the `example/` folder above.
29
+ [See the example Sinatra app for full examples](https://github.com/mkdynamic/omniauth-facebook/blob/master/example/config.ru) of both the server and client-side flows (including using the Facebook Javascript SDK).
30
30
 
31
31
  ## Configuring
32
32
 
33
33
  You can configure several options, which you pass in to the `provider` method via a `Hash`:
34
34
 
35
- * `scope`: A comma-separated list of permissions you want to request from the user. See the Facebook docs for a full list of available permissions: http://developers.facebook.com/docs/reference/api/permissions. Default: `email,offline_access`
36
- * `display`: The display context to show the authentication page. Options are: `page`, `popup`, `iframe`, `touch` and `wap`. Read the Facebook docs for more details: http://developers.facebook.com/docs/reference/dialogs#display. Default: `page`
35
+ * `scope`: A comma-separated list of permissions you want to request from the user. See the Facebook docs for a full list of available permissions: http://developers.facebook.com/docs/reference/api/permissions. Default: `email`
36
+ * `display`: The display context to show the authentication page. Options are: `page`, `popup` and `touch`. Read the Facebook docs for more details: https://developers.facebook.com/docs/reference/dialogs/oauth/. Default: `page`
37
+ * `secure_image_url`: Set to `true` to use https for the avatar image url returned in the auth hash. Default is `false`.
38
+ * `image_size`: Set the size for the returned image url in the auth hash. Valid options are `square` (50x50), `small` (50 pixels wide, variable height), `normal` (100 pixels wide, variable height), or `large` (about 200 pixels wide, variable height). Default is `square` (50x50).
39
+
40
+ For example, to request `email`, `user_birthday` and `read_stream` permissions and display the authentication page in a popup window:
37
41
 
38
- For example, to request `email`, `offline_access` and `read_stream` permissions and display the authentication page in a popup window:
39
-
40
42
  ```ruby
41
43
  Rails.application.config.middleware.use OmniAuth::Builder do
42
- provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], :scope => 'email,offline_access,read_stream', :display => 'popup'
44
+ provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'],
45
+ :scope => 'email,user_birthday,read_stream', :display => 'popup'
43
46
  end
44
47
  ```
45
48
 
46
- *NB.* If you want to set the `display` format on a per-request basis, you can just pass it to the OmniAuth request phase URL, for example: `/auth/facebook?display=popup`.
49
+ ### Per-Request Options
50
+
51
+ If you want to set the `display` format or `scope` on a per-request basis, you can just pass it to the OmniAuth request phase URL, for example: `/auth/facebook?display=popup` or `/auth/facebook?scope=email`.
52
+
53
+ You can also pass through a `state` param which will be passed along to the callback url.
54
+
55
+ ### Custom Callback URL/Path
47
56
 
48
- ## Authentication Hash
57
+ You can set a custom `callback_url` or `callback_path` option to override the default value. See [OmniAuth::Strategy#callback_url](https://github.com/intridea/omniauth/blob/master/lib/omniauth/strategy.rb#L387) for more details on the default.
49
58
 
50
- Here's an example *Authentication Hash* available in `request.env['omniauth.auth']`:
59
+ ## Auth Hash
60
+
61
+ Here's an example *Auth Hash* available in `request.env['omniauth.auth']`:
51
62
 
52
63
  ```ruby
53
64
  {
@@ -61,12 +72,13 @@ Here's an example *Authentication Hash* available in `request.env['omniauth.auth
61
72
  :last_name => 'Bloggs',
62
73
  :image => 'http://graph.facebook.com/1234567/picture?type=square',
63
74
  :urls => { :Facebook => 'http://www.facebook.com/jbloggs' },
64
- :location => 'Palo Alto, California'
75
+ :location => 'Palo Alto, California',
76
+ :verified => true
65
77
  },
66
78
  :credentials => {
67
79
  :token => 'ABCDEF...', # OAuth 2.0 access_token, which you may wish to store
68
- :expires_at => 1321747205, # when the access token expires (if it expires)
69
- :expires => true # if you request `offline_access` this will be false
80
+ :expires_at => 1321747205, # when the access token expires (it always will)
81
+ :expires => true # this will always be true
70
82
  },
71
83
  :extra => {
72
84
  :raw_info => {
@@ -92,9 +104,53 @@ The precise information available may depend on the permissions which you reques
92
104
 
93
105
  ## Client-side Flow
94
106
 
95
- The client-side flow supports parsing the authorization code from the signed request which Facebook puts into a cookie. This means you can to use the Facebook Javascript SDK as you would normally, and you just hit the callback endpoint (`/auth/facebook/callback` by default) once the user has authenticated in the `FB.login` success callback.
107
+ You can use the Facebook Javascript SDK with `FB.login`, and just hit the callback endpoint (`/auth/facebook/callback` by default) once the user has authenticated in the success callback.
108
+
109
+ Note that you must enable cookies in the `FB.init` config for this process to work.
110
+
111
+ See the example Sinatra app under `example/` and read the [Facebook docs on Client-Side Authentication](https://developers.facebook.com/docs/authentication/client-side/) for more details.
112
+
113
+ ### How it Works
114
+
115
+ The client-side flow is supported by parsing the authorization code from the signed request which Facebook places in a cookie.
116
+
117
+ When you call `/auth/facebook/callback` in the success callback of `FB.login` that will pass the cookie back to the server. omniauth-facebook will see this cookie and:
118
+
119
+ 1. parse it,
120
+ 2. extract the authorization code contained in it
121
+ 3. and hit Facebook and obtain an access token which will get placed in the `request.env['omniauth.auth']['credentials']` hash.
122
+
123
+ Note that this access token will be the same token obtained and available in the client through the hash [as (detailed in the Facebook docs](https://developers.facebook.com/docs/authentication/client-side/)).
124
+
125
+ ## Canvas Apps
126
+
127
+ Canvas apps will send a signed request with the initial POST, therefore you *can* (if it makes sense for your app) pass this to the authorize endpoint (`/auth/facebook` by default) in the querystring.
128
+
129
+ There are then 2 scenarios for what happens next:
130
+
131
+ 1. A user has already granted access to your app, this will contain an access token. In this case, omniauth-facebook will skip asking the user for authentication and immediately redirect to the callback endpoint (`/auth/facebook/callback` by default) with the access token present in the `request.env['omniauth.auth']['credentials']` hash.
132
+
133
+ 2. A user has not granted access to your app, and the signed request *will not* contain an access token. In this case omniauth-facebook will simply follow the standard auth flow.
134
+
135
+ Take a look at [the example Sinatra app for one option of how you can integrate with a canvas page](https://github.com/mkdynamic/omniauth-facebook/blob/master/example/config.ru).
136
+
137
+ Bear in mind you have several options (including [authenticated referrals](https://developers.facebook.com/docs/opengraph/authentication/#referrals)). Read [the Facebook docs on canvas page authentication](https://developers.facebook.com/docs/authentication/canvas/) for more info.
138
+
139
+ ## Token Expiry
140
+
141
+ Since Facebook deprecated the `offline_access` permission, this has become more complex. The expiration time of the access token you obtain will depend on which flow you are using. See below for more details.
142
+
143
+ ### Client-Side Flow
144
+
145
+ If you use the client-side flow, Facebook will give you back a short lived access token (~ 2 hours).
146
+
147
+ You can exchange this short lived access token for a longer lived version. Read the [Facebook docs about the offline_access deprecation](https://developers.facebook.com/roadmap/offline-access-removal/) for more information.
148
+
149
+ ### Server-Side Flow
150
+
151
+ If you use the server-side flow, Facebook will give you back a longer loved access token (~ 60 days).
96
152
 
97
- See the example Sinatra app under `example/` for more details.
153
+ If you're having issue getting a long lived token with the server-side flow, make sure to enable the 'deprecate offline_access setting' in you Facebook app config. Read the [Facebook docs about the offline_access deprecation](https://developers.facebook.com/roadmap/offline-access-removal/) for more information.
98
154
 
99
155
  ## Supported Rubies
100
156
 
@@ -113,7 +169,7 @@ gem 'jruby-openssl', :platform => :jruby
113
169
 
114
170
  ## License
115
171
 
116
- Copyright (c) 2011 by Mark Dodwell
172
+ Copyright (c) 2012 by Mark Dodwell
117
173
 
118
174
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
119
175
 
@@ -1,7 +1,4 @@
1
1
  source :rubygems
2
2
 
3
- # https://github.com/mkdynamic/omniauth-facebook/issues/20
4
- gem 'rack', '~> 1.3.6'
5
-
6
3
  gem 'sinatra'
7
4
  gem 'omniauth-facebook', :path => '../'
@@ -1,30 +1,29 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- omniauth-facebook (1.1.0)
5
- omniauth-oauth2 (~> 1.0.0)
4
+ omniauth-facebook (1.2.0)
5
+ omniauth-oauth2 (~> 1.0.2)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- addressable (2.2.6)
11
- faraday (0.7.5)
12
- addressable (~> 2.2.6)
13
- multipart-post (~> 1.1.3)
14
- rack (>= 1.1.0, < 2)
10
+ faraday (0.8.0)
11
+ multipart-post (~> 1.1)
15
12
  hashie (1.2.0)
16
- multi_json (1.0.4)
17
- multipart-post (1.1.4)
18
- oauth2 (0.5.2)
13
+ httpauth (0.1)
14
+ multi_json (1.3.4)
15
+ multipart-post (1.1.5)
16
+ oauth2 (0.6.1)
19
17
  faraday (~> 0.7)
20
- multi_json (~> 1.0)
21
- omniauth (1.0.1)
18
+ httpauth (~> 0.1)
19
+ multi_json (~> 1.3)
20
+ omniauth (1.1.0)
22
21
  hashie (~> 1.2)
23
22
  rack
24
- omniauth-oauth2 (1.0.0)
25
- oauth2 (~> 0.5.0)
23
+ omniauth-oauth2 (1.0.2)
24
+ oauth2 (~> 0.6.0)
26
25
  omniauth (~> 1.0)
27
- rack (1.3.6)
26
+ rack (1.4.1)
28
27
  rack-protection (1.2.0)
29
28
  rack
30
29
  sinatra (1.3.2)
@@ -38,5 +37,4 @@ PLATFORMS
38
37
 
39
38
  DEPENDENCIES
40
39
  omniauth-facebook!
41
- rack (~> 1.3.6)
42
40
  sinatra
@@ -5,14 +5,17 @@ require 'omniauth-facebook'
5
5
  SCOPE = 'email,read_stream'
6
6
 
7
7
  class App < Sinatra::Base
8
+ # turn off sinatra default X-Frame-Options for FB canvas
9
+ set :protection, :except => :frame_options
10
+
8
11
  # server-side flow
9
12
  get '/' do
10
13
  # NOTE: you would just hit this endpoint directly from the browser
11
- # in a real app. the redirect is just here to setup the root
14
+ # in a real app. the redirect is just here to setup the root
12
15
  # path in this example sinatra app.
13
16
  redirect '/auth/facebook'
14
17
  end
15
-
18
+
16
19
  # client-side flow
17
20
  get '/client-side' do
18
21
  content_type 'text/html'
@@ -37,7 +40,6 @@ class App < Sinatra::Base
37
40
  appId : '#{ENV['APP_ID']}',
38
41
  status : true, // check login status
39
42
  cookie : true, // enable cookies to allow the server to access the session
40
- oauth : true, // enable OAuth 2.0
41
43
  xfbml : true // parse XFBML
42
44
  });
43
45
  };
@@ -48,33 +50,56 @@ class App < Sinatra::Base
48
50
  js.src = "//connect.facebook.net/en_US/all.js";
49
51
  d.getElementsByTagName('head')[0].appendChild(js);
50
52
  }(document));
51
-
53
+
52
54
  $(function() {
53
55
  $('a').click(function(e) {
54
56
  e.preventDefault();
55
-
57
+
56
58
  FB.login(function(response) {
57
59
  if (response.authResponse) {
58
- $.get('/auth/facebook/callback');
60
+ $('#connect').html('Connected! Hitting OmniAuth callback (GET /auth/facebook/callback)...');
61
+
62
+ // since we have cookies enabled, this request will allow omniauth to parse
63
+ // out the auth code from the signed request in the fbsr_XXX cookie
64
+ $.getJSON('/auth/facebook/callback', function(json) {
65
+ $('#connect').html('Connected! Callback complete.');
66
+ $('#results').html(JSON.stringify(json));
67
+ });
59
68
  }
60
69
  }, { scope: '#{SCOPE}' });
61
70
  });
62
71
  });
63
72
  </script>
64
-
65
- <p>
73
+
74
+ <p id="connect">
66
75
  <a href="#">Connect to FB</a>
67
76
  </p>
77
+
78
+ <p id="results" />
68
79
  </body>
69
80
  </html>
70
81
  END
71
82
  end
72
83
 
84
+ # auth via FB canvas and signed request param
85
+ post '/canvas/' do
86
+ # we just redirect to /auth/facebook here which will parse the
87
+ # signed_request FB sends us, asking for auth if the user has
88
+ # not already granted access, or simply moving straight to the
89
+ # callback where they have already granted access.
90
+ #
91
+ # we pass the state parameter which we can detect in our callback
92
+ # to do custom rendering/redirection for the canvas app page
93
+ redirect "/auth/facebook?signed_request=#{request.params['signed_request']}&state=canvas"
94
+ end
95
+
73
96
  get '/auth/:provider/callback' do
97
+ # we can do something special here is +state+ param is canvas
98
+ # (see notes above in /canvas/ method for more details)
74
99
  content_type 'application/json'
75
100
  MultiJson.encode(request.env)
76
101
  end
77
-
102
+
78
103
  get '/auth/failure' do
79
104
  content_type 'application/json'
80
105
  MultiJson.encode(request.env)
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module Facebook
3
- VERSION = "1.2.0"
3
+ VERSION = "1.3.0"
4
4
  end
5
5
  end
@@ -1,12 +1,15 @@
1
1
  require 'omniauth/strategies/oauth2'
2
2
  require 'base64'
3
3
  require 'openssl'
4
+ require 'rack/utils'
4
5
 
5
6
  module OmniAuth
6
7
  module Strategies
7
8
  class Facebook < OmniAuth::Strategies::OAuth2
8
- DEFAULT_SCOPE = 'email,offline_access'
9
-
9
+ class NoAuthorizationCodeError < StandardError; end
10
+
11
+ DEFAULT_SCOPE = 'email'
12
+
10
13
  option :client_options, {
11
14
  :site => 'https://graph.facebook.com',
12
15
  :token_url => '/oauth/access_token'
@@ -20,11 +23,11 @@ module OmniAuth
20
23
  :header_format => 'OAuth %s',
21
24
  :param_name => 'access_token'
22
25
  }
23
-
26
+
24
27
  option :authorize_options, [:scope, :display]
25
-
28
+
26
29
  uid { raw_info['id'] }
27
-
30
+
28
31
  info do
29
32
  prune!({
30
33
  'nickname' => raw_info['username'],
@@ -32,105 +35,162 @@ module OmniAuth
32
35
  'name' => raw_info['name'],
33
36
  'first_name' => raw_info['first_name'],
34
37
  'last_name' => raw_info['last_name'],
35
- 'image' => "http://graph.facebook.com/#{uid}/picture?type=square",
38
+ 'image' => "#{options[:secure_image_url] ? 'https' : 'http'}://graph.facebook.com/#{uid}/picture?type=#{options[:image_size] || 'square'}",
36
39
  'description' => raw_info['bio'],
37
40
  'urls' => {
38
41
  'Facebook' => raw_info['link'],
39
42
  'Website' => raw_info['website']
40
43
  },
41
- 'location' => (raw_info['location'] || {})['name']
44
+ 'location' => (raw_info['location'] || {})['name'],
45
+ 'verified' => raw_info['verified']
42
46
  })
43
47
  end
44
-
48
+
45
49
  credentials do
46
50
  prune!({
47
51
  'expires' => access_token.expires?,
48
52
  'expires_at' => access_token.expires_at
49
53
  })
50
54
  end
51
-
55
+
52
56
  extra do
53
57
  prune!({
54
58
  'raw_info' => raw_info
55
59
  })
56
60
  end
57
-
61
+
58
62
  def raw_info
59
- @raw_info ||= access_token.get('/me').parsed
63
+ @raw_info ||= access_token.get('/me').parsed || {}
60
64
  end
61
65
 
62
66
  def build_access_token
63
- with_authorization_code { super }.tap do |token|
64
- token.options.merge!(access_token_options)
67
+ if signed_request_contains_access_token?
68
+ hash = signed_request.clone
69
+ ::OAuth2::AccessToken.new(
70
+ client,
71
+ hash.delete('oauth_token'),
72
+ hash.merge!(access_token_options.merge(:expires_at => hash.delete('expires')))
73
+ )
74
+ else
75
+ with_authorization_code! { super }.tap do |token|
76
+ token.options.merge!(access_token_options)
77
+ end
65
78
  end
66
79
  end
67
-
68
- # NOTE if we're using code from the signed request cookie
80
+
81
+ def request_phase
82
+ if signed_request_contains_access_token?
83
+ # if we already have an access token, we can just hit the
84
+ # callback URL directly and pass the signed request along
85
+ params = { :signed_request => raw_signed_request }
86
+ params[:state] = request.params['state'] if request.params['state']
87
+ query = Rack::Utils.build_query(params)
88
+
89
+ url = callback_url
90
+ url << "?" unless url.match(/\?/)
91
+ url << "&" unless url.match(/[\&\?]$/)
92
+ url << query
93
+
94
+ redirect url
95
+ else
96
+ super
97
+ end
98
+ end
99
+
100
+ # NOTE if we're using code from the signed request
69
101
  # then FB sets the redirect_uri to '' during the authorize
70
102
  # phase + it must match during the access_token phase:
71
103
  # https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php#L348
72
104
  def callback_url
73
- if @authorization_code_from_cookie
105
+ if @authorization_code_from_signed_request
74
106
  ''
75
107
  else
76
- if options.authorize_options.respond_to?(:callback_url)
77
- options.authorize_options.callback_url
78
- else
79
- super
80
- end
108
+ options[:callback_url] || super
81
109
  end
82
110
  end
83
111
 
84
112
  def access_token_options
85
113
  options.access_token_options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
86
114
  end
87
-
115
+
116
+ ##
117
+ # You can pass +display+, +state+ or +scope+ params to the auth request, if
118
+ # you need to set them dynamically. You can also set these options
119
+ # in the OmniAuth config :authorize_params option.
120
+ #
121
+ # /auth/facebook?display=popup&state=ABC
122
+ #
88
123
  def authorize_params
89
124
  super.tap do |params|
90
- params.merge!(:display => request.params['display']) if request.params['display']
91
- params.merge!(:state => request.params['state']) if request.params['state']
125
+ %w[display state scope].each { |v| params[v.to_sym] = request.params[v] if request.params[v] }
92
126
  params[:scope] ||= DEFAULT_SCOPE
93
127
  end
94
128
  end
95
129
 
130
+ ##
131
+ # Parse signed request in order, from:
132
+ #
133
+ # 1. the request 'signed_request' param (server-side flow from canvas pages) or
134
+ # 2. a cookie (client-side flow via JS SDK)
135
+ #
96
136
  def signed_request
97
- @signed_request ||= begin
98
- cookie = request.cookies["fbsr_#{client.id}"] and
99
- parse_signed_request(cookie)
100
- end
137
+ @signed_request ||= raw_signed_request &&
138
+ parse_signed_request(raw_signed_request)
101
139
  end
102
-
140
+
103
141
  private
104
-
105
- # picks the authorization code in order, from:
106
- # 1. the request param
107
- # 2. a signed cookie
108
- def with_authorization_code
142
+
143
+ def raw_signed_request
144
+ request.params['signed_request'] ||
145
+ request.cookies["fbsr_#{client.id}"]
146
+ end
147
+
148
+ ##
149
+ # If the signed_request comes from a FB canvas page and the user
150
+ # has already authorized your application, the JSON object will be
151
+ # contain the access token.
152
+ #
153
+ # https://developers.facebook.com/docs/authentication/canvas/
154
+ #
155
+ def signed_request_contains_access_token?
156
+ signed_request &&
157
+ signed_request['oauth_token']
158
+ end
159
+
160
+ ##
161
+ # Picks the authorization code in order, from:
162
+ #
163
+ # 1. the request 'code' param (manual callback from standard server-side flow)
164
+ # 2. a signed request (see #signed_request for more)
165
+ #
166
+ def with_authorization_code!
109
167
  if request.params.key?('code')
110
168
  yield
111
- else code_from_cookie = signed_request && signed_request['code']
112
- request.params['code'] = code_from_cookie
113
- @authorization_code_from_cookie = true
169
+ elsif code_from_signed_request = signed_request && signed_request['code']
170
+ request.params['code'] = code_from_signed_request
171
+ @authorization_code_from_signed_request = true
114
172
  begin
115
173
  yield
116
174
  ensure
117
175
  request.params.delete('code')
118
- @authorization_code_from_cookie = false
176
+ @authorization_code_from_signed_request = false
119
177
  end
178
+ else
179
+ raise NoAuthorizationCodeError, 'must pass either a `code` parameter or a signed request (via `signed_request` parameter or a `fbsr_XXX` cookie)'
120
180
  end
121
181
  end
122
-
182
+
123
183
  def prune!(hash)
124
- hash.delete_if do |_, value|
184
+ hash.delete_if do |_, value|
125
185
  prune!(value) if value.is_a?(Hash)
126
186
  value.nil? || (value.respond_to?(:empty?) && value.empty?)
127
187
  end
128
188
  end
129
-
189
+
130
190
  def parse_signed_request(value)
131
191
  signature, encoded_payload = value.split('.')
132
192
 
133
- decoded_hex_signature = base64_decode_url(signature)#.unpack('H*')
193
+ decoded_hex_signature = base64_decode_url(signature)
134
194
  decoded_payload = MultiJson.decode(base64_decode_url(encoded_payload))
135
195
 
136
196
  unless decoded_payload['algorithm'] == 'HMAC-SHA256'
@@ -15,8 +15,8 @@ Gem::Specification.new do |s|
15
15
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
16
16
  s.require_paths = ['lib']
17
17
 
18
- s.add_runtime_dependency 'omniauth-oauth2', '~> 1.0.0'
18
+ s.add_runtime_dependency 'omniauth-oauth2', '~> 1.0.2'
19
19
 
20
- s.add_development_dependency 'rspec', '~> 2.7.0'
20
+ s.add_development_dependency 'rspec', '~> 2'
21
21
  s.add_development_dependency 'rake'
22
22
  end
@@ -8,11 +8,12 @@ describe OmniAuth::Strategies::Facebook do
8
8
  @request = double('Request')
9
9
  @request.stub(:params) { {} }
10
10
  @request.stub(:cookies) { {} }
11
-
11
+ @request.stub(:env) { {} }
12
+
12
13
  @client_id = '123'
13
14
  @client_secret = '53cr3tz'
14
15
  end
15
-
16
+
16
17
  subject do
17
18
  args = [@client_id, @client_secret, @options].compact
18
19
  OmniAuth::Strategies::Facebook.new(nil, *args).tap do |strategy|
@@ -37,26 +38,34 @@ describe OmniAuth::Strategies::Facebook do
37
38
  end
38
39
 
39
40
  describe '#callback_url' do
40
- it "returns value from #authorize_options" do
41
- url = 'http://auth.myapp.com/auth/fb/callback'
42
- @options = { :authorize_options => { :callback_url => url } }
43
- subject.callback_url.should eq(url)
41
+ it "returns the default callback url" do
42
+ url_base = 'http://auth.request.com'
43
+ @request.stub(:url) { "#{url_base}/some/page" }
44
+ subject.stub(:script_name) { '' } # as not to depend on Rack env
45
+ subject.callback_url.should eq("#{url_base}/auth/facebook/callback")
44
46
  end
45
47
 
46
- it "callback_url from request" do
48
+ it "returns path from callback_path option" do
49
+ @options = { :callback_path => "/auth/FB/done"}
47
50
  url_base = 'http://auth.request.com'
48
51
  @request.stub(:url) { "#{url_base}/page/path" }
49
- subject.stub(:script_name) { "" } # to not depend from Rack env
50
- subject.callback_url.should eq("#{url_base}/auth/facebook/callback")
52
+ subject.stub(:script_name) { '' } # as not to depend on Rack env
53
+ subject.callback_url.should eq("#{url_base}/auth/FB/done")
54
+ end
55
+
56
+ it "returns url from callback_url option" do
57
+ url = 'https://auth.myapp.com/auth/fb/callback'
58
+ @options = { :callback_url => url }
59
+ subject.callback_url.should eq(url)
51
60
  end
52
61
  end
53
62
 
54
63
  describe '#authorize_params' do
55
- it 'includes default scope for email and offline access' do
64
+ it 'includes default scope for email' do
56
65
  subject.authorize_params.should be_a(Hash)
57
- subject.authorize_params[:scope].should eq('email,offline_access')
66
+ subject.authorize_params[:scope].should eq('email')
58
67
  end
59
-
68
+
60
69
  it 'includes display parameter from request when present' do
61
70
  @request.stub(:params) { { 'display' => 'touch' } }
62
71
  subject.authorize_params.should be_a(Hash)
@@ -68,6 +77,12 @@ describe OmniAuth::Strategies::Facebook do
68
77
  subject.authorize_params.should be_a(Hash)
69
78
  subject.authorize_params[:state].should eq('some_state')
70
79
  end
80
+
81
+ it 'overrides default scope with parameter passed from request' do
82
+ @request.stub(:params) { { 'scope' => 'email' } }
83
+ subject.authorize_params.should be_a(Hash)
84
+ subject.authorize_params[:scope].should eq('email')
85
+ end
71
86
  end
72
87
 
73
88
  describe '#token_params' do
@@ -85,24 +100,24 @@ describe OmniAuth::Strategies::Facebook do
85
100
  subject.access_token_options[:header_format].should eq('OAuth %s')
86
101
  end
87
102
  end
88
-
103
+
89
104
  describe '#uid' do
90
105
  before :each do
91
106
  subject.stub(:raw_info) { { 'id' => '123' } }
92
107
  end
93
-
108
+
94
109
  it 'returns the id from raw_info' do
95
110
  subject.uid.should eq('123')
96
111
  end
97
112
  end
98
-
113
+
99
114
  describe '#info' do
100
- before :each do
101
- @raw_info ||= { 'name' => 'Fred Smith' }
102
- subject.stub(:raw_info) { @raw_info }
103
- end
104
-
105
115
  context 'when optional data is not present in raw info' do
116
+ before :each do
117
+ @raw_info ||= { 'name' => 'Fred Smith' }
118
+ subject.stub(:raw_info) { @raw_info }
119
+ end
120
+
106
121
  it 'has no email key' do
107
122
  subject.info.should_not have_key('email')
108
123
  end
@@ -110,33 +125,42 @@ describe OmniAuth::Strategies::Facebook do
110
125
  it 'has no nickname key' do
111
126
  subject.info.should_not have_key('nickname')
112
127
  end
113
-
128
+
114
129
  it 'has no first name key' do
115
130
  subject.info.should_not have_key('first_name')
116
131
  end
117
-
132
+
118
133
  it 'has no last name key' do
119
134
  subject.info.should_not have_key('last_name')
120
135
  end
121
-
136
+
122
137
  it 'has no location key' do
123
138
  subject.info.should_not have_key('location')
124
139
  end
125
-
140
+
126
141
  it 'has no description key' do
127
142
  subject.info.should_not have_key('description')
128
143
  end
129
-
144
+
130
145
  it 'has no urls' do
131
146
  subject.info.should_not have_key('urls')
132
147
  end
148
+
149
+ it 'has no verified key' do
150
+ subject.info.should_not have_key('verified')
151
+ end
133
152
  end
134
-
135
- context 'when data is present in raw info' do
153
+
154
+ context 'when optional data is present in raw info' do
155
+ before :each do
156
+ @raw_info ||= { 'name' => 'Fred Smith' }
157
+ subject.stub(:raw_info) { @raw_info }
158
+ end
159
+
136
160
  it 'returns the name' do
137
161
  subject.info['name'].should eq('Fred Smith')
138
162
  end
139
-
163
+
140
164
  it 'returns the email' do
141
165
  @raw_info['email'] = 'fred@smith.com'
142
166
  subject.info['email'].should eq('fred@smith.com')
@@ -146,44 +170,44 @@ describe OmniAuth::Strategies::Facebook do
146
170
  @raw_info['username'] = 'fredsmith'
147
171
  subject.info['nickname'].should eq('fredsmith')
148
172
  end
149
-
173
+
150
174
  it 'returns the first name' do
151
175
  @raw_info['first_name'] = 'Fred'
152
176
  subject.info['first_name'].should eq('Fred')
153
177
  end
154
-
178
+
155
179
  it 'returns the last name' do
156
180
  @raw_info['last_name'] = 'Smith'
157
181
  subject.info['last_name'].should eq('Smith')
158
182
  end
159
-
183
+
160
184
  it 'returns the location name as location' do
161
185
  @raw_info['location'] = { 'id' => '104022926303756', 'name' => 'Palo Alto, California' }
162
186
  subject.info['location'].should eq('Palo Alto, California')
163
187
  end
164
-
188
+
165
189
  it 'returns bio as description' do
166
190
  @raw_info['bio'] = 'I am great'
167
191
  subject.info['description'].should eq('I am great')
168
192
  end
169
-
193
+
170
194
  it 'returns the square format facebook avatar url' do
171
195
  @raw_info['id'] = '321'
172
196
  subject.info['image'].should eq('http://graph.facebook.com/321/picture?type=square')
173
197
  end
174
-
198
+
175
199
  it 'returns the Facebook link as the Facebook url' do
176
200
  @raw_info['link'] = 'http://www.facebook.com/fredsmith'
177
201
  subject.info['urls'].should be_a(Hash)
178
202
  subject.info['urls']['Facebook'].should eq('http://www.facebook.com/fredsmith')
179
203
  end
180
-
204
+
181
205
  it 'returns website url' do
182
206
  @raw_info['website'] = 'https://my-wonderful-site.com'
183
207
  subject.info['urls'].should be_a(Hash)
184
208
  subject.info['urls']['Website'].should eq('https://my-wonderful-site.com')
185
209
  end
186
-
210
+
187
211
  it 'return both Facebook link and website urls' do
188
212
  @raw_info['link'] = 'http://www.facebook.com/fredsmith'
189
213
  @raw_info['website'] = 'https://my-wonderful-site.com'
@@ -191,21 +215,45 @@ describe OmniAuth::Strategies::Facebook do
191
215
  subject.info['urls']['Facebook'].should eq('http://www.facebook.com/fredsmith')
192
216
  subject.info['urls']['Website'].should eq('https://my-wonderful-site.com')
193
217
  end
218
+
219
+ it 'returns the positive verified status' do
220
+ @raw_info['verified'] = true
221
+ subject.info['verified'].should be_true
222
+ end
223
+
224
+ it 'returns the negative verified status' do
225
+ @raw_info['verified'] = false
226
+ subject.info['verified'].should be_false
227
+ end
228
+ end
229
+
230
+ it 'returns the secure facebook avatar url when `secure_image_url` option is specified' do
231
+ @options = { :secure_image_url => true }
232
+ raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
233
+ subject.stub(:raw_info) { raw_info }
234
+ subject.info['image'].should eq('https://graph.facebook.com/321/picture?type=square')
235
+ end
236
+
237
+ it 'returns the image size specified in the `image_size` option' do
238
+ @options = { :image_size => 'normal' }
239
+ raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
240
+ subject.stub(:raw_info) { raw_info }
241
+ subject.info['image'].should eq('http://graph.facebook.com/321/picture?type=normal')
194
242
  end
195
243
  end
196
-
244
+
197
245
  describe '#raw_info' do
198
246
  before :each do
199
247
  @access_token = double('OAuth2::AccessToken')
200
248
  subject.stub(:access_token) { @access_token }
201
249
  end
202
-
250
+
203
251
  it 'performs a GET to https://graph.facebook.com/me' do
204
252
  @access_token.stub(:get) { double('OAuth2::Response').as_null_object }
205
253
  @access_token.should_receive(:get).with('/me')
206
254
  subject.raw_info
207
255
  end
208
-
256
+
209
257
  it 'returns a Hash' do
210
258
  @access_token.stub(:get).with('/me') do
211
259
  raw_response = double('Faraday::Response')
@@ -217,6 +265,15 @@ describe OmniAuth::Strategies::Facebook do
217
265
  subject.raw_info.should be_a(Hash)
218
266
  subject.raw_info['ohai'].should eq('thar')
219
267
  end
268
+
269
+ it 'returns an empty hash when the response is false' do
270
+ @access_token.stub(:get).with('/me') do
271
+ response = double('OAuth2::Response')
272
+ response.stub(:parsed => false)
273
+ response
274
+ end
275
+ subject.raw_info.should be_a(Hash)
276
+ end
220
277
  end
221
278
 
222
279
  describe '#credentials' do
@@ -228,24 +285,24 @@ describe OmniAuth::Strategies::Facebook do
228
285
  @access_token.stub(:refresh_token)
229
286
  subject.stub(:access_token) { @access_token }
230
287
  end
231
-
288
+
232
289
  it 'returns a Hash' do
233
290
  subject.credentials.should be_a(Hash)
234
291
  end
235
-
292
+
236
293
  it 'returns the token' do
237
294
  @access_token.stub(:token) { '123' }
238
295
  subject.credentials['token'].should eq('123')
239
296
  end
240
-
297
+
241
298
  it 'returns the expiry status' do
242
299
  @access_token.stub(:expires?) { true }
243
300
  subject.credentials['expires'].should eq(true)
244
-
301
+
245
302
  @access_token.stub(:expires?) { false }
246
303
  subject.credentials['expires'].should eq(false)
247
304
  end
248
-
305
+
249
306
  it 'returns the refresh token and expiry time when expiring' do
250
307
  ten_mins_from_now = (Time.now + 600).to_i
251
308
  @access_token.stub(:expires?) { true }
@@ -254,14 +311,14 @@ describe OmniAuth::Strategies::Facebook do
254
311
  subject.credentials['refresh_token'].should eq('321')
255
312
  subject.credentials['expires_at'].should eq(ten_mins_from_now)
256
313
  end
257
-
314
+
258
315
  it 'does not return the refresh token when it is nil and expiring' do
259
316
  @access_token.stub(:expires?) { true }
260
317
  @access_token.stub(:refresh_token) { nil }
261
318
  subject.credentials['refresh_token'].should be_nil
262
319
  subject.credentials.should_not have_key('refresh_token')
263
320
  end
264
-
321
+
265
322
  it 'does not return the refresh token when not expiring' do
266
323
  @access_token.stub(:expires?) { false }
267
324
  @access_token.stub(:refresh_token) { 'XXX' }
@@ -269,29 +326,29 @@ describe OmniAuth::Strategies::Facebook do
269
326
  subject.credentials.should_not have_key('refresh_token')
270
327
  end
271
328
  end
272
-
329
+
273
330
  describe '#extra' do
274
331
  before :each do
275
332
  @raw_info = { 'name' => 'Fred Smith' }
276
333
  subject.stub(:raw_info) { @raw_info }
277
334
  end
278
-
335
+
279
336
  it 'returns a Hash' do
280
337
  subject.extra.should be_a(Hash)
281
338
  end
282
-
339
+
283
340
  it 'contains raw info' do
284
341
  subject.extra.should eq({ 'raw_info' => @raw_info })
285
342
  end
286
343
  end
287
344
 
288
345
  describe '#signed_request' do
289
- context 'cookie not present' do
346
+ context 'cookie/param not present' do
290
347
  it 'is nil' do
291
348
  subject.send(:signed_request).should be_nil
292
349
  end
293
350
  end
294
-
351
+
295
352
  context 'cookie present' do
296
353
  before :each do
297
354
  @payload = {
@@ -310,6 +367,102 @@ describe OmniAuth::Strategies::Facebook do
310
367
  subject.send(:signed_request).should eq(@payload)
311
368
  end
312
369
  end
370
+
371
+ context 'param present' do
372
+ before :each do
373
+ @payload = {
374
+ 'algorithm' => 'HMAC-SHA256',
375
+ 'oauth_token' => 'XXX',
376
+ 'issued_at' => Time.now.to_i,
377
+ 'user_id' => '123456'
378
+ }
379
+
380
+ @request.stub(:params) do
381
+ { 'signed_request' => signed_request(@payload, @client_secret) }
382
+ end
383
+ end
384
+
385
+ it 'parses the access code out from the param' do
386
+ subject.send(:signed_request).should eq(@payload)
387
+ end
388
+ end
389
+
390
+ context 'cookie + param present' do
391
+ before :each do
392
+ @payload_from_cookie = {
393
+ 'algorithm' => 'HMAC-SHA256',
394
+ 'from' => 'cookie'
395
+ }
396
+
397
+ @request.stub(:cookies) do
398
+ { "fbsr_#{@client_id}" => signed_request(@payload_from_cookie, @client_secret) }
399
+ end
400
+
401
+ @payload_from_param = {
402
+ 'algorithm' => 'HMAC-SHA256',
403
+ 'from' => 'param'
404
+ }
405
+
406
+ @request.stub(:params) do
407
+ { 'signed_request' => signed_request(@payload_from_param, @client_secret) }
408
+ end
409
+ end
410
+
411
+ it 'picks param over cookie' do
412
+ subject.send(:signed_request).should eq(@payload_from_param)
413
+ end
414
+ end
415
+ end
416
+
417
+ describe '#request_phase' do
418
+ describe 'params contain a signed request with an access token' do
419
+ before do
420
+ payload = {
421
+ 'algorithm' => 'HMAC-SHA256',
422
+ 'oauth_token' => 'm4c0d3z'
423
+ }
424
+ @raw_signed_request = signed_request(payload, @client_secret)
425
+ @request.stub(:params) do
426
+ { "signed_request" => @raw_signed_request }
427
+ end
428
+
429
+ subject.stub(:callback_url) { '/' }
430
+ end
431
+
432
+ it 'redirects to callback passing along signed request' do
433
+ subject.should_receive(:redirect).with("/?signed_request=#{Rack::Utils.escape(@raw_signed_request)}").once
434
+ subject.request_phase
435
+ end
436
+ end
437
+ end
438
+
439
+ describe '#build_access_token' do
440
+ describe 'params contain a signed request with an access token' do
441
+ before do
442
+ @payload = {
443
+ 'algorithm' => 'HMAC-SHA256',
444
+ 'oauth_token' => 'm4c0d3z',
445
+ 'expires' => Time.now.to_i
446
+ }
447
+ @raw_signed_request = signed_request(@payload, @client_secret)
448
+ @request.stub(:params) do
449
+ { "signed_request" => @raw_signed_request }
450
+ end
451
+
452
+ subject.stub(:callback_url) { '/' }
453
+ end
454
+
455
+ it 'returns a new access token from the signed request' do
456
+ result = subject.build_access_token
457
+ result.should be_an_instance_of(::OAuth2::AccessToken)
458
+ result.token.should eq(@payload['oauth_token'])
459
+ end
460
+
461
+ it 'returns an access token with the correct expiry time' do
462
+ result = subject.build_access_token
463
+ result.expires_at.should eq(@payload['expires'])
464
+ end
465
+ end
313
466
  end
314
467
 
315
468
  private
@@ -324,7 +477,7 @@ private
324
477
  Base64.encode64(value).tr('+/', '-_').gsub(/\n/, '')
325
478
  end
326
479
 
327
- def signature(payload, secret, algorithm = OpenSSL::Digest::SHA256.new)
480
+ def signature(payload, secret, algorithm = OpenSSL::Digest::SHA256.new)
328
481
  OpenSSL::HMAC.digest(algorithm, secret, payload)
329
482
  end
330
483
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-facebook
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,33 +9,43 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-06 00:00:00.000000000Z
12
+ date: 2012-05-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: omniauth-oauth2
16
- requirement: &70265885281820 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 1.0.0
21
+ version: 1.0.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70265885281820
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.2
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rspec
27
- requirement: &70265885281320 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
31
36
  - !ruby/object:Gem::Version
32
- version: 2.7.0
37
+ version: '2'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *70265885281320
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: rake
38
- requirement: &70265885280920 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,7 +53,12 @@ dependencies:
43
53
  version: '0'
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *70265885280920
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  description:
48
63
  email:
49
64
  - mark@mkdynamic.co.uk
@@ -79,21 +94,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
94
  - - ! '>='
80
95
  - !ruby/object:Gem::Version
81
96
  version: '0'
82
- segments:
83
- - 0
84
- hash: 896289232930300063
85
97
  required_rubygems_version: !ruby/object:Gem::Requirement
86
98
  none: false
87
99
  requirements:
88
100
  - - ! '>='
89
101
  - !ruby/object:Gem::Version
90
102
  version: '0'
91
- segments:
92
- - 0
93
- hash: 896289232930300063
94
103
  requirements: []
95
104
  rubyforge_project:
96
- rubygems_version: 1.8.10
105
+ rubygems_version: 1.8.22
97
106
  signing_key:
98
107
  specification_version: 3
99
108
  summary: Facebook strategy for OmniAuth
@@ -101,3 +110,4 @@ test_files:
101
110
  - spec/omniauth/strategies/facebook_spec.rb
102
111
  - spec/spec_helper.rb
103
112
  - spec/support/shared_examples.rb
113
+ has_rdoc: