koala 0.4 → 3.7.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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +32 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +25 -0
  7. data/ISSUE_TEMPLATE +25 -0
  8. data/LICENSE +22 -0
  9. data/Manifest +32 -5
  10. data/PULL_REQUEST_TEMPLATE +11 -0
  11. data/Rakefile +12 -12
  12. data/changelog.md +781 -0
  13. data/code_of_conduct.md +74 -0
  14. data/koala.gemspec +28 -24
  15. data/lib/koala/api/batch_operation.rb +86 -0
  16. data/lib/koala/api/graph_api_methods.rb +504 -0
  17. data/lib/koala/api/graph_batch_api.rb +167 -0
  18. data/lib/koala/api/graph_collection.rb +129 -0
  19. data/lib/koala/api/graph_error_checker.rb +72 -0
  20. data/lib/koala/api.rb +159 -0
  21. data/lib/koala/configuration.rb +56 -0
  22. data/lib/koala/errors.rb +126 -0
  23. data/lib/koala/http_service/request.rb +133 -0
  24. data/lib/koala/http_service/response.rb +20 -0
  25. data/lib/koala/http_service/uploadable_io.rb +183 -0
  26. data/lib/koala/http_service.rb +108 -0
  27. data/lib/koala/oauth.rb +342 -0
  28. data/lib/koala/realtime_updates.rb +151 -0
  29. data/lib/koala/test_users.rb +189 -0
  30. data/lib/koala/utils.rb +41 -0
  31. data/lib/koala/version.rb +3 -0
  32. data/lib/koala.rb +51 -291
  33. data/readme.md +269 -21
  34. data/spec/cases/api_spec.rb +362 -0
  35. data/spec/cases/configuration_spec.rb +11 -0
  36. data/spec/cases/error_spec.rb +143 -0
  37. data/spec/cases/graph_api_batch_spec.rb +788 -0
  38. data/spec/cases/graph_api_spec.rb +76 -0
  39. data/spec/cases/graph_collection_spec.rb +192 -0
  40. data/spec/cases/graph_error_checker_spec.rb +147 -0
  41. data/spec/cases/http_service/request_spec.rb +250 -0
  42. data/spec/cases/http_service/response_spec.rb +24 -0
  43. data/spec/cases/http_service_spec.rb +280 -0
  44. data/spec/cases/koala_spec.rb +57 -0
  45. data/spec/cases/koala_test_spec.rb +5 -0
  46. data/spec/cases/oauth_spec.rb +647 -0
  47. data/spec/cases/realtime_updates_spec.rb +327 -0
  48. data/spec/cases/test_users_spec.rb +383 -0
  49. data/spec/cases/uploadable_io_spec.rb +266 -0
  50. data/spec/cases/utils_spec.rb +55 -0
  51. data/spec/fixtures/beach.jpg +0 -0
  52. data/spec/fixtures/cat.m4v +0 -0
  53. data/spec/fixtures/facebook_data.yml +63 -0
  54. data/spec/fixtures/mock_facebook_responses.yml +483 -0
  55. data/spec/fixtures/vcr_cassettes/app_test_accounts.yml +97 -0
  56. data/spec/fixtures/vcr_cassettes/friend_list_next_page.yml +121 -0
  57. data/spec/integration/graph_collection_spec.rb +24 -0
  58. data/spec/spec_helper.rb +25 -0
  59. data/spec/support/custom_matchers.rb +28 -0
  60. data/spec/support/graph_api_shared_examples.rb +534 -0
  61. data/spec/support/koala_test.rb +251 -0
  62. data/spec/support/mock_http_service.rb +140 -0
  63. data/spec/support/uploadable_io_shared_examples.rb +70 -0
  64. metadata +206 -62
  65. data/CHANGELOG +0 -24
  66. data/init.rb +0 -2
  67. data/lib/http_services.rb +0 -60
  68. data/test/facebook_data.yml +0 -5
  69. data/test/koala/facebook_no_access_token_tests.rb +0 -119
  70. data/test/koala/facebook_with_access_token_tests.rb +0 -106
  71. data/test/koala_tests.rb +0 -30
@@ -0,0 +1,151 @@
1
+ module Koala
2
+ module Facebook
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.
9
+
10
+ attr_reader :app_id, :app_access_token, :secret
11
+
12
+ # Create a new RealtimeUpdates instance.
13
+ # If you don't have your app's access token, provide the app's secret and
14
+ # Koala will make a request to Facebook for the appropriate token.
15
+ #
16
+ # @param options initialization options.
17
+ # @option options :app_id the application's ID.
18
+ # @option options :app_access_token an application access token, if known.
19
+ # @option options :secret the application's secret.
20
+ #
21
+ # @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
22
+ def initialize(options = {})
23
+ @app_id = options[:app_id] || Koala.config.app_id
24
+ @app_access_token = options[:app_access_token] || Koala.config.app_access_token
25
+ @secret = options[:secret] || Koala.config.app_secret
26
+ unless @app_id && (@app_access_token || @secret) # make sure we have what we need
27
+ raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
28
+ end
29
+ end
30
+
31
+ # The app access token, either provided on initialization or fetched from Facebook using the
32
+ # app_id and secret.
33
+ def app_access_token
34
+ # If a token isn't provided but we need it, fetch it
35
+ @app_access_token ||= Koala::Facebook::OAuth.new(@app_id, @secret).get_app_access_token
36
+ end
37
+
38
+ # The application API interface used to communicate with Facebook.
39
+ # @return [Koala::Facebook::API]
40
+ def api
41
+ # Only instantiate the API if needed. validate_update doesn't require it, so we shouldn't
42
+ # make an unnecessary request to get the app_access_token.
43
+ @api ||= API.new(app_access_token)
44
+ end
45
+
46
+ # Subscribe to realtime updates for certain fields on a given object (user, page, etc.).
47
+ # See {http://developers.facebook.com/docs/reference/api/realtime the realtime updates documentation}
48
+ # for more information on what objects and fields you can register for.
49
+ #
50
+ # @note Your callback_url must be set up to handle the verification request or the subscription will not be set up.
51
+ #
52
+ # @param object a Facebook ID (name or number)
53
+ # @param fields the fields you want your app to be updated about
54
+ # @param callback_url the URL Facebook should ping when an update is available
55
+ # @param verify_token a token included in the verification request, allowing you to ensure the call is genuine
56
+ # (see the docs for more information)
57
+ # @param options (see Koala::HTTPService.make_request)
58
+ #
59
+ # @raise A subclass of Koala::Facebook::APIError if the subscription request failed.
60
+ def subscribe(object, fields, callback_url, verify_token, options = {})
61
+ args = {
62
+ :object => object,
63
+ :fields => fields,
64
+ :callback_url => callback_url,
65
+ }.merge(verify_token ? {:verify_token => verify_token} : {})
66
+ # a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
67
+ api.graph_call(subscription_path, args, 'post', options)
68
+ end
69
+
70
+ # Unsubscribe from updates for a particular object or from updates.
71
+ #
72
+ # @param object the object whose subscriptions to delete.
73
+ # If no object is provided, all subscriptions will be removed.
74
+ # @param options (see Koala::HTTPService.make_request)
75
+ #
76
+ # @raise A subclass of Koala::Facebook::APIError if the subscription request failed.
77
+ def unsubscribe(object = nil, options = {})
78
+ api.graph_call(subscription_path, object ? {:object => object} : {}, "delete", options)
79
+ end
80
+
81
+ # List all active subscriptions for this application.
82
+ #
83
+ # @param options (see Koala::HTTPService.make_request)
84
+ #
85
+ # @return [Array] a list of active subscriptions
86
+ def list_subscriptions(options = {})
87
+ api.graph_call(subscription_path, {}, "get", options)
88
+ end
89
+
90
+ # As a security measure (to prevent DDoS attacks), Facebook sends a verification request to your server
91
+ # after you request a subscription.
92
+ # This method parses the challenge params and makes sure the call is legitimate.
93
+ #
94
+ # @param params the request parameters sent by Facebook. (You can pass in a Rails params hash.)
95
+ # @param verify_token the verify token sent in the {#subscribe subscription request}, if you provided one
96
+ #
97
+ # @yield verify_token if you need to compute the verification token
98
+ # (for instance, if your callback URL includes a record ID, which you look up
99
+ # and use to calculate a hash), you can pass meet_challenge a block, which
100
+ # will receive the verify_token received back from Facebook.
101
+ #
102
+ # @return the challenge string to be sent back to Facebook, or false if the request is invalid.
103
+ def self.meet_challenge(params, verify_token = nil, &verification_block)
104
+ if params["hub.mode"] == "subscribe" &&
105
+ # you can make sure this is legitimate through two ways
106
+ # if your store the token across the calls, you can pass in the token value
107
+ # and we'll make sure it matches
108
+ ((verify_token && params["hub.verify_token"] == verify_token) ||
109
+ # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
110
+ # you can pass in a block, which we'll call with the verify_token sent by Facebook
111
+ # if it's legit, return anything that evaluates to true; otherwise, return nil or false
112
+ (verification_block && yield(params["hub.verify_token"])))
113
+ params["hub.challenge"]
114
+ else
115
+ false
116
+ end
117
+ end
118
+
119
+ # Public: As a security measure, all updates from facebook are signed using
120
+ # X-Hub-Signature: sha1=XXXX where XXX is the sha1 of the json payload
121
+ # using your application secret as the key.
122
+ #
123
+ # Example:
124
+ # # in Rails controller
125
+ # # @oauth being a previously defined Koala::Facebook::OAuth instance
126
+ # def receive_update
127
+ # if @oauth.validate_update(request.body, headers)
128
+ # ...
129
+ # end
130
+ # end
131
+ def validate_update(body, headers)
132
+ unless @secret
133
+ raise AppSecretNotDefinedError, "You must init RealtimeUpdates with your app secret in order to validate updates"
134
+ end
135
+
136
+ request_signature = headers['X-Hub-Signature'] || headers['HTTP_X_HUB_SIGNATURE']
137
+ return unless request_signature
138
+
139
+ signature_parts = request_signature.split("sha1=")
140
+ request_signature = signature_parts[1]
141
+ calculated_signature = OpenSSL::HMAC.hexdigest('sha1', @secret, body)
142
+ calculated_signature == request_signature
143
+ end
144
+
145
+ # The Facebook subscription management URL for your application.
146
+ def subscription_path
147
+ @subscription_path ||= "#{@app_id}/subscriptions"
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,189 @@
1
+ require 'koala'
2
+
3
+ module Koala
4
+ module Facebook
5
+
6
+ # Create and manage test users for your application.
7
+ # A test user is a user account associated with an app created for the purpose
8
+ # of testing the functionality of that app.
9
+ # You can use test users for manual or automated testing --
10
+ # Koala's live test suite uses test users to verify the library works with Facebook.
11
+ #
12
+ # @note the test user API is fairly slow compared to other interfaces
13
+ # (which makes sense -- it's creating whole new user accounts!).
14
+ #
15
+ # See http://developers.facebook.com/docs/test_users/.
16
+ class TestUsers
17
+ # The application API interface used to communicate with Facebook.
18
+ # @return [Koala::Facebook::API]
19
+ attr_reader :api
20
+ attr_reader :app_id, :app_access_token, :secret
21
+
22
+ # Create a new TestUsers instance.
23
+ # If you don't have your app's access token, provide the app's secret and
24
+ # Koala will make a request to Facebook for the appropriate token.
25
+ #
26
+ # @param options initialization options.
27
+ # @option options :app_id the application's ID.
28
+ # @option options :app_access_token an application access token, if known.
29
+ # @option options :secret the application's secret.
30
+ #
31
+ # @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
32
+ def initialize(options = {})
33
+ @app_id = options[:app_id] || Koala.config.app_id
34
+ @app_access_token = options[:app_access_token] || Koala.config.app_access_token
35
+ @secret = options[:secret] || Koala.config.app_secret
36
+
37
+ unless @app_id && (@app_access_token || @secret) # make sure we have what we need
38
+ raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
39
+ end
40
+
41
+ # fetch the access token if we're provided a secret
42
+ if @secret && !@app_access_token
43
+ oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
44
+ @app_access_token = oauth.get_app_access_token
45
+ end
46
+
47
+ @api = API.new(@app_access_token)
48
+ end
49
+
50
+ # Create a new test user.
51
+ #
52
+ # @param installed whether the user has installed your app
53
+ # @param permissions a comma-separated string or array of permissions the user has granted (if installed)
54
+ # @param args any additional arguments for the create call (name, etc.)
55
+ # @param options (see Koala::Facebook::API#api)
56
+ #
57
+ # @return a hash of information for the new user (id, access token, login URL, etc.)
58
+ def create(installed, permissions = nil, args = {}, options = {})
59
+ # Creates and returns a test user
60
+ args['installed'] = installed
61
+ args['permissions'] = (permissions.is_a?(Array) ? permissions.join(",") : permissions) if installed
62
+ @api.graph_call(test_user_accounts_path, args, "post", options)
63
+ end
64
+
65
+ # List all test users for the app.
66
+ #
67
+ # @param options (see Koala::Facebook::API#api)
68
+ #
69
+ # @return an array of hashes of user information (id, access token, etc.)
70
+ def list(options = {})
71
+ @api.graph_call(test_user_accounts_path, {}, "get", options)
72
+ end
73
+
74
+ # Delete a test user.
75
+ #
76
+ # @param test_user the user to delete; can be either a Facebook ID or the hash returned by {#create}
77
+ # @param options (see Koala::Facebook::API#api)
78
+ #
79
+ # @return true if successful, false (or an {Koala::Facebook::APIError APIError}) if not
80
+ def delete(test_user, options = {})
81
+ test_user = test_user["id"] if test_user.is_a?(Hash)
82
+ @api.delete_object(test_user, options)
83
+ end
84
+
85
+ # Deletes all test users in batches of 50.
86
+ #
87
+ # @note if you have a lot of test users (> 20), this operation can take a long time.
88
+ #
89
+ # @param options (see Koala::Facebook::API#api)
90
+ #
91
+ # @return a list of the test users that have been deleted
92
+ def delete_all(options = {})
93
+ # ideally we'd save a call by checking next_page_params, but at the time of writing
94
+ # Facebook isn't consistently returning full pages after the first one
95
+ previous_list = nil
96
+ while (test_user_list = list(options)).length > 0
97
+ # avoid infinite loops if Facebook returns buggy users you can't delete
98
+ # see http://developers.facebook.com/bugs/223629371047398
99
+ # since the hashes may change across calls, even if the IDs don't,
100
+ # we just compare the IDs.
101
+ test_user_ids = test_user_list.map {|u| u['id']}
102
+ previous_user_ids = (previous_list || []).map {|u| u['id']}
103
+ break if (test_user_ids - previous_user_ids).empty?
104
+
105
+ test_user_list.each_slice(50) do |users|
106
+ self.api.batch(options) {|batch_api| users.each {|u| batch_api.delete_object(u["id"]) }}
107
+ end
108
+
109
+ previous_list = test_user_list
110
+ end
111
+ end
112
+
113
+ # Updates a test user's attributes.
114
+ #
115
+ # @note currently, only name and password can be changed;
116
+ # see {http://developers.facebook.com/docs/test_users/ the Facebook documentation}.
117
+ #
118
+ # @param test_user the user to update; can be either a Facebook ID or the hash returned by {#create}
119
+ # @param args the updates to make
120
+ # @param options (see Koala::Facebook::API#api)
121
+ #
122
+ # @return true if successful, false (or an {Koala::Facebook::APIError APIError}) if not
123
+ def update(test_user, args = {}, options = {})
124
+ test_user = test_user["id"] if test_user.is_a?(Hash)
125
+ @api.graph_call(test_user, args, "post", options)
126
+ end
127
+
128
+ # Make two test users friends.
129
+ #
130
+ # @note there's no way to unfriend test users; you can always just create a new one.
131
+ #
132
+ # @param user1_hash one of the users to friend; the hash must contain both ID and access token (as returned by {#create})
133
+ # @param user2_hash the other user to friend
134
+ # @param options (see Koala::Facebook::API#api)
135
+ #
136
+ # @return true if successful, false (or an {Koala::Facebook::APIError APIError}) if not
137
+ def befriend(user1_hash, user2_hash, options = {})
138
+ user1_id = user1_hash["id"] || user1_hash[:id]
139
+ user2_id = user2_hash["id"] || user2_hash[:id]
140
+ user1_token = user1_hash["access_token"] || user1_hash[:access_token]
141
+ user2_token = user2_hash["access_token"] || user2_hash[:access_token]
142
+ unless user1_id && user2_id && user1_token && user2_token
143
+ # we explicitly raise an error here to minimize the risk of confusing output
144
+ # if you pass in a string (as was previously supported) no local exception would be raised
145
+ # but the Facebook call would fail
146
+ raise ArgumentError, "TestUsers#befriend requires hash arguments for both users with id and access_token"
147
+ end
148
+
149
+ u1_graph_api = API.new(user1_token, secret)
150
+ u2_graph_api = API.new(user2_token, secret)
151
+
152
+ # if we have a secret token, flag that we want the appsecret_proof to be generated
153
+ u1_graph_api.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post", options.merge(appsecret_proof: !!secret)) &&
154
+ u2_graph_api.graph_call("#{user2_id}/friends/#{user1_id}", {}, "post", options.merge(appsecret_proof: !!secret))
155
+ end
156
+
157
+ # Create a network of test users, all of whom are friends and have the same permissions.
158
+ #
159
+ # @note this call slows down dramatically the more users you create
160
+ # (test user calls are slow, and more users => more 1-on-1 connections to be made).
161
+ # Use carefully.
162
+ #
163
+ # @param network_size how many users to create
164
+ # @param installed whether the users have installed your app (see {#create})
165
+ # @param permissions what permissions the users have granted (see {#create})
166
+ # @param options (see Koala::Facebook::API#api)
167
+ #
168
+ # @return the list of users created
169
+ def create_network(network_size, installed = true, permissions = '', options = {})
170
+ users = (0...network_size).collect { create(installed, permissions, {}, options) }
171
+ friends = users.clone
172
+ users.each do |user|
173
+ # Remove this user from list of friends
174
+ friends.delete_at(0)
175
+ # befriend all the others
176
+ friends.each do |friend|
177
+ befriend(user, friend, options)
178
+ end
179
+ end
180
+ return users
181
+ end
182
+
183
+ # The Facebook test users management URL for your application.
184
+ def test_user_accounts_path
185
+ @test_user_accounts_path ||= "/#{@app_id}/accounts/test-users"
186
+ end
187
+ end # TestUserMethods
188
+ end # Facebook
189
+ end # Koala
@@ -0,0 +1,41 @@
1
+ module Koala
2
+ module Utils
3
+
4
+ # Utility methods used by Koala.
5
+ require 'logger'
6
+ require 'forwardable'
7
+
8
+ extend Forwardable
9
+ extend self
10
+
11
+ def_delegators :logger, :debug, :info, :warn, :error, :fatal, :level, :level=
12
+
13
+ # The Koala logger, an instance of the standard Ruby logger, pointing to STDOUT by default.
14
+ # In Rails projects, you can set this to Rails.logger.
15
+ attr_accessor :logger
16
+ self.logger = Logger.new(STDOUT)
17
+ self.logger.level = Logger::ERROR
18
+
19
+ # @private
20
+ DEPRECATION_PREFIX = "KOALA: Deprecation warning: "
21
+
22
+ # Prints a deprecation message.
23
+ # Each individual message will only be printed once to avoid spamming.
24
+ def deprecate(message)
25
+ @posted_deprecations ||= []
26
+ unless @posted_deprecations.include?(message)
27
+ # only include each message once
28
+ Kernel.warn("#{DEPRECATION_PREFIX}#{message}")
29
+ @posted_deprecations << message
30
+ end
31
+ end
32
+
33
+ # Ensures that a hash uses symbols as opposed to strings
34
+ # Useful for allowing either syntax for end users
35
+ def symbolize_hash(hash)
36
+ return hash unless hash.is_a?(Hash)
37
+
38
+ hash.inject({}){ |memo,(key,value)| memo[key.to_sym] = symbolize_hash(value); memo }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Koala
2
+ VERSION = "3.7.0"
3
+ end