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/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
@@ -1,3 +1,3 @@
1
1
  module Koala
2
- VERSION = "1.3.0"
2
+ VERSION = "1.4.0"
3
3
  end
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.get_connection("me", "mutualfriends/#{friend_id}")
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
- failed_insights, koppel = @api.batch do |batch_api|
519
- batch_api.get_connections(@app_id, 'insights')
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
- failed_insights.should be_a(Koala::Facebook::APIError)
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
- data, koppel = @api.batch do |batch_api|
606
- batch_api.get_connections(@app_id, 'insights', {}, :batch_args => {:name => "getdata"})
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
- data.should be_a(Koala::Facebook::APIError)
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]
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe KoalaTest do
4
+ pending "should have tests, because the test suite depends on it"
5
+ end