fbauth 0.9.9.9 → 1.0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.mdown CHANGED
@@ -1,54 +1,239 @@
1
- FBAuth
2
- ======
1
+ # FBAuth #
3
2
 
4
- This gem provides authentication and basic Facebook functions for your Rails application.
3
+ This gem provides authentication and basic Facebook functions for your
4
+ Rails application.
5
5
 
6
- The Authentication Challenge
7
- ----------------------------
6
+ ## The Authentication Challenge ##
8
7
 
9
- Facebook is an evolving platform, over the past couple years we've seen a lot of change in how it authenticates users of
10
- third-party applications.
8
+ Facebook is an evolving platform, over the past couple years we've seen
9
+ a lot of change in how it authenticates users of third-party
10
+ applications.
11
11
 
12
- And as of this writing, authentication with the Javascript SDK remains broken due to the reliance on cross-domain
13
- cookies, which are simply not supported in mobile Safari, by default on Safari for Windows, and reportedly on the
14
- Android Webkit based browser.
12
+ The documentation for Facebook is minimal, without historic reference or
13
+ in-depth discussion of the different implementation scenarios that our
14
+ applications may take.
15
15
 
16
- This plugin uses a few techniques to locate your "access token" and prefers to use the OAuth API to get what you need as
17
- a Facebook app to ensure your users have correctly added your app, authenticated in Facebook, and to communicate with
18
- the Graph API.
16
+ For example, the preferred method of doing "Canvas" applications, those
17
+ that live within the Facebook "wrapper" is now to use an iFrame. If you
18
+ choose to utilize the Javascript Connect SDK and rely on its
19
+ authentication mechanisms this will fail under any browsers that block
20
+ cross-domain cookies - which is more prominent as time goes on due to
21
+ the security risks involved in allowing cross-domain cookies.
22
+
23
+ Therefore, as of this writing, applications using the prescribed iFrame
24
+ style and using the JS SDK will fail on mobile Safari, Safari on
25
+ Windows' default settings, and various mobile implementations of the
26
+ Android browser.
27
+
28
+ This plugin uses a few techniques to locate your "access token" and
29
+ prefers to use the OAuth API to get what you need as a Facebook app to
30
+ ensure your users have correctly added your app, authenticated in
31
+ Facebook, and to communicate with the Graph and Query APIs.
19
32
 
20
33
  Here are the scenarios we currently handle:
21
34
 
22
- iFrame Apps
23
- -----------
35
+ __iFrame Apps__
24
36
 
25
- - first page load as an iFrame app inside Facebook, where authentication params are sent in the URL used for your iFrame
26
- - this is particularly required for mobile Safari and other browsers blocking cross-domain cookies by default
27
- - handles the old session parameter, as well as the new signed_request parameter
37
+ - first page load as an iFrame app inside Facebook, where authentication
38
+ params are sent in the URL used for your iFrame
39
+ - this is particularly required for mobile Safari and other browsers
40
+ blocking cross-domain cookies by default
41
+ - handles the old session parameter, as well as the new signed_request
42
+ parameter
28
43
 
29
44
  - loading from the cookie initialized by the JavaScript API
30
45
  - works great for browsers supporting cross-domain cookies by default
31
46
 
32
- - the access token you get is time-limited, if it has expired you need to be re-authenticated
47
+ - the access token you get is time-limited, if it has expired you need
48
+ to be re-authenticated
49
+
50
+ __External (Connect) Apps__
51
+
52
+ - handling an OAuth exchange back & forth with Facebook to handle
53
+ authentication and capture URL parameters back for token
54
+
55
+ ## Integration with Your Rails App ##
56
+
57
+ Add fbauth to your Gemfile
58
+
59
+ gem 'fbauth', '~> 1.0'
60
+
61
+ Create `config/facebook.yml`
62
+
63
+ development:
64
+ app_id: 'xxxxxxxxxxxxx'
65
+ app_context: 'my-app-dev'
66
+ auth_path: '/login'
67
+ canvas_url: 'http://dev.myapp.com/facebook'
68
+ app_secret: 'xxxxxxxxxxxx'
69
+
70
+ test:
71
+ app_id: 'fake_id'
72
+ app_context: 'my-app'
73
+ auth_path: '/login'
74
+ canvas_url: 'http://myapp.com/facebook'
75
+ app_secret: 'fake_secret'
76
+
77
+ production:
78
+ app_id: 'xxxxxxxxxxxxx'
79
+ app_context: 'my-app'
80
+ auth_path: '/login'
81
+ canvas_url: 'http://myapp.com/facebook'
82
+ app_secret: 'xxxxxxxxxxxx'
83
+
84
+ - `app_id` - this is your Facebook App ID
85
+ - `app_context` - this is your Canvas Page path, ie.
86
+ `http://apps.facebook.com/my-app-dev`
87
+ - `auth_path` - the path in your application to your login page (must be
88
+ a string, not a logical route name)
89
+ - `canvas_url` - this is the Facebook Canvas URL, the base URL that gets
90
+ to the Facebook iFrame pages for your Canvas app
91
+ - `app_secret` - the Facebook App Secret code for your application
92
+
93
+ Note: _We are assuming that you will be registering **two** facebook
94
+ applications, one that you will be using to actively develop your
95
+ application and the other for production use for your users._
96
+
97
+ Include the fbauth modules in your application controllers
98
+
99
+ include FacebookAuthFunctions
100
+ include FbauthHelper
101
+
102
+ In the controllers you want restricted to authorized users, or in your
103
+ `Application` controller, add the filters
104
+
105
+ before_filter :require_facebook_auth
106
+
107
+ Create the controller and method that will live at your `auth_path`
108
+ specified in your facebook.yml file.
109
+
110
+ class AuthController < ApplicationController
111
+
112
+ # Use this if you've included FacebookAuthFunctions in your
113
+ # Application controller...
114
+ # skip_before_filter :require_facebook_auth
115
+
116
+ def login
117
+ end
118
+ end
119
+
120
+ And then create your login template (we use HAML, but you can use ERB if
121
+ you want.
122
+
123
+ %div.fblogin
124
+ You are not logged in to Facebook, please click here:
125
+ %fb:login-button{:perms => 'publish_stream'} Log In to Facebook
126
+
127
+ %div.fbadd
128
+ Please add this Application - it's great!
129
+ %fb:login-button{:perms => 'publish_stream'} Add This Application
130
+
131
+ %div.fbready
132
+ Please wait...
33
133
 
34
- External (Connect) Apps
35
- -----------------------
134
+ = fbauth_login_javascript(:login => '.fblogin', :add => '.fbadd', :ready => '.fbready')
36
135
 
37
- - handling an OAuth exchange back & forth with Facebook to handle authentication and capture URL parameters back for
38
- token
136
+ The three areas noted above are exposed in the following scenarios:
39
137
 
40
- Things Remaining Unclear
41
- ========================
138
+ - `:login` - this element is exposed when the user has not logged into
139
+ Facebook, they may or may not have added your application but we don't
140
+ know that yet.
141
+ - `:add` - this element is exposed when the user is logged in to
142
+ Facebook but has not added / authorized your application. Note we are
143
+ requesting certain permissions when the app is added:
144
+ `<fb:login-button perms="publish_stream"></fb:login-button>`
145
+ - `:ready` - once the user has satisfied the pop-ups that appear when
146
+ logging in and/or adding the application, or if the user loads this
147
+ screen and is already fully authenticated, this element appears and
148
+ a redirect is done to your application root_path
42
149
 
43
- Documentation for the Facebook platform is a little fragmented, so we haven't (that we recall) come across the answers
44
- to these questions yet:
150
+ ## Using the Facebook APIs ##
45
151
 
46
- - what timezone is the OAuth token expiry value in? (we get it in Epoch, no TZ data, currently assuming San Francisco)
152
+ ### Graph API ###
153
+
154
+ The Graph API gives you the ability to request a great deal of
155
+ information about individual social relationships and perform basic
156
+ updates to that graph (ie. posting news-feed events).
157
+
158
+ Certain actions (posting changes, reading certain restricted attributes)
159
+ are only available if you provide an access token and make an
160
+ authenticated call.
161
+
162
+ __Unauthenticated Call__
163
+
164
+ graph = FacebookGraph.new
165
+ graph.call('svetzal')
166
+
167
+ This will retrieve publicly available information on the provided user's
168
+ Facebook UID or handle in a Ruby hash structure.
169
+
170
+ {"name"=>"Steven Vetzal", "gender"=>"male", "id"=>"849395216", "last_name"=>"Vetzal",
171
+ "locale"=>"en_US", "link"=>"http://www.facebook.com/svetzal", "first_name"=>"Steven"}
172
+
173
+ __Authenticated Call__
174
+
175
+ graph = FacebookGraph.new(fbauth.access_token)
176
+ graph.call('svetzal', { :scope => "birthday" })
177
+
178
+ This will make a similar call but as an authenticated caller we can
179
+ request a restricted scope, in this case information about the user's
180
+ birthday (if our app has requested the birthday permission from the
181
+ user).
182
+
183
+ ### Query API (FQL) ###
184
+
185
+ __Unauthenticated Call__
186
+
187
+ query = FacebookQuery.new
188
+ query.fql('SELECT name, first_name, last_name FROM user WHERE uid in ("849395216","58001611","1018515154")')
189
+
190
+ FQL is handy for performing multiple lookups at once, and can save you a
191
+ lot of latency.
192
+
193
+ [
194
+ {"name"=>"Steven Vetzal", "last_name"=>"Vetzal", "first_name"=>"Steven"},
195
+ {"name"=>"Nate Smith", "last_name"=>"Smith", "first_name"=>"Nate"},
196
+ {"name"=>"Craig Savolainen", "last_name"=>"Savolainen", "first_name"=>"Craig"}
197
+ ]
198
+
199
+ __Authenticated Call__
200
+
201
+ query = FacebookQuery.new(fbauth.access_token)
202
+
203
+ __Manually Building an Access Token__
204
+
205
+ If you scan your logs, you'll see the `signed_request` OAuth GET
206
+ parameter being sent to your application. You can manually build
207
+ yourself an authentication token from this using the following code.
208
+
209
+ data = FacebookDecoder.decode('{"signed_request"=>"oXWWpLi2tX7QW3cjX...(removed)"}')
210
+ fbauth = FacebookAuth.create(data)
211
+ fbauth.validate
212
+
213
+ The `.validate` step is optional, but pre-warns you if the token you are
214
+ about to use is good. If it is not, you can find out why by looking at
215
+ `fbauth.validation_error`.
216
+
217
+ # Things Remaining Unclear #
218
+
219
+ Documentation for the Facebook platform is a little fragmented, so we
220
+ haven't (that we recall) come across the answers to these questions yet:
221
+
222
+ - what timezone is the OAuth token expiry value in? (we get it in Epoch,
223
+ no TZ data, currently assuming San Francisco)
47
224
  - what happens when time approaches the OAuth token expiry?
48
225
  - do we get a new one?
49
226
  - are we expected to stop functioning and redirect to a FB login?
50
227
 
51
- Change Log
52
- ==========
228
+ # Change Log #
229
+
230
+ v1.0.0.0
231
+
232
+ - Changed call semantics for FacebookGraph and FacebookQuery,
233
+ use objects instead of class methods
234
+ - Preparing for public release
53
235
 
54
- v0.9.9.6 - Raising info-rich exceptions when errors returned from Facebook on graph calls
236
+ v0.9.9.6
237
+
238
+ - Raising info-rich exceptions when errors returned from
239
+ Facebook on graph calls
@@ -5,9 +5,6 @@ module FacebookAuthFunctions
5
5
  end
6
6
 
7
7
  def require_facebook_auth
8
- # Prep IE so it will take our cookies in a Facebook iFrame
9
- response.headers['P3P'] = 'CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'
10
-
11
8
  setup_facebook_auth
12
9
  if @facebook_auth.nil?
13
10
  redirect_to build_auth_url
@@ -21,6 +18,9 @@ private
21
18
  end
22
19
 
23
20
  def facebook_auth
21
+ # Prep IE so it will take our cookies in a Facebook iFrame
22
+ response.headers['P3P'] = 'CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'
23
+
24
24
  # If we have valid auth in session, use it
25
25
  data = parse_session
26
26
  auth = validate_and_save(data) unless data.nil?
@@ -70,6 +70,7 @@ private
70
70
  parms = JSON.parse(params[:session])
71
71
  logger.warn("Parsed facebook params from session parameter (deprecated)")
72
72
  elsif params[:signed_request].present?
73
+ logger.warn("Found signed_request param")
73
74
  begin
74
75
  parms = FacebookDecoder.decode(params[:signed_request])
75
76
  logger.warn("Parsed facebook params from signed_request parameter")
data/lib/facebook_auth.rb CHANGED
@@ -36,7 +36,7 @@ class FacebookAuth
36
36
  msgs = []
37
37
  unless self.uid.nil? || self.access_token.nil?
38
38
  begin
39
- self.user_data = FacebookGraph.call(self.uid, self.access_token)
39
+ self.user_data = FacebookGraph.new(self.access_token).call(self.uid)
40
40
  rescue => e
41
41
  msgs << "Error calling FacebookGraph - #{e}"
42
42
  end
@@ -2,56 +2,25 @@ require 'net/http'
2
2
  require 'uri'
3
3
 
4
4
  class FacebookGraph
5
+ include FacebookHttp
5
6
 
6
- def self.call(path, access_token = nil, options = {})
7
- params = []
8
- options.keys.each do |k|
9
- params << "#{k}=#{options[k]}" unless options[k].nil?
10
- end
11
- token = access_token ? CGI::escape(access_token) : nil
12
- url = "https://graph.facebook.com/#{path}&access_token=#{token}"
13
- url = "#{url}?#{params.join('&')}" unless params.empty?
14
- uri = URI.parse(url)
15
- http = Net::HTTP.new uri.host, uri.port
16
- begin
17
- http.use_ssl = (uri.scheme == "https")
18
- req = Net::HTTP::Get.new(uri.path)
19
- response = http.request(req)
20
- raise "Facebook error response #{response.code} - #{response.body}" unless response.code == '200'
21
- begin
22
- json = JSON.parse(response.body)
23
- rescue => e
24
- raise "Error parsing Facebook response: #{response.body}"
25
- end
26
- ensure
27
- http.finish if http.started?
28
- end
29
- json
7
+ FB_GRAPH_URL = "https://graph.facebook.com"
8
+
9
+ def initialize(access_token = nil, options = {})
10
+ @options = options.merge({ :access_token => access_token })
11
+ end
12
+
13
+ # Generic Graph call to lookup data for any path
14
+ def call(path, options = {})
15
+ get "#{FB_GRAPH_URL}/#{path}", merged_options(options)
30
16
  end
31
17
 
32
- # Available options:
33
- # message, picture, link, name, caption, description
34
- def self.publish_to_member_feed(uid, access_token, options)
35
- token = access_token ? CGI::escape(access_token) : nil
36
- if %w{staging production}.include? ENV['RAILS_ENV']
37
- url = "https://graph.facebook.com/#{uid}/feed&access_token=#{token}"
38
- uri = URI.parse(url)
39
- http = Net::HTTP.new uri.host, uri.port
40
- begin
41
- http.use_ssl = (uri.scheme == "https")
42
- req = Net::HTTP::Post.new(uri.path)
43
- req.set_form_data(options)
44
- response = http.request(req)
45
- raise "Facebook error response #{response.code} - #{response.body}" unless response.code == '200'
46
- begin
47
- json = JSON.parse(response.body)
48
- rescue => e
49
- raise "Error parsing Facebook response: #{response.body}"
50
- end
51
- ensure
52
- http.finish if http.started?
53
- end
54
- json
18
+ # Post item to member's wall
19
+ # Available options: message, picture, link, name, caption, description
20
+ def publish_to_member_feed(uid, options)
21
+ raise "access_token required" unless has_access_token?(options)
22
+ if %w{staging production}.include? Rails.env
23
+ post "#{FB_GRAPH_URL}/#{uid}/feed", merged_options(options)
55
24
  end
56
25
  end
57
26
  end
@@ -0,0 +1,87 @@
1
+ require 'benchmark'
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'cgi'
5
+
6
+ module FacebookHttp
7
+
8
+ def build_get_url(url, params = {})
9
+ q = build_query_string(params)
10
+ if q
11
+ url + q
12
+ else
13
+ url
14
+ end
15
+ end
16
+
17
+ def get(url, params = {})
18
+ json = nil
19
+ uri = URI.parse(build_get_url(url, params))
20
+ bench = Benchmark.measure do
21
+ http = Net::HTTP.new uri.host, uri.port
22
+ begin
23
+ http.use_ssl = (uri.scheme == "https")
24
+ req = Net::HTTP::Get.new(uri.request_uri)
25
+ response = http.request(req)
26
+ raise "Facebook error response #{response.code} - #{response.body}" unless response.code == '200'
27
+ begin
28
+ json = JSON.parse(response.body)
29
+ rescue => e
30
+ raise "Error parsing facebook response: #{response.body}"
31
+ end
32
+ ensure
33
+ http.finish if http.started?
34
+ end
35
+ end
36
+ logger.warn("Facebook GET call to #{uri.to_s} completed in #{bench.total} seconds")
37
+ json
38
+ end
39
+
40
+ def post(url, params = {})
41
+ json = nil
42
+ uri = URI.parse(url)
43
+ bench = Benchmark.measure do
44
+ http = Net::HTTP.new uri.host, uri.port
45
+ begin
46
+ http.use_ssl = (uri.scheme == "https")
47
+ req = Net::HTTP::Post.new(uri.path)
48
+ req.set_form_data(params)
49
+ response = http.request(req)
50
+ raise "Facebook error response #{response.code} - #{response.body}" unless response.code == '200'
51
+ begin
52
+ json = JSON.parse(response.body)
53
+ rescue => e
54
+ raise "Error parsing Facebook response: #{response.body}"
55
+ end
56
+ ensure
57
+ http.finish if http.started?
58
+ end
59
+ end
60
+ logger.warn("Facebook POST call to #{uri.to_s} completed in #{bench.total} seconds")
61
+ json
62
+ end
63
+
64
+ def build_query_string options={}
65
+ params = []
66
+ str_keys = options.keys.collect{ |k| k.to_s }
67
+ str_keys.sort.each do |str_key|
68
+ key = str_key.to_sym
69
+ value = options[key]
70
+ params << "#{key.to_s}=#{URI.escape(value.to_s)}" unless value.nil?
71
+ end
72
+ "?#{params.join('&')}" unless params.empty?
73
+ end
74
+
75
+ def merged_options(options = {})
76
+ options.merge!(@options) if @options
77
+ options
78
+ end
79
+
80
+ def has_access_token?(options = {})
81
+ merged_options.has_key? :access_token
82
+ end
83
+
84
+ def logger
85
+ Rails.logger
86
+ end
87
+ end
@@ -0,0 +1,16 @@
1
+ require 'uri'
2
+
3
+ class FacebookQuery
4
+ include FacebookHttp
5
+
6
+ FB_API_URL = "https://api.facebook.com/method/fql.query"
7
+
8
+ def initialize(access_token = nil, options = {})
9
+ @options = options.merge({ :access_token => access_token })
10
+ @options.merge!({ :format => "JSON" }) unless @options.has_key?(:format)
11
+ end
12
+
13
+ def fql(query, options = {})
14
+ get FB_API_URL, merged_options(options.merge({ :query => query }))
15
+ end
16
+ end
data/lib/fbauth.rb CHANGED
@@ -12,4 +12,6 @@ end
12
12
  require 'facebook_decoder.rb'
13
13
  require 'facebook_auth.rb'
14
14
  require 'facebook_config.rb'
15
+ require 'facebook_http.rb'
15
16
  require 'facebook_graph.rb'
17
+ require 'facebook_query.rb'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fbauth
3
3
  version: !ruby/object:Gem::Version
4
- hash: 49
4
+ hash: 95
5
5
  prerelease: false
6
6
  segments:
7
+ - 1
7
8
  - 0
8
- - 9
9
- - 9
10
- - 9
11
- version: 0.9.9.9
9
+ - 0
10
+ - 0
11
+ version: 1.0.0.0
12
12
  platform: ruby
13
13
  authors:
14
14
  - Three Wise Men Inc.
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-02-28 00:00:00 -05:00
19
+ date: 2011-03-03 00:00:00 -05:00
20
20
  default_executable:
21
21
  dependencies: []
22
22
 
@@ -33,6 +33,8 @@ files:
33
33
  - lib/facebook_config.rb
34
34
  - lib/facebook_decoder.rb
35
35
  - lib/facebook_graph.rb
36
+ - lib/facebook_http.rb
37
+ - lib/facebook_query.rb
36
38
  - lib/fbauth.rb
37
39
  - app/controllers/facebook_auth_functions.rb
38
40
  - app/helpers/fbauth_helper.rb