koala 1.2.0 → 1.3.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.
Files changed (54) hide show
  1. data/.gitignore +3 -1
  2. data/.rspec +1 -0
  3. data/.travis.yml +4 -0
  4. data/.yardopts +3 -0
  5. data/CHANGELOG +43 -0
  6. data/Gemfile +14 -0
  7. data/Guardfile +6 -0
  8. data/koala.gemspec +4 -4
  9. data/lib/koala/api/batch_operation.rb +83 -0
  10. data/lib/koala/api/graph_api.rb +476 -0
  11. data/lib/koala/{graph_batch_api.rb → api/graph_batch_api.rb} +22 -17
  12. data/lib/koala/api/graph_collection.rb +107 -0
  13. data/lib/koala/api/legacy.rb +26 -0
  14. data/lib/koala/{rest_api.rb → api/rest_api.rb} +36 -10
  15. data/lib/koala/api.rb +93 -0
  16. data/lib/koala/http_service/multipart_request.rb +41 -0
  17. data/lib/koala/http_service/response.rb +18 -0
  18. data/lib/koala/http_service/uploadable_io.rb +187 -0
  19. data/lib/koala/http_service.rb +73 -23
  20. data/lib/koala/oauth.rb +173 -36
  21. data/lib/koala/realtime_updates.rb +89 -51
  22. data/lib/koala/test_users.rb +122 -32
  23. data/lib/koala/utils.rb +11 -4
  24. data/lib/koala/version.rb +1 -1
  25. data/lib/koala.rb +16 -95
  26. data/readme.md +30 -13
  27. data/spec/cases/api_spec.rb +28 -21
  28. data/spec/cases/error_spec.rb +10 -0
  29. data/spec/cases/graph_api_batch_spec.rb +100 -58
  30. data/spec/cases/graph_collection_spec.rb +23 -7
  31. data/spec/cases/http_service_spec.rb +14 -34
  32. data/spec/cases/koala_spec.rb +22 -4
  33. data/spec/cases/legacy_spec.rb +115 -0
  34. data/spec/cases/multipart_request_spec.rb +66 -0
  35. data/spec/cases/oauth_spec.rb +232 -108
  36. data/spec/cases/realtime_updates_spec.rb +154 -47
  37. data/spec/cases/test_users_spec.rb +276 -217
  38. data/spec/cases/uploadable_io_spec.rb +1 -1
  39. data/spec/cases/utils_spec.rb +29 -5
  40. data/spec/fixtures/mock_facebook_responses.yml +50 -26
  41. data/spec/spec_helper.rb +3 -0
  42. data/spec/support/custom_matchers.rb +28 -0
  43. data/spec/support/graph_api_shared_examples.rb +274 -70
  44. data/spec/support/koala_test.rb +27 -16
  45. data/spec/support/mock_http_service.rb +2 -2
  46. data/spec/support/rest_api_shared_examples.rb +46 -162
  47. metadata +33 -25
  48. data/lib/koala/batch_operation.rb +0 -74
  49. data/lib/koala/graph_api.rb +0 -270
  50. data/lib/koala/graph_collection.rb +0 -59
  51. data/lib/koala/uploadable_io.rb +0 -181
  52. data/spec/cases/graph_and_rest_api_spec.rb +0 -22
  53. data/spec/cases/graph_api_spec.rb +0 -22
  54. data/spec/cases/rest_api_spec.rb +0 -41
data/lib/koala/oauth.rb CHANGED
@@ -1,65 +1,165 @@
1
+ # OpenSSL and Base64 are required to support signed_request
2
+ require 'openssl'
3
+ require 'base64'
4
+
1
5
  module Koala
2
6
  module Facebook
7
+
8
+ DIALOG_HOST = "www.facebook.com"
9
+
3
10
  class OAuth
4
11
  attr_reader :app_id, :app_secret, :oauth_callback_url
12
+
13
+ # Creates a new OAuth client.
14
+ #
15
+ # @param app_id [String, Integer] a Facebook application ID
16
+ # @param app_secret a Facebook application secret
17
+ # @param oauth_callback_url the URL in your app to which users authenticating with OAuth will be sent
5
18
  def initialize(app_id, app_secret, oauth_callback_url = nil)
6
19
  @app_id = app_id
7
20
  @app_secret = app_secret
8
21
  @oauth_callback_url = oauth_callback_url
9
22
  end
10
-
11
- def get_user_info_from_cookie(cookie_hash)
12
- # Parses the cookie set Facebook's JavaScript SDK.
13
- # You can pass Rack/Rails/Sinatra's cookie hash directly to this method.
14
- #
15
- # If the user is logged in via Facebook, we return a dictionary with the
16
- # keys "uid" and "access_token". The former is the user's Facebook ID,
17
- # and the latter can be used to make authenticated requests to the Graph API.
18
- # If the user is not logged in, we return None.
19
-
23
+
24
+ # Parses the cookie set Facebook's JavaScript SDK.
25
+ #
26
+ # @note in parsing Facebook's new signed cookie format this method has to make a request to Facebook.
27
+ # We recommend storing authenticated user info in your Rails session (or equivalent) and only
28
+ # calling this when needed.
29
+ #
30
+ # @param cookie_hash a set of cookies that includes the Facebook cookie.
31
+ # You can pass Rack/Rails/Sinatra's cookie hash directly to this method.
32
+ #
33
+ # @return the authenticated user's information as a hash, or nil.
34
+ def get_user_info_from_cookies(cookie_hash)
20
35
  if signed_cookie = cookie_hash["fbsr_#{@app_id}"]
21
36
  parse_signed_cookie(signed_cookie)
22
37
  elsif unsigned_cookie = cookie_hash["fbs_#{@app_id}"]
23
38
  parse_unsigned_cookie(unsigned_cookie)
24
39
  end
25
40
  end
26
- alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
41
+ alias_method :get_user_info_from_cookie, :get_user_info_from_cookies
27
42
 
28
- def get_user_from_cookie(cookies)
29
- if info = get_user_info_from_cookies(cookies)
30
- string = info["uid"]
43
+ # Parses the cookie set Facebook's JavaScript SDK and returns only the user ID.
44
+ #
45
+ # @note (see #get_user_info_from_cookie)
46
+ #
47
+ # @param (see #get_user_info_from_cookie)
48
+ #
49
+ # @return the authenticated user's Facebook ID, or nil.
50
+ def get_user_from_cookies(cookies)
51
+ if signed_cookie = cookies["fbsr_#{@app_id}"]
52
+ if components = parse_signed_request(signed_cookie)
53
+ components["user_id"]
54
+ end
55
+ elsif info = get_user_info_from_cookies(cookies)
56
+ # Parsing unsigned cookie
57
+ info["uid"]
31
58
  end
32
59
  end
33
- alias_method :get_user_from_cookies, :get_user_from_cookie
60
+ alias_method :get_user_from_cookie, :get_user_from_cookies
34
61
 
35
62
  # URLs
36
-
63
+
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
66
+ # See http://developers.facebook.com/docs/authentication/.
67
+ #
68
+ # @see #url_for_access_token
69
+ #
70
+ # @note The server-side authentication and dialog methods should only be used
71
+ # if your application can't use the Facebook Javascript SDK,
72
+ # which provides a much better user experience.
73
+ # See http://developers.facebook.com/docs/reference/javascript/.
74
+ #
75
+ # @param options any query values to add to the URL, as well as any special/required values listed below.
76
+ # @option options permissions an array or comma-separated string of desired permissions
77
+ #
78
+ # @raise ArgumentError if no OAuth callback was specified in OAuth#new or in options as :redirect_uri
79
+ #
80
+ # @return an OAuth URL you can send your users to
37
81
  def url_for_oauth_code(options = {})
38
82
  # for permissions, see http://developers.facebook.com/docs/authentication/permissions
39
- permissions = options[:permissions]
40
- scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
41
- display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
42
-
43
- callback = options[:callback] || @oauth_callback_url
44
- raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
45
-
83
+ if permissions = options.delete(:permissions)
84
+ options[:scope] = permissions.is_a?(Array) ? permissions.join(",") : permissions
85
+ end
86
+ url_options = {:client_id => @app_id}.merge(options)
87
+
46
88
  # Creates the URL for oauth authorization for a given callback and optional set of permissions
47
- "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}#{display}"
89
+ build_url("https://#{GRAPH_SERVER}/oauth/authorize", true, url_options)
48
90
  end
49
91
 
92
+ # Once you receive an OAuth code, you need to redeem it from Facebook using an appropriate URL.
93
+ # (This is done by your server behind the scenes.)
94
+ # See http://developers.facebook.com/docs/authentication/.
95
+ #
96
+ # @see #url_for_oauth_code
97
+ #
98
+ # @note (see #url_for_oauth_code)
99
+ #
100
+ # @param code an OAuth code received from Facebook
101
+ # @param options any additional query parameters to add to the URL
102
+ #
103
+ # @raise (see #url_for_oauth_code)
104
+ #
105
+ # @return an URL your server can query for the user's access token
50
106
  def url_for_access_token(code, options = {})
51
107
  # Creates the URL for the token corresponding to a given code generated by Facebook
52
- callback = options[:callback] || @oauth_callback_url
53
- raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
54
- "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
108
+ url_options = {
109
+ :client_id => @app_id,
110
+ :code => code,
111
+ :client_secret => @app_secret
112
+ }.merge(options)
113
+ build_url("https://#{GRAPH_SERVER}/oauth/access_token", true, url_options)
55
114
  end
56
115
 
116
+ # Builds a URL for a given dialog (feed, friends, OAuth, pay, send, etc.)
117
+ # See http://developers.facebook.com/docs/reference/dialogs/.
118
+ #
119
+ # @note (see #url_for_oauth_code)
120
+ #
121
+ # @param dialog_type the kind of Facebook dialog you want to show
122
+ # @param options any additional query parameters to add to the URL
123
+ #
124
+ # @return an URL your server can query for the user's access token
125
+ def url_for_dialog(dialog_type, options = {})
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)
128
+ build_url("http://#{DIALOG_HOST}/dialog/#{dialog_type}", true, url_options)
129
+ end
130
+
131
+ # access tokens
132
+
133
+ # Fetches an access token, token expiration, and other info from Facebook.
134
+ # Useful when you've received an OAuth code using the server-side authentication process.
135
+ # @see url_for_oauth_code
136
+ #
137
+ # @note (see #url_for_oauth_code)
138
+ #
139
+ # @param code (see #url_for_access_token)
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.)
57
145
  def get_access_token_info(code, options = {})
58
146
  # convenience method to get a parsed token from Facebook for a given code
59
147
  # should this require an OAuth callback URL?
60
148
  get_token_from_server({:code => code, :redirect_uri => options[:redirect_uri] || @oauth_callback_url}, false, options)
61
149
  end
62
150
 
151
+
152
+ # Fetches the access token (ignoring expiration and other info) from Facebook.
153
+ # Useful when you've received an OAuth code using the server-side authentication process.
154
+ # @see get_access_token_info
155
+ #
156
+ # @note (see #url_for_oauth_code)
157
+ #
158
+ # @param (see #get_access_token_info)
159
+ #
160
+ # @raise (see #get_access_token_info)
161
+ #
162
+ # @return the access token
63
163
  def get_access_token(code, options = {})
64
164
  # upstream methods will throw errors if needed
65
165
  if info = get_access_token_info(code, options)
@@ -67,24 +167,40 @@ module Koala
67
167
  end
68
168
  end
69
169
 
170
+ # Fetches the application's access token, along with any other information provided by Facebook.
171
+ # See http://developers.facebook.com/docs/authentication/ (search for App Login).
172
+ #
173
+ # @param options any additional parameters to send to Facebook when redeeming the token
174
+ #
175
+ # @return the application access token and other information (expiration, etc.)
70
176
  def get_app_access_token_info(options = {})
71
177
  # convenience method to get a the application's sessionless access token
72
178
  get_token_from_server({:type => 'client_cred'}, true, options)
73
179
  end
74
180
 
181
+ # Fetches the application's access token (ignoring expiration and other info).
182
+ # @see get_app_access_token_info
183
+ #
184
+ # @param (see #get_app_access_token_info)
185
+ #
186
+ # @return the application access token
75
187
  def get_app_access_token(options = {})
76
188
  if info = get_app_access_token_info(options)
77
189
  string = info["access_token"]
78
190
  end
79
191
  end
80
192
 
81
- # Originally provided directly by Facebook, however this has changed
82
- # as their concept of crypto changed. For historic purposes, this is their proposal:
83
- # https://developers.facebook.com/docs/authentication/canvas/encryption_proposal/
84
- # Currently see https://github.com/facebook/php-sdk/blob/master/src/facebook.php#L758
85
- # for a more accurate reference implementation strategy.
193
+ # Parses a signed request string provided by Facebook to canvas apps or in a secure cookie.
194
+ #
195
+ # @param input the signed request from Facebook
196
+ #
197
+ # @raise RuntimeError if the signature is incomplete, invalid, or using an unsupported algorithm
198
+ #
199
+ # @return a hash of the validated request information
86
200
  def parse_signed_request(input)
87
201
  encoded_sig, encoded_envelope = input.split('.', 2)
202
+ raise 'SignedRequest: Invalid (incomplete) signature data' unless encoded_sig && encoded_envelope
203
+
88
204
  signature = base64_url_decode(encoded_sig).unpack("H*").first
89
205
  envelope = MultiJson.decode(base64_url_decode(encoded_envelope))
90
206
 
@@ -94,11 +210,15 @@ module Koala
94
210
  hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope)
95
211
  raise 'SignedRequest: Invalid signature' if (signature != hmac)
96
212
 
97
- return envelope
213
+ envelope
98
214
  end
99
215
 
100
- # from session keys
216
+ # Old session key code
217
+
218
+ # @deprecated Facebook no longer provides session keys.
101
219
  def get_token_info_from_session_keys(sessions, options = {})
220
+ Koala::Utils.deprecate("Facebook no longer provides session keys. The relevant OAuth methods will be removed in the next release.")
221
+
102
222
  # fetch the OAuth tokens from Facebook
103
223
  response = fetch_token_string({
104
224
  :type => 'client_cred',
@@ -116,6 +236,7 @@ module Koala
116
236
  MultiJson.decode(response)
117
237
  end
118
238
 
239
+ # @deprecated (see #get_token_info_from_session_keys)
119
240
  def get_tokens_from_session_keys(sessions, options = {})
120
241
  # get the original hash results
121
242
  results = get_token_info_from_session_keys(sessions, options)
@@ -123,6 +244,7 @@ module Koala
123
244
  results.collect { |r| r ? r["access_token"] : nil }
124
245
  end
125
246
 
247
+ # @deprecated (see #get_token_info_from_session_keys)
126
248
  def get_token_from_session_key(session, options = {})
127
249
  # convenience method for a single key
128
250
  # gets the overlaoded strings automatically
@@ -166,8 +288,15 @@ module Koala
166
288
 
167
289
  def parse_signed_cookie(fb_cookie)
168
290
  components = parse_signed_request(fb_cookie)
169
- if (code = components["code"]) && token_info = get_access_token_info(code, :redirect_uri => '')
170
- components.merge(token_info)
291
+ if code = components["code"]
292
+ begin
293
+ token_info = get_access_token_info(code, :redirect_uri => '')
294
+ rescue Koala::Facebook::APIError => err
295
+ return nil if err.message =~ /Code was invalid or expired/
296
+ raise
297
+ end
298
+
299
+ components.merge(token_info) if token_info
171
300
  else
172
301
  nil
173
302
  end
@@ -186,6 +315,14 @@ module Koala
186
315
  str += '=' * (4 - str.length.modulo(4))
187
316
  Base64.decode64(str.tr('-_', '+/'))
188
317
  end
318
+
319
+ 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)
321
+ raise ArgumentError, "url_for_dialog must get a callback either from the OAuth object or in the parameters!"
322
+ end
323
+
324
+ "#{base}?#{Koala::HTTPService.encode_params(url_options)}"
325
+ end
189
326
  end
190
327
  end
191
328
  end
@@ -1,36 +1,27 @@
1
1
  module Koala
2
2
  module Facebook
3
- module RealtimeUpdateMethods
4
- # note: to subscribe to real-time updates, you must have an application access token
3
+ class RealtimeUpdates
4
+ # Manage realtime callbacks for changes to users' information.
5
+ # See http://developers.facebook.com/docs/reference/api/realtime.
6
+ #
7
+ # @note: to subscribe to real-time updates, you must have an application access token
8
+ # or provide the app secret when initializing your RealtimeUpdates object.
5
9
 
6
- def self.included(base)
7
- # make the attributes readable
8
- base.class_eval do
9
- attr_reader :api, :app_id, :app_access_token, :secret
10
-
11
- # parses the challenge params and makes sure the call is legitimate
12
- # returns the challenge string to be sent back to facebook if true
13
- # returns false otherwise
14
- # this is a class method, since you don't need to know anything about the app
15
- # saves a potential trip fetching the app access token
16
- def self.meet_challenge(params, verify_token = nil, &verification_block)
17
- if params["hub.mode"] == "subscribe" &&
18
- # you can make sure this is legitimate through two ways
19
- # if your store the token across the calls, you can pass in the token value
20
- # and we'll make sure it matches
21
- (verify_token && params["hub.verify_token"] == verify_token) ||
22
- # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
23
- # you can pass in a block, which we'll call with the verify_token sent by Facebook
24
- # if it's legit, return anything that evaluates to true; otherwise, return nil or false
25
- (verification_block && yield(params["hub.verify_token"]))
26
- params["hub.challenge"]
27
- else
28
- false
29
- end
30
- end
31
- end
32
- end
10
+ # The application API interface used to communicate with Facebook.
11
+ # @return [Koala::Facebook::API]
12
+ attr_reader :api
13
+ attr_reader :app_id, :app_access_token, :secret
33
14
 
15
+ # Create a new RealtimeUpdates instance.
16
+ # If you don't have your app's access token, provide the app's secret and
17
+ # Koala will make a request to Facebook for the appropriate token.
18
+ #
19
+ # @param options initialization options.
20
+ # @option options :app_id the application's ID.
21
+ # @option options :app_access_token an application access token, if known.
22
+ # @option options :secret the application's secret.
23
+ #
24
+ # @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
34
25
  def initialize(options = {})
35
26
  @app_id = options[:app_id]
36
27
  @app_access_token = options[:app_access_token]
@@ -45,45 +36,92 @@ module Koala
45
36
  @app_access_token = oauth.get_app_access_token
46
37
  end
47
38
 
48
- @graph_api = API.new(@app_access_token)
39
+ @api = API.new(@app_access_token)
49
40
  end
50
41
 
51
- # subscribes for realtime updates
52
- # your callback_url must be set up to handle the verification request or the subscription will not be set up
53
- # http://developers.facebook.com/docs/api/realtime
54
- def subscribe(object, fields, callback_url, verify_token)
42
+ # Subscribe to realtime updates for certain fields on a given object (user, page, etc.).
43
+ # See {http://developers.facebook.com/docs/reference/api/realtime the realtime updates documentation}
44
+ # for more information on what objects and fields you can register for.
45
+ #
46
+ # @note Your callback_url must be set up to handle the verification request or the subscription will not be set up.
47
+ #
48
+ # @param object a Facebook ID (name or number)
49
+ # @param fields the fields you want your app to be updated about
50
+ # @param callback_url the URL Facebook should ping when an update is available
51
+ # @param verify_token a token included in the verification request, allowing you to ensure the call is genuine
52
+ # (see the docs for more information)
53
+ # @param options (see Koala::HTTPService.make_request)
54
+ #
55
+ # @return true if successful, false (or an APIError) otherwise.
56
+ def subscribe(object, fields, callback_url, verify_token, options = {})
55
57
  args = {
56
58
  :object => object,
57
59
  :fields => fields,
58
60
  :callback_url => callback_url,
59
- :verify_token => verify_token
60
- }
61
+ }.merge(verify_token ? {:verify_token => verify_token} : {})
61
62
  # a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
62
- @graph_api.graph_call(subscription_path, args, 'post', :http_component => :status) == 200
63
+ @api.graph_call(subscription_path, args, 'post', options.merge(:http_component => :status)) == 200
63
64
  end
64
65
 
65
- # removes subscription for object
66
- # if object is nil, it will remove all subscriptions
67
- def unsubscribe(object = nil)
68
- args = {}
69
- args[:object] = object if object
70
- @graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
66
+ # Unsubscribe from updates for a particular object or from updates.
67
+ #
68
+ # @param object the object whose subscriptions to delete.
69
+ # If no object is provided, all subscriptions will be removed.
70
+ # @param options (see Koala::HTTPService.make_request)
71
+ #
72
+ # @return true if the unsubscription is successful, false (or an APIError) otherwise.
73
+ def unsubscribe(object = nil, options = {})
74
+ @api.graph_call(subscription_path, object ? {:object => object} : {}, "delete", options.merge(:http_component => :status)) == 200
71
75
  end
72
76
 
73
- def list_subscriptions
74
- @graph_api.graph_call(subscription_path)
77
+ # List all active subscriptions for this application.
78
+ #
79
+ # @param options (see Koala::HTTPService.make_request)
80
+ #
81
+ # @return [Array] a list of active subscriptions
82
+ def list_subscriptions(options = {})
83
+ @api.graph_call(subscription_path, {}, "get", options)
75
84
  end
76
85
 
77
- def graph_api
78
- Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
79
- @api
86
+ # As a security measure (to prevent DDoS attacks), Facebook sends a verification request to your server
87
+ # after you request a subscription.
88
+ # This method parses the challenge params and makes sure the call is legitimate.
89
+ #
90
+ # @param params the request parameters sent by Facebook. (You can pass in a Rails params hash.)
91
+ # @param verify_token the verify token sent in the {#subscribe subscription request}, if you provided one
92
+ #
93
+ # @yield verify_token if you need to compute the verification token
94
+ # (for instance, if your callback URL includes a record ID, which you look up
95
+ # and use to calculate a hash), you can pass meet_challenge a block, which
96
+ # will receive the verify_token received back from Facebook.
97
+ #
98
+ # @return the challenge string to be sent back to Facebook, or false if the request is invalid.
99
+ def self.meet_challenge(params, verify_token = nil, &verification_block)
100
+ if params["hub.mode"] == "subscribe" &&
101
+ # you can make sure this is legitimate through two ways
102
+ # if your store the token across the calls, you can pass in the token value
103
+ # and we'll make sure it matches
104
+ (verify_token && params["hub.verify_token"] == verify_token) ||
105
+ # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
106
+ # you can pass in a block, which we'll call with the verify_token sent by Facebook
107
+ # if it's legit, return anything that evaluates to true; otherwise, return nil or false
108
+ (verification_block && yield(params["hub.verify_token"]))
109
+ params["hub.challenge"]
110
+ else
111
+ false
112
+ end
80
113
  end
81
-
82
- protected
83
-
114
+
115
+ # The Facebook subscription management URL for your application.
84
116
  def subscription_path
85
117
  @subscription_path ||= "#{@app_id}/subscriptions"
86
118
  end
119
+
120
+ # @private
121
+ def graph_api
122
+ Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
123
+ @api
124
+ end
87
125
  end
88
126
  end
89
127
  end