koala 1.3.0 → 1.4.0
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.
- data/.travis.yml +6 -9
- data/CHANGELOG +15 -1
- data/Gemfile +3 -4
- data/koala.gemspec +1 -2
- data/lib/koala/api/graph_api.rb +57 -53
- data/lib/koala/http_service.rb +2 -2
- data/lib/koala/oauth.rb +63 -36
- data/lib/koala/realtime_updates.rb +2 -2
- data/lib/koala/version.rb +1 -1
- data/readme.md +1 -1
- data/spec/cases/graph_api_batch_spec.rb +19 -19
- data/spec/cases/http_service_spec.rb +7 -0
- data/spec/cases/koala_test_spec.rb +5 -0
- data/spec/cases/oauth_spec.rb +72 -32
- data/spec/cases/realtime_updates_spec.rb +7 -0
- data/spec/fixtures/facebook_data.yml +1 -1
- data/spec/fixtures/mock_facebook_responses.yml +12 -3
- data/spec/support/graph_api_shared_examples.rb +19 -21
- data/spec/support/mock_http_service.rb +1 -1
- metadata +14 -12
data/lib/koala/oauth.rb
CHANGED
|
@@ -4,12 +4,12 @@ require 'base64'
|
|
|
4
4
|
|
|
5
5
|
module Koala
|
|
6
6
|
module Facebook
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
DIALOG_HOST = "www.facebook.com"
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
class OAuth
|
|
11
11
|
attr_reader :app_id, :app_secret, :oauth_callback_url
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
# Creates a new OAuth client.
|
|
14
14
|
#
|
|
15
15
|
# @param app_id [String, Integer] a Facebook application ID
|
|
@@ -20,14 +20,14 @@ module Koala
|
|
|
20
20
|
@app_secret = app_secret
|
|
21
21
|
@oauth_callback_url = oauth_callback_url
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# Parses the cookie set Facebook's JavaScript SDK.
|
|
25
25
|
#
|
|
26
26
|
# @note in parsing Facebook's new signed cookie format this method has to make a request to Facebook.
|
|
27
27
|
# We recommend storing authenticated user info in your Rails session (or equivalent) and only
|
|
28
28
|
# calling this when needed.
|
|
29
29
|
#
|
|
30
|
-
# @param cookie_hash a set of cookies that includes the Facebook cookie.
|
|
30
|
+
# @param cookie_hash a set of cookies that includes the Facebook cookie.
|
|
31
31
|
# You can pass Rack/Rails/Sinatra's cookie hash directly to this method.
|
|
32
32
|
#
|
|
33
33
|
# @return the authenticated user's information as a hash, or nil.
|
|
@@ -60,21 +60,21 @@ module Koala
|
|
|
60
60
|
alias_method :get_user_from_cookie, :get_user_from_cookies
|
|
61
61
|
|
|
62
62
|
# URLs
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
# Builds an OAuth URL, where users will be prompted to log in and for any desired permissions.
|
|
65
|
-
# When the users log in, you receive a callback with their
|
|
65
|
+
# When the users log in, you receive a callback with their
|
|
66
66
|
# See http://developers.facebook.com/docs/authentication/.
|
|
67
67
|
#
|
|
68
68
|
# @see #url_for_access_token
|
|
69
69
|
#
|
|
70
|
-
# @note The server-side authentication and dialog methods should only be used
|
|
70
|
+
# @note The server-side authentication and dialog methods should only be used
|
|
71
71
|
# if your application can't use the Facebook Javascript SDK,
|
|
72
72
|
# which provides a much better user experience.
|
|
73
73
|
# See http://developers.facebook.com/docs/reference/javascript/.
|
|
74
74
|
#
|
|
75
75
|
# @param options any query values to add to the URL, as well as any special/required values listed below.
|
|
76
76
|
# @option options permissions an array or comma-separated string of desired permissions
|
|
77
|
-
#
|
|
77
|
+
#
|
|
78
78
|
# @raise ArgumentError if no OAuth callback was specified in OAuth#new or in options as :redirect_uri
|
|
79
79
|
#
|
|
80
80
|
# @return an OAuth URL you can send your users to
|
|
@@ -84,7 +84,7 @@ module Koala
|
|
|
84
84
|
options[:scope] = permissions.is_a?(Array) ? permissions.join(",") : permissions
|
|
85
85
|
end
|
|
86
86
|
url_options = {:client_id => @app_id}.merge(options)
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
# Creates the URL for oauth authorization for a given callback and optional set of permissions
|
|
89
89
|
build_url("https://#{GRAPH_SERVER}/oauth/authorize", true, url_options)
|
|
90
90
|
end
|
|
@@ -92,9 +92,9 @@ module Koala
|
|
|
92
92
|
# Once you receive an OAuth code, you need to redeem it from Facebook using an appropriate URL.
|
|
93
93
|
# (This is done by your server behind the scenes.)
|
|
94
94
|
# See http://developers.facebook.com/docs/authentication/.
|
|
95
|
-
#
|
|
95
|
+
#
|
|
96
96
|
# @see #url_for_oauth_code
|
|
97
|
-
#
|
|
97
|
+
#
|
|
98
98
|
# @note (see #url_for_oauth_code)
|
|
99
99
|
#
|
|
100
100
|
# @param code an OAuth code received from Facebook
|
|
@@ -106,7 +106,7 @@ module Koala
|
|
|
106
106
|
def url_for_access_token(code, options = {})
|
|
107
107
|
# Creates the URL for the token corresponding to a given code generated by Facebook
|
|
108
108
|
url_options = {
|
|
109
|
-
:client_id => @app_id,
|
|
109
|
+
:client_id => @app_id,
|
|
110
110
|
:code => code,
|
|
111
111
|
:client_secret => @app_secret
|
|
112
112
|
}.merge(options)
|
|
@@ -115,7 +115,7 @@ module Koala
|
|
|
115
115
|
|
|
116
116
|
# Builds a URL for a given dialog (feed, friends, OAuth, pay, send, etc.)
|
|
117
117
|
# See http://developers.facebook.com/docs/reference/dialogs/.
|
|
118
|
-
#
|
|
118
|
+
#
|
|
119
119
|
# @note (see #url_for_oauth_code)
|
|
120
120
|
#
|
|
121
121
|
# @param dialog_type the kind of Facebook dialog you want to show
|
|
@@ -124,37 +124,37 @@ module Koala
|
|
|
124
124
|
# @return an URL your server can query for the user's access token
|
|
125
125
|
def url_for_dialog(dialog_type, options = {})
|
|
126
126
|
# some endpoints require app_id, some client_id, supply both doesn't seem to hurt
|
|
127
|
-
url_options = {:app_id => @app_id, :client_id => @app_id}.merge(options)
|
|
127
|
+
url_options = {:app_id => @app_id, :client_id => @app_id}.merge(options)
|
|
128
128
|
build_url("http://#{DIALOG_HOST}/dialog/#{dialog_type}", true, url_options)
|
|
129
129
|
end
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
# access tokens
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
# Fetches an access token, token expiration, and other info from Facebook.
|
|
134
134
|
# Useful when you've received an OAuth code using the server-side authentication process.
|
|
135
135
|
# @see url_for_oauth_code
|
|
136
136
|
#
|
|
137
137
|
# @note (see #url_for_oauth_code)
|
|
138
|
-
#
|
|
138
|
+
#
|
|
139
139
|
# @param code (see #url_for_access_token)
|
|
140
140
|
# @param options any additional parameters to send to Facebook when redeeming the token
|
|
141
|
-
#
|
|
142
|
-
# @raise Koala::Facebook::APIError if Facebook returns an error response
|
|
143
|
-
#
|
|
144
|
-
# @return a hash of the access token info returned by Facebook (token, expiration, etc.)
|
|
141
|
+
#
|
|
142
|
+
# @raise Koala::Facebook::APIError if Facebook returns an error response
|
|
143
|
+
#
|
|
144
|
+
# @return a hash of the access token info returned by Facebook (token, expiration, etc.)
|
|
145
145
|
def get_access_token_info(code, options = {})
|
|
146
146
|
# convenience method to get a parsed token from Facebook for a given code
|
|
147
147
|
# should this require an OAuth callback URL?
|
|
148
148
|
get_token_from_server({:code => code, :redirect_uri => options[:redirect_uri] || @oauth_callback_url}, false, options)
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
# Fetches the access token (ignoring expiration and other info) from Facebook.
|
|
153
153
|
# Useful when you've received an OAuth code using the server-side authentication process.
|
|
154
154
|
# @see get_access_token_info
|
|
155
|
-
#
|
|
155
|
+
#
|
|
156
156
|
# @note (see #url_for_oauth_code)
|
|
157
|
-
#
|
|
157
|
+
#
|
|
158
158
|
# @param (see #get_access_token_info)
|
|
159
159
|
#
|
|
160
160
|
# @raise (see #get_access_token_info)
|
|
@@ -169,7 +169,7 @@ module Koala
|
|
|
169
169
|
|
|
170
170
|
# Fetches the application's access token, along with any other information provided by Facebook.
|
|
171
171
|
# See http://developers.facebook.com/docs/authentication/ (search for App Login).
|
|
172
|
-
#
|
|
172
|
+
#
|
|
173
173
|
# @param options any additional parameters to send to Facebook when redeeming the token
|
|
174
174
|
#
|
|
175
175
|
# @return the application access token and other information (expiration, etc.)
|
|
@@ -180,7 +180,7 @@ module Koala
|
|
|
180
180
|
|
|
181
181
|
# Fetches the application's access token (ignoring expiration and other info).
|
|
182
182
|
# @see get_app_access_token_info
|
|
183
|
-
#
|
|
183
|
+
#
|
|
184
184
|
# @param (see #get_app_access_token_info)
|
|
185
185
|
#
|
|
186
186
|
# @return the application access token
|
|
@@ -190,13 +190,40 @@ module Koala
|
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
+
# Fetches an access_token with extended expiration time, along with any other information provided by Facebook.
|
|
194
|
+
# See https://developers.facebook.com/docs/offline-access-deprecation/#extend_token (search for fb_exchange_token).
|
|
195
|
+
#
|
|
196
|
+
# @param access_token the access token to exchange
|
|
197
|
+
# @param options any additional parameters to send to Facebook when exchanging tokens.
|
|
198
|
+
#
|
|
199
|
+
# @return the access token with extended expiration time and other information (expiration, etc.)
|
|
200
|
+
def exchange_access_token_info(access_token, options = {})
|
|
201
|
+
get_token_from_server({
|
|
202
|
+
:grant_type => 'fb_exchange_token',
|
|
203
|
+
:fb_exchange_token => access_token
|
|
204
|
+
}, true, options)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Fetches an access token with extended expiration time (ignoring expiration and other info).
|
|
208
|
+
|
|
209
|
+
# @see exchange_access_token_info
|
|
210
|
+
#
|
|
211
|
+
# @param (see #exchange_access_token_info)
|
|
212
|
+
#
|
|
213
|
+
# @return A new access token or the existing one, set to expire in 60 days.
|
|
214
|
+
def exchange_access_token(access_token, options = {})
|
|
215
|
+
if info = exchange_access_token_info(access_token, options)
|
|
216
|
+
info["access_token"]
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
193
220
|
# Parses a signed request string provided by Facebook to canvas apps or in a secure cookie.
|
|
194
221
|
#
|
|
195
222
|
# @param input the signed request from Facebook
|
|
196
|
-
#
|
|
223
|
+
#
|
|
197
224
|
# @raise RuntimeError if the signature is incomplete, invalid, or using an unsupported algorithm
|
|
198
|
-
#
|
|
199
|
-
# @return a hash of the validated request information
|
|
225
|
+
#
|
|
226
|
+
# @return a hash of the validated request information
|
|
200
227
|
def parse_signed_request(input)
|
|
201
228
|
encoded_sig, encoded_envelope = input.split('.', 2)
|
|
202
229
|
raise 'SignedRequest: Invalid (incomplete) signature data' unless encoded_sig && encoded_envelope
|
|
@@ -214,7 +241,7 @@ module Koala
|
|
|
214
241
|
end
|
|
215
242
|
|
|
216
243
|
# Old session key code
|
|
217
|
-
|
|
244
|
+
|
|
218
245
|
# @deprecated Facebook no longer provides session keys.
|
|
219
246
|
def get_token_info_from_session_keys(sessions, options = {})
|
|
220
247
|
Koala::Utils.deprecate("Facebook no longer provides session keys. The relevant OAuth methods will be removed in the next release.")
|
|
@@ -271,7 +298,7 @@ module Koala
|
|
|
271
298
|
end
|
|
272
299
|
components
|
|
273
300
|
end
|
|
274
|
-
|
|
301
|
+
|
|
275
302
|
def parse_unsigned_cookie(fb_cookie)
|
|
276
303
|
# remove the opening/closing quote
|
|
277
304
|
fb_cookie = fb_cookie.gsub(/\"/, "")
|
|
@@ -285,7 +312,7 @@ module Koala
|
|
|
285
312
|
sig = Digest::MD5.hexdigest(auth_string + @app_secret)
|
|
286
313
|
sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
|
|
287
314
|
end
|
|
288
|
-
|
|
315
|
+
|
|
289
316
|
def parse_signed_cookie(fb_cookie)
|
|
290
317
|
components = parse_signed_request(fb_cookie)
|
|
291
318
|
if code = components["code"]
|
|
@@ -315,12 +342,12 @@ module Koala
|
|
|
315
342
|
str += '=' * (4 - str.length.modulo(4))
|
|
316
343
|
Base64.decode64(str.tr('-_', '+/'))
|
|
317
344
|
end
|
|
318
|
-
|
|
345
|
+
|
|
319
346
|
def build_url(base, require_redirect_uri = false, url_options = {})
|
|
320
|
-
if require_redirect_uri && !(url_options[:redirect_uri] ||= url_options.delete(:callback) || @oauth_callback_url)
|
|
347
|
+
if require_redirect_uri && !(url_options[:redirect_uri] ||= url_options.delete(:callback) || @oauth_callback_url)
|
|
321
348
|
raise ArgumentError, "url_for_dialog must get a callback either from the OAuth object or in the parameters!"
|
|
322
349
|
end
|
|
323
|
-
|
|
350
|
+
|
|
324
351
|
"#{base}?#{Koala::HTTPService.encode_params(url_options)}"
|
|
325
352
|
end
|
|
326
353
|
end
|
|
@@ -101,11 +101,11 @@ module Koala
|
|
|
101
101
|
# you can make sure this is legitimate through two ways
|
|
102
102
|
# if your store the token across the calls, you can pass in the token value
|
|
103
103
|
# and we'll make sure it matches
|
|
104
|
-
(verify_token && params["hub.verify_token"] == verify_token) ||
|
|
104
|
+
((verify_token && params["hub.verify_token"] == verify_token) ||
|
|
105
105
|
# alternately, if you sent a specially-constructed value (such as a hash of various secret values)
|
|
106
106
|
# you can pass in a block, which we'll call with the verify_token sent by Facebook
|
|
107
107
|
# if it's legit, return anything that evaluates to true; otherwise, return nil or false
|
|
108
|
-
(verification_block && yield(params["hub.verify_token"]))
|
|
108
|
+
(verification_block && yield(params["hub.verify_token"])))
|
|
109
109
|
params["hub.challenge"]
|
|
110
110
|
else
|
|
111
111
|
false
|
data/lib/koala/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -32,7 +32,7 @@ The Graph API is the simple, slick new interface to Facebook's data. Using it w
|
|
|
32
32
|
@graph.put_object("me", "feed", :message => "I am writing on my wall!")
|
|
33
33
|
|
|
34
34
|
# three-part queries are easy too!
|
|
35
|
-
@graph.
|
|
35
|
+
@graph.get_connections("me", "mutualfriends/#{friend_id}")
|
|
36
36
|
|
|
37
37
|
# you can even use the new Timeline API
|
|
38
38
|
# see https://developers.facebook.com/docs/beta/opengraph/tutorial/
|
|
@@ -372,11 +372,11 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
|
|
|
372
372
|
end
|
|
373
373
|
result[0].should == {"Content-Type" => "text/javascript; charset=UTF-8"}
|
|
374
374
|
end
|
|
375
|
-
|
|
375
|
+
|
|
376
376
|
describe "if it errors" do
|
|
377
377
|
it "raises an APIError if the response is not 200" do
|
|
378
378
|
Koala.stub(:make_request).and_return(Koala::HTTPService::Response.new(500, "[]", {}))
|
|
379
|
-
expect {
|
|
379
|
+
expect {
|
|
380
380
|
Koala::Facebook::API.new("foo").batch {|batch_api| batch_api.get_object('me') }
|
|
381
381
|
}.to raise_exception(Koala::Facebook::APIError)
|
|
382
382
|
end
|
|
@@ -385,21 +385,21 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
|
|
|
385
385
|
before :each do
|
|
386
386
|
Koala.stub(:make_request).and_return(Koala::HTTPService::Response.new(200, '{"error":190,"error_description":"Error validating access token."}', {}))
|
|
387
387
|
end
|
|
388
|
-
|
|
388
|
+
|
|
389
389
|
it "throws an error if the response is an old Batch API-style error" do
|
|
390
|
-
expect {
|
|
390
|
+
expect {
|
|
391
391
|
Koala::Facebook::API.new("foo").batch {|batch_api| batch_api.get_object('me') }
|
|
392
392
|
}.to raise_exception(Koala::Facebook::APIError)
|
|
393
|
-
end
|
|
394
|
-
|
|
393
|
+
end
|
|
394
|
+
|
|
395
395
|
it "provides a type for the error if the response is an old Batch API-style error" do
|
|
396
396
|
begin
|
|
397
397
|
Koala::Facebook::API.new("foo").batch {|batch_api| batch_api.get_object('me') }
|
|
398
398
|
rescue Koala::Facebook::APIError => err
|
|
399
399
|
end
|
|
400
400
|
err.fb_error_type.should
|
|
401
|
-
end
|
|
402
|
-
|
|
401
|
+
end
|
|
402
|
+
|
|
403
403
|
it "passes all the error details if an old Batch API-style error is raised" do
|
|
404
404
|
begin
|
|
405
405
|
Koala::Facebook::API.new("foo").batch {|batch_api| batch_api.get_object('me') }
|
|
@@ -408,18 +408,18 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
|
|
|
408
408
|
err.raw_response["error"].should == 190
|
|
409
409
|
end
|
|
410
410
|
end
|
|
411
|
-
|
|
412
|
-
context "with the new style" do
|
|
411
|
+
|
|
412
|
+
context "with the new style" do
|
|
413
413
|
before :each do
|
|
414
414
|
Koala.stub(:make_request).and_return(Koala::HTTPService::Response.new(200, '{"error":{"message":"Request 0 cannot depend on an unresolved request with name f. Requests can only depend on preceding requests","type":"GraphBatchException"}}', {}))
|
|
415
415
|
end
|
|
416
416
|
|
|
417
417
|
it "throws an error if the response is a new Graph API-style error" do
|
|
418
|
-
expect {
|
|
418
|
+
expect {
|
|
419
419
|
Koala::Facebook::API.new("foo").batch {|batch_api| batch_api.get_object('me') }
|
|
420
420
|
}.to raise_exception(Koala::Facebook::APIError)
|
|
421
421
|
end
|
|
422
|
-
|
|
422
|
+
|
|
423
423
|
it "passes all the error details if an old Batch API-style error is raised" do
|
|
424
424
|
begin
|
|
425
425
|
Koala::Facebook::API.new("foo").batch {|batch_api| batch_api.get_object('me') }
|
|
@@ -488,7 +488,7 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
|
|
|
488
488
|
end
|
|
489
489
|
friends.should be_a(Koala::Facebook::GraphCollection)
|
|
490
490
|
end
|
|
491
|
-
|
|
491
|
+
|
|
492
492
|
it 'turns pageable results into GraphCollections' do
|
|
493
493
|
me, friends = @api.batch do |batch_api|
|
|
494
494
|
batch_api.get_object('me')
|
|
@@ -515,11 +515,11 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
|
|
|
515
515
|
end
|
|
516
516
|
|
|
517
517
|
it "inserts errors in the appropriate place, without breaking other results" do
|
|
518
|
-
|
|
519
|
-
batch_api.
|
|
518
|
+
failed_call, koppel = @api.batch do |batch_api|
|
|
519
|
+
batch_api.get_connection("2", "invalidconnection")
|
|
520
520
|
batch_api.get_object(KoalaTest.user1, {}, {"access_token" => @app_api.access_token})
|
|
521
521
|
end
|
|
522
|
-
|
|
522
|
+
failed_call.should be_a(Koala::Facebook::APIError)
|
|
523
523
|
koppel["id"].should_not be_nil
|
|
524
524
|
end
|
|
525
525
|
|
|
@@ -602,12 +602,12 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
|
|
|
602
602
|
end
|
|
603
603
|
|
|
604
604
|
it "properly handles dependencies that fail" do
|
|
605
|
-
|
|
606
|
-
batch_api.get_connections(
|
|
605
|
+
failed_call, koppel = @api.batch do |batch_api|
|
|
606
|
+
batch_api.get_connections("2", "invalidconnection", {}, :batch_args => {:name => "getdata"})
|
|
607
607
|
batch_api.get_object(KoalaTest.user1, {}, :batch_args => {:depends_on => "getdata"})
|
|
608
608
|
end
|
|
609
609
|
|
|
610
|
-
|
|
610
|
+
failed_call.should be_a(Koala::Facebook::APIError)
|
|
611
611
|
koppel.should be_nil
|
|
612
612
|
end
|
|
613
613
|
|
|
@@ -123,6 +123,13 @@ describe "Koala::HTTPService" do
|
|
|
123
123
|
val.should == CGI.escape(args[key])
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
|
+
|
|
127
|
+
it "encodes parameters in alphabetical order" do
|
|
128
|
+
args = {:b => '2', 'a' => '1'}
|
|
129
|
+
|
|
130
|
+
result = Koala::HTTPService.encode_params(args)
|
|
131
|
+
result.split('&').map{|key_val| key_val.split('=')[0]}.should == ['a', 'b']
|
|
132
|
+
end
|
|
126
133
|
|
|
127
134
|
it "converts all keys to Strings" do
|
|
128
135
|
args = Hash[*(1..4).map {|i| [i, "val#{i}"]}.flatten]
|