rack-fb 0.0.1 → 0.0.2
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.
- metadata +14 -37
- data/README.markdown +0 -75
- data/Rakefile +0 -74
- data/lib/mini_fb.rb +0 -279
- data/lib/rack/facebook.rb +0 -154
metadata
CHANGED
@@ -1,58 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-fb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- ,,,
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-18 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
name: rack
|
17
|
-
type: :runtime
|
18
|
-
version_requirement:
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ~>
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 1.0.1
|
24
|
-
version:
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: rspec
|
27
|
-
type: :development
|
28
|
-
version_requirement:
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: "0"
|
34
|
-
version:
|
14
|
+
dependencies: []
|
15
|
+
|
35
16
|
description:
|
36
|
-
email:
|
17
|
+
email: youremail@example.com
|
37
18
|
executables: []
|
38
19
|
|
39
20
|
extensions: []
|
40
21
|
|
41
|
-
extra_rdoc_files:
|
42
|
-
|
43
|
-
files:
|
44
|
-
|
45
|
-
- Rakefile
|
46
|
-
- lib/rack/facebook.rb
|
47
|
-
- lib/mini_fb.rb
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files: []
|
25
|
+
|
48
26
|
has_rdoc: true
|
49
|
-
homepage: http://
|
27
|
+
homepage: http://yoursite.example.com
|
50
28
|
licenses: []
|
51
29
|
|
52
30
|
post_install_message:
|
53
|
-
rdoc_options:
|
54
|
-
|
55
|
-
- README.markdown
|
31
|
+
rdoc_options: []
|
32
|
+
|
56
33
|
require_paths:
|
57
34
|
- lib
|
58
35
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -73,6 +50,6 @@ rubyforge_project: rack-fb
|
|
73
50
|
rubygems_version: 1.3.5
|
74
51
|
signing_key:
|
75
52
|
specification_version: 3
|
76
|
-
summary:
|
53
|
+
summary: Don't use this gem
|
77
54
|
test_files: []
|
78
55
|
|
data/README.markdown
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
= rack-fb
|
2
|
-
|
3
|
-
Rack-fb is currently a work in progress. It aims to be a lightweight client for the [Facebook API](http://wiki.developers.facebook.com/index.php/API).
|
4
|
-
|
5
|
-
Installation
|
6
|
-
-------------
|
7
|
-
|
8
|
-
gem install rack-fb
|
9
|
-
|
10
|
-
General Usage
|
11
|
-
-------------
|
12
|
-
|
13
|
-
The most general case is to use MiniFB.call method:
|
14
|
-
|
15
|
-
user_hash = MiniFB.call(FB_API_KEY, FB_SECRET, "Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)
|
16
|
-
|
17
|
-
Which simply returns the parsed json response from Facebook.
|
18
|
-
|
19
|
-
Some Higher Level Objects for Common Uses
|
20
|
-
----------------------
|
21
|
-
|
22
|
-
Get a MiniFB::Session:
|
23
|
-
|
24
|
-
@fb = MiniFB::Session.new(FB_API_KEY, FB_SECRET, @fb_session, @fb_uid)
|
25
|
-
|
26
|
-
With the session, you can then get the user information for the session/uid.
|
27
|
-
|
28
|
-
user = @fb.user
|
29
|
-
|
30
|
-
Then get info from the user:
|
31
|
-
|
32
|
-
first_name = user["first_name"]
|
33
|
-
|
34
|
-
Or profile photos:
|
35
|
-
|
36
|
-
photos = user.profile_photos
|
37
|
-
|
38
|
-
Or if you want other photos, try:
|
39
|
-
|
40
|
-
photos = @fb.photos("pids"=>[12343243,920382343,9208348])
|
41
|
-
|
42
|
-
Support
|
43
|
-
--------
|
44
|
-
|
45
|
-
Join our Discussion Group at: http://groups.google.com/group/mini_fb
|
46
|
-
|
47
|
-
=======
|
48
|
-
This Rack middleware checks the signature of Facebook params, and
|
49
|
-
converts them to Ruby objects when appropiate. Also, it converts
|
50
|
-
the request method from the Facebook POST to the original HTTP
|
51
|
-
method used by the client.
|
52
|
-
|
53
|
-
If the signature is wrong, it returns a "400 Invalid Facebook Signature".
|
54
|
-
|
55
|
-
Optionally, it can take a block that receives the Rack environment
|
56
|
-
and returns a value that evaluates to true when we want the middleware to
|
57
|
-
be executed for the specific request.
|
58
|
-
|
59
|
-
# Usage
|
60
|
-
|
61
|
-
In your config.ru:
|
62
|
-
|
63
|
-
require 'rack/facebook'
|
64
|
-
use Rack::Facebook, "my_facebook_secret_key"
|
65
|
-
|
66
|
-
Using a block condition:
|
67
|
-
|
68
|
-
use Rack::Facebook, "my_facebook_secret_key" do |env|
|
69
|
-
env['REQUEST_URI'] =~ /^\/facebook_only/
|
70
|
-
end
|
71
|
-
|
72
|
-
# Credits
|
73
|
-
|
74
|
-
Carlos Paramio
|
75
|
-
|
data/Rakefile
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require "rubygems"
|
4
|
-
require "rake/gempackagetask"
|
5
|
-
require "rake/rdoctask"
|
6
|
-
|
7
|
-
require "spec"
|
8
|
-
require "spec/rake/spectask"
|
9
|
-
Spec::Rake::SpecTask.new do |t|
|
10
|
-
t.spec_opts = %w(--format specdoc --colour)
|
11
|
-
t.libs = ["spec"]
|
12
|
-
end
|
13
|
-
|
14
|
-
|
15
|
-
task :default => ["spec"]
|
16
|
-
|
17
|
-
# This builds the actual gem. For details of what all these options
|
18
|
-
# mean, and other ones you can add, check the documentation here:
|
19
|
-
#
|
20
|
-
# http://rubygems.org/read/chapter/20
|
21
|
-
#
|
22
|
-
spec = Gem::Specification.new do |s|
|
23
|
-
|
24
|
-
# Change these as appropriate
|
25
|
-
s.name = "rack-fb"
|
26
|
-
s.version = "0.0.1"
|
27
|
-
s.summary = "Facebook middleware and API client"
|
28
|
-
s.author = "John Mendonca"
|
29
|
-
s.email = "joaosinho@gmail.com"
|
30
|
-
s.homepage = "http://github.com/johnmendonca/rack-fb"
|
31
|
-
|
32
|
-
s.has_rdoc = true
|
33
|
-
s.extra_rdoc_files = %w(README.markdown)
|
34
|
-
s.rdoc_options = %w(--main README.markdown)
|
35
|
-
|
36
|
-
# Add any extra files to include in the gem
|
37
|
-
s.files = %w(README.markdown Rakefile) + Dir.glob("{spec,lib/**/*}")
|
38
|
-
s.require_paths = ["lib"]
|
39
|
-
|
40
|
-
# If you want to depend on other gems, add them here, along with any
|
41
|
-
# relevant versions
|
42
|
-
s.add_dependency("rack", "~> 1.0.1")
|
43
|
-
|
44
|
-
# If your tests use any gems, include them here
|
45
|
-
s.add_development_dependency("rspec")
|
46
|
-
|
47
|
-
# If you want to publish automatically to rubyforge, you'll may need
|
48
|
-
# to tweak this, and the publishing task below too.
|
49
|
-
s.rubyforge_project = "rack-fb"
|
50
|
-
end
|
51
|
-
|
52
|
-
# This task actually builds the gem. We also regenerate a static
|
53
|
-
# .gemspec file, which is useful if something (i.e. GitHub) will
|
54
|
-
# be automatically building a gem for this project. If you're not
|
55
|
-
# using GitHub, edit as appropriate.
|
56
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
57
|
-
pkg.gem_spec = spec
|
58
|
-
|
59
|
-
# Generate the gemspec file for github.
|
60
|
-
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
61
|
-
File.open(file, "w") {|f| f << spec.to_ruby }
|
62
|
-
end
|
63
|
-
|
64
|
-
# Generate documentation
|
65
|
-
Rake::RDocTask.new do |rd|
|
66
|
-
rd.main = "README.markdown"
|
67
|
-
rd.rdoc_files.include("README.markdown", "lib/**/*.rb")
|
68
|
-
rd.rdoc_dir = "rdoc"
|
69
|
-
end
|
70
|
-
|
71
|
-
desc 'Clear out RDoc and generated packages'
|
72
|
-
task :clean => [:clobber_rdoc, :clobber_package] do
|
73
|
-
rm "#{spec.name}.gemspec"
|
74
|
-
end
|
data/lib/mini_fb.rb
DELETED
@@ -1,279 +0,0 @@
|
|
1
|
-
require 'digest/md5'
|
2
|
-
require 'erb'
|
3
|
-
require 'json' unless defined? JSON
|
4
|
-
|
5
|
-
module MiniFB
|
6
|
-
|
7
|
-
# Global constants
|
8
|
-
FB_URL = "http://api.facebook.com/restserver.php"
|
9
|
-
FB_API_VERSION = "1.0"
|
10
|
-
|
11
|
-
@@logging = false
|
12
|
-
|
13
|
-
def enable_logging
|
14
|
-
@@logging = true
|
15
|
-
end
|
16
|
-
|
17
|
-
def disable_logging
|
18
|
-
@@logging = false
|
19
|
-
end
|
20
|
-
|
21
|
-
class FaceBookError < StandardError
|
22
|
-
# Error that happens during a facebook call.
|
23
|
-
def initialize( error_code, error_msg )
|
24
|
-
super("Facebook error #{error_code}: #{error_msg}" )
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class Session
|
29
|
-
attr_accessor :api_key, :secret_key, :session_key, :uid
|
30
|
-
|
31
|
-
|
32
|
-
def initialize(api_key, secret_key, session_key, uid)
|
33
|
-
@api_key = api_key
|
34
|
-
@secret_key = FaceBookSecret.new secret_key
|
35
|
-
@session_key = session_key
|
36
|
-
@uid = uid
|
37
|
-
end
|
38
|
-
|
39
|
-
# returns current user
|
40
|
-
def user
|
41
|
-
return @user unless @user.nil?
|
42
|
-
@user = User.new(MiniFB.call(@api_key, @secret_key, "Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)[0], self)
|
43
|
-
@user
|
44
|
-
end
|
45
|
-
|
46
|
-
def photos
|
47
|
-
Photos.new(self)
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
|
-
def call(method, params={})
|
52
|
-
return MiniFB.call(api_key, secret_key, method, params.update("session_key"=>session_key))
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
class User
|
58
|
-
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_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo, :pic_small_with_logo, :pic_square_with_logo]
|
59
|
-
STANDARD_FIELDS = [:uid, :first_name, :last_name, :name, :timezone, :birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email]
|
60
|
-
|
61
|
-
def self.all_fields
|
62
|
-
FIELDS.join(",")
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.standard_fields
|
66
|
-
STANDARD_FIELDS.join(",")
|
67
|
-
end
|
68
|
-
|
69
|
-
def initialize(fb_hash, session)
|
70
|
-
@fb_hash = fb_hash
|
71
|
-
@session = session
|
72
|
-
end
|
73
|
-
|
74
|
-
def [](key)
|
75
|
-
@fb_hash[key]
|
76
|
-
end
|
77
|
-
|
78
|
-
def uid
|
79
|
-
return self["uid"]
|
80
|
-
end
|
81
|
-
|
82
|
-
def profile_photos
|
83
|
-
@session.photos.get("uid"=>uid, "aid"=>profile_pic_album_id)
|
84
|
-
end
|
85
|
-
|
86
|
-
def profile_pic_album_id
|
87
|
-
merge_aid(-3, uid)
|
88
|
-
end
|
89
|
-
|
90
|
-
def merge_aid(aid, uid)
|
91
|
-
uid = uid.to_i
|
92
|
-
ret = (uid << 32) + (aid & 0xFFFFFFFF)
|
93
|
-
# puts 'merge_aid=' + ret.inspect
|
94
|
-
return ret
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
class Photos
|
99
|
-
|
100
|
-
def initialize(session)
|
101
|
-
@session = session
|
102
|
-
end
|
103
|
-
|
104
|
-
def get(params)
|
105
|
-
pids = params["pids"]
|
106
|
-
if !pids.nil? && pids.is_a?(Array)
|
107
|
-
pids = pids.join(",")
|
108
|
-
params["pids"] = pids
|
109
|
-
end
|
110
|
-
@session.call("photos.get", params)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
BAD_JSON_METHODS = ["users.getLoggedInUser","auth.promoteSession"]
|
115
|
-
|
116
|
-
# Call facebook server with a method request. Most keyword arguments
|
117
|
-
# are passed directly to the server with a few exceptions.
|
118
|
-
# The 'sig' value will always be computed automatically.
|
119
|
-
# The 'v' version will be supplied automatically if needed.
|
120
|
-
# The 'call_id' defaults to True, which will generate a valid
|
121
|
-
# number. Otherwise it should be a valid number or False to disable.
|
122
|
-
|
123
|
-
# The default return is a parsed json object.
|
124
|
-
# Unless the 'format' and/or 'callback' arguments are given,
|
125
|
-
# in which case the raw text of the reply is returned. The string
|
126
|
-
# will always be returned, even during errors.
|
127
|
-
|
128
|
-
# If an error occurs, a FacebookError exception will be raised
|
129
|
-
# with the proper code and message.
|
130
|
-
|
131
|
-
# The secret argument should be an instance of FacebookSecret
|
132
|
-
# to hide value from simple introspection.
|
133
|
-
def MiniFB.call( api_key, secret, method, kwargs )
|
134
|
-
|
135
|
-
puts 'kwargs=' + kwargs.inspect
|
136
|
-
|
137
|
-
if secret.is_a? String
|
138
|
-
secret = FaceBookSecret.new(secret)
|
139
|
-
end
|
140
|
-
|
141
|
-
# Prepare arguments for call
|
142
|
-
call_id = kwargs.fetch("call_id", true)
|
143
|
-
if call_id == true then
|
144
|
-
kwargs["call_id"] = Time.now.tv_sec.to_s
|
145
|
-
else
|
146
|
-
kwargs.delete("call_id")
|
147
|
-
end
|
148
|
-
|
149
|
-
custom_format = kwargs.include?("format") or kwargs.include?("callback")
|
150
|
-
kwargs["format"] ||= "JSON"
|
151
|
-
kwargs["v"] ||= FB_API_VERSION
|
152
|
-
kwargs["api_key"]||= api_key
|
153
|
-
kwargs["method"] ||= method
|
154
|
-
|
155
|
-
# Hash with secret
|
156
|
-
arg_string = String.new
|
157
|
-
# todo: convert symbols to strings, symbols break the next line
|
158
|
-
kwargs.sort.each { |kv| arg_string << kv[0] << "=" << kv[1].to_s }
|
159
|
-
kwargs["sig"] = Digest::MD5.hexdigest( arg_string + secret.value.call )
|
160
|
-
|
161
|
-
# Call website with POST request
|
162
|
-
begin
|
163
|
-
response = Net::HTTP.post_form( URI.parse(FB_URL), kwargs )
|
164
|
-
rescue SocketError => err
|
165
|
-
raise IOError.new( "Cannot connect to the facebook server: " + err )
|
166
|
-
end
|
167
|
-
|
168
|
-
# Handle response
|
169
|
-
return response.body if custom_format
|
170
|
-
|
171
|
-
fb_method = kwargs["method"]
|
172
|
-
body = response.body
|
173
|
-
|
174
|
-
begin
|
175
|
-
data = JSON.parse( body )
|
176
|
-
puts 'response=' + data.inspect if @@logging
|
177
|
-
if data.include?( "error_msg" ) then
|
178
|
-
raise FaceBookError.new( data["error_code"] || 1, data["error_msg"] )
|
179
|
-
end
|
180
|
-
|
181
|
-
rescue JSON::ParserError => ex
|
182
|
-
if BAD_JSON_METHODS.include?(fb_method) # Little hack because this response isn't valid JSON
|
183
|
-
return body
|
184
|
-
else
|
185
|
-
raise ex
|
186
|
-
end
|
187
|
-
end
|
188
|
-
return data
|
189
|
-
end
|
190
|
-
|
191
|
-
# Returns true is signature is valid, false otherwise.
|
192
|
-
def MiniFB.verify_signature( secret, arguments )
|
193
|
-
signature = arguments.delete( "fb_sig" )
|
194
|
-
return false if signature.nil?
|
195
|
-
|
196
|
-
unsigned = Hash.new
|
197
|
-
signed = Hash.new
|
198
|
-
|
199
|
-
arguments.each do |k, v|
|
200
|
-
if k =~ /^fb_sig_(.*)/ then
|
201
|
-
signed[$1] = v
|
202
|
-
else
|
203
|
-
unsigned[k] = v
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
arg_string = String.new
|
208
|
-
signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
|
209
|
-
if Digest::MD5.hexdigest( arg_string + secret ) == signature
|
210
|
-
return true
|
211
|
-
end
|
212
|
-
return false
|
213
|
-
end
|
214
|
-
|
215
|
-
# Returns the login/add app url for your application.
|
216
|
-
#
|
217
|
-
# options:
|
218
|
-
# - :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
|
219
|
-
# - :canvas => true/false - to say whether this is a canvas app or not
|
220
|
-
def self.login_url(api_key, options={})
|
221
|
-
login_url = "http://api.facebook.com/login.php?api_key=#{api_key}"
|
222
|
-
login_url << "&next=#{options[:next]}" if options[:next]
|
223
|
-
login_url << "&canvas" if options[:canvas]
|
224
|
-
login_url
|
225
|
-
end
|
226
|
-
|
227
|
-
# This function expects arguments as a hash, so
|
228
|
-
# it is agnostic to different POST handling variants in ruby.
|
229
|
-
#
|
230
|
-
# Validate the arguments received from facebook. This is usually
|
231
|
-
# sent for the iframe in Facebook's canvas. It is not necessary
|
232
|
-
# to use this on the auth_token and uid passed to callbacks like
|
233
|
-
# post-add and post-remove.
|
234
|
-
#
|
235
|
-
# The arguments must be a mapping of to string keys and values
|
236
|
-
# or a string of http request data.
|
237
|
-
#
|
238
|
-
# If the data is invalid or not signed properly, an empty
|
239
|
-
# dictionary is returned.
|
240
|
-
#
|
241
|
-
# The secret argument should be an instance of FacebookSecret
|
242
|
-
# to hide value from simple introspection.
|
243
|
-
#
|
244
|
-
# DEPRECATED, use verify_signature instead
|
245
|
-
def MiniFB.validate( secret, arguments )
|
246
|
-
|
247
|
-
signature = arguments.delete( "fb_sig" )
|
248
|
-
return arguments if signature.nil?
|
249
|
-
|
250
|
-
unsigned = Hash.new
|
251
|
-
signed = Hash.new
|
252
|
-
|
253
|
-
arguments.each do |k, v|
|
254
|
-
if k =~ /^fb_sig_(.*)/ then
|
255
|
-
signed[$1] = v
|
256
|
-
else
|
257
|
-
unsigned[k] = v
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
arg_string = String.new
|
262
|
-
signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
|
263
|
-
if Digest::MD5.hexdigest( arg_string + secret ) != signature
|
264
|
-
unsigned # Hash is incorrect, return only unsigned fields.
|
265
|
-
else
|
266
|
-
unsigned.merge signed
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
class FaceBookSecret
|
271
|
-
# Simple container that stores a secret value.
|
272
|
-
# Proc cannot be dumped or introspected by normal tools.
|
273
|
-
attr_reader :value
|
274
|
-
|
275
|
-
def initialize( value )
|
276
|
-
@value = Proc.new { value }
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
data/lib/rack/facebook.rb
DELETED
@@ -1,154 +0,0 @@
|
|
1
|
-
require 'digest'
|
2
|
-
require 'rack/request'
|
3
|
-
|
4
|
-
module Rack
|
5
|
-
# This Rack middleware checks the signature of Facebook params and
|
6
|
-
# converts them to Ruby objects when appropiate. Also, it converts
|
7
|
-
# the request method from the Facebook POST to the original HTTP
|
8
|
-
# method used by the client.
|
9
|
-
#
|
10
|
-
# If the signature is wrong, it returns a "400 Invalid Facebook Signature".
|
11
|
-
#
|
12
|
-
# Optionally, it can take a block that receives the Rack environment
|
13
|
-
# and returns a value that evaluates to true when we want the middleware to
|
14
|
-
# be executed for the specific request.
|
15
|
-
#
|
16
|
-
# == Usage
|
17
|
-
#
|
18
|
-
# In your rack builder:
|
19
|
-
#
|
20
|
-
# use Rack::Facebook, :application_secret => "SECRET", :api_key => "APIKEY"
|
21
|
-
#
|
22
|
-
# Using a block condition:
|
23
|
-
#
|
24
|
-
# use Rack::Facebook, options do |env|
|
25
|
-
# env['REQUEST_URI'] =~ /^\/facebook_only/
|
26
|
-
# end
|
27
|
-
#
|
28
|
-
# == References
|
29
|
-
# * http://wiki.developers.facebook.com/index.php/Authorizing_Applications
|
30
|
-
# * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
|
31
|
-
#
|
32
|
-
class Facebook
|
33
|
-
def initialize(app, options, &condition)
|
34
|
-
@app = app
|
35
|
-
@options = options
|
36
|
-
@condition = condition
|
37
|
-
end
|
38
|
-
|
39
|
-
def app_name
|
40
|
-
@options[:application_name]
|
41
|
-
end
|
42
|
-
|
43
|
-
def secret
|
44
|
-
@options[:application_secret]
|
45
|
-
end
|
46
|
-
|
47
|
-
def api_key
|
48
|
-
@options[:api_key]
|
49
|
-
end
|
50
|
-
|
51
|
-
def call(env)
|
52
|
-
request = Request.new(env)
|
53
|
-
request.api_key = api_key
|
54
|
-
|
55
|
-
if passes_condition?(request) and request.facebook?
|
56
|
-
valid = true
|
57
|
-
|
58
|
-
if request.params_signature
|
59
|
-
fb_params = request.extract_facebook_params(:post)
|
60
|
-
|
61
|
-
if valid = valid_signature?(fb_params, request.params_signature)
|
62
|
-
env["facebook.original_method"] = env["REQUEST_METHOD"]
|
63
|
-
env["REQUEST_METHOD"] = fb_params.delete("request_method")
|
64
|
-
save_facebook_params(fb_params, env)
|
65
|
-
end
|
66
|
-
elsif request.cookies_signature
|
67
|
-
cookie_params = request.extract_facebook_params(:cookies)
|
68
|
-
valid = valid_signature?(cookie_params, request.cookies_signature)
|
69
|
-
end
|
70
|
-
|
71
|
-
unless valid
|
72
|
-
return [400, {"Content-Type" => "text/html"}, ["Invalid Facebook signature"]]
|
73
|
-
end
|
74
|
-
end
|
75
|
-
return @app.call(env)
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def passes_condition?(request)
|
81
|
-
@condition.nil? or @condition.call(request.env)
|
82
|
-
end
|
83
|
-
|
84
|
-
def valid_signature?(fb_params, actual_sig)
|
85
|
-
actual_sig == calculate_signature(fb_params)
|
86
|
-
end
|
87
|
-
|
88
|
-
def calculate_signature(hash)
|
89
|
-
raw_string = hash.map{ |*pair| pair.join('=') }.sort.join
|
90
|
-
Digest::MD5.hexdigest([raw_string, secret].join)
|
91
|
-
end
|
92
|
-
|
93
|
-
def save_facebook_params(params, env)
|
94
|
-
params.each do |key, value|
|
95
|
-
ruby_value = case key
|
96
|
-
when 'added', 'page_added', 'in_canvas', 'in_profile_tab', 'in_new_facebook', 'position_fix', 'logged_out_facebook'
|
97
|
-
value == '1'
|
98
|
-
when 'expires', 'profile_update_time', 'time'
|
99
|
-
Time.at(value.to_f) rescue TypeError
|
100
|
-
when 'friends'
|
101
|
-
value.split(',')
|
102
|
-
else
|
103
|
-
value
|
104
|
-
end
|
105
|
-
|
106
|
-
env["facebook.#{key}"] = ruby_value
|
107
|
-
end
|
108
|
-
|
109
|
-
env["facebook.app_name"] = app_name
|
110
|
-
env["facebook.api_key"] = api_key
|
111
|
-
env["facebook.secret"] = secret
|
112
|
-
end
|
113
|
-
|
114
|
-
class Request < ::Rack::Request
|
115
|
-
FB_PREFIX = "fb_sig".freeze
|
116
|
-
attr_accessor :api_key
|
117
|
-
|
118
|
-
def facebook?
|
119
|
-
params_signature or cookies_signature
|
120
|
-
end
|
121
|
-
|
122
|
-
def params_signature
|
123
|
-
return @params_signature if @params_signature or @params_signature == false
|
124
|
-
@params_signature = self.POST.delete(FB_PREFIX) || false
|
125
|
-
end
|
126
|
-
|
127
|
-
def cookies_signature
|
128
|
-
cookies[@api_key]
|
129
|
-
end
|
130
|
-
|
131
|
-
def extract_facebook_params(where)
|
132
|
-
|
133
|
-
case where
|
134
|
-
when :post
|
135
|
-
source = self.POST
|
136
|
-
prefix = FB_PREFIX
|
137
|
-
when :cookies
|
138
|
-
source = cookies
|
139
|
-
prefix = @api_key
|
140
|
-
end
|
141
|
-
|
142
|
-
prefix = "#{prefix}_"
|
143
|
-
|
144
|
-
source.inject({}) do |extracted, (key, value)|
|
145
|
-
if key.index(prefix) == 0
|
146
|
-
extracted[key.sub(prefix, '')] = value
|
147
|
-
source.delete(key) if :post == where
|
148
|
-
end
|
149
|
-
extracted
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|