rplatform-rails 0.0.2

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/CHANGELOG ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,2 @@
1
+ == 0.0.3
2
+ now the base tests really do pass!
data/Manifest.txt ADDED
@@ -0,0 +1,22 @@
1
+ CHANGELOG
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ init.rb
7
+ lib/rplatform-rails.rb
8
+ lib/rplatform_rails/controller_extensions.rb
9
+ lib/rplatform_rails/model_extensions.rb
10
+ lib/rplatform_rails/session_extensions.rb
11
+ lib/rplatform_rails/status_manager.rb
12
+ lib/rplatform_rails/view_extensions.rb
13
+ tasks/all.rake
14
+ templates/debug_panel.rhtml
15
+ templates/exception_backtrace.rhtml
16
+ test/api_test.rb
17
+ test/controller_test.rb
18
+ test/initialization_test.rb
19
+ test/model_test.rb
20
+ test/session_test.rb
21
+ test/test_helper.rb
22
+ test/view_test.rb
data/README.txt ADDED
@@ -0,0 +1,33 @@
1
+ == REQUIREMENTS:
2
+
3
+ * the rplatform gem
4
+
5
+ == INSTALL:
6
+ * sudo gem install rplatform
7
+ * sudo gem install rplatform-rails
8
+
9
+ == LICENSE:
10
+
11
+ (The MIT License)
12
+
13
+ Copyright (c) 2008, Curtis Edmond (www.okwithfailure.com)
14
+ All rights reserved.
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining
17
+ a copy of this software and associated documentation files (the
18
+ 'Software'), to deal in the Software without restriction, including
19
+ without limitation the rights to use, copy, modify, merge, publish,
20
+ distribute, sublicense, and/or sell copies of the Software, and to
21
+ permit persons to whom the Software is furnished to do so, subject to
22
+ the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be
25
+ included in all copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
28
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
31
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
32
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
33
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/rplatform-rails.rb'
4
+
5
+ Hoe.new('rplatform-rails', RPlatform::Rails::VERSION) do |p|
6
+ p.rubyforge_name = 'rplatform'
7
+ p.author = 'Curtis Edmond'
8
+ p.email = 'curtis.edmond@gmail.com'
9
+ p.summary = "A rails interface for Facebook's Platform API"
10
+ p.url= 'http://rplatform.rubyforge.org/'
11
+ p.test_globs = ['test/*']
12
+ p.clean_globs = ['test/actual']
13
+ p.remote_rdoc_dir = 'http://rplatform.rubyforge.org/'
14
+ p.changes = p.paragraphs_of('CHANGELOG', 0..1).join("\n\n")
15
+ end
data/init.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'rplatform'
2
+ require 'rplatform_rails/controller_extensions'
3
+ require 'rplatform_rails/model_extensions'
4
+ require 'rplatform_rails/session_extensions'
5
+ require 'rplatform_rails/status_manager'
6
+ require 'rplatform_rails/view_extensions'
7
+
8
+ # inject methods to Rails MVC classes
9
+ ActionView::Base.send(:include, RPlatform::Rails::ViewExtensions)
10
+ ActionController::Base.send(:include, RPlatform::Rails::ControllerExtensions)
11
+ ActiveRecord::Base.send(:include, RPlatform::Rails::ModelExtensions)
12
+
13
+ # inject methods to Rails session management classes
14
+ CGI::Session.send(:include, RPlatform::Rails::SessionExtensions)
15
+
16
+ # TODO: document SessionStoreExtensions as API so that anyone can patch their own custom session container in addition to these
17
+ CGI::Session::PStore.send(:include, RPlatform::Rails::SessionStoreExtensions)
18
+ CGI::Session::ActiveRecordStore.send(:include, RPlatform::Rails::SessionStoreExtensions)
19
+ CGI::Session::DRbStore.send(:include, RPlatform::Rails::SessionStoreExtensions)
20
+ CGI::Session::FileStore.send(:include, RPlatform::Rails::SessionStoreExtensions)
21
+ CGI::Session::MemoryStore.send(:include, RPlatform::Rails::SessionStoreExtensions)
22
+ CGI::Session::MemCacheStore.send(:include, RPlatform::Rails::SessionStoreExtensions) if defined?(CGI::Session::MemCacheStore)
@@ -0,0 +1,45 @@
1
+ require 'rplatform'
2
+ # load Facebook YAML configuration file (credit: Evan Weaver)
3
+ ::NETWORKS = {}
4
+ if defined?(RAILS_ROOT) #find out something more elegant instead of this check...
5
+ yamlFile = YAML.load_file("#{RAILS_ROOT}/config/facebook.yml")
6
+
7
+
8
+ if File.exist?(yamlFile)
9
+ if yamlFile[RAILS_ENV]
10
+ NETWORKS.merge!(yamlFile[RAILS_ENV])
11
+ else
12
+ raise StandardError, "config/facebook.yml exists, but doesn't have a configuration for RAILS_ENV=#{RAILS_ENV}."
13
+ end
14
+ else
15
+ raise StandardError, "config/facebook.yml does not exist."
16
+ end
17
+ end
18
+
19
+ module RPlatform::Rails
20
+ VERSION = '0.0.2'
21
+
22
+ def self.fix_path(path)
23
+ # check to ensure that the path is relative
24
+ if matchData = /(\w+)(\:\/\/)([\w0-9\.]+)([\:0-9]*)(.*)/.match(path)
25
+ relativePath = matchData.captures[4]
26
+ RAILS_DEFAULT_LOGGER.info "** RFACEBOOK INFO: It looks like you used a full URL '#{path}' in facebook.yml. RFacebook expected a relative path and has automatically converted this URL to '#{relativePath}'."
27
+ path = relativePath
28
+ end
29
+
30
+ # check for the proper leading/trailing slashes
31
+ if (path and path.size>0)
32
+ # force leading slash, then trailing slash
33
+ path = "/#{path}" unless path.starts_with?("/")
34
+ path = "#{path}/" unless path.reverse.starts_with?("/")
35
+ end
36
+
37
+ return path
38
+ end
39
+ end
40
+
41
+
42
+ NETWORKS.values.each do |network|
43
+ network["canvas_path"] = RPlatform::Rails::fix_path(network["canvas_path"])
44
+ network["callback_path"] = RPlatform::Rails::fix_path(network["callback_path"])
45
+ end
@@ -0,0 +1,572 @@
1
+ # AUTHORS:
2
+ # - Curtis Edmond (www.okwithfailure.com)
3
+ # thanks to matt for rfacebook
4
+ # - Matt Pizzimenti (www.livelearncode.com)
5
+
6
+ # LICENSE:
7
+ # Redistribution and use in source and binary forms, with or without modification,
8
+ # are permitted provided that the following conditions are met:
9
+ #
10
+ # Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ #
13
+ # Redistributions in binary form must reproduce the above copyright notice,
14
+ # this list of conditions and the following disclaimer in the documentation
15
+ # and/or other materials provided with the distribution.
16
+ #
17
+ # Neither the name of the original author nor the names of contributors
18
+ # may be used to endorse or promote products derived from this software
19
+ # without specific prior written permission.
20
+ #
21
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+
32
+ require "facebook_web_session"
33
+ require File.join(File.dirname(__FILE__), "status_manager")
34
+
35
+ module RPlatform
36
+ module Rails
37
+ module ControllerExtensions
38
+
39
+ # signatures are allowed at most a 30 minute delta in the sig_time
40
+ FACEBOOK_SIGNATURE_TIME_SLACK = 48*3600
41
+
42
+ def get_network
43
+ network = get_network_from_yml
44
+ # check to see if there's a network configured for the given value. If not then default to facebook
45
+ NETWORKS[network].nil? ? 'facebook' : network
46
+ end
47
+
48
+ def get_network_from_yml
49
+ # host_parts = request.host.split('.')
50
+ # network = host_parts[0]
51
+
52
+ # TODO: finish this
53
+ # networks = NETWORKS
54
+ # networks.each {|n| n.value?()}
55
+ return 'facebook'
56
+ end
57
+
58
+ ################################################################################################
59
+ ################################################################################################
60
+ # :section: Core API variables
61
+ ################################################################################################
62
+
63
+ # Facebook API key, as parsed from the YAML file
64
+ def facebook_api_key
65
+ NETWORKS[get_network]["key"]
66
+ end
67
+
68
+ # Facebook API secret, as parsed from the YAML file
69
+ def facebook_api_secret
70
+ NETWORKS[get_network]["secret"]
71
+ end
72
+
73
+ # Facebook canvas path, as parsed from the YAML file (may be nil if this application is an external app)
74
+ def facebook_canvas_path
75
+ NETWORKS[get_network]["canvas_path"]
76
+ end
77
+
78
+ # Facebook callback path, as parsed from the YAML file (may be nil if this application is an external app)
79
+ def facebook_callback_path
80
+ NETWORKS[get_network]["callback_path"]
81
+ end
82
+
83
+ ################################################################################################
84
+ ################################################################################################
85
+ # :section: Special Facebook variables
86
+ ################################################################################################
87
+
88
+ # Accessor for all params beginning with "fb_sig_". The signature is verified
89
+ # to prevent replay attacks and other calls that don't originate from Facebook.
90
+ # (the "fb_sig_" prefix is removed from the parameter name)
91
+ def fbparams
92
+ # check to see if we have parsed the fb_sig_ params yet
93
+ if @fbparams.nil?
94
+ # first, look in the params hash
95
+ sourceParams = (params || {}).dup
96
+ @fbparams = parse_fb_sig_params(sourceParams)
97
+
98
+ # second, look in the cookies hash
99
+ if @fbparams.size == 0
100
+ sourceParams = (cookies || {}).dup
101
+ @fbparams = parse_fb_sig_params(sourceParams)
102
+ end
103
+
104
+ # ensure that these parameters aren't being replayed
105
+ sigTime = @fbparams["time"] ? @fbparams["time"].to_i : nil
106
+ if (sigTime.nil? or (sigTime > 0 and Time.now.to_i > (sigTime + FACEBOOK_SIGNATURE_TIME_SLACK)))
107
+ # signature expired, fbparams are not valid
108
+ @fbparams = {}
109
+ end
110
+
111
+ # ensure that signature validates properly from Facebook
112
+ expectedSignature = fbsession_holder.signature(@fbparams)
113
+ actualSignature = sourceParams["fb_sig"]
114
+ if (actualSignature.nil? or expectedSignature != actualSignature)
115
+ # signatures didn't match, fbparams are not valid
116
+ @fbparams = {}
117
+ end
118
+ end
119
+
120
+ # as a last resort, if we are an iframe app, we might have saved the
121
+ # fbparams to the session previously
122
+ if @fbparams.size == 0
123
+ @fbparams ||= session[:_rfacebook_fbparams] || {}
124
+ end
125
+
126
+ # return fbparams (may or may not be populated)
127
+ return @fbparams
128
+ end
129
+
130
+ # Gives direct access to a Facebook session (of type RFacebook::FacebookWebSession)
131
+ # for this user. An attempt will be made to activate this session (either using
132
+ # canvas params or an auth_token for external apps), but if the user
133
+ # has not been forced to log in to Facebook, the session will NOT be
134
+ # ready for usage. To double-check this, simply call 'ready?' to
135
+ # see if the session is okay to use.
136
+ def fbsession
137
+
138
+ # do a check to ensure that we nil out the fbsession_holder in case there is a new user visiting
139
+ if session[:_rfacebook_fbsession_holder] and fbparams["session_key"] and session[:_rfacebook_fbsession_holder].session_key != fbparams["session_key"]
140
+ session[:_rfacebook_fbsession_holder] = nil
141
+ end
142
+
143
+ # if we have verified fb_sig_* params, we should be able to activate the session here
144
+ if (!fbsession_holder.ready? and facebook_platform_signature_verified?)
145
+ # then try to activate the session somehow (or retrieve from previous state)
146
+ # these might be nil
147
+ facebookUserId = fbparams["user"]
148
+ facebookSessionKey = fbparams["session_key"]
149
+ expirationTime = fbparams["expires"]
150
+
151
+ # activate the session if we got all the pieces of information we needed
152
+ if (facebookUserId and facebookSessionKey and expirationTime)
153
+ fbsession_holder.activate_with_previous_session(facebookSessionKey, facebookUserId, expirationTime)
154
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: Activated session from inside the canvas (user=#{facebookUserId}, session_key=#{facebookSessionKey}, expires=#{expirationTime})"
155
+
156
+ # warn that we couldn't get a valid Facebook session since we were missing data
157
+ else
158
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK WARNING: Tried to get a valid Facebook session from POST params, but failed"
159
+ end
160
+ end
161
+
162
+ # if we still don't have a session, check the Rails session
163
+ # (used for external and iframe apps when fb_sig POST params weren't present)
164
+ if (!fbsession_holder.ready? and session[:_rfacebook_fbsession_holder] and session[:_rfacebook_fbsession_holder].ready?)
165
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: grabbing Facebook session from Rails session"
166
+ @fbsession_holder = session[:_rfacebook_fbsession_holder]
167
+ @fbsession_holder.logger = RAILS_DEFAULT_LOGGER
168
+ end
169
+
170
+ # set the network that should be used for this session REVIEW There is likely a better way to do this
171
+ fbsession_holder.network = get_network
172
+
173
+ # if all went well, we should definitely have a valid Facebook session object
174
+ return fbsession_holder
175
+ end
176
+
177
+ ################################################################################################
178
+ ################################################################################################
179
+ # :section: Facebook helper methods
180
+ ################################################################################################
181
+
182
+ # returns true if the user is viewing the page in the canvas
183
+ def in_facebook_canvas?
184
+ # TODO: make this check fbparams instead (signature is validated there)
185
+ return (params and params["fb_sig_in_canvas"] == "1")
186
+ end
187
+
188
+ # returns true if the user is viewing the page in an iframe
189
+ def in_facebook_frame?
190
+ # TODO: make this check fbparams instead (signature is validated there)
191
+ return (params and params["fb_sig_in_iframe"] == "1")
192
+ end
193
+
194
+ # returns true if the current request is a mock-ajax request
195
+ def in_mock_ajax?
196
+ # TODO: make this check fbparams instead (signature is validated there)
197
+ return (params and params["fb_sig_is_mockajax"] == "1")
198
+ end
199
+
200
+ # returns true if the current request is an FBJS ajax request
201
+ def in_ajax?
202
+ # TODO: make this check fbparams instead (signature is validated there)
203
+ return (params and params["fb_sig_is_ajax"] == "1")
204
+ end
205
+
206
+ # returns true if the user is viewing the page from an external website
207
+ def in_external_app?
208
+ # FIXME: once you click away in an iframe app, you are considered to be an external app
209
+ # TODO: read up on the hacks for avoiding nested iframes
210
+ return (params and params["fb_sig"] == nil and !in_facebook_frame?)
211
+ end
212
+
213
+ # returns true if the user has added (installed) the current application
214
+ def added_facebook_application?
215
+ # TODO: make this check fbparams instead (signature is validated there)
216
+ return (params and params["fb_sig_added"] == "1")
217
+ end
218
+
219
+ # clear the current session so that a new user can log in
220
+ def log_out_of_facebook
221
+ session[:_rfacebook_fbsession_holder] = nil
222
+ session[:_rfacebook_fbparams] = nil
223
+ @fbsession_holder = nil
224
+ end
225
+
226
+ # returns true if the fb_sig_* parameters have been verified with a correct signature
227
+ def facebook_platform_signature_verified?
228
+ return (fbparams.size != 0)
229
+ end
230
+
231
+ # this is a callback method for EXTERNAL web applications, you should define this method to do something
232
+ # (for example, redirect the user to your main page, etc.)
233
+ def finish_facebook_login
234
+ # do nothing by default
235
+ end
236
+
237
+ ################################################################################################
238
+ ################################################################################################
239
+ # :section: before_filters
240
+ ################################################################################################
241
+
242
+ # force the user to log in to Facebook
243
+ def require_facebook_login(urlOptions={})
244
+ # check to be sure we haven't already performed a redirect or other action
245
+ if !performed?
246
+
247
+ # handle invalid sessions by forcing the user to log in
248
+ if !fbsession.ready?
249
+
250
+ # external applications need to be redirected
251
+ if in_external_app?
252
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: Redirecting to login for external app"
253
+ redirect_to fbsession.get_login_url(urlOptions)
254
+ return false
255
+
256
+ # iframe and canvas apps need *validated* fbparams, otherwise session activation cannot happen
257
+ elsif !facebook_platform_signature_verified?
258
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK WARNING: Failed to verified canvas parameters from Facebook (probably due to a bad API key or API secret)"
259
+ render :text => facebook_debug_panel
260
+ return false
261
+
262
+ else
263
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: Redirecting to login for canvas app"
264
+ urlOptions.merge!({:canvas=>true})
265
+ redirect_to fbsession.get_login_url(urlOptions)
266
+ return false
267
+
268
+ end
269
+ end
270
+ end
271
+
272
+ # by default, the filter passes
273
+ return true
274
+ end
275
+
276
+ # force the user to install your Facebook application
277
+ def require_facebook_install(urlOptions={})
278
+ # if in_facebook_frame? and not added_facebook_application?
279
+ # render :text => %Q(<script language="javascript">top.location.href="#{fbsession.get_install_url}&next=#{request.path.gsub(/#{facebook_callback_path}/, "")}"</script>)
280
+ # end
281
+ if (in_facebook_canvas? or in_facebook_frame?)
282
+ if (!fbsession.ready? or !added_facebook_application?)
283
+ redirect_to fbsession.get_install_url(urlOptions)
284
+ return false
285
+ end
286
+ else
287
+ RAILS_DEFAULT_LOGGER.info "** RFACEBOOK WARNING: require_facebook_install is not intended for external applications, using require_facebook_login instead"
288
+ return require_facebook_login(urlOptions)
289
+ end
290
+ return true
291
+ end
292
+
293
+ ################################################################################################
294
+ ################################################################################################
295
+ # :section: Debug panel
296
+ ################################################################################################
297
+
298
+ # special rendering method to use when debugging
299
+ def render_with_facebook_debug_panel(options={})
300
+ begin
301
+ renderedOutput = render_to_string(options)
302
+ rescue Exception => e
303
+ renderedOutput = facebook_canvas_backtrace(e)
304
+ end
305
+ render :text => "#{facebook_debug_panel}#{renderedOutput}"
306
+ end
307
+
308
+ # returns HTML containing information about the current environment (API key, API secret, etc.)
309
+ def facebook_debug_panel
310
+ templatePath = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'debug_panel.rhtml')
311
+ template = File.read(templatePath)
312
+ return ERB.new(template).result(Proc.new{})
313
+ end
314
+
315
+ # used for the debug panel, runs a series of tests to determine what might be wrong
316
+ # with your particular environment
317
+ def facebook_status_manager
318
+ checks = [
319
+ SessionStatusCheck.new(self),
320
+ (FacebookParamsStatusCheck.new(self) unless (!in_facebook_canvas? and !in_facebook_frame?)),
321
+ InCanvasStatusCheck.new(self),
322
+ InFrameStatusCheck.new(self),
323
+ (CanvasPathStatusCheck.new(self) unless (!in_facebook_canvas? or !in_facebook_frame?)),
324
+ (CallbackPathStatusCheck.new(self) unless (!in_facebook_canvas? or !in_facebook_frame?)),
325
+ (FinishFacebookLoginStatusCheck.new(self) unless (in_facebook_canvas? or in_facebook_frame?)),
326
+ APIKeyStatusCheck.new(self),
327
+ APISecretStatusCheck.new(self)
328
+ ].compact
329
+ return StatusManager.new(checks)
330
+ end
331
+
332
+ ################################################################################################
333
+ ################################################################################################
334
+ # :section: Utility methods
335
+ ################################################################################################
336
+ private
337
+
338
+ # this before_filter is used by all controllers to activate a session in the case
339
+ # that this is an external website
340
+ # NOTE: this will change a bit in future releases (to be optionally executed)
341
+ def handle_facebook_login
342
+ # FIXME: make this optionally executed
343
+ # when we don't have a valid set of fbparams, we can try using an auth_token
344
+ # if it exists in our current parameters
345
+ if (in_external_app? and params["auth_token"])
346
+
347
+ # attempt to activate (or re-activate) with the auth token
348
+ begin
349
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: attempting to activate a new Facebook session from auth_token"
350
+ fbsession_holder.activate_with_token(params["auth_token"])
351
+ finish_facebook_login if fbsession_holder.ready?
352
+ rescue StandardError => e
353
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: Tried to use a stale auth_token (#{e.to_s})"
354
+ end
355
+
356
+ # log a warning if the session was not activated during this attempt
357
+ unless fbsession_holder.ready?
358
+ RAILS_DEFAULT_LOGGER.info "** RFACEBOOK WARNING: Tried to activate (or re-activate) a Facebook session with auth_token and failed"
359
+ end
360
+ end
361
+
362
+ # this before_filter never stops page load
363
+ return true
364
+ end
365
+
366
+ # return the current FacebookWebSession (can be un-activated)
367
+ def fbsession_holder # :nodoc:
368
+ if (@fbsession_holder == nil)
369
+ @fbsession_holder = FacebookWebSession.new(facebook_api_key, facebook_api_secret)
370
+ @fbsession_holder.logger = RAILS_DEFAULT_LOGGER
371
+ end
372
+ return @fbsession_holder
373
+ end
374
+
375
+ # keeps a reference to the fbsession in the current user's session
376
+ def persist_fbsession
377
+ if (!in_facebook_canvas? and fbsession_holder.ready?)
378
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: persisting Facebook session information into Rails session"
379
+ session[:_rfacebook_fbsession_holder] = @fbsession_holder.dup
380
+ if in_facebook_frame?
381
+ # we need iframe apps to remember they are iframe apps
382
+ session[:_rfacebook_fbparams] = fbparams
383
+ end
384
+ end
385
+ end
386
+
387
+ # given a parameter hash, parses out only the ones prefixed with fb_sig_
388
+ def parse_fb_sig_params(sourceParams)
389
+ # get the params prefixed by "fb_sig_" (and remove the prefix)
390
+ fbSigParams = {}
391
+ sourceParams.each do |k,v|
392
+ if matches = k.match(/fb_sig_(.+)/)
393
+ keyWithoutPrefix = matches[1]
394
+ fbSigParams[keyWithoutPrefix] = v
395
+ end
396
+ end
397
+
398
+ # return the new hash
399
+ return fbSigParams
400
+ end
401
+
402
+ # override the reset_session so that the entire session is cleared
403
+ # (patch from chrisff)
404
+ def reset_session_with_rplatform
405
+ @fbsession_holder=nil
406
+ @fbparams=nil
407
+ reset_session_without_rplatform
408
+ end
409
+
410
+ ################################################################################################
411
+ ################################################################################################
412
+ # :section: Backtrace handling
413
+ ################################################################################################
414
+
415
+ # overrides to allow backtraces in Facebook canvas pages
416
+ def rescue_action_with_rplatform(exception)
417
+ # render a special backtrace for canvas pages
418
+ if in_facebook_canvas?
419
+ render(:text => "#{facebook_debug_panel}#{facebook_canvas_backtrace(exception)}")
420
+
421
+ # all other pages get the default rescue behavior
422
+ else
423
+ rescue_action_without_rplatform(exception)
424
+ end
425
+ end
426
+
427
+ # returns HTML containing an exception backtrace that is viewable within a Facebook canvas page
428
+ def facebook_canvas_backtrace(exception)
429
+ # parse the actual exception backtrace
430
+ rfacebookBacktraceLines = []
431
+ exception.backtrace.each do |line|
432
+
433
+ # escape HTML
434
+ cleanLine = line.gsub(RAILS_ROOT, "").gsub("<", "&lt;").gsub(">", "&gt;")
435
+
436
+ # split up these lines by carriage return
437
+ pieces = cleanLine.split("\n")
438
+ if (pieces and pieces.size> 0)
439
+ pieces.each do |piece|
440
+ if matches = /.*[\/\\]+((.*)\:([0-9]+)\:\s*in\s*\`(.*)\')/.match(piece)
441
+ # for each parsed line, add to the array for later rendering in the template
442
+ rfacebookBacktraceLines << {
443
+ :filename => matches[2],
444
+ :line => matches[3],
445
+ :method => matches[4],
446
+ :rawsummary => piece,
447
+ }
448
+ end
449
+ end
450
+ end
451
+ end
452
+
453
+ # render to the ERB template
454
+ templatePath = File.join(File.dirname(__FILE__), "..", "templates", "exception_backtrace.rhtml")
455
+ template = File.read(templatePath)
456
+ return ERB.new(template).result(Proc.new{})
457
+ end
458
+
459
+ ################################################################################################
460
+ ################################################################################################
461
+ # :section: URL Management
462
+ ################################################################################################
463
+
464
+ # overrides url_for to account for canvas and iframe environments
465
+ def url_for_with_rplatform(options={})
466
+
467
+ # fix problems that some Rails installations had with sending nil options
468
+ options ||= {}
469
+
470
+ # use special URL rewriting when inside the canvas
471
+ # setting the full_callback option to true will override this
472
+ # and force usage of regular Rails rewriting
473
+ if options.is_a? Hash
474
+ fullCallback = (options[:full_callback] == true) ? true : false # TODO: is there already a Rails param for this?
475
+ options.delete(:full_callback)
476
+ end
477
+
478
+ if ((in_facebook_canvas? or in_mock_ajax? or in_ajax?) and !fullCallback) #TODO: do something separate for in_facebook_frame?
479
+
480
+ if options.is_a? Hash
481
+ options[:only_path] = true if options[:only_path].nil?
482
+ end
483
+
484
+ # try to get a regular URL
485
+ path = url_for_without_rplatform(options)
486
+
487
+ # replace anything that references the callback with the
488
+ # Facebook canvas equivalent (apps.facebook.com/*)
489
+ if path.starts_with?(self.facebook_callback_path)
490
+ path.sub!(self.facebook_callback_path, self.facebook_canvas_path)
491
+ path = "http://apps.#{get_network}.com#{path}"
492
+ elsif "#{path}/".starts_with?(self.facebook_callback_path)
493
+ path.sub!(self.facebook_callback_path.chop, self.facebook_canvas_path.chop)
494
+ path = "http://apps.#{get_network}.com#{path}"
495
+ elsif (path.starts_with?("http://www.#{get_network}.com") or path.starts_with?("https://www.#{get_network}.com"))
496
+ # be sure that URLs that go to some other Facebook service redirect back to the canvas
497
+ if path.include?("?")
498
+ path = "#{path}&canvas=true"
499
+ else
500
+ path = "#{path}?canvas=true"
501
+ end
502
+ elsif (!path.starts_with?("http://") and !path.starts_with?("https://"))
503
+ # default to a full URL (will link externally)
504
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: failed to get canvas-friendly URL ("+path+") for ["+options.inspect+"], creating an external URL instead"
505
+ path = "#{request.protocol}#{request.host}:#{request.port}#{path}"
506
+ end
507
+
508
+ # full callback rewriting
509
+ elsif fullCallback
510
+ options[:only_path] = true
511
+ path = "#{request.protocol}#{request.host}:#{request.port}#{url_for_without_rplatform(options)}"
512
+
513
+ # regular Rails rewriting
514
+ else
515
+ path = url_for_without_rplatform(options)
516
+ end
517
+
518
+ return path
519
+ end
520
+
521
+ # overrides redirect_to to account for canvas and iframe environments
522
+ def redirect_to_with_rplatform(options = {}, responseStatus = {})
523
+ # get the url
524
+ redirectUrl = url_for(options)
525
+
526
+ # canvas redirect
527
+ if in_facebook_canvas?
528
+
529
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: Canvas redirect to #{redirectUrl}"
530
+ render :text => "<fb:redirect url=\"#{redirectUrl}\" />"
531
+
532
+ # iframe redirect
533
+ elsif redirectUrl.match(/^https?:\/\/([^\/]*\.)?#{get_network}\.com(:\d+)?/i)
534
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: iframe redirect to #{redirectUrl}"
535
+ render :text => %Q(<script type="text/javascript">\ntop.location.href='#{redirectUrl}';\n</script>)
536
+
537
+ # otherwise, we only need to do a standard redirect
538
+ else
539
+ RAILS_DEFAULT_LOGGER.debug "** RFACEBOOK INFO: Regular redirect_to"
540
+ redirect_to_without_rplatform(options)
541
+ end
542
+ end
543
+
544
+ ################################################################################################
545
+ ################################################################################################
546
+ # :section: Extension management
547
+ ################################################################################################
548
+
549
+ def self.included(base) # :nodoc:
550
+ # since we currently override ALL controllers, we have to alias
551
+ # the old methods. Future versions will avoid this inclusion.
552
+ # TODO: allow controller-by-controller inclusion
553
+ base.class_eval do
554
+ alias_method_chain :url_for, :rplatform
555
+ alias_method_chain :redirect_to, :rplatform
556
+ alias_method_chain :reset_session, :rplatform
557
+ alias_method_chain :rescue_action, :rplatform
558
+ end
559
+
560
+ # ensure that every action handles facebook login
561
+ base.before_filter(:handle_facebook_login)
562
+
563
+ # ensure that we persist the Facebook session into the Rails session (if possible)
564
+ base.after_filter(:persist_fbsession)
565
+
566
+ # fix third party cookies in IE
567
+ base.before_filter{ |c| c.headers['P3P'] = %|CP="NOI DSP COR NID ADMa OPTa OUR NOR"| }
568
+ end
569
+
570
+ end
571
+ end
572
+ end