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 +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
|