facebookrb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README.rdoc +47 -0
  2. data/Rakefile +72 -0
  3. data/lib/facebookrb.rb +209 -0
  4. metadata +77 -0
data/README.rdoc ADDED
@@ -0,0 +1,47 @@
1
+ = facebookrb
2
+
3
+ Facebookrb is currently a work in progress.
4
+ It aims to be a lightweight yet fully featured client for the [Facebook API](http://wiki.developers.facebook.com/index.php/API).
5
+ It is mostly based on MiniFB (http://github.com/appoxy/mini_fb) and includes some features inspired by rack-facebook (http://github.com/carlosparamio/rack-facebook).
6
+ In other instances, it emulates the official Facebook PHP library.
7
+
8
+ == Installation
9
+
10
+ gem install facebookrb
11
+
12
+ == General Usage
13
+
14
+ require 'facebookrb'
15
+
16
+ You will want to create a client to make calls to Facebook
17
+
18
+ fb_client = FacebookRb::Client.new("API_KEY", "SECRET")
19
+
20
+ If you are using the middleware, there will be one created for you already,
21
+ available in `env['facebook.client']`.
22
+
23
+ user_hash = fb_client.call("Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)
24
+
25
+ Which simply returns the parsed json response from Facebook.
26
+ The client will ask for results in JSON by default.
27
+
28
+ == Middleware
29
+
30
+ The middleware checks the signature of Facebook params, and stores them in env.
31
+
32
+ require 'facebookrb'
33
+
34
+ use Rack::Facebook, :api_key => "APIKEY", :secret => "SECRET"
35
+
36
+ The Facebook parameters in the request are stored in `env['facebook.params']` and `env['facebook.client'].fb_params` if the request is valid (http://wiki.developers.facebook.com/index.php/Verifying_The_Signature)
37
+
38
+ == Planned Features
39
+
40
+ * Facebook Connect
41
+ * Batching (http://wiki.developers.facebook.com/index.php/Using_Batching_API)
42
+ * Base domain (http://wiki.developers.facebook.com/index.php/Base_Domain)
43
+ * Preload FQL (http://wiki.developers.facebook.com/index.php/Preload_FQL)
44
+ * Permissions API (http://wiki.developers.facebook.com/index.php/Permissions_API)
45
+ * Session Secret (http://wiki.developers.facebook.com/index.php/Session_Secret)
46
+ * Converting the request method from the Facebook POST to the original HTTP method used by the client. (http://wiki.developers.facebook.com/index.php/Fb_sig_request_method)
47
+
data/Rakefile ADDED
@@ -0,0 +1,72 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/rdoctask"
4
+
5
+ require "spec"
6
+ require "spec/rake/spectask"
7
+ Spec::Rake::SpecTask.new do |t|
8
+ t.spec_opts = %w(--format specdoc --colour)
9
+ t.libs = ["spec", "lib"]
10
+ end
11
+
12
+
13
+ task :default => ["spec"]
14
+
15
+ # This builds the actual gem. For details of what all these options
16
+ # mean, and other ones you can add, check the documentation here:
17
+ #
18
+ # http://rubygems.org/read/chapter/20
19
+ #
20
+ spec = Gem::Specification.new do |s|
21
+
22
+ # Change these as appropriate
23
+ s.name = "facebookrb"
24
+ s.version = "0.1.0"
25
+ s.summary = "Simple Facebook API client and middleware"
26
+ s.author = "John Mendonca"
27
+ s.email = "joaosinho@gmail.com"
28
+ s.homepage = "http://github.com/johnmendonca/facebookrb"
29
+
30
+ s.has_rdoc = true
31
+ s.extra_rdoc_files = %w(README.rdoc)
32
+ s.rdoc_options = %w(--main README.rdoc)
33
+
34
+ # Add any extra files to include in the gem
35
+ s.files = %w(README.rdoc Rakefile) + Dir.glob("{spec,lib/**/*}")
36
+ s.require_paths = ["lib"]
37
+
38
+ # If you want to depend on other gems, add them here, along with any
39
+ # relevant versions
40
+ s.add_dependency("yajl-ruby")
41
+
42
+ # If your tests use any gems, include them here
43
+ s.add_development_dependency("rspec")
44
+
45
+ # If you want to publish automatically to rubyforge, you'll may need
46
+ # to tweak this, and the publishing task below too.
47
+ s.rubyforge_project = "facebookrb"
48
+ end
49
+
50
+ # This task actually builds the gem. We also regenerate a static
51
+ # .gemspec file, which is useful if something (i.e. GitHub) will
52
+ # be automatically building a gem for this project. If you're not
53
+ # using GitHub, edit as appropriate.
54
+ Rake::GemPackageTask.new(spec) do |pkg|
55
+ pkg.gem_spec = spec
56
+
57
+ # Generate the gemspec file for github.
58
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
59
+ File.open(file, "w") {|f| f << spec.to_ruby }
60
+ end
61
+
62
+ # Generate documentation
63
+ Rake::RDocTask.new do |rd|
64
+ rd.main = "README.rdoc"
65
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
66
+ rd.rdoc_dir = "rdoc"
67
+ end
68
+
69
+ desc 'Clear out RDoc and generated packages'
70
+ task :clean => [:clobber_rdoc, :clobber_package] do
71
+ rm "#{spec.name}.gemspec"
72
+ end
data/lib/facebookrb.rb ADDED
@@ -0,0 +1,209 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'digest/md5'
4
+ require 'yajl'
5
+
6
+ module FacebookRb
7
+ class FacebookError < RuntimeError
8
+ attr_accessor :fb_code, :fb_message
9
+ def initialize(error_code, error_msg)
10
+ self.fb_code = error_code
11
+ self.fb_message = error_msg
12
+ super("Facebook error #{error_code}: #{error_msg}" )
13
+ end
14
+ end
15
+
16
+ class Client
17
+ FB_URL = "http://api.facebook.com/restserver.php"
18
+ FB_API_VERSION = "1.0"
19
+ USER_FIELDS = [:uid, :status, :political, :pic_small, :name, :quotes,
20
+ :is_app_user, :tv, :profile_update_time, :meeting_sex, :hs_info,
21
+ :timezone, :relationship_status, :hometown_location, :about_me,
22
+ :wall_count, :significant_other_id, :pic_big, :music, :work_history,
23
+ :sex, :religion, :notes_count, :activities, :pic_square, :movies,
24
+ :has_added_app, :education_history, :birthday, :birthday_date,
25
+ :first_name, :meeting_for, :last_name, :interests, :current_location,
26
+ :pic, :books, :affiliations, :locale, :profile_url, :proxied_email,
27
+ :email_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo,
28
+ :pic_small_with_logo, :pic_square_with_logo]
29
+ USER_FIELDS_STANDARD = [:uid, :first_name, :last_name, :name, :timezone,
30
+ :birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email]
31
+
32
+ attr_accessor :api_key, :secret, :session_key, :fb_params
33
+ attr_accessor :last_response
34
+
35
+ def initialize(api_key, secret, session_key=nil)
36
+ @api_key = api_key
37
+ @secret = secret
38
+ @session_key = session_key
39
+ end
40
+
41
+ def fb_params=(params)
42
+ @fb_params = params
43
+ @session_key = params['session_key'] if params
44
+ end
45
+
46
+ #
47
+ # Call facebook server with a method request. Most keyword arguments
48
+ # are passed directly to the server with a few exceptions.
49
+ #
50
+ # The default return is a parsed json object.
51
+ # Unless the 'format' and/or 'callback' arguments are given,
52
+ # in which case the raw text of the reply is returned. The string
53
+ # will always be returned, even during errors.
54
+ #
55
+ # If an error occurs, a FacebookError exception will be raised
56
+ # with the proper code and message.
57
+ #
58
+ def call(method, params={})
59
+ api_params = params.dup
60
+
61
+ # Prepare standard arguments for call
62
+ api_params['method'] ||= method
63
+ api_params['api_key'] ||= api_key
64
+ api_params['session_key'] ||= session_key
65
+ api_params['call_id'] ||= Time.now.to_s
66
+ api_params['v'] ||= FB_API_VERSION
67
+ api_params['format'] ||= 'JSON'
68
+
69
+ # Encode any Array or Hash arguments into JSON
70
+ json_encoder = Yajl::Encoder.new
71
+ api_params.each do |key, value|
72
+ if value.is_a?(Array) || value.is_a?(Hash)
73
+ api_params[key] = json_encoder.encode(value)
74
+ end
75
+ end
76
+
77
+ api_params['sig'] = generate_signature(api_params, self.secret)
78
+
79
+ # Call website with POST request
80
+ response = Net::HTTP.post_form( URI.parse(FB_URL), api_params )
81
+
82
+ # Handle response
83
+ self.last_response = response.body
84
+ data = Yajl::Parser.parse(response.body)
85
+
86
+ if data.include?('error_msg')
87
+ raise FacebookError.new(data['error_code'], data['error_msg'])
88
+ end
89
+
90
+ return data
91
+ end
92
+
93
+ def add_special_params(method, params)
94
+ #call_as_apikey for Permissions API
95
+ #ss Session secret
96
+ #use_ssl_resources
97
+ end
98
+
99
+ # Get the signed parameters that were sent from Facebook. Validates the set
100
+ # of parameters against the included signature.
101
+ #
102
+ # Since Facebook sends data to your callback URL via unsecured means, the
103
+ # signature is the only way to make sure that the data actually came from
104
+ # Facebook. So if an app receives a request at the callback URL, it should
105
+ # always verify the signature that comes with against your own secret key.
106
+ # Otherwise, it's possible for someone to spoof a request by
107
+ # pretending to be someone else, i.e.:
108
+ # www.your-callback-url.com/?fb_user=10101
109
+ #
110
+ # Parameters:
111
+ # params A hash of parameters, i.e., from a HTTP request, or cookies
112
+ # namespace prefix string for the set of parameters we want to verify.
113
+ # i.e., fb_sig or fb_post_sig
114
+ # Returns:
115
+ # the subset of parameters containing the given prefix, and also matching
116
+ # the signature associated with them, or nil if the params do not validate
117
+ #
118
+ def get_valid_fb_params(params, namespace='fb_sig')
119
+ prefix = "#{namespace}_"
120
+ fb_params = Hash.new
121
+ params.each do |key, value|
122
+ if key =~ /^#{prefix}(.*)$/
123
+ fb_params[$1] = value
124
+ end
125
+ end
126
+
127
+ signature = params[namespace]
128
+ return fb_params if signature && valid_signature?(fb_params, signature)
129
+ nil
130
+ end
131
+
132
+ # Validates that a given set of parameters match their signature.
133
+ # Parameters all match a given input prefix, such as "fb_sig".
134
+ #
135
+ # Parameters:
136
+ # fb_params an array of all Facebook-sent parameters, not
137
+ # including the signature itself
138
+ # expected_sig the expected result to check against
139
+ #
140
+ def valid_signature?(fb_params, expected_sig)
141
+ expected_sig == generate_signature(fb_params, self.secret)
142
+ end
143
+
144
+ # Generate a signature using the application secret key.
145
+ #
146
+ # The only two entities that know your secret key are you and Facebook,
147
+ # according to the Terms of Service. Since nobody else can generate
148
+ # the signature, you can rely on it to verify that the information
149
+ # came from Facebook.
150
+ #
151
+ # Parameters:
152
+ # fb_params an array of all Facebook-sent parameters, NOT INCLUDING
153
+ # the signature itself
154
+ # secret your app's secret key
155
+ #
156
+ # Returns:
157
+ # a md5 hash to be checked against the signature provided by Facebook
158
+ #
159
+ def generate_signature(fb_params, secret)
160
+ #Convert any symbol keys to strings
161
+ #otherwise sort() bombs
162
+ fb_params.each_key do |key|
163
+ fb_params[key.to_s] = fb_params.delete(key) if key.is_a?(Symbol)
164
+ end
165
+
166
+ str = String.new
167
+ fb_params.sort.each do |key, value|
168
+ str << "#{key.to_s}=#{value}"
169
+ end
170
+ Digest::MD5.hexdigest("#{str}#{secret}")
171
+ end
172
+ end
173
+
174
+ # This Rack middleware checks the signature of Facebook params and
175
+ # converts them to Ruby objects when appropiate. Also, it converts
176
+ # the request method from the Facebook POST to the original HTTP
177
+ # method used by the client.
178
+ #
179
+ # == Usage
180
+ #
181
+ # In your rack builder:
182
+ #
183
+ # use Rack::Facebook, :api_key => "APIKEY", :secret => "SECRET"
184
+ #
185
+ class Middleware
186
+ def initialize(app, options={})
187
+ @app = app
188
+ @options = options
189
+ end
190
+
191
+ def call(env)
192
+ request = Rack::Request.new(env)
193
+
194
+ client = Client.new(@options[:api_key], @options[:secret])
195
+
196
+ if fb_params = client.get_valid_fb_params(request.params, 'fb_sig')
197
+ #env["facebook.original_method"] = env["REQUEST_METHOD"]
198
+ #env["REQUEST_METHOD"] = fb_params['request_method']
199
+ else
200
+ fb_params = client.get_valid_fb_params(request.cookies, @options[:api_key])
201
+ end
202
+ client.fb_params = fb_params
203
+ env['facebook.params'] = fb_params
204
+ env['facebook.client'] = client
205
+
206
+ return @app.call(env)
207
+ end
208
+ end
209
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: facebookrb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - John Mendonca
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-18 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: yajl-ruby
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
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:
35
+ description:
36
+ email: joaosinho@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - README.rdoc
45
+ - Rakefile
46
+ - lib/facebookrb.rb
47
+ has_rdoc: true
48
+ homepage: http://github.com/johnmendonca/facebookrb
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --main
54
+ - README.rdoc
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project: facebookrb
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Simple Facebook API client and middleware
76
+ test_files: []
77
+