canvas app work, need to write tests for it

This commit is contained in:
John Bintz 2010-09-17 13:12:58 -04:00
parent 0937d2ba9d
commit 6a3f2f73d5
6 changed files with 129 additions and 28 deletions

39
README
View File

@ -36,7 +36,7 @@ shared login partial.
<%= fb_connect_async_js %> <%= fb_connect_async_js %>
<% if current_facebook_user %> <% if current_facebook_user %>
<%= "Welcome #{current_facebook_user.first_name} #{current_facebook_user.last_name}!" %> <%= "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 <%= "Hello #{fb_name(current_facebook_user, :useyou => false)}!" # link to facebook profile
%> %>
<%= fb_logout_link("Logout of fb", request.url) %><br /> <%= fb_logout_link("Logout of fb", request.url) %><br />
@ -48,4 +48,41 @@ shared login partial.
<%= fb_login_and_redirect('<your URL here>', :perms => 'email,user_birthday') %> <%= fb_login_and_redirect('<your URL here>', :perms => 'email,user_birthday') %>
<% end %> <% 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: <your 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 Mike Mangino, released under the MIT license
Copyright (c) 2010 John Bintz, released under the MIT license

View File

@ -1,35 +1,37 @@
# Facebooker2 # Facebooker2
require "mogli" require "mogli"
module Facebooker2 module Facebooker2
class NotConfigured < Exception; end class NotConfigured < Exception; end
class << self class << self
attr_accessor :api_key, :secret, :app_id attr_accessor :api_key, :secret, :app_id, :canvas_page_name
end end
def self.secret def self.secret
@secret || raise_unconfigured_exception @secret || raise_unconfigured_exception
end end
def self.app_id def self.app_id
@app_id || raise_unconfigured_exception @app_id || raise_unconfigured_exception
end end
def self.raise_unconfigured_exception 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") 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 end
def self.configuration=(hash) def self.configuration=(hash)
self.api_key = hash[:api_key] self.api_key = hash[:api_key]
self.secret = hash[:secret] self.secret = hash[:secret]
self.app_id = hash[:app_id] self.app_id = hash[:app_id]
self.canvas_page_name = hash[:canvas_page_name]
end end
def self.load_facebooker_yaml def self.load_facebooker_yaml
config = YAML.load(File.read(File.join(::Rails.root,"config","facebooker.yml")))[::Rails.env] 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? 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 self.configuration = config.with_indifferent_access
end end
def self.cast_to_facebook_id(object) def self.cast_to_facebook_id(object)
if object.kind_of?(Mogli::Profile) if object.kind_of?(Mogli::Profile)
object.id object.id
@ -43,8 +45,10 @@ end
require "facebooker2/rails/controller" require "facebooker2/rails/controller"
require "facebooker2/rails/controller/canvas_oauth"
require "facebooker2/rails/helpers/facebook_connect" require "facebooker2/rails/helpers/facebook_connect"
require "facebooker2/rails/helpers/javascript" require "facebooker2/rails/helpers/javascript"
require "facebooker2/rails/helpers/request_forms" require "facebooker2/rails/helpers/request_forms"
require "facebooker2/rails/helpers/user" require "facebooker2/rails/helpers/user"
require "facebooker2/rails/helpers" require "facebooker2/rails/helpers"
require "facebooker2/oauth_exception"

View File

@ -0,0 +1,4 @@
module Facebooker2
class OAuthException < StandardError; end
end

View File

@ -3,31 +3,31 @@ require "hmac-sha2"
module Facebooker2 module Facebooker2
module Rails module Rails
module Controller module Controller
def self.included(controller) def self.included(controller)
controller.helper Facebooker2::Rails::Helpers controller.helper Facebooker2::Rails::Helpers
controller.helper_method :current_facebook_user controller.helper_method :current_facebook_user
controller.helper_method :current_facebook_client controller.helper_method :current_facebook_client
controller.helper_method :facebook_params controller.helper_method :facebook_params
end end
def current_facebook_user def current_facebook_user
fetch_client_and_user fetch_client_and_user
@_current_facebook_user @_current_facebook_user
end end
def current_facebook_client def current_facebook_client
fetch_client_and_user fetch_client_and_user
@_current_facebook_client @_current_facebook_client
end end
def fetch_client_and_user def fetch_client_and_user
return if @_fb_user_fetched return if @_fb_user_fetched
fetch_client_and_user_from_cookie fetch_client_and_user_from_cookie
fetch_client_and_user_from_signed_request unless @_current_facebook_client fetch_client_and_user_from_signed_request unless @_current_facebook_client
@_fb_user_fetched = true @_fb_user_fetched = true
end end
def fetch_client_and_user_from_cookie def fetch_client_and_user_from_cookie
app_id = Facebooker2.app_id app_id = Facebooker2.app_id
if (hash_data = fb_cookie_hash_for_app_id(app_id)) and 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"]) fb_create_user_and_client(hash_data["access_token"],hash_data["expires"],hash_data["uid"])
end end
end end
def fb_create_user_and_client(token,expires,userid) def fb_create_user_and_client(token,expires,userid)
client = Mogli::Client.new(token,expires.to_i) client = Mogli::Client.new(token,expires.to_i)
user = Mogli::User.new(:id=>userid) user = Mogli::User.new(:id=>userid)
fb_sign_in_user_and_client(user,client) fb_sign_in_user_and_client(user,client)
end end
def fb_sign_in_user_and_client(user,client) def fb_sign_in_user_and_client(user,client)
user.client = client user.client = client
@_current_facebook_user = user @_current_facebook_user = user
@_current_facebook_client = client @_current_facebook_client = client
@_fb_user_fetched = true @_fb_user_fetched = true
end end
def fb_cookie_hash_for_app_id(app_id) def fb_cookie_hash_for_app_id(app_id)
return nil unless fb_cookie_for_app_id?(app_id) return nil unless fb_cookie_for_app_id?(app_id)
hash={} hash={}
@ -59,15 +59,15 @@ module Facebooker2
end end
hash hash
end end
def fb_cookie_for_app_id?(app_id) def fb_cookie_for_app_id?(app_id)
!fb_cookie_for_app_id(app_id).nil? !fb_cookie_for_app_id(app_id).nil?
end end
def fb_cookie_for_app_id(app_id) def fb_cookie_for_app_id(app_id)
cookies["fbs_#{app_id}"] cookies["fbs_#{app_id}"]
end end
def fb_cookie_signature_correct?(hash,secret) def fb_cookie_signature_correct?(hash,secret)
sorted_keys = hash.keys.reject {|k| k=="sig"}.sort sorted_keys = hash.keys.reject {|k| k=="sig"}.sort
test_string = "" test_string = ""
@ -77,13 +77,13 @@ module Facebooker2
test_string += secret test_string += secret
Digest::MD5.hexdigest(test_string) == hash["sig"] Digest::MD5.hexdigest(test_string) == hash["sig"]
end end
def fb_signed_request_json(encoded) def fb_signed_request_json(encoded)
chars_to_add = 4-(encoded.size % 4) chars_to_add = 4-(encoded.size % 4)
encoded += ("=" * chars_to_add) encoded += ("=" * chars_to_add)
Base64.decode64(encoded) Base64.decode64(encoded)
end end
def facebook_params def facebook_params
@facebook_param ||= fb_load_facebook_params @facebook_param ||= fb_load_facebook_params
end end
@ -93,20 +93,21 @@ module Facebooker2
sig,encoded_json = params[:signed_request].split(".") sig,encoded_json = params[:signed_request].split(".")
return {} unless fb_signed_request_sig_valid?(sig,encoded_json) return {} unless fb_signed_request_sig_valid?(sig,encoded_json)
ActiveSupport::JSON.decode(fb_signed_request_json(encoded_json)).with_indifferent_access ActiveSupport::JSON.decode(fb_signed_request_json(encoded_json)).with_indifferent_access
end end
def fb_signed_request_sig_valid?(sig,encoded) def fb_signed_request_sig_valid?(sig,encoded)
base64 = Base64.encode64(HMAC::SHA256.digest(Facebooker2.secret,encoded)) base64 = Base64.encode64(HMAC::SHA256.digest(Facebooker2.secret,encoded))
#now make the url changes that facebook makes #now make the url changes that facebook makes
url_escaped_base64 = base64.gsub(/=*\n?$/,"").tr("+/","-_") url_escaped_base64 = base64.gsub(/=*\n?$/,"").tr("+/","-_")
sig == url_escaped_base64 sig == url_escaped_base64
end end
def fetch_client_and_user_from_signed_request def fetch_client_and_user_from_signed_request
if facebook_params[:oauth_token] if facebook_params[:oauth_token]
fb_create_user_and_client(facebook_params[:oauth_token],facebook_params[:expires],facebook_params[:user_id]) fb_create_user_and_client(facebook_params[:oauth_token],facebook_params[:expires],facebook_params[:user_id])
end end
end end
end end
end end
end end

View File

@ -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: <your url>." 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 => "<script>top.location.href = 'https://graph.facebook.com/oauth/authorize?client_id=#{Facebooker2.app_id}&redirect_uri=#{callback_url}&scope=#{[ self.class._facebooker_scope ].flatten * ','}'</script>"
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