mini_fb 1.1.3 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mini_fb.rb +136 -30
- data/test/test_mini_fb.rb +15 -2
- metadata +3 -9
data/lib/mini_fb.rb
CHANGED
@@ -16,6 +16,9 @@ require 'erb'
|
|
16
16
|
require 'json' unless defined? JSON
|
17
17
|
require 'rest_client'
|
18
18
|
require 'hashie'
|
19
|
+
require 'base64'
|
20
|
+
require 'openssl'
|
21
|
+
require 'logger'
|
19
22
|
|
20
23
|
module MiniFB
|
21
24
|
|
@@ -24,13 +27,35 @@ module MiniFB
|
|
24
27
|
FB_API_VERSION = "1.0"
|
25
28
|
|
26
29
|
@@logging = false
|
30
|
+
@@log = Logger.new(STDOUT)
|
31
|
+
|
32
|
+
def self.log_level=(level)
|
33
|
+
if level.is_a? Numeric
|
34
|
+
@@log.level = level
|
35
|
+
else
|
36
|
+
@@log.level = case level
|
37
|
+
when :fatal
|
38
|
+
@@log.level = Logger::FATAL
|
39
|
+
when :error
|
40
|
+
@@log.level = Logger::ERROR
|
41
|
+
when :warn
|
42
|
+
@@log.level = Logger::WARN
|
43
|
+
when :info
|
44
|
+
@@log.level = Logger::INFO
|
45
|
+
when :debug
|
46
|
+
@@log.level = Logger::DEBUG
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
27
50
|
|
28
51
|
def self.enable_logging
|
29
52
|
@@logging = true
|
53
|
+
@@log.level = Logger::DEBUG
|
30
54
|
end
|
31
55
|
|
32
56
|
def self.disable_logging
|
33
57
|
@@logging = false
|
58
|
+
@@log.level = Logger::ERROR
|
34
59
|
end
|
35
60
|
|
36
61
|
class FaceBookError < StandardError
|
@@ -135,6 +160,8 @@ module MiniFB
|
|
135
160
|
"dashboard.incrementcount", "dashboard.setcount"
|
136
161
|
].collect { |x| x.downcase }
|
137
162
|
|
163
|
+
# THIS IS FOR THE OLD FACEBOOK API, NOT THE GRAPH ONE. See MiniFB.get and MiniFB.post for Graph API
|
164
|
+
#
|
138
165
|
# Call facebook server with a method request. Most keyword arguments
|
139
166
|
# are passed directly to the server with a few exceptions.
|
140
167
|
# The 'sig' value will always be computed automatically.
|
@@ -247,28 +274,61 @@ module MiniFB
|
|
247
274
|
|
248
275
|
# Returns true is signature is valid, false otherwise.
|
249
276
|
def MiniFB.verify_signature(secret, arguments)
|
250
|
-
|
251
|
-
|
277
|
+
if arguments.is_a? String
|
278
|
+
#new way: params[:session]
|
279
|
+
session = JSON.parse(arguments)
|
252
280
|
|
253
|
-
|
254
|
-
|
281
|
+
signature = session.delete('sig')
|
282
|
+
return false if signature.nil?
|
255
283
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
unsigned[k] = v
|
284
|
+
arg_string = String.new
|
285
|
+
session.sort.each { |k, v| arg_string << "#{k}=#{v}" }
|
286
|
+
if Digest::MD5.hexdigest(arg_string + secret) == signature
|
287
|
+
return true
|
261
288
|
end
|
262
|
-
|
289
|
+
else
|
290
|
+
#old way
|
263
291
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
292
|
+
signature = arguments.delete("fb_sig")
|
293
|
+
return false if signature.nil?
|
294
|
+
|
295
|
+
unsigned = Hash.new
|
296
|
+
signed = Hash.new
|
297
|
+
|
298
|
+
arguments.each do |k, v|
|
299
|
+
if k =~ /^fb_sig_(.*)/ then
|
300
|
+
signed[$1] = v
|
301
|
+
else
|
302
|
+
unsigned[k] = v
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
arg_string = String.new
|
307
|
+
signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
|
308
|
+
if Digest::MD5.hexdigest(arg_string + secret) == signature
|
309
|
+
return true
|
310
|
+
end
|
268
311
|
end
|
269
312
|
return false
|
270
313
|
end
|
271
314
|
|
315
|
+
# This function takes the app secret and the signed request, and verifies if the request is valid.
|
316
|
+
def self.verify_signed_request(secret, req)
|
317
|
+
s, p = req.split(".")
|
318
|
+
sig = base64_url_decode(s)
|
319
|
+
expected_sig = OpenSSL::HMAC.digest('SHA256', secret, p.tr("-_", "+/"))
|
320
|
+
return sig == expected_sig
|
321
|
+
end
|
322
|
+
|
323
|
+
# Ruby's implementation of base64 decoding seems to be reading the string in multiples of 4 and ignoring
|
324
|
+
# any extra characters if there are no white-space characters at the end. Since facebook does not take this
|
325
|
+
# into account, this function fills any string with white spaces up to the point where it becomes divisible
|
326
|
+
# by 4, then it replaces '-' with '+' and '_' with '/' (URL-safe decoding), and decodes the result.
|
327
|
+
def self.base64_url_decode(str)
|
328
|
+
str = str + "=" * (4 - str.size % 4) unless str.size % 4 == 0
|
329
|
+
return Base64.decode64(str.tr("-_", "+/"))
|
330
|
+
end
|
331
|
+
|
272
332
|
# Parses cookies in order to extract the facebook cookie and parse it into a useable hash
|
273
333
|
#
|
274
334
|
# options:
|
@@ -364,9 +424,9 @@ module MiniFB
|
|
364
424
|
def initialize(session_or_token, id)
|
365
425
|
@oauth_session = if session_or_token.is_a?(MiniFB::OAuthSession)
|
366
426
|
session_or_token
|
367
|
-
|
368
|
-
|
369
|
-
|
427
|
+
else
|
428
|
+
MiniFB::OAuthSession.new(session_or_token)
|
429
|
+
end
|
370
430
|
@id = id
|
371
431
|
@object = @oauth_session.get(id, :metadata => true)
|
372
432
|
@connections_cache = {}
|
@@ -447,6 +507,36 @@ module MiniFB
|
|
447
507
|
return params
|
448
508
|
end
|
449
509
|
|
510
|
+
# Return a JSON object of working Oauth tokens from working session keys, returned in order given
|
511
|
+
def self.oauth_exchange_session(app_id, secret, session_keys)
|
512
|
+
url = "#{graph_base}oauth/exchange_sessions"
|
513
|
+
params = {}
|
514
|
+
params["client_id"] = "#{app_id}"
|
515
|
+
params["client_secret"] = "#{secret}"
|
516
|
+
params["sessions"] = "#{session_keys}"
|
517
|
+
options = {}
|
518
|
+
options[:params] = params
|
519
|
+
options[:method] = :post
|
520
|
+
return fetch(url, options)
|
521
|
+
end
|
522
|
+
|
523
|
+
# Return a JSON object of working Oauth tokens from working session keys, returned in order given
|
524
|
+
def self.authenticate_as_app(app_id, secret)
|
525
|
+
url = "#{graph_base}oauth/access_token"
|
526
|
+
params = {}
|
527
|
+
params["type"] = "client_cred"
|
528
|
+
params["client_id"] = "#{app_id}"
|
529
|
+
params["client_secret"] = "#{secret}"
|
530
|
+
# resp = RestClient.get url
|
531
|
+
options = {}
|
532
|
+
options[:params] = params
|
533
|
+
options[:method] = :get
|
534
|
+
options[:response_type] = :params
|
535
|
+
resp = fetch(url, options)
|
536
|
+
puts 'resp=' + resp.body.to_s if @@logging
|
537
|
+
resp
|
538
|
+
end
|
539
|
+
|
450
540
|
# Gets data from the Facebook Graph API
|
451
541
|
# options:
|
452
542
|
# - type: eg: feed, home, etc
|
@@ -458,6 +548,7 @@ module MiniFB
|
|
458
548
|
params = options[:params] || {}
|
459
549
|
params["access_token"] = "#{(access_token)}"
|
460
550
|
params["metadata"] = "1" if options[:metadata]
|
551
|
+
params["fields"] = options[:fields].join(",") if options[:fields]
|
461
552
|
options[:params] = params
|
462
553
|
return fetch(url, options)
|
463
554
|
end
|
@@ -527,28 +618,42 @@ module MiniFB
|
|
527
618
|
return fetch(url, options)
|
528
619
|
end
|
529
620
|
|
530
|
-
|
531
621
|
def self.fetch(url, options={})
|
532
622
|
|
533
623
|
begin
|
534
624
|
if options[:method] == :post
|
535
|
-
|
625
|
+
@@log.debug 'url_post=' + url if @@logging
|
536
626
|
resp = RestClient.post url, options[:params]
|
537
627
|
else
|
538
628
|
if options[:params] && options[:params].size > 0
|
539
629
|
url += '?' + options[:params].map { |k, v| URI.escape("%s=%s" % [k, v]) }.join('&')
|
540
630
|
end
|
541
|
-
|
631
|
+
@@log.debug 'url_get=' + url if @@logging
|
542
632
|
resp = RestClient.get url
|
543
633
|
end
|
544
634
|
|
545
|
-
|
635
|
+
@@log.debug 'resp=' + resp.to_s
|
546
636
|
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
637
|
+
if options[:response_type] == :params
|
638
|
+
# Some methods return a param like string, for example: access_token=11935261234123|rW9JMxbN65v_pFWQl5LmHHABC
|
639
|
+
params = {}
|
640
|
+
params_array = resp.split("&")
|
641
|
+
params_array.each do |p|
|
642
|
+
ps = p.split("=")
|
643
|
+
params[ps[0]] = ps[1]
|
644
|
+
end
|
645
|
+
return params
|
646
|
+
else
|
647
|
+
begin
|
648
|
+
res_hash = JSON.parse(resp.to_s)
|
649
|
+
rescue
|
650
|
+
# quick fix for things like stream.publish that don't return json
|
651
|
+
res_hash = JSON.parse("{\"response\": #{resp.to_s}}")
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
if res_hash.size == 1 && res_hash["data"]
|
656
|
+
res_hash = res_hash["data"]
|
552
657
|
end
|
553
658
|
|
554
659
|
if res_hash.is_a? Array # fql return this
|
@@ -574,10 +679,10 @@ module MiniFB
|
|
574
679
|
# Returns all available scopes.
|
575
680
|
def self.scopes
|
576
681
|
scopes = %w{
|
577
|
-
about_me activities birthday education_history
|
578
|
-
hometown interests likes location notes
|
579
|
-
photo_video_tags photos relationships
|
580
|
-
status videos website work_history
|
682
|
+
about_me activities birthday checkins education_history
|
683
|
+
events groups hometown interests likes location notes
|
684
|
+
online_presence photo_video_tags photos relationships
|
685
|
+
religion_politics status videos website work_history
|
581
686
|
}
|
582
687
|
scopes.map! do |scope|
|
583
688
|
["user_#{scope}", "friends_#{scope}"]
|
@@ -590,6 +695,7 @@ module MiniFB
|
|
590
695
|
}
|
591
696
|
end
|
592
697
|
|
698
|
+
|
593
699
|
# This function expects arguments as a hash, so
|
594
700
|
# it is agnostic to different POST handling variants in ruby.
|
595
701
|
#
|
data/test/test_mini_fb.rb
CHANGED
@@ -1,19 +1,32 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
require 'active_support'
|
5
|
+
require '../lib/mini_fb'
|
3
6
|
|
4
7
|
class MiniFBTests < Test::Unit::TestCase
|
5
8
|
|
6
9
|
|
7
10
|
def setup
|
11
|
+
@config = File.open(File.expand_path("~/.mini_fb_tests.yml")) { |yf| YAML::load(yf) }
|
12
|
+
puts @config.inspect
|
13
|
+
MiniFB.log_level = :debug
|
8
14
|
end
|
9
15
|
|
10
16
|
def teardown
|
11
17
|
|
12
18
|
end
|
13
19
|
|
20
|
+
def test_authenticate_as_app
|
21
|
+
res = MiniFB.authenticate_as_app(@config["fb_api_key"], @config["fb_secret"])
|
22
|
+
puts 'res=' + res.inspect
|
23
|
+
assert res["access_token"].present?
|
24
|
+
assert res["access_token"].starts_with?(@config["fb_app_id"].to_s)
|
25
|
+
end
|
26
|
+
|
14
27
|
# Test signature verification.
|
15
28
|
def test_signature
|
16
|
-
|
29
|
+
|
17
30
|
end
|
18
31
|
|
19
32
|
def test_basic_calls
|
@@ -25,7 +38,7 @@ class MiniFBTests < Test::Unit::TestCase
|
|
25
38
|
end
|
26
39
|
|
27
40
|
def test_photos
|
28
|
-
|
41
|
+
|
29
42
|
end
|
30
43
|
|
31
44
|
def test_uri_escape
|
metadata
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mini_fb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 21
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 1
|
8
7
|
- 1
|
9
|
-
-
|
10
|
-
version: 1.1.
|
8
|
+
- 4
|
9
|
+
version: 1.1.4
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Travis Reeder
|
14
|
-
- Aaron Hurley
|
15
13
|
autorequire:
|
16
14
|
bindir: bin
|
17
15
|
cert_chain: []
|
18
16
|
|
19
|
-
date: 2010-
|
17
|
+
date: 2010-10-30 00:00:00 -07:00
|
20
18
|
default_executable:
|
21
19
|
dependencies:
|
22
20
|
- !ruby/object:Gem::Dependency
|
@@ -27,7 +25,6 @@ dependencies:
|
|
27
25
|
requirements:
|
28
26
|
- - ">="
|
29
27
|
- !ruby/object:Gem::Version
|
30
|
-
hash: 3
|
31
28
|
segments:
|
32
29
|
- 0
|
33
30
|
version: "0"
|
@@ -41,7 +38,6 @@ dependencies:
|
|
41
38
|
requirements:
|
42
39
|
- - ">="
|
43
40
|
- !ruby/object:Gem::Version
|
44
|
-
hash: 3
|
45
41
|
segments:
|
46
42
|
- 0
|
47
43
|
version: "0"
|
@@ -73,7 +69,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
69
|
requirements:
|
74
70
|
- - ">="
|
75
71
|
- !ruby/object:Gem::Version
|
76
|
-
hash: 3
|
77
72
|
segments:
|
78
73
|
- 0
|
79
74
|
version: "0"
|
@@ -82,7 +77,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
77
|
requirements:
|
83
78
|
- - ">="
|
84
79
|
- !ruby/object:Gem::Version
|
85
|
-
hash: 3
|
86
80
|
segments:
|
87
81
|
- 0
|
88
82
|
version: "0"
|