omniauth-facebook 1.0.0 → 1.1.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/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