koala 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,206 @@
1
+ <div id="header">
2
+ <h1>Facebook OAuth Playground</h1>
3
+ <h4>Powered by <a href="http://github.com/arsduo/koala" target="_blank">Koala</a></h2>
4
+ <h4>For novelty use only. Remember, the application's secret key is public.</h4>
5
+ </div>
6
+
7
+ <style>
8
+ #header {
9
+ text-align: center;
10
+ }
11
+
12
+ h1, h2, h3, h4 { margin: 0; }
13
+
14
+ .section {
15
+ border: 1px solid black;
16
+ padding: 10px;
17
+ -moz-border-radius: 9px;
18
+ -webkit-border-radius: 9px;
19
+ margin-bottom: 10px;
20
+ width: 100%;
21
+ }
22
+
23
+ #contents {
24
+ border: 2px solid #CCC;
25
+ border-width: 2px 0;
26
+ background-color: #EEE;
27
+ padding: 14px 3px 8px 8px;
28
+ }
29
+
30
+ #configurationInfo {
31
+ float:left;
32
+ margin-right: 2.5%;
33
+ width: 25%;
34
+ }
35
+
36
+ #configurationInfo .section {
37
+ background-color: #CCC;
38
+ }
39
+
40
+ #permissions .header {
41
+ margin-bottom: 5px;
42
+ }
43
+
44
+ #permissions .list {
45
+ height: 380px;
46
+ overflow: auto;
47
+ }
48
+
49
+ #generatedInfo {
50
+ float: left;
51
+ width: 70%;
52
+ }
53
+
54
+ #generatedInfo .section {
55
+ border-color: gray;
56
+ background: #DDD;
57
+ overflow: auto;
58
+ }
59
+
60
+ .clearFloat {
61
+ height: 1px;
62
+ height: 0px;
63
+ clear: both;
64
+ overflow: hidden;
65
+ }
66
+
67
+ ul, li { margin: 0; padding: 0; list-style: none; }
68
+ ul { margin: 12px 0; }
69
+
70
+ .code {
71
+ font-family: Courier, fixed;
72
+ font-size: 1.15em;
73
+ width: 100%;
74
+ }
75
+
76
+ .explanation {
77
+ font-size: 0.9em;
78
+ }
79
+
80
+ .datumName {
81
+ font-weight: bold;
82
+ }
83
+
84
+ .permissionType {
85
+ font-style: italic;
86
+ }
87
+ </style>
88
+
89
+ </style>
90
+
91
+ <div id="contents">
92
+
93
+ <div id="configurationInfo">
94
+ <div id="fbApp" class="section">
95
+ <h3>Facebook App Info</h3>
96
+ <ul>
97
+ <% @app_data.each_pair do |key, value| %>
98
+ <li><span class="datumName"><%= key %>:</span> <%= value %></li>
99
+ <% end %>
100
+ </ul>
101
+ </div>
102
+
103
+ <div id="permissions" class="section">
104
+ <form method="get">
105
+ <div class="header">
106
+ <h3>Permissions</h3>
107
+ <input type="submit" value="Update permissions" />
108
+ <a href="/">Reset Selection</a>
109
+ <div class="explanation">Choose permissions for the OAuth URL and the fb:login button.</div>
110
+ <% if @fetched_permissions %>
111
+ <div class="permissionType">Showing currently-active permissions</div>
112
+ <div class="explanation">(These were fetched by Koala as the page loaded!)</div>
113
+ <% else %>
114
+ <div class="permissionType">Showing selected permissions</div>
115
+ <% end %>
116
+ </div>
117
+
118
+ <div class="list">
119
+ <% @available_permissions.each do |permissions| %>
120
+ <h4><%= permissions[:name] %> Permissions</h4>
121
+ <ul>
122
+ <% permissions[:perms].each do |p| %>
123
+ <li>
124
+ <input type="checkbox" id="permission<%= p %>" name="permissions[]" value="<%= p %>" <%= @active_permissions.include?(p) ? "checked='checked'" : "" %> />
125
+ <label for="permission<%= p %>"><%= p %></a> (<a href="#" onclick="FB.api({method: 'auth.revokeExtendedPermission', perm: '<%= p %>'}, function(response) { alert(response) }); return false;">revoke</a>)
126
+ </li>
127
+ <% end %>
128
+ </ul>
129
+ <% end %>
130
+ </div>
131
+ </form>
132
+ </div>
133
+ </div>
134
+
135
+ <div id="generatedInfo">
136
+ <div id="oauthURLs" class="section">
137
+ <h3>OAuth URLs</h3>
138
+ <ul>
139
+ <li><span class="datumName">Generate a code:</span> <a href="<%= @oauth.url_for_oauth_code(:permissions => @permissions) %>"><%= @oauth.url_for_oauth_code(:permissions => @permissions) %></a></li>
140
+ <li><span class="datumName">OAuth code:</span> <%= @code || "click on the link above" %></li>
141
+ <li>
142
+ <span class="datumName">Access token:</span> <%= @oauth_access_token || "click on the link above" %>
143
+ <% if @oauth_access_token %><div class="explanation">This was fetched by Koala as the page loaded!</div><% end %>
144
+ </li>
145
+ <li><span class="datumName">Expiration:</span> <%= @expiration || "click on the link above" %></li>
146
+ <li><span class="datumName">Raw access response:</span> <%= @raw_access_response || "click on the link above" %></li>
147
+ <li><span class="datumName">URL for access code:</span>
148
+ <% if @code %>
149
+ <a href="<%= @oauth.url_for_access_token(@code) %>"><%= @oauth.url_for_access_token(@code) %></a>
150
+ <% else %>
151
+ click on the link above
152
+ <% end %>
153
+ </li>
154
+ </ul>
155
+ </div>
156
+
157
+ <div id="jsLogin" class="section">
158
+ <h3>Javascript Login (e.g. Facebook Connect)</h3>
159
+ <p>
160
+ <fb:login-button onlogin="location.reload()" perms="<%= (@permissions || []).join(",") %>"></fb:login-button>
161
+ <% if @permissions %>
162
+ and prompt for <%= @permissions.join(", ") %>
163
+ <% end %>
164
+ <% if @facebook_cookies %>
165
+ <div class="logout">
166
+ <a href="#" onclick="FB.logout(function() { location.reload() }); return false;">Logout</a>
167
+ </div>
168
+ <% end %>
169
+ </p>
170
+ </div>
171
+
172
+ <div id="cookieInfo" class="section">
173
+ <h3>Cookie info</h3>
174
+ <ul>
175
+ <% if @facebook_cookies %>
176
+ <% @facebook_cookies.each_pair do |key, value| %>
177
+ <li><span class="datumName"><%= key %>:</span> <%= value %></li>
178
+ <% end %>
179
+ <% else %>
180
+ <li>You're not signed in via Javascript. Login below.</li>
181
+ <% end %>
182
+ <li><span class="datumName">Raw hash</span>:
183
+ <div class="code"><%= request.cookies.inspect %></div>
184
+ </li>
185
+ </ul>
186
+ </div>
187
+
188
+ <div id="koala" class="section">
189
+ <h3>Koala</h3>
190
+ <ul>
191
+ <li><span class="datumName">GraphAPI:</span>
192
+ <div class="code"><%= @access_token ? "@graph = Koala::Facebook::GraphAPI.new(\"#{@access_token}\")" : "sign in above" %></div>
193
+ </li>
194
+ <li><span class="datumName">OAuth:</span>
195
+ <div class="code">@oauth = Koala::Facebook::OAuth.new(<%= @app_data["app_id"] %>, "<%= @app_data["secret_key"] %>", "<%= @app_data["callback_url"] %>")</div>
196
+ </li>
197
+ </ul>
198
+ </div>
199
+ </div>
200
+
201
+ <div class="clearFloat">&nbsp;</div>
202
+ </div>
203
+
204
+ <center>
205
+ <h5>Check out the playground's code at <a href="http://github.com/arsduo/oauth_playground">http://github.com/arsduo/oauth_playground</a>!</h5>
206
+ </center>
@@ -0,0 +1,39 @@
1
+ <html>
2
+ <head>
3
+ <title>Facebook OAuth Playground</title>
4
+ <meta name="description" content="Making it easier to play with Facebook's new Graph API and OAuth authentication."></meta>
5
+ <style>
6
+ body {
7
+ font-family: verdana, arial, sans-serif;
8
+ font-size: 12px;
9
+ margin: 0px;
10
+ }
11
+ </style>
12
+ </head>
13
+ <body>
14
+
15
+ <script type="text/javascript">
16
+
17
+ var _gaq = _gaq || [];
18
+ _gaq.push(['_setAccount', 'UA-16395421-1']);
19
+ _gaq.push(['_trackPageview']);
20
+
21
+ (function() {
22
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
23
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
24
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
25
+ })();
26
+
27
+ </script>
28
+
29
+ <%= yield %>
30
+
31
+ <div id="fb-root"></div>
32
+ <script src="http://connect.facebook.net/en_US/all.js"></script>
33
+ <script>
34
+ FB.init({ appId: <%= @app_data["app_id"] %>, cookie: true, status: true, xfbml: true });
35
+ </script>
36
+
37
+ </body>
38
+
39
+ </html>
data/koala.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{koala}
5
- s.version = "0.8.0"
5
+ s.version = "0.9.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Alex Koppel, Chris Baclig, Rafi Jacoby, Context Optional"]
9
- s.date = %q{2010-06-27}
9
+ s.date = %q{2010-09-30}
10
10
  s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph API and the older REST API, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
11
11
  s.email = %q{alex@alexkoppel.com}
12
12
  s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb"]
13
- s.files = ["CHANGELOG", "LICENSE", "Manifest", "Rakefile", "init.rb", "koala.gemspec", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb", "readme.md", "spec/facebook_data.yml", "spec/koala/api_base_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb", "spec/koala/graph_api/graph_api_no_access_token_tests.rb", "spec/koala/graph_api/graph_api_with_access_token_tests.rb", "spec/koala/live_testing_data_helper.rb", "spec/koala/net_http_service_tests.rb", "spec/koala/oauth/oauth_tests.rb", "spec/koala/realtime_updates/realtime_updates_tests.rb", "spec/koala/rest_api/rest_api_no_access_token_tests.rb", "spec/koala/rest_api/rest_api_with_access_token_tests.rb", "spec/koala_spec.rb", "spec/koala_spec_helper.rb", "spec/koala_spec_without_mocks.rb", "spec/mock_facebook_responses.yml", "spec/mock_http_service.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "Rakefile", "examples/oauth_playground/Capfile", "examples/oauth_playground/LICENSE", "examples/oauth_playground/Rakefile", "examples/oauth_playground/config.ru", "examples/oauth_playground/config/deploy.rb", "examples/oauth_playground/config/facebook.yml", "examples/oauth_playground/lib/load_facebook.rb", "examples/oauth_playground/lib/oauth_playground.rb", "examples/oauth_playground/readme.md", "examples/oauth_playground/spec/oauth_playground_spec.rb", "examples/oauth_playground/spec/spec_helper.rb", "examples/oauth_playground/tmp/restart.txt", "examples/oauth_playground/views/index.erb", "examples/oauth_playground/views/layout.erb", "init.rb", "koala.gemspec", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb", "readme.md", "spec/facebook_data.yml", "spec/koala/api_base_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb", "spec/koala/graph_api/graph_api_no_access_token_tests.rb", "spec/koala/graph_api/graph_api_with_access_token_tests.rb", "spec/koala/graph_api/graph_collection_tests.rb", "spec/koala/live_testing_data_helper.rb", "spec/koala/net_http_service_tests.rb", "spec/koala/oauth/oauth_tests.rb", "spec/koala/realtime_updates/realtime_updates_tests.rb", "spec/koala/rest_api/rest_api_no_access_token_tests.rb", "spec/koala/rest_api/rest_api_with_access_token_tests.rb", "spec/koala_spec.rb", "spec/koala_spec_helper.rb", "spec/koala_spec_without_mocks.rb", "spec/mock_facebook_responses.yml", "spec/mock_http_service.rb"]
14
14
  s.homepage = %q{http://github.com/arsduo/koala}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala", "--main", "readme.md"]
16
16
  s.require_paths = ["lib"]
data/lib/koala.rb CHANGED
@@ -5,6 +5,9 @@ require 'digest/md5'
5
5
  require 'rubygems'
6
6
  require 'json'
7
7
 
8
+ # openssl is required to support signed_request
9
+ require 'openssl'
10
+
8
11
  # include default http services
9
12
  require 'koala/http_services'
10
13
 
@@ -49,9 +52,13 @@ module Koala
49
52
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
50
53
  # Fetches the given path in the Graph API.
51
54
  args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
55
+
56
+ # add a leading /
57
+ path = "/#{path}" unless path =~ /^\//
58
+
52
59
  # make the request via the provided service
53
60
  result = Koala.make_request(path, args, verb, options)
54
-
61
+
55
62
  # Check for any 500 errors before parsing the body
56
63
  # since we're not guaranteed that the body is valid JSON
57
64
  # in the case of a server error
@@ -189,6 +196,22 @@ module Koala
189
196
  string = info["access_token"]
190
197
  end
191
198
  end
199
+
200
+ # signed_request
201
+ def parse_signed_request(request)
202
+ # Facebook's signed requests come in two parts -- the signature and the data payload
203
+ # see http://developers.facebook.com/docs/authentication/canvas
204
+ encoded_sig, payload = request.split(".")
205
+
206
+ sig = base64_url_decode(encoded_sig)
207
+
208
+ # if the signature matches, return the data, decoded and parsed as JSON
209
+ if OpenSSL::HMAC.digest("sha256", @app_secret, payload) == sig
210
+ JSON.parse(base64_url_decode(payload))
211
+ else
212
+ nil
213
+ end
214
+ end
192
215
 
193
216
  # from session keys
194
217
  def get_token_info_from_session_keys(sessions)
@@ -198,9 +221,8 @@ module Koala
198
221
  :sessions => sessions.join(",")
199
222
  }, true, "exchange_sessions")
200
223
 
201
- # get_token_from_session_key should return an empty body if an empty string or nil is provided
202
- # if invalid tokens are provided, it returns an array of nulls, which is a valid result
203
- if response == ""
224
+ # Facebook returns an empty body in certain error conditions
225
+ if response == ""
204
226
  raise APIError.new("ArgumentError", "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!")
205
227
  end
206
228
 
@@ -211,7 +233,7 @@ module Koala
211
233
  # get the original hash results
212
234
  results = get_token_info_from_session_keys(sessions)
213
235
  # now recollect them as just the access tokens
214
- results.collect { |r| string = r["access_token"] }
236
+ results.collect { |r| r ? r["access_token"] : nil }
215
237
  end
216
238
 
217
239
  def get_token_from_session_key(session)
@@ -242,11 +264,19 @@ module Koala
242
264
  end
243
265
 
244
266
  def fetch_token_string(args, post = false, endpoint = "access_token")
245
- Koala.make_request("oauth/#{endpoint}", {
267
+ Koala.make_request("/oauth/#{endpoint}", {
246
268
  :client_id => @app_id,
247
269
  :client_secret => @app_secret
248
270
  }.merge!(args), post ? "post" : "get").body
249
271
  end
272
+
273
+ # base 64
274
+ def base64_url_decode(string)
275
+ # to properly decode what Facebook provides, we need to add == to the end
276
+ # and translate certain characters to others before running the actual decoding
277
+ # see http://developers.facebook.com/docs/authentication/canvas
278
+ "#{string}==".tr("-_", "+/").unpack("m")[0]
279
+ end
250
280
  end
251
281
  end
252
282
 
@@ -2,6 +2,59 @@ module Koala
2
2
  module Facebook
3
3
  GRAPH_SERVER = "graph.facebook.com"
4
4
 
5
+ class GraphCollection < Array
6
+ #This class is a light wrapper for collections returned
7
+ #from the Graph API.
8
+ #
9
+ #It extends Array to allow direct access to the data colleciton
10
+ #which should allow it to drop in seamlessly.
11
+ #
12
+ #It also allows access to paging information and the
13
+ #ability to get the next/previous page in the collection
14
+ #by calling next_page or previous_page.
15
+ attr_reader :paging
16
+ attr_reader :api
17
+
18
+ def initialize(response, api)
19
+ super response["data"]
20
+ @paging = response["paging"]
21
+ @api = api
22
+ end
23
+
24
+ # defines methods for NEXT and PREVIOUS pages
25
+ %w{next previous}.each do |this|
26
+
27
+ # def next_page
28
+ # def previous_page
29
+ define_method "#{this.to_sym}_page" do
30
+ base, args = send("#{this}_page_params")
31
+ base ? @api.get_page([base, args]) : nil
32
+ end
33
+
34
+ # def next_page_params
35
+ # def previous_page_params
36
+ define_method "#{this.to_sym}_page_params" do
37
+ return nil unless @paging and @paging[this]
38
+ parse_page_url(@paging[this])
39
+ end
40
+ end
41
+
42
+ def parse_page_url(url)
43
+ match = url.match(/.com\/(.*)\?(.*)/)
44
+ base = match[1]
45
+ args = match[2]
46
+ params = CGI.parse(args)
47
+ new_params = {}
48
+ params.each_pair do |key,value|
49
+ new_params[key] = value.join ","
50
+ end
51
+ [base,new_params]
52
+ end
53
+
54
+ end
55
+
56
+
57
+
5
58
  module GraphAPIMethods
6
59
  # A client for the Facebook Graph API.
7
60
  #
@@ -41,11 +94,18 @@ module Koala
41
94
  # we raise an exception.
42
95
  graph_call("", args.merge("ids" => ids.join(",")))
43
96
  end
97
+
98
+ def get_page(params)
99
+ result = graph_call(*params)
100
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
101
+ end
44
102
 
45
103
  def get_connections(id, connection_name, args = {})
46
104
  # Fetchs the connections for given object.
47
- graph_call("#{id}/#{connection_name}", args)["data"]
105
+ result = graph_call("#{id}/#{connection_name}", args)
106
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
48
107
  end
108
+
49
109
 
50
110
  def get_picture(object, args = {})
51
111
  result = graph_call("#{object}/picture", args, "get", :http_component => :headers)
@@ -113,7 +173,8 @@ module Koala
113
173
 
114
174
  def search(search_terms, args = {})
115
175
  # Searches for a given term
116
- graph_call("search", args.merge({:q => search_terms}))
176
+ result = graph_call("search", args.merge({:q => search_terms}))
177
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
117
178
  end
118
179
 
119
180
  def graph_call(*args)
@@ -39,7 +39,7 @@ module Koala
39
39
 
40
40
  protected
41
41
  def self.encode_params(param_hash)
42
- # TODO investigating whether a built-in method handles this
42
+ # unfortunately, we can't use to_query because that's Rails, not Ruby
43
43
  # if no hash (e.g. no auth token) return empty string
44
44
  ((param_hash || {}).collect do |key_and_value|
45
45
  key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
@@ -61,7 +61,8 @@ module Koala
61
61
  # if the verb isn't get or post, send it as a post argument
62
62
  args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
63
63
  server = options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER
64
- response = self.send(verb, "https://#{server}/#{path}", :params => args)
64
+ typhoeus_options = {:params => args}.merge(options[:typhoeus_options] || {})
65
+ response = self.send(verb, "https://#{server}#{path}", typhoeus_options)
65
66
  Koala::Response.new(response.code, response.body, response.headers_hash)
66
67
  end
67
68
  end # class_eval