fbdoorman 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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'