diff --git a/facebooker2.gemspec b/facebooker2.gemspec index c22f7a2..11a6831 100644 --- a/facebooker2.gemspec +++ b/facebooker2.gemspec @@ -1,6 +1,6 @@ spec = Gem::Specification.new do |s| s.name = 'facebooker2' - s.version = '0.0.4' + s.version = '0.0.5' s.summary = "Facebook Connect integration library for ruby and rails" s.description = "Facebook Connect integration library for ruby and rails" s.files = Dir['lib/**/*.rb'] @@ -9,5 +9,6 @@ spec = Gem::Specification.new do |s| s.author = "Mike Mangino" s.email = "mmangino@elevatedrails.com" s.homepage = "http://developers.facebook.com/docs/api" - s.add_dependency('mogli', ">=0.0.4") + s.add_dependency('mogli', ">=0.0.12") + s.add_dependency('ruby-hmac') end diff --git a/lib/facebooker2.rb b/lib/facebooker2.rb index b91c22b..3d4e793 100644 --- a/lib/facebooker2.rb +++ b/lib/facebooker2.rb @@ -1,6 +1,5 @@ # Facebooker2 require "mogli" - module Facebooker2 class NotConfigured < Exception; end class << self diff --git a/lib/facebooker2/rails/controller.rb b/lib/facebooker2/rails/controller.rb index 447968a..aacf299 100644 --- a/lib/facebooker2/rails/controller.rb +++ b/lib/facebooker2/rails/controller.rb @@ -1,4 +1,6 @@ require "digest/md5" +require "hmac-sha2" +require "ruby-debug" module Facebooker2 module Rails module Controller @@ -10,28 +12,36 @@ module Facebooker2 end def current_facebook_user - fetch_client_and_user_from_cookie + fetch_client_and_user @_current_facebook_user end def current_facebook_client - fetch_client_and_user_from_cookie + fetch_client_and_user @_current_facebook_client end - def fetch_client_and_user_from_cookie + def fetch_client_and_user return if @_fb_user_fetched - app_id = Facebooker2.app_id - if (hash_data = fb_cookie_hash_for_app_id(app_id)) and - fb_cookie_signature_correct?(fb_cookie_hash_for_app_id(app_id),Facebooker2.secret) - client = Mogli::Client.new(hash_data["access_token"],hash_data["expires"].to_i) - user = Mogli::User.new(:id=>hash_data["uid"]) - user.client = @_current_facebook_client - fb_sign_in_user_and_client(user,client) - end + 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 + fb_cookie_signature_correct?(fb_cookie_hash_for_app_id(app_id),Facebooker2.secret) + 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) + end + def fb_sign_in_user_and_client(user,client) user.client = client @_current_facebook_user = user @@ -67,6 +77,36 @@ 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 + + def fb_load_facebook_params + return {} if params[:signed_request].blank? + 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) + 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/helpers/javascript.rb b/lib/facebooker2/rails/helpers/javascript.rb index 8c164ee..16c9a91 100644 --- a/lib/facebooker2/rails/helpers/javascript.rb +++ b/lib/facebooker2/rails/helpers/javascript.rb @@ -21,7 +21,7 @@ module Facebooker2 }; (function() { - ar s = document.createElement('div'); + var s = document.createElement('div'); s.setAttribute('id','fb-root'); document.documentElement.getElementsByTagName("HEAD")[0].appendChild(s); var e = document.createElement('script'); diff --git a/spec/rails/controller_spec.rb b/spec/rails/controller_spec.rb index 09d28d8..ab67bfb 100644 --- a/spec/rails/controller_spec.rb +++ b/spec/rails/controller_spec.rb @@ -12,6 +12,7 @@ describe Facebooker2::Rails::Controller do let :controller do controller = FakeController.new + controller.stub!(:params).and_return({}) controller.stub!(:cookies).and_return("fbs_12345"=>"\"access_token=114355055262088|57f0206b01ad48bf84ac86f1-12451752|63WyZjRQbzowpN8ibdIfrsg80OA.&expires=0&secret=1e3375dcc4527e7ead0f82c095421690&session_key=57f0206b01ad48bf84ac86f1-12451752&sig=4337fcdee4cc68bb70ec495c0eebf89c&uid=12451752\"") controller end @@ -57,6 +58,16 @@ describe Facebooker2::Rails::Controller do controller.current_facebook_client.access_token.should == "114355055262088|57f0206b01ad48bf84ac86f1-12451752|63WyZjRQbzowpN8ibdIfrsg80OA." end + it "creates a client from params" do + controller.stub!(:cookies).and_return({}) + controller.stub!(:facebook_params).and_return( + :oauth_token => "103188716396725|2.N0kBq5D0cbwjTGm9J4xRgA__.3600.1279814400-585612657|Txwy8S7sWBIJnyAXebEgSx6ntgY.", + :expires=>"1279814400", + :user_id => "585612657") + controller.current_facebook_client.access_token.should == "103188716396725|2.N0kBq5D0cbwjTGm9J4xRgA__.3600.1279814400-585612657|Txwy8S7sWBIJnyAXebEgSx6ntgY." + controller.current_facebook_user.id.should == "585612657" + end + it "verifies that the signature is correct" do controller.fb_cookie_signature_correct?({ "access_token" => "114355055262088|57f0206b01ad48bf84ac86f1-12451752|63WyZjRQbzowpN8ibdIfrsg80OA.", @@ -85,6 +96,26 @@ describe Facebooker2::Rails::Controller do end + describe "Signed Request handling" do + it "should correctly parse JSON that is poorly encoded" do + controller.fb_signed_request_json("eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOjEyNzk4MTQ0MDAsIm9hdXRoX3Rva2VuIjoiMTAzMTg4NzE2Mzk2NzI1fDIuTjBrQnE1RDBjYndqVEdtOUo0eFJnQV9fLjM2MDAuMTI3OTgxNDQwMC01ODU2MTI2NTd8VHh3eThTN3NXQklKbnlBWGViRWdTeDZudGdZLiIsInVzZXJfaWQiOiI1ODU2MTI2NTcifQ"). + should == "{\"algorithm\":\"HMAC-SHA256\",\"expires\":1279814400,\"oauth_token\":\"103188716396725|2.N0kBq5D0cbwjTGm9J4xRgA__.3600.1279814400-585612657|Txwy8S7sWBIJnyAXebEgSx6ntgY.\",\"user_id\":\"585612657\"}" + end + + it "provides facebook_params if the sig is valid" do + Facebooker2.secret = "mysecretkey" + controller.stub!(:params).and_return(:signed_request=>"N1JJFILX63MufS1zpHZwN109VK1ggzEsD0N4pH-yPtc.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOjEyNzk4MjE2MDAsIm9hdXRoX3Rva2VuIjoiMTAzMTg4NzE2Mzk2NzI1fDIucnJRSktyRzFRYXpGYTFoa2Z6MWpMZ19fLjM2MDAuMTI3OTgyMTYwMC01MzI4Mjg4Njh8TWF4QVdxTWtVS3lKbEFwOVgwZldGWEF0M004LiIsInVzZXJfaWQiOiI1MzI4Mjg4NjgifQ") + controller.facebook_params[:user_id].should == "532828868" + end + + it "doesn't provide facebook params if the sig is invalid" do + Facebooker2.secret = "mysecretkey" + controller.stub!(:params).and_return(:signed_request=>"invalid.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOjEyNzk4MjE2MDAsIm9hdXRoX3Rva2VuIjoiMTAzMTg4NzE2Mzk2NzI1fDIucnJRSktyRzFRYXpGYTFoa2Z6MWpMZ19fLjM2MDAuMTI3OTgyMTYwMC01MzI4Mjg4Njh8TWF4QVdxTWtVS3lKbEFwOVgwZldGWEF0M004LiIsInVzZXJfaWQiOiI1MzI4Mjg4NjgifQ") + controller.facebook_params.should be_blank + + end + end + describe "Methods" do it "allows you to sign in a user" do