diff --git a/README b/README
index 074479b..454934e 100644
--- a/README
+++ b/README
@@ -36,7 +36,7 @@ shared login partial.
<%= fb_connect_async_js %>
<% if current_facebook_user %>
<%= "Welcome #{current_facebook_user.first_name} #{current_facebook_user.last_name}!" %>
- or
+ or
<%= "Hello #{fb_name(current_facebook_user, :useyou => false)}!" # link to facebook profile
%>
<%= fb_logout_link("Logout of fb", request.url) %>
@@ -48,4 +48,41 @@ shared login partial.
<%= fb_login_and_redirect('', :perms => 'email,user_birthday') %>
<% end %>
+Using with Canvas Applications
+==============================
+
+To improve integration with Facebook and iframe canvas applications, the primary goal
+being things like FB.ui work as a dialog rather than a popup, the application
+needs to be authenticated against the user's account via OAuth before use.
+
+0. Prerequisite: You need a Facebook app. Have your canvas page name handy.
+
+1. Install facebooker2.
+
+2. Create config/facebooker.yml as above, but add the following key:
+
+production:
+ canvas_page_name:
+
+3. Add the following lines to your app/controllers/application_controller.rb
+
+include Facebooker2::Rails::Controller::CanvasOAuth
+
+ensure_canvas_connected_to_facebook :oauth_url, 'publish_stream'
+create_facebook_oauth_callback :oauth
+
+rescue_from Facebooker2::OAuthException do |exception|
+ redirect_to 'http://www.facebook.com/'
+end
+
+4. Create a route that generates a URL for the OAuth callback and calls the appropriate
+action on your controller:
+
+map.oauth '/oauth', :controller => :application, :action => :oauth
+
+5. Your canvas application will now ensure that the current user has authorized
+the application before anything else is allowed. The authorization and FB.ui dialogs
+will appear inline instead of as popups, improving user experience.
+
Copyright (c) 2010 Mike Mangino, released under the MIT license
+Copyright (c) 2010 John Bintz, released under the MIT license
diff --git a/lib/facebooker2.rb b/lib/facebooker2.rb
index 3d4e793..2695982 100644
--- a/lib/facebooker2.rb
+++ b/lib/facebooker2.rb
@@ -1,35 +1,37 @@
# Facebooker2
require "mogli"
+
module Facebooker2
class NotConfigured < Exception; end
class << self
- attr_accessor :api_key, :secret, :app_id
+ attr_accessor :api_key, :secret, :app_id, :canvas_page_name
end
-
+
def self.secret
- @secret || raise_unconfigured_exception
+ @secret || raise_unconfigured_exception
end
-
+
def self.app_id
@app_id || raise_unconfigured_exception
end
-
+
def self.raise_unconfigured_exception
raise NotConfigured.new("No configuration provided for Facebooker2. Either set the app_id and secret or call Facebooker2.load_facebooker_yaml in an initializer")
end
-
+
def self.configuration=(hash)
self.api_key = hash[:api_key]
self.secret = hash[:secret]
self.app_id = hash[:app_id]
+ self.canvas_page_name = hash[:canvas_page_name]
end
-
+
def self.load_facebooker_yaml
config = YAML.load(File.read(File.join(::Rails.root,"config","facebooker.yml")))[::Rails.env]
raise NotConfigured.new("Unable to load configuration for #{::Rails.env} from facebooker.yml. Is it set up?") if config.nil?
self.configuration = config.with_indifferent_access
end
-
+
def self.cast_to_facebook_id(object)
if object.kind_of?(Mogli::Profile)
object.id
@@ -43,8 +45,10 @@ end
require "facebooker2/rails/controller"
+require "facebooker2/rails/controller/canvas_oauth"
require "facebooker2/rails/helpers/facebook_connect"
require "facebooker2/rails/helpers/javascript"
require "facebooker2/rails/helpers/request_forms"
require "facebooker2/rails/helpers/user"
-require "facebooker2/rails/helpers"
\ No newline at end of file
+require "facebooker2/rails/helpers"
+require "facebooker2/oauth_exception"
diff --git a/lib/facebooker2/oauth_exception.rb b/lib/facebooker2/oauth_exception.rb
new file mode 100644
index 0000000..b588a86
--- /dev/null
+++ b/lib/facebooker2/oauth_exception.rb
@@ -0,0 +1,4 @@
+module Facebooker2
+ class OAuthException < StandardError; end
+end
+
diff --git a/lib/facebooker2/rails/controller.rb b/lib/facebooker2/rails/controller.rb
index e69dc08..66fb886 100644
--- a/lib/facebooker2/rails/controller.rb
+++ b/lib/facebooker2/rails/controller.rb
@@ -3,31 +3,31 @@ require "hmac-sha2"
module Facebooker2
module Rails
module Controller
-
+
def self.included(controller)
controller.helper Facebooker2::Rails::Helpers
controller.helper_method :current_facebook_user
controller.helper_method :current_facebook_client
controller.helper_method :facebook_params
end
-
+
def current_facebook_user
fetch_client_and_user
@_current_facebook_user
end
-
+
def current_facebook_client
fetch_client_and_user
@_current_facebook_client
end
-
+
def fetch_client_and_user
return if @_fb_user_fetched
fetch_client_and_user_from_cookie
fetch_client_and_user_from_signed_request unless @_current_facebook_client
@_fb_user_fetched = true
end
-
+
def fetch_client_and_user_from_cookie
app_id = Facebooker2.app_id
if (hash_data = fb_cookie_hash_for_app_id(app_id)) and
@@ -35,20 +35,20 @@ module Facebooker2
fb_create_user_and_client(hash_data["access_token"],hash_data["expires"],hash_data["uid"])
end
end
-
+
def fb_create_user_and_client(token,expires,userid)
client = Mogli::Client.new(token,expires.to_i)
user = Mogli::User.new(:id=>userid)
- fb_sign_in_user_and_client(user,client)
+ fb_sign_in_user_and_client(user,client)
end
-
+
def fb_sign_in_user_and_client(user,client)
user.client = client
@_current_facebook_user = user
@_current_facebook_client = client
@_fb_user_fetched = true
end
-
+
def fb_cookie_hash_for_app_id(app_id)
return nil unless fb_cookie_for_app_id?(app_id)
hash={}
@@ -59,15 +59,15 @@ module Facebooker2
end
hash
end
-
+
def fb_cookie_for_app_id?(app_id)
!fb_cookie_for_app_id(app_id).nil?
end
-
+
def fb_cookie_for_app_id(app_id)
cookies["fbs_#{app_id}"]
end
-
+
def fb_cookie_signature_correct?(hash,secret)
sorted_keys = hash.keys.reject {|k| k=="sig"}.sort
test_string = ""
@@ -77,13 +77,13 @@ module Facebooker2
test_string += secret
Digest::MD5.hexdigest(test_string) == hash["sig"]
end
-
+
def fb_signed_request_json(encoded)
chars_to_add = 4-(encoded.size % 4)
encoded += ("=" * chars_to_add)
Base64.decode64(encoded)
end
-
+
def facebook_params
@facebook_param ||= fb_load_facebook_params
end
@@ -93,20 +93,21 @@ module Facebooker2
sig,encoded_json = params[:signed_request].split(".")
return {} unless fb_signed_request_sig_valid?(sig,encoded_json)
ActiveSupport::JSON.decode(fb_signed_request_json(encoded_json)).with_indifferent_access
- end
-
- def fb_signed_request_sig_valid?(sig,encoded)
+ end
+
+ def fb_signed_request_sig_valid?(sig,encoded)
base64 = Base64.encode64(HMAC::SHA256.digest(Facebooker2.secret,encoded))
#now make the url changes that facebook makes
url_escaped_base64 = base64.gsub(/=*\n?$/,"").tr("+/","-_")
sig == url_escaped_base64
end
-
+
def fetch_client_and_user_from_signed_request
if facebook_params[:oauth_token]
fb_create_user_and_client(facebook_params[:oauth_token],facebook_params[:expires],facebook_params[:user_id])
end
end
+
end
end
end
diff --git a/lib/facebooker2/rails/controller/canvas_oauth.rb b/lib/facebooker2/rails/controller/canvas_oauth.rb
new file mode 100644
index 0000000..3d7f602
--- /dev/null
+++ b/lib/facebooker2/rails/controller/canvas_oauth.rb
@@ -0,0 +1,55 @@
+module Facebooker2
+ module Rails
+ module Controller
+ module CanvasOAuth
+ def self.included(controller)
+ controller.extend(CanvasOAuthClass)
+
+ class << controller
+ attr_accessor :_facebooker_oauth_callback_url, :_facebooker_scope
+ end
+ end
+
+ protected
+ def canvas_oauth_connect
+ raise "Canvas page name not defined! Define it in config/facebooker.yml as #{::Rails.env}: canvas_page_name: ." if !Facebooker2.canvas_page_name
+ if params[:error]
+ raise Facebooker2::OAuthException.new(params[:error][:message])
+ else
+ redirect_to ('http://apps.facebook.com/' + Facebooker2.canvas_page_name) if params[:code]
+ end
+ return false
+ end
+
+ def ensure_canvas_connected
+ case self.class._facebooker_oauth_callback_url
+ when Symbol
+ callback_url = send(self.class._facebooker_oauth_callback_url)
+ end
+
+ if current_facebook_user == nil && !params[:code] && !params[:error]
+ render :text => ""
+ return false
+ end
+ end
+ end
+
+ module CanvasOAuthClass
+ def ensure_canvas_connected_to_facebook(oauth_callback_url, *scope)
+ self._facebooker_oauth_callback_url = oauth_callback_url
+ self._facebooker_scope = scope
+
+ before_filter :ensure_canvas_connected
+ end
+
+ def create_facebook_oauth_callback(method_name)
+ self.class_eval(<<-EOT)
+ def #{method_name}
+ return canvas_oauth_connect
+ end
+ EOT
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rails/controller/canvas_oauth_spec.rb b/spec/rails/controller/canvas_oauth_spec.rb
new file mode 100644
index 0000000..e69de29