fbdoorman 0.0.1

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.
Files changed (42) hide show
  1. data/CHANGELOG.md +3 -0
  2. data/LICENSE +21 -0
  3. data/README.md +142 -0
  4. data/Rakefile +27 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/clearance/confirmations_controller.rb +76 -0
  7. data/app/controllers/clearance/facebook_controller.rb +66 -0
  8. data/app/controllers/clearance/passwords_controller.rb +85 -0
  9. data/app/controllers/clearance/sessions_controller.rb +67 -0
  10. data/app/controllers/clearance/users_controller.rb +36 -0
  11. data/app/models/clearance_mailer.rb +21 -0
  12. data/app/views/clearance_mailer/change_password.html.erb +9 -0
  13. data/app/views/clearance_mailer/confirmation.html.erb +5 -0
  14. data/app/views/facebook/_fbjs.html.erb +14 -0
  15. data/app/views/facebook/closed.html.erb +1 -0
  16. data/app/views/passwords/edit.html.erb +23 -0
  17. data/app/views/passwords/new.html.erb +15 -0
  18. data/app/views/sessions/new.html.erb +25 -0
  19. data/app/views/users/_form.html.erb +13 -0
  20. data/app/views/users/new.html.erb +6 -0
  21. data/generators/fbdoorman/USAGE +1 -0
  22. data/generators/fbdoorman/fbdoorman_generator.rb +68 -0
  23. data/generators/fbdoorman/lib/insert_commands.rb +33 -0
  24. data/generators/fbdoorman/lib/rake_commands.rb +22 -0
  25. data/generators/fbdoorman/templates/README +43 -0
  26. data/generators/fbdoorman/templates/clearance.rb +3 -0
  27. data/generators/fbdoorman/templates/facebook.yml +7 -0
  28. data/generators/fbdoorman/templates/factories.rb +13 -0
  29. data/generators/fbdoorman/templates/migrations/create_users.rb +24 -0
  30. data/generators/fbdoorman/templates/migrations/update_users.rb +44 -0
  31. data/generators/fbdoorman/templates/user.rb +3 -0
  32. data/lib/clearance/authentication.rb +143 -0
  33. data/lib/clearance/configuration.rb +25 -0
  34. data/lib/clearance/extensions/errors.rb +6 -0
  35. data/lib/clearance/extensions/rescue.rb +5 -0
  36. data/lib/clearance/routes.rb +55 -0
  37. data/lib/clearance/user.rb +207 -0
  38. data/lib/facebook_helpers.rb +48 -0
  39. data/lib/fbdoorman.rb +27 -0
  40. data/lib/mini_fb.rb +673 -0
  41. data/rails/init.rb +1 -0
  42. metadata +110 -0
data/lib/mini_fb.rb ADDED
@@ -0,0 +1,673 @@
1
+ #MiniFB - the simple miniature facebook library
2
+ #MiniFB is a small, lightweight Ruby library for interacting with the Facebook API.
3
+ #
4
+ #Brought to you by: www.appoxy.com
5
+ #
6
+ #Support
7
+ #
8
+ #Join our Discussion Group at: http://groups.google.com/group/mini_fb
9
+ #
10
+ #Demo Rails Application
11
+ #
12
+ #There is a demo Rails app that uses mini_fb graph api at: http://github.com/appoxy/mini_fb_demo
13
+
14
+ require 'digest/md5'
15
+ require 'erb'
16
+ require 'json' unless defined? JSON
17
+ require 'rest_client'
18
+ require 'hashie'
19
+
20
+ module MiniFB
21
+
22
+ # Global constants
23
+ FB_URL = "http://api.facebook.com/restserver.php"
24
+ FB_API_VERSION = "1.0"
25
+ #Define here the values for your application
26
+ #FB_URL = "http://api.facebook.com/restserver.php"
27
+ #FB_API_VERSION = "1.0"
28
+
29
+ @@logging = false
30
+
31
+ def self.enable_logging
32
+ @@logging = true
33
+ end
34
+
35
+ def self.disable_logging
36
+ @@logging = false
37
+ end
38
+
39
+ class FaceBookError < StandardError
40
+ attr_accessor :code
41
+ # Error that happens during a facebook call.
42
+ def initialize(error_code, error_msg)
43
+ @code = error_code
44
+ super("Facebook error #{error_code}: #{error_msg}")
45
+ end
46
+ end
47
+
48
+ class Session
49
+ attr_accessor :api_key, :secret_key, :session_key, :uid
50
+
51
+
52
+ def initialize(api_key, secret_key, session_key, uid)
53
+ @api_key = api_key
54
+ @secret_key = FaceBookSecret.new secret_key
55
+ @session_key = session_key
56
+ @uid = uid
57
+ end
58
+
59
+ # returns current user
60
+ def user
61
+ return @user unless @user.nil?
62
+ @user = User.new(MiniFB.call(@api_key, @secret_key, "Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)[0], self)
63
+ @user
64
+ end
65
+
66
+ def photos
67
+ Photos.new(self)
68
+ end
69
+
70
+
71
+ def call(method, params={})
72
+ return MiniFB.call(api_key, secret_key, method, params.update("session_key"=>session_key))
73
+ end
74
+
75
+ end
76
+
77
+ class User
78
+ FIELDS = [:uid, :status, :political, :pic_small, :name, :quotes, :is_app_user, :tv, :profile_update_time, :meeting_sex, :hs_info, :timezone, :relationship_status, :hometown_location, :about_me, :wall_count, :significant_other_id, :pic_big, :music, :work_history, :sex, :religion, :notes_count, :activities, :pic_square, :movies, :has_added_app, :education_history, :birthday, :birthday_date, :first_name, :meeting_for, :last_name, :interests, :current_location, :pic, :books, :affiliations, :locale, :profile_url, :proxied_email, :email, :email_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo, :pic_small_with_logo, :pic_square_with_logo]
79
+ STANDARD_FIELDS = [:uid, :first_name, :last_name, :name, :timezone, :birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email, :email]
80
+
81
+ def self.all_fields
82
+ FIELDS.join(",")
83
+ end
84
+
85
+ def self.standard_fields
86
+ STANDARD_FIELDS.join(",")
87
+ end
88
+
89
+ def initialize(fb_hash, session)
90
+ @fb_hash = fb_hash
91
+ @session = session
92
+ end
93
+
94
+ def [](key)
95
+ @fb_hash[key]
96
+ end
97
+
98
+ def uid
99
+ return self["uid"]
100
+ end
101
+
102
+ def profile_photos
103
+ @session.photos.get("uid"=>uid, "aid"=>profile_pic_album_id)
104
+ end
105
+
106
+ def profile_pic_album_id
107
+ merge_aid(-3, uid)
108
+ end
109
+
110
+ def merge_aid(aid, uid)
111
+ uid = uid.to_i
112
+ ret = (uid << 32) + (aid & 0xFFFFFFFF)
113
+ # puts 'merge_aid=' + ret.inspect
114
+ return ret
115
+ end
116
+ end
117
+
118
+ class Photos
119
+
120
+ def initialize(session)
121
+ @session = session
122
+ end
123
+
124
+ def get(params)
125
+ pids = params["pids"]
126
+ if !pids.nil? && pids.is_a?(Array)
127
+ pids = pids.join(",")
128
+ params["pids"] = pids
129
+ end
130
+ @session.call("photos.get", params)
131
+ end
132
+ end
133
+
134
+ BAD_JSON_METHODS = ["users.getloggedinuser", "auth.promotesession", "users.hasapppermission",
135
+ "Auth.revokeExtendedPermission", "pages.isAdmin", "pages.isFan",
136
+ "stream.publish",
137
+ "dashboard.addNews", "dashboard.addGlobalNews", "dashboard.publishActivity",
138
+ "dashboard.incrementcount", "dashboard.setcount"
139
+ ].collect { |x| x.downcase }
140
+
141
+ # Call facebook server with a method request. Most keyword arguments
142
+ # are passed directly to the server with a few exceptions.
143
+ # The 'sig' value will always be computed automatically.
144
+ # The 'v' version will be supplied automatically if needed.
145
+ # The 'call_id' defaults to True, which will generate a valid
146
+ # number. Otherwise it should be a valid number or False to disable.
147
+
148
+ # The default return is a parsed json object.
149
+ # Unless the 'format' and/or 'callback' arguments are given,
150
+ # in which case the raw text of the reply is returned. The string
151
+ # will always be returned, even during errors.
152
+
153
+ # If an error occurs, a FacebookError exception will be raised
154
+ # with the proper code and message.
155
+
156
+ # The secret argument should be an instance of FacebookSecret
157
+ # to hide value from simple introspection.
158
+ def MiniFB.call(api_key, secret, method, kwargs)
159
+
160
+ puts 'kwargs=' + kwargs.inspect if @@logging
161
+
162
+ if secret.is_a? String
163
+ secret = FaceBookSecret.new(secret)
164
+ end
165
+
166
+ # Prepare arguments for call
167
+ call_id = kwargs.fetch("call_id", true)
168
+ if call_id == true
169
+ kwargs["call_id"] = Time.now.tv_sec.to_s
170
+ else
171
+ kwargs.delete("call_id")
172
+ end
173
+
174
+ custom_format = kwargs.include?("format") || kwargs.include?("callback")
175
+ kwargs["format"] ||= "JSON"
176
+ kwargs["v"] ||= FB_API_VERSION
177
+ kwargs["api_key"]||= api_key
178
+ kwargs["method"] ||= method
179
+
180
+ file_name = kwargs.delete("filename")
181
+
182
+ kwargs["sig"] = signature_for(kwargs, secret.value.call)
183
+
184
+ fb_method = kwargs["method"].downcase
185
+ if fb_method == "photos.upload"
186
+ # Then we need a multipart post
187
+ response = MiniFB.post_upload(file_name, kwargs)
188
+ else
189
+
190
+ begin
191
+ response = Net::HTTP.post_form(URI.parse(FB_URL), post_params(kwargs))
192
+ rescue SocketError => err
193
+ # why are we catching this and throwing as different error? hmmm..
194
+ # raise IOError.new( "Cannot connect to the facebook server: " + err )
195
+ raise err
196
+ end
197
+ end
198
+
199
+ # Handle response
200
+ return response.body if custom_format
201
+
202
+ body = response.body
203
+
204
+ puts 'response=' + body.inspect if @@logging
205
+ begin
206
+ data = JSON.parse(body)
207
+ if data.include?("error_msg")
208
+ raise FaceBookError.new(data["error_code"] || 1, data["error_msg"])
209
+ end
210
+
211
+ rescue JSON::ParserError => ex
212
+ if BAD_JSON_METHODS.include?(fb_method) # Little hack because this response isn't valid JSON
213
+ if body == "0" || body == "false"
214
+ return false
215
+ end
216
+ return body
217
+ else
218
+ raise ex
219
+ end
220
+ end
221
+ return data
222
+ end
223
+
224
+ def MiniFB.post_upload(filename, kwargs)
225
+ content = File.open(filename, 'rb') { |f| f.read }
226
+ boundary = Digest::MD5.hexdigest(content)
227
+ header = {'Content-type' => "multipart/form-data, boundary=#{boundary}"}
228
+
229
+ # Build query
230
+ query = ''
231
+ kwargs.each { |a, v|
232
+ query <<
233
+ "--#{boundary}\r\n" <<
234
+ "Content-Disposition: form-data; name=\"#{a}\"\r\n\r\n" <<
235
+ "#{v}\r\n"
236
+ }
237
+ query <<
238
+ "--#{boundary}\r\n" <<
239
+ "Content-Disposition: form-data; filename=\"#{File.basename(filename)}\"\r\n" <<
240
+ "Content-Transfer-Encoding: binary\r\n" <<
241
+ "Content-Type: image/jpeg\r\n\r\n" <<
242
+ content <<
243
+ "\r\n" <<
244
+ "--#{boundary}--"
245
+
246
+ # Call Facebook with POST multipart/form-data request
247
+ uri = URI.parse(FB_URL)
248
+ Net::HTTP.start(uri.host) { |http| http.post uri.path, query, header }
249
+ end
250
+
251
+ # Returns true is signature is valid, false otherwise.
252
+ def MiniFB.verify_signature(secret, arguments)
253
+ signature = arguments.delete("fb_sig")
254
+ return false if signature.nil?
255
+
256
+ unsigned = Hash.new
257
+ signed = Hash.new
258
+
259
+ arguments.each do |k, v|
260
+ if k =~ /^fb_sig_(.*)/ then
261
+ signed[$1] = v
262
+ else
263
+ unsigned[k] = v
264
+ end
265
+ end
266
+
267
+ arg_string = String.new
268
+ signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
269
+ if Digest::MD5.hexdigest(arg_string + secret) == signature
270
+ return true
271
+ end
272
+ return false
273
+ end
274
+
275
+ # Parses cookies in order to extract the facebook cookie and parse it into a useable hash
276
+ #
277
+ # options:
278
+ # * app_id - the connect applications app_id (some users may find they have to use their facebook API key)
279
+ # * secret - the connect application secret
280
+ # * cookies - the cookies given by facebook - it is ok to just pass all of the cookies, the method will do the filtering for you.
281
+ def MiniFB.parse_cookie_information(app_id, cookies)
282
+ return nil if cookies["fbs_#{app_id}"].nil?
283
+ Hash[*cookies["fbs_#{app_id}"].split('&').map { |v| v.gsub('"', '').split('=', 2) }.flatten]
284
+ end
285
+
286
+ # Validates that the cookies sent by the user are those that were set by facebook. Since your
287
+ # secret is only known by you and facebook it is used to sign all of the cookies set.
288
+ #
289
+ # options:
290
+ # * app_id - the connect applications app_id (some users may find they have to use their facebook API key)
291
+ # * secret - the connect application secret
292
+ # * cookies - the cookies given by facebook - it is ok to just pass all of the cookies, the method will do the filtering for you.
293
+ def MiniFB.verify_cookie_signature(app_id, secret, cookies)
294
+ fb_keys = MiniFB.parse_cookie_information(app_id, cookies)
295
+ return false if fb_keys.nil?
296
+
297
+ signature = fb_keys.delete('sig')
298
+ return signature == Digest::MD5.hexdigest(fb_keys.map { |k, v| "#{k}=#{v}" }.sort.join + secret)
299
+ end
300
+
301
+ # <b>DEPRECATED:</b> Please use <tt>verify_cookie_signature</tt> instead.
302
+ def MiniFB.verify_connect_signature(api_key, secret, cookies)
303
+ warn "DEPRECATION WARNING: 'verify_connect_signature' has been renamed to 'verify_cookie_signature' as Facebook no longer calls this 'connect'"
304
+ MiniFB.verify_cookie_signature(api_key, secret, cookies)
305
+ end
306
+
307
+ # Returns the login/add app url for your application.
308
+ #
309
+ # options:
310
+ # - :next => a relative next page to go to. relative to your facebook connect url or if :canvas is true, then relative to facebook app url
311
+ # - :canvas => true/false - to say whether this is a canvas app or not
312
+ def self.login_url(api_key, options={})
313
+ login_url = "http://api.facebook.com/login.php?api_key=#{api_key}"
314
+ login_url << "&next=#{options[:next]}" if options[:next]
315
+ login_url << "&canvas" if options[:canvas]
316
+ login_url
317
+ end
318
+
319
+ # Manages access_token and locale params for an OAuth connection
320
+ class OAuthSession
321
+
322
+ def initialize(access_token, locale="en_US")
323
+ @access_token = access_token
324
+ @locale = locale
325
+ end
326
+
327
+ def get(id, options={})
328
+ MiniFB.get(@access_token, id, session_options(options))
329
+ end
330
+
331
+ def post(id, options={})
332
+ MiniFB.post(@access_token, id, session_options(options))
333
+ end
334
+
335
+ def fql(fql_query, options={})
336
+ MiniFB.fql(@access_token, fql_query, session_options(options))
337
+ end
338
+
339
+ def multifql(fql_queries, options={})
340
+ MiniFB.multifql(@access_token, fql_queries, session_options(options))
341
+ end
342
+
343
+ def rest(api_method, options={})
344
+ MiniFB.rest(@access_token, api_method, session_options(options))
345
+ end
346
+
347
+ # Returns a GraphObject for the given id
348
+ def graph_object(id)
349
+ MiniFB::GraphObject.new(self, id)
350
+ end
351
+
352
+ # Returns and caches a GraphObject for the user
353
+ def me
354
+ @me ||= graph_object('me')
355
+ end
356
+
357
+ private
358
+ def session_options(options)
359
+ (options[:params] ||= {})[:locale] ||= @locale
360
+ options
361
+ end
362
+ end
363
+
364
+ # Wraps a graph object for easily accessing its connections
365
+ class GraphObject
366
+ # Creates a GraphObject using an OAuthSession or access_token
367
+ def initialize(session_or_token, id)
368
+ @oauth_session = if session_or_token.is_a?(MiniFB::OAuthSession)
369
+ session_or_token
370
+ else
371
+ MiniFB::OAuthSession.new(session_or_token)
372
+ end
373
+ @id = id
374
+ @object = @oauth_session.get(id, :metadata => true)
375
+ @connections_cache = {}
376
+ end
377
+
378
+ def inspect
379
+ "<##{self.class.name} #{@object.inspect}>"
380
+ end
381
+
382
+ def connections
383
+ @object.metadata.connections.keys
384
+ end
385
+
386
+ unless RUBY_VERSION =~ /1\.9/
387
+ undef :id, :type
388
+ end
389
+
390
+ def methods
391
+ super + @object.keys.include?(key) + connections.include?(key)
392
+ end
393
+
394
+ def respond_to?(method)
395
+ @object.keys.include?(key) || connections.include?(key) || super
396
+ end
397
+
398
+ def keys
399
+ @object.keys
400
+ end
401
+
402
+ def [](key)
403
+ @object[key]
404
+ end
405
+
406
+ def method_missing(method, *args, &block)
407
+ key = method.to_s
408
+ if @object.keys.include?(key)
409
+ @object[key]
410
+ elsif @connections_cache.has_key?(key)
411
+ @connections_cache[key]
412
+ elsif connections.include?(key)
413
+ @connections_cache[key] = @oauth_session.get(@id, :type => key)
414
+ else
415
+ super
416
+ end
417
+ end
418
+ end
419
+
420
+ def self.graph_base
421
+ "https://graph.facebook.com/"
422
+ end
423
+
424
+ # options:
425
+ # - scope: comma separated list of extends permissions. see http://developers.facebook.com/docs/authentication/permissions
426
+ def self.oauth_url(app_id, redirect_uri, options={})
427
+ oauth_url = "#{graph_base}oauth/authorize"
428
+ oauth_url << "?client_id=#{app_id}"
429
+ oauth_url << "&redirect_uri=#{URI.escape(redirect_uri)}"
430
+ # oauth_url << "&scope=#{options[:scope]}" if options[:scope]
431
+ oauth_url << ("&" + options.map { |k, v| "%s=%s" % [k, v] }.join('&')) unless options.empty?
432
+ oauth_url
433
+ end
434
+
435
+ # returns a hash with one value being 'access_token', the other being 'expires'
436
+ def self.oauth_access_token(app_id, redirect_uri, secret, code)
437
+ oauth_url = "#{graph_base}oauth/access_token"
438
+ oauth_url << "?client_id=#{app_id}"
439
+ oauth_url << "&redirect_uri=#{URI.escape(redirect_uri)}"
440
+ oauth_url << "&client_secret=#{secret}"
441
+ oauth_url << "&code=#{URI.escape(code)}"
442
+ resp = RestClient.get oauth_url
443
+ puts 'resp=' + resp.body.to_s if @@logging
444
+ params = {}
445
+ params_array = resp.split("&")
446
+ params_array.each do |p|
447
+ ps = p.split("=")
448
+ params[ps[0]] = ps[1]
449
+ end
450
+ return params
451
+ end
452
+
453
+ # Gets data from the Facebook Graph API
454
+ # options:
455
+ # - type: eg: feed, home, etc
456
+ # - metadata: to include metadata in response. true/false
457
+ # - params: Any additional parameters you would like to submit
458
+ def self.get(access_token, id, options={})
459
+ url = "#{graph_base}#{id}"
460
+ url << "/#{options[:type]}" if options[:type]
461
+ params = options[:params] || {}
462
+ params["access_token"] = "#{(access_token)}"
463
+ params["metadata"] = "1" if options[:metadata]
464
+ options[:params] = params
465
+ return fetch(url, options)
466
+ end
467
+
468
+ # Posts data to the Facebook Graph API
469
+ # options:
470
+ # - type: eg: feed, home, etc
471
+ # - metadata: to include metadata in response. true/false
472
+ # - params: Any additional parameters you would like to submit
473
+ def self.post(access_token, id, options={})
474
+ url = "#{graph_base}#{id}"
475
+ url << "/#{options[:type]}" if options[:type]
476
+ options.delete(:type)
477
+ params = options[:params] || {}
478
+ options.each do |key, value|
479
+ if value.kind_of?(File)
480
+ params[key] = value
481
+ else
482
+ params[key] = "#{value}"
483
+ end
484
+ end
485
+ params["access_token"] = "#{(access_token)}"
486
+ params["metadata"] = "1" if options[:metadata]
487
+ options[:params] = params
488
+ options[:method] = :post
489
+ return fetch(url, options)
490
+
491
+ end
492
+
493
+ # Executes an FQL query
494
+ def self.fql(access_token, fql_query, options={})
495
+ url = "https://api.facebook.com/method/fql.query"
496
+ params = options[:params] || {}
497
+ params["access_token"] = "#{(access_token)}"
498
+ params["metadata"] = "1" if options[:metadata]
499
+ params["query"] = fql_query
500
+ params["format"] = "JSON"
501
+ options[:params] = params
502
+ return fetch(url, options)
503
+ end
504
+
505
+ # Executes multiple FQL queries
506
+ # Example:
507
+ #
508
+ # MiniFB.multifql(access_token, { :statuses => "SELECT status_id, message FROM status WHERE uid = 12345",
509
+ # :privacy => "SELECT object_id, description FROM privacy WHERE object_id IN (SELECT status_id FROM #statuses)" })
510
+ def self.multifql(access_token, fql_queries, options={})
511
+ url = "https://api.facebook.com/method/fql.multiquery"
512
+ params = options[:params] || {}
513
+ params["access_token"] = "#{(access_token)}"
514
+ params["metadata"] = "1" if options[:metadata]
515
+ params["queries"] = JSON[fql_queries]
516
+ params[:format] = "JSON"
517
+ options[:params] = params
518
+ return fetch(url, options)
519
+ end
520
+
521
+ # Uses new Oauth 2 authentication against old Facebook REST API
522
+ # options:
523
+ # - params: Any additional parameters you would like to submit
524
+ def self.rest(access_token, api_method, options={})
525
+ url = "https://api.facebook.com/method/#{api_method}"
526
+ params = options[:params] || {}
527
+ params[:access_token] = access_token
528
+ params[:format] = "JSON"
529
+ options[:params] = params
530
+ return fetch(url, options)
531
+ end
532
+
533
+
534
+ def self.fetch(url, options={})
535
+
536
+ begin
537
+ if options[:method] == :post
538
+ puts 'url_post=' + url if @@logging
539
+ resp = RestClient.post url, options[:params]
540
+ else
541
+ if options[:params] && options[:params].size > 0
542
+ url += '?' + options[:params].map { |k, v| URI.escape("%s=%s" % [k, v]) }.join('&')
543
+ end
544
+ puts 'url_get=' + url if @@logging
545
+ resp = RestClient.get url
546
+ end
547
+
548
+ puts 'resp=' + resp.to_s if @@logging
549
+
550
+ begin
551
+ res_hash = JSON.parse(resp.to_s)
552
+ rescue
553
+ # quick fix for things like stream.publish that don't return json
554
+ res_hash = JSON.parse("{\"response\": #{resp.to_s}}")
555
+ end
556
+
557
+ if res_hash.is_a? Array # fql return this
558
+ res_hash.collect! { |x| Hashie::Mash.new(x) }
559
+ else
560
+ res_hash = Hashie::Mash.new(res_hash)
561
+ end
562
+
563
+ if res_hash.include?("error_msg")
564
+ raise FaceBookError.new(res_hash["error_code"] || 1, res_hash["error_msg"])
565
+ end
566
+
567
+ return res_hash
568
+ rescue RestClient::Exception => ex
569
+ puts ex.http_code.to_s
570
+ puts 'ex.http_body=' + ex.http_body if @@logging
571
+ res_hash = JSON.parse(ex.http_body) # probably should ensure it has a good response
572
+ raise MiniFB::FaceBookError.new(ex.http_code, "#{res_hash["error"]["type"]}: #{res_hash["error"]["message"]}")
573
+ end
574
+
575
+ end
576
+
577
+ # Returns all available scopes.
578
+ def self.scopes
579
+ scopes = %w{
580
+ about_me activities birthday education_history events groups
581
+ hometown interests likes location notes online_presence
582
+ photo_video_tags photos relationships religion_politics
583
+ status videos website work_history
584
+ }
585
+ scopes.map! do |scope|
586
+ ["user_#{scope}", "friends_#{scope}"]
587
+ end.flatten!
588
+
589
+ scopes += %w{
590
+ read_insights read_stream read_mailbox read_friendlists read_requests
591
+ email ads_management xmpp_login
592
+ publish_stream create_event rsvp_event sms offline_access
593
+ }
594
+ end
595
+
596
+ # This function expects arguments as a hash, so
597
+ # it is agnostic to different POST handling variants in ruby.
598
+ #
599
+ # Validate the arguments received from facebook. This is usually
600
+ # sent for the iframe in Facebook's canvas. It is not necessary
601
+ # to use this on the auth_token and uid passed to callbacks like
602
+ # post-add and post-remove.
603
+ #
604
+ # The arguments must be a mapping of to string keys and values
605
+ # or a string of http request data.
606
+ #
607
+ # If the data is invalid or not signed properly, an empty
608
+ # dictionary is returned.
609
+ #
610
+ # The secret argument should be an instance of FacebookSecret
611
+ # to hide value from simple introspection.
612
+ #
613
+ # DEPRECATED, use verify_signature instead
614
+ def MiniFB.validate(secret, arguments)
615
+
616
+ signature = arguments.delete("fb_sig")
617
+ return arguments if signature.nil?
618
+
619
+ unsigned = Hash.new
620
+ signed = Hash.new
621
+
622
+ arguments.each do |k, v|
623
+ if k =~ /^fb_sig_(.*)/ then
624
+ signed[$1] = v
625
+ else
626
+ unsigned[k] = v
627
+ end
628
+ end
629
+
630
+ arg_string = String.new
631
+ signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
632
+ if Digest::MD5.hexdigest(arg_string + secret) != signature
633
+ unsigned # Hash is incorrect, return only unsigned fields.
634
+ else
635
+ unsigned.merge signed
636
+ end
637
+ end
638
+
639
+ class FaceBookSecret
640
+ # Simple container that stores a secret value.
641
+ # Proc cannot be dumped or introspected by normal tools.
642
+ attr_reader :value
643
+
644
+ def initialize(value)
645
+ @value = Proc.new { value }
646
+ end
647
+ end
648
+
649
+ private
650
+ def self.post_params(params)
651
+ post_params = {}
652
+ params.each do |k, v|
653
+ k = k.to_s unless k.is_a?(String)
654
+ if Array === v || Hash === v
655
+ post_params[k] = JSON.dump(v)
656
+ else
657
+ post_params[k] = v
658
+ end
659
+ end
660
+ post_params
661
+ end
662
+
663
+ def self.signature_for(params, secret)
664
+ params.delete_if { |k, v| v.nil? }
665
+ raw_string = params.inject([]) do |collection, pair|
666
+ collection << pair.map { |x|
667
+ Array === x ? JSON.dump(x) : x
668
+ }.join("=")
669
+ collection
670
+ end.sort.join
671
+ Digest::MD5.hexdigest([raw_string, secret].join)
672
+ end
673
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'clearance'