rplatform-rails 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +0 -0
- data/History.txt +2 -0
- data/Manifest.txt +22 -0
- data/README.txt +33 -0
- data/Rakefile +15 -0
- data/init.rb +22 -0
- data/lib/rplatform-rails.rb +45 -0
- data/lib/rplatform_rails/controller_extensions.rb +572 -0
- data/lib/rplatform_rails/model_extensions.rb +218 -0
- data/lib/rplatform_rails/session_extensions.rb +198 -0
- data/lib/rplatform_rails/status_manager.rb +312 -0
- data/lib/rplatform_rails/view_extensions.rb +93 -0
- data/tasks/all.rake +176 -0
- data/templates/debug_panel.rhtml +220 -0
- data/templates/exception_backtrace.rhtml +105 -0
- data/test/api_test.rb +203 -0
- data/test/controller_test.rb +257 -0
- data/test/initialization_test.rb +29 -0
- data/test/model_test.rb +142 -0
- data/test/session_test.rb +64 -0
- data/test/test_helper.rb +105 -0
- data/test/view_test.rb +30 -0
- metadata +92 -0
data/CHANGELOG
ADDED
File without changes
|
data/History.txt
ADDED
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("<", "<").gsub(">", ">")
|
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
|