rplatform-rails 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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