omniauth-facebook 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source :rubygems
2
2
 
3
3
  gemspec
4
+
5
+ gem 'jruby-openssl', :platform => :jruby
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This gem contains the Facebook strategy for OmniAuth 1.0.
4
4
 
5
- Supports the OAuth 2.0 server-side flow. Read the Facebook docs for more details: http://developers.facebook.com/docs/authentication
5
+ Supports the OAuth 2.0 server-side and client-side flows. Read the Facebook docs for more details: http://developers.facebook.com/docs/authentication
6
6
 
7
7
  ## Installing
8
8
 
@@ -26,6 +26,8 @@ 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.
30
+
29
31
  ## Configuring
30
32
 
31
33
  You can configure several options, which you pass in to the `provider` method via a `Hash`:
@@ -88,6 +90,12 @@ Here's an example *Authentication Hash* available in `request.env['omniauth.auth
88
90
 
89
91
  The precise information available may depend on the permissions which you request.
90
92
 
93
+ ## Client-side Flow
94
+
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.
96
+
97
+ See the example Sinatra app under `example/` for more details.
98
+
91
99
  ## Supported Rubies
92
100
 
93
101
  Actively tested with the following Ruby versions:
@@ -97,6 +105,12 @@ Actively tested with the following Ruby versions:
97
105
  - MRI 1.8.7
98
106
  - JRuby 1.6.5
99
107
 
108
+ *NB.* For JRuby, you'll need to install the `jruby-openssl` gem. There's no way to automatically specify this in a Rubygem gemspec, so you need to manually add it your project's own Gemfile:
109
+
110
+ ```ruby
111
+ gem 'jruby-openssl', :platform => :jruby
112
+ ```
113
+
100
114
  ## License
101
115
 
102
116
  Copyright (c) 2011 by Mark Dodwell
@@ -2,10 +2,73 @@ require 'bundler/setup'
2
2
  require 'sinatra/base'
3
3
  require 'omniauth-facebook'
4
4
 
5
+ SCOPE = 'email,read_stream'
6
+
5
7
  class App < Sinatra::Base
8
+ # server-side flow
6
9
  get '/' do
10
+ # 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
12
+ # path in this example sinatra app.
7
13
  redirect '/auth/facebook'
8
14
  end
15
+
16
+ # client-side flow
17
+ get '/client-side' do
18
+ content_type 'text/html'
19
+ # NOTE: when you enable cookie below in the FB.init call
20
+ # the GET request in the FB.login callback will send
21
+ # a signed request in a cookie back the OmniAuth callback
22
+ # which will parse out the authorization code and obtain
23
+ # the access_token. This will be the exact same access_token
24
+ # returned to the client in response.authResponse.accessToken.
25
+ <<-END
26
+ <html>
27
+ <head>
28
+ <title>Client-side Flow Example</title>
29
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js" type="text/javascript"></script>
30
+ </head>
31
+ <body>
32
+ <div id="fb-root"></div>
33
+
34
+ <script type="text/javascript">
35
+ window.fbAsyncInit = function() {
36
+ FB.init({
37
+ appId : '#{ENV['APP_ID']}',
38
+ status : true, // check login status
39
+ cookie : true, // enable cookies to allow the server to access the session
40
+ oauth : true, // enable OAuth 2.0
41
+ xfbml : true // parse XFBML
42
+ });
43
+ };
44
+
45
+ (function(d) {
46
+ var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
47
+ js = d.createElement('script'); js.id = id; js.async = true;
48
+ js.src = "//connect.facebook.net/en_US/all.js";
49
+ d.getElementsByTagName('head')[0].appendChild(js);
50
+ }(document));
51
+
52
+ $(function() {
53
+ $('a').click(function(e) {
54
+ e.preventDefault();
55
+
56
+ FB.login(function(response) {
57
+ if (response.authResponse) {
58
+ $.get('/auth/facebook/callback');
59
+ }
60
+ }, { scope: '#{SCOPE}' });
61
+ });
62
+ });
63
+ </script>
64
+
65
+ <p>
66
+ <a href="#">Connect to FB</a>
67
+ </p>
68
+ </body>
69
+ </html>
70
+ END
71
+ end
9
72
 
10
73
  get '/auth/:provider/callback' do
11
74
  content_type 'application/json'
@@ -21,7 +84,7 @@ end
21
84
  use Rack::Session::Cookie
22
85
 
23
86
  use OmniAuth::Builder do
24
- provider :facebook, ENV['APP_ID'], ENV['APP_SECRET'], :scope => 'email,read_stream', :display => 'popup'
87
+ provider :facebook, ENV['APP_ID'], ENV['APP_SECRET'], :scope => SCOPE
25
88
  end
26
89
 
27
90
  run App.new
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module Facebook
3
- VERSION = "1.0.0"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
  end
@@ -1,4 +1,6 @@
1
1
  require 'omniauth/strategies/oauth2'
2
+ require 'base64'
3
+ require 'openssl'
2
4
 
3
5
  module OmniAuth
4
6
  module Strategies
@@ -58,10 +60,26 @@ module OmniAuth
58
60
  end
59
61
 
60
62
  def build_access_token
61
- super.tap do |token|
63
+ with_authorization_code { super }.tap do |token|
62
64
  token.options.merge!(access_token_options)
63
65
  end
64
66
  end
67
+
68
+ # NOTE if we're using code from the signed request cookie
69
+ # then FB sets the redirect_uri to '' during the authorize
70
+ # phase + it must match during the access_token phase:
71
+ # https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php#L348
72
+ def callback_url
73
+ if @authorization_code_from_cookie
74
+ ''
75
+ else
76
+ if options.authorize_options.respond_to?(:callback_url)
77
+ options.authorize_options.callback_url
78
+ else
79
+ super
80
+ end
81
+ end
82
+ end
65
83
 
66
84
  def access_token_options
67
85
  options.access_token_options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
@@ -73,15 +91,64 @@ module OmniAuth
73
91
  params[:scope] ||= DEFAULT_SCOPE
74
92
  end
75
93
  end
94
+
95
+ def signed_request
96
+ @signed_request ||= begin
97
+ cookie = request.cookies["fbsr_#{client.id}"] and
98
+ parse_signed_request(cookie)
99
+ end
100
+ end
76
101
 
77
102
  private
78
103
 
104
+ # picks the authorization code in order, from:
105
+ # 1. the request param
106
+ # 2. a signed cookie
107
+ def with_authorization_code
108
+ if request.params.key?('code')
109
+ yield
110
+ else code_from_cookie = signed_request && signed_request['code']
111
+ request.params['code'] = code_from_cookie
112
+ @authorization_code_from_cookie = true
113
+ begin
114
+ yield
115
+ ensure
116
+ request.params.delete('code')
117
+ @authorization_code_from_cookie = false
118
+ end
119
+ end
120
+ end
121
+
79
122
  def prune!(hash)
80
123
  hash.delete_if do |_, value|
81
124
  prune!(value) if value.is_a?(Hash)
82
125
  value.nil? || (value.respond_to?(:empty?) && value.empty?)
83
126
  end
84
127
  end
128
+
129
+ def parse_signed_request(value)
130
+ signature, encoded_payload = value.split('.')
131
+
132
+ decoded_hex_signature = base64_decode_url(signature)#.unpack('H*')
133
+ decoded_payload = MultiJson.decode(base64_decode_url(encoded_payload))
134
+
135
+ unless decoded_payload['algorithm'] == 'HMAC-SHA256'
136
+ raise NotImplementedError, "unkown algorithm: #{decoded_payload['algorithm']}"
137
+ end
138
+
139
+ if valid_signature?(client.secret, decoded_hex_signature, encoded_payload)
140
+ decoded_payload
141
+ end
142
+ end
143
+
144
+ def valid_signature?(secret, signature, payload, algorithm = OpenSSL::Digest::SHA256.new)
145
+ OpenSSL::HMAC.digest(algorithm, secret, payload) == signature
146
+ end
147
+
148
+ def base64_decode_url(value)
149
+ value += '=' * (4 - value.size.modulo(4))
150
+ Base64.decode64(value.tr('-_', '+/'))
151
+ end
85
152
  end
86
153
  end
87
154
  end
@@ -1,14 +1,21 @@
1
1
  require 'spec_helper'
2
2
  require 'omniauth-facebook'
3
+ require 'openssl'
4
+ require 'base64'
3
5
 
4
6
  describe OmniAuth::Strategies::Facebook do
5
7
  before :each do
6
8
  @request = double('Request')
7
9
  @request.stub(:params) { {} }
10
+ @request.stub(:cookies) { {} }
11
+
12
+ @client_id = '123'
13
+ @client_secret = '53cr3tz'
8
14
  end
9
15
 
10
16
  subject do
11
- OmniAuth::Strategies::Facebook.new(nil, @options || {}).tap do |strategy|
17
+ args = [@client_id, @client_secret, @options].compact
18
+ OmniAuth::Strategies::Facebook.new(nil, *args).tap do |strategy|
12
19
  strategy.stub(:request) { @request }
13
20
  end
14
21
  end
@@ -29,6 +36,21 @@ describe OmniAuth::Strategies::Facebook do
29
36
  end
30
37
  end
31
38
 
39
+ 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)
44
+ end
45
+
46
+ it "callback_url from request" do
47
+ url_base = 'http://auth.request.com'
48
+ @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")
51
+ end
52
+ end
53
+
32
54
  describe '#authorize_params' do
33
55
  it 'includes default scope for email and offline access' do
34
56
  subject.authorize_params.should be_a(Hash)
@@ -256,4 +278,47 @@ describe OmniAuth::Strategies::Facebook do
256
278
  subject.extra.should eq({ 'raw_info' => @raw_info })
257
279
  end
258
280
  end
281
+
282
+ describe '#signed_request' do
283
+ context 'cookie not present' do
284
+ it 'is nil' do
285
+ subject.send(:signed_request).should be_nil
286
+ end
287
+ end
288
+
289
+ context 'cookie present' do
290
+ before :each do
291
+ @payload = {
292
+ 'algorithm' => 'HMAC-SHA256',
293
+ 'code' => 'm4c0d3z',
294
+ 'issued_at' => Time.now.to_i,
295
+ 'user_id' => '123456'
296
+ }
297
+
298
+ @request.stub(:cookies) do
299
+ { "fbsr_#{@client_id}" => signed_request(@payload, @client_secret) }
300
+ end
301
+ end
302
+
303
+ it 'parses the access code out from the cookie' do
304
+ subject.send(:signed_request).should eq(@payload)
305
+ end
306
+ end
307
+ end
308
+
309
+ private
310
+
311
+ def signed_request(payload, secret)
312
+ encoded_payload = base64_encode_url(MultiJson.encode(payload))
313
+ encoded_signature = base64_encode_url(signature(encoded_payload, secret))
314
+ [encoded_signature, encoded_payload].join('.')
315
+ end
316
+
317
+ def base64_encode_url(value)
318
+ Base64.encode64(value).tr('+/', '-_').gsub(/\n/, '')
319
+ end
320
+
321
+ def signature(payload, secret, algorithm = OpenSSL::Digest::SHA256.new)
322
+ OpenSSL::HMAC.digest(algorithm, secret, payload)
323
+ end
259
324
  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.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-19 00:00:00.000000000 Z
12
+ date: 2011-12-10 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: omniauth-oauth2
16
- requirement: &70230182116160 !ruby/object:Gem::Requirement
16
+ requirement: &70284008423900 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70230182116160
24
+ version_requirements: *70284008423900
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70230182114320 !ruby/object:Gem::Requirement
27
+ requirement: &70284008422620 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 2.7.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70230182114320
35
+ version_requirements: *70284008422620
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &70230182112600 !ruby/object:Gem::Requirement
38
+ requirement: &70284008421960 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70230182112600
46
+ version_requirements: *70284008421960
47
47
  description:
48
48
  email:
49
49
  - mark@mkdynamic.co.uk
@@ -80,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
80
80
  version: '0'
81
81
  segments:
82
82
  - 0
83
- hash: -728790844477512045
83
+ hash: -3067911989244546828
84
84
  required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  none: false
86
86
  requirements:
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  version: '0'
90
90
  segments:
91
91
  - 0
92
- hash: -728790844477512045
92
+ hash: -3067911989244546828
93
93
  requirements: []
94
94
  rubyforge_project:
95
95
  rubygems_version: 1.8.10