facepalm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+
3
+ group :dev do # not development, otherwise would add unneeded development dependencies in gemspec
4
+ gem 'rails', ENV['RAILS']
5
+ gem 'redgreen'
6
+ gem 'rake'
7
+ gem 'jeweler'
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,96 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionmailer (3.1.3)
5
+ actionpack (= 3.1.3)
6
+ mail (~> 2.3.0)
7
+ actionpack (3.1.3)
8
+ activemodel (= 3.1.3)
9
+ activesupport (= 3.1.3)
10
+ builder (~> 3.0.0)
11
+ erubis (~> 2.7.0)
12
+ i18n (~> 0.6)
13
+ rack (~> 1.3.5)
14
+ rack-cache (~> 1.1)
15
+ rack-mount (~> 0.8.2)
16
+ rack-test (~> 0.6.1)
17
+ sprockets (~> 2.0.3)
18
+ activemodel (3.1.3)
19
+ activesupport (= 3.1.3)
20
+ builder (~> 3.0.0)
21
+ i18n (~> 0.6)
22
+ activerecord (3.1.3)
23
+ activemodel (= 3.1.3)
24
+ activesupport (= 3.1.3)
25
+ arel (~> 2.2.1)
26
+ tzinfo (~> 0.3.29)
27
+ activeresource (3.1.3)
28
+ activemodel (= 3.1.3)
29
+ activesupport (= 3.1.3)
30
+ activesupport (3.1.3)
31
+ multi_json (~> 1.0)
32
+ arel (2.2.1)
33
+ builder (3.0.0)
34
+ erubis (2.7.0)
35
+ git (1.2.5)
36
+ hike (1.2.1)
37
+ i18n (0.6.0)
38
+ jeweler (1.6.4)
39
+ bundler (~> 1.0)
40
+ git (>= 1.2.5)
41
+ rake
42
+ json (1.6.4)
43
+ mail (2.3.0)
44
+ i18n (>= 0.4.0)
45
+ mime-types (~> 1.16)
46
+ treetop (~> 1.4.8)
47
+ mime-types (1.17.2)
48
+ multi_json (1.0.4)
49
+ polyglot (0.3.3)
50
+ rack (1.3.5)
51
+ rack-cache (1.1)
52
+ rack (>= 0.4)
53
+ rack-mount (0.8.3)
54
+ rack (>= 1.0.0)
55
+ rack-ssl (1.3.2)
56
+ rack
57
+ rack-test (0.6.1)
58
+ rack (>= 1.0)
59
+ rails (3.1.3)
60
+ actionmailer (= 3.1.3)
61
+ actionpack (= 3.1.3)
62
+ activerecord (= 3.1.3)
63
+ activeresource (= 3.1.3)
64
+ activesupport (= 3.1.3)
65
+ bundler (~> 1.0)
66
+ railties (= 3.1.3)
67
+ railties (3.1.3)
68
+ actionpack (= 3.1.3)
69
+ activesupport (= 3.1.3)
70
+ rack-ssl (~> 1.3.2)
71
+ rake (>= 0.8.7)
72
+ rdoc (~> 3.4)
73
+ thor (~> 0.14.6)
74
+ rake (0.9.2.2)
75
+ rdoc (3.12)
76
+ json (~> 1.4)
77
+ redgreen (1.2.2)
78
+ sprockets (2.0.3)
79
+ hike (~> 1.2)
80
+ rack (~> 1.0)
81
+ tilt (~> 1.1, != 1.3.0)
82
+ thor (0.14.6)
83
+ tilt (1.3.3)
84
+ treetop (1.4.10)
85
+ polyglot
86
+ polyglot (>= 0.3.1)
87
+ tzinfo (0.3.31)
88
+
89
+ PLATFORMS
90
+ ruby
91
+
92
+ DEPENDENCIES
93
+ jeweler
94
+ rails
95
+ rake
96
+ redgreen
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ LICENSE
2
+
3
+ The MIT License
4
+
5
+ Copyright (c) 2008 Jon Yurek and thoughtbot, inc.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ Facepalm
2
+ ===========
3
+
4
+ Provides a set of classes, methods, and helpers to ease development of Facebook applications with Rails.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ In order to install Facepalm you should add it to your Gemfile:
10
+
11
+ gem 'facepalm'
12
+
13
+ Usage
14
+ -----
15
+
16
+ **Requesting Permissions**
17
+
18
+ Facepalm makes it simple to require Facebook authentication to access certain controller actions. You can require user to provide certain permissions to your application:
19
+
20
+ class PostsController < ApplicationController
21
+ facepalm_authentication :email, :publish_actions, :only => :index
22
+
23
+ def index
24
+ ...
25
+ end
26
+ end
27
+
28
+ This code will redirect user to Facebook permission request page if user wasn't authenticated yet.
29
+
30
+ You can also check user authentication right in your action code and request certain permission if necessary:
31
+
32
+ class PostsController < ApplicationController
33
+ facepalm_authentication :email, :publish_actions, :only => :index
34
+
35
+ def edit
36
+ if facepalm_require_authentication(:email)
37
+ ...
38
+ end
39
+ end
40
+ end
41
+
42
+ **Accessing Current User**
43
+
44
+ Current Facebook user data can be accessed using the ```current_facebook_user``` method:
45
+
46
+ class UsersController < ApplicationController
47
+ def profile
48
+ @user = User.find_by_facebook_id(current_facebook_user.uid)
49
+ end
50
+ end
51
+
52
+ This method is also accessible as a view helper.
53
+
54
+ **Application Configuration**
55
+
56
+ In order to use Facepalm you should set a default configuration for your Facebook application. The config file should be placed at RAILS_ROOT/config/facebook.yml
57
+
58
+ Sample config file:
59
+
60
+ development:
61
+ app_id: ...
62
+ secret: ...
63
+ namespace: your-app-namespace
64
+ callback_domain: yourdomain.com
65
+
66
+ test:
67
+ app_id: ...
68
+ secret: ...
69
+ namespace: test
70
+ callback_domain: callback.url
71
+
72
+ All these attributes attributes can be obtained or defined in your application settings at the [Facebook developer page](https://developers.facebook.com/apps).
73
+
74
+ Default configuration will be automatically loaded at the first request and won't reload. If you want to change application settings you should restart your application server instance.
75
+
76
+ Default configuration can be accessed from any parts of your code:
77
+
78
+ Facepalm::Config.default # => #<Facepalm::Config:0x108c03f38 @config={:secret=>"...", :namespace=>"...", :callback_domain=>"...", :app_id=>...}>
79
+
80
+ Current configuration is also accessible as a ```facepalm``` method in your controller. You can override this method to provide application configuration on a per-request basis:
81
+
82
+ class PostsController < ApplicationController
83
+ def current_fb_app
84
+ FbApp.find_by_app_id(params[:app_id])
85
+ end
86
+
87
+ def facepalm
88
+ if current_fb_app
89
+ Facepalm::Config.new(current_fb_app.attributes)
90
+ else
91
+ Facepalm::Config.default
92
+ end
93
+ end
94
+ end
95
+
96
+ Credits
97
+ -------
98
+
99
+ Big thanks to authors of the Facebooker2 plugin for inspiring me to develop this piece of software. I also copied some code snippets from this project so big thanks to everyone who put their effort to develop Facebooker2.
100
+
101
+
102
+ Copyright & License
103
+ -------------------
104
+
105
+ Copyright (c) 2011-2012 Aleksey V. Dmitriev.It is free software, and may be redistributed under the terms specified in the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new(:test) do |test|
4
+ test.libs << 'lib'
5
+ test.pattern = 'test/**/*_test.rb'
6
+ test.verbose = true
7
+ end
8
+
9
+ task :default do
10
+ sh "RAILS=2.3.12 && (bundle || bundle install) && bundle exec rake test"
11
+ sh "RAILS=3.0.10 && (bundle || bundle install) && exec rake test"
12
+ sh "RAILS=3.1.2 && (bundle || bundle install) && exec rake test"
13
+ sh "git checkout Gemfile.lock"
14
+ end
15
+
16
+ begin
17
+ require 'jeweler'
18
+
19
+ Jeweler::Tasks.new do |gem|
20
+ gem.name = 'facepalm'
21
+ gem.summary = "Facebook integration for Rack & Rails application"
22
+ gem.email = "rene.dekart@gmail.com"
23
+ gem.homepage = "http://github.com/dekart/facepalm"
24
+ gem.authors = ["Aleksey V. Dmitriev"]
25
+
26
+ gem.add_dependency "ie_iframe_cookies", '~> 0.1.2'
27
+ gem.add_dependency "koala", '~> 1.2.1'
28
+ end
29
+
30
+ Jeweler::GemcutterTasks.new
31
+ rescue LoadError
32
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
33
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/app/.gitkeep ADDED
@@ -0,0 +1 @@
1
+ This folder is necessary to correctly include routes in Rails 2.3. Without this folder routes won't be loaded correctly.
@@ -0,0 +1,28 @@
1
+ module Facepalm
2
+ class EndpointController < ActionController::Base
3
+ # OAuth 2.0 endpoint action added to ApplicationController and mounted to /facebook_oauth
4
+ def show
5
+ if params[:error]
6
+ raise Facepalm::OAuthException.new(params[:error][:message])
7
+ else
8
+ # this is where you get a code for requesting an access_token to do additional OAuth requests
9
+ # outside of using the FB JavaScript library (see Authenticating Users in a Web Application
10
+ # under the Authentication docs at http://developers.facebook.com/docs/authentication/)
11
+ if params[:code]
12
+ begin
13
+ # Decrypting return URL and redirecting to it
14
+ redirect_to(facebook_canvas_page_url + facepalm_url_encryptor.decrypt(params[:fb_return_to].to_s))
15
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage
16
+ ::Rails.logger.fatal "Failed to decrypt return URL: #{ params[:fb_return_to] }"
17
+
18
+ redirect_to facebook_canvas_page_url
19
+ end
20
+
21
+ false
22
+ else
23
+ raise Facepalm::OAuthException.new('No code returned.')
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,16 @@
1
+ if Rails::VERSION::MAJOR < 3
2
+
3
+ ActionController::Routing::Routes.draw do |map|
4
+ # OAuth 2.0 endpoint for facebook authentication
5
+ map.facepalm_endpoint '/facepalm/endpoint',
6
+ :controller => "facepalm/endpoint",
7
+ :action => "show"
8
+ end
9
+
10
+ else
11
+
12
+ Rails.application.routes.draw do
13
+ match '/facepalm/endpoint' => 'facepalm/endpoint#show', :as => :facepalm_endpoint
14
+ end
15
+
16
+ end
data/facepalm.gemspec ADDED
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "facepalm"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aleksey V. Dmitriev"]
12
+ s.date = "2011-12-28"
13
+ s.email = "rene.dekart@gmail.com"
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ "Gemfile",
20
+ "Gemfile.lock",
21
+ "LICENSE",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "app/.gitkeep",
26
+ "app/controllers/facepalm/endpoint_controller.rb",
27
+ "config/routes.rb",
28
+ "facepalm.gemspec",
29
+ "init.rb",
30
+ "install",
31
+ "lib/facepalm.rb",
32
+ "lib/facepalm/config.rb",
33
+ "lib/facepalm/engine.rb",
34
+ "lib/facepalm/rack/post_canvas_middleware.rb",
35
+ "lib/facepalm/rails/controller.rb",
36
+ "lib/facepalm/rails/controller/oauth_access.rb",
37
+ "lib/facepalm/rails/controller/redirects.rb",
38
+ "lib/facepalm/rails/controller/url_rewriting.rb",
39
+ "lib/facepalm/rails/helpers.rb",
40
+ "lib/facepalm/rails/helpers/javascript_helper.rb",
41
+ "lib/facepalm/user.rb"
42
+ ]
43
+ s.homepage = "http://github.com/dekart/facepalm"
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = "1.8.13"
46
+ s.summary = "Facebook integration for Rack & Rails application"
47
+
48
+ if s.respond_to? :specification_version then
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
+ s.add_runtime_dependency(%q<ie_iframe_cookies>, ["~> 0.1.2"])
53
+ s.add_runtime_dependency(%q<koala>, ["~> 1.2.1"])
54
+ else
55
+ s.add_dependency(%q<ie_iframe_cookies>, ["~> 0.1.2"])
56
+ s.add_dependency(%q<koala>, ["~> 1.2.1"])
57
+ end
58
+ else
59
+ s.add_dependency(%q<ie_iframe_cookies>, ["~> 0.1.2"])
60
+ s.add_dependency(%q<koala>, ["~> 1.2.1"])
61
+ end
62
+ end
63
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "lib", "facepalm")
data/install ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env sh
2
+
3
+ bundle exec rake gemspec:generate build
4
+ cd pkg
5
+ gem uninstall facepalm
6
+ gem install --local facepalm
7
+ cd ..
@@ -0,0 +1,66 @@
1
+ module Facepalm
2
+ # Facebook application configuration class
3
+ class Config
4
+ attr_accessor :config
5
+
6
+ class << self
7
+ # A shortcut to access default configuration stored in RAILS_ROOT/config/facebook.yml
8
+ def default
9
+ @@default ||= self.new(load_default_config_from_file)
10
+ end
11
+
12
+ def load_default_config_from_file
13
+ config_data = YAML.load(
14
+ ERB.new(
15
+ File.read(::Rails.root.join("config", "facebook.yml"))
16
+ ).result
17
+ )[::Rails.env]
18
+
19
+ raise NotConfigured.new("Unable to load configuration for #{ ::Rails.env } from config/facebook.yml") unless config_data
20
+
21
+ config_data
22
+ end
23
+ end
24
+
25
+ def initialize(options = {})
26
+ self.config = options.to_options
27
+ end
28
+
29
+ # Defining methods for quick access to config values
30
+ %w{app_id secret namespace callback_domain}.each do |attribute|
31
+ class_eval %{
32
+ def #{ attribute }
33
+ config[:#{ attribute }]
34
+ end
35
+ }
36
+ end
37
+
38
+ def oauth_client
39
+ @oauth_client ||= Koala::Facebook::OAuth.new(app_id, secret)
40
+ end
41
+
42
+ # Koala Facebook API client instantiated with application access token
43
+ def api_client
44
+ @api_client ||= Koala::Facebook::API.new(app_access_token)
45
+ end
46
+
47
+ # Fetches application access token
48
+ def app_access_token
49
+ @app_access_token ||= oauth_client.get_app_access_token
50
+ end
51
+
52
+ def subscription_token
53
+ Digest::MD5.hexdigest(secret)
54
+ end
55
+
56
+ # URL of the application canvas page
57
+ def canvas_page_url(protocol)
58
+ "#{ protocol }apps.facebook.com/#{ namespace }"
59
+ end
60
+
61
+ # Application callback URL
62
+ def callback_url(protocol)
63
+ protocol + callback_domain
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,28 @@
1
+ if Rails::VERSION::MAJOR > 2
2
+
3
+ module Facepalm
4
+ class Engine < ::Rails::Engine
5
+ initializer "facepalm.middleware" do |app|
6
+ app.middleware.insert_after(ActionDispatch::ParamsParser, Facepalm::Rack::PostCanvasMiddleware)
7
+ end
8
+
9
+ initializer "facepalm.controller_extension" do
10
+ ActiveSupport.on_load :action_controller do
11
+ ActionController::Base.send(:include, Facepalm::Rails::Controller)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ else
18
+ ActionController::Routing::Routes.add_configuration_file(File.expand_path('../../../config/routes.rb', __FILE__))
19
+
20
+ ActionController::Dispatcher.middleware.insert_after(ActionController::ParamsParser, Facepalm::Rack::PostCanvasMiddleware)
21
+
22
+ ActionController::Base.send(:include, Facepalm::Rails::Controller)
23
+
24
+ # Loading plugin controllers manually because the're not loaded automatically from gems
25
+ Dir[File.expand_path('../../../app/controllers/**/*.rb', __FILE__)].each do |file|
26
+ require file
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ module Facepalm
2
+ module Rack
3
+
4
+ # This rack middleware converts POST requests from Facebook to GET requests.
5
+ # It's necessary to make RESTful routes work as expected without any changes
6
+ # in the application.
7
+ class PostCanvasMiddleware
8
+ def initialize(app, options = {})
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ request = ::Rack::Request.new(env)
14
+
15
+ if request.POST['signed_request'] && request.post? && request.params['_method'].blank?
16
+ env['REQUEST_METHOD'] = 'GET'
17
+ end
18
+
19
+ # Put signed_request parameter to the same place where HTTP header X-Signed-Request come.
20
+ # This let us work both with params and HTTP headers in the same way. Very useful for AJAX.
21
+ env['HTTP_SIGNED_REQUEST'] ||= request.POST['signed_request']
22
+
23
+ @app.call(env)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,104 @@
1
+ module Facepalm
2
+ class OAuthException < StandardError; end
3
+
4
+ module Rails
5
+ module Controller
6
+
7
+ # OAuth 2.0 authentication module
8
+ module OauthAccess
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ end
12
+
13
+ # A filter class for Rails
14
+ class AccessFilter
15
+ def initialize(*permissions)
16
+ @permissions = permissions
17
+ end
18
+
19
+ def filter(controller)
20
+ controller.send(:facepalm_require_authentication, *@permissions)
21
+ end
22
+ end
23
+
24
+ module ClassMethods
25
+ # Requires Facebook authentication for the whole set of controller actions.
26
+ # Use it to setup a given set of permissions for the whole controller
27
+ #
28
+ # @param permissions An array of permissions to require
29
+ # @param options A hash of options to control filter application, similar to
30
+ # options hash for before_filter
31
+ #
32
+ # @example
33
+ # class MyController < ApplicationController
34
+ # facepalm_authentication :email, :publish_actions, :only => :index
35
+ # end
36
+ def facepalm_authentication(*permissions)
37
+ options = permissions.extract_options!
38
+
39
+ cattr_accessor :facepalm_authentication_filter
40
+ self.facepalm_authentication_filter = AccessFilter.new(*permissions)
41
+
42
+ before_filter(facepalm_authentication_filter, options)
43
+ end
44
+ end
45
+
46
+ # Requires a given set of permissions in context of the current action.
47
+ # Use it to require permissions in a single action or custom filter.
48
+ #
49
+ # NOTE: Facepalm doesn't check if user provided all required permissions.
50
+ # It only checks if user was authenticated and redirects to permission
51
+ # request page with a given set of permissions.
52
+ #
53
+ # @param permissions An array of permissions to require
54
+ #
55
+ # @return true if user authorized the application, false otehrwise
56
+ #
57
+ # @example
58
+ # class MyController < ApplicationController
59
+ # before_filter :my_custom_filter, :only => :show
60
+ #
61
+ # def my_custom_filter
62
+ # my_custom_condition? and facepalm_require_authentication(:publish_actions)
63
+ # end
64
+ #
65
+ # def index
66
+ # if facepalm_require_authentication(:email)
67
+ # ... do what you need ...
68
+ # end
69
+ # end
70
+ # end
71
+ def facepalm_require_authentication(*permissions)
72
+ if current_facebook_user.try(:authenticated?)
73
+ true
74
+ else
75
+ # Encrypting return URL to pass it to Facebook
76
+ return_code = facepalm_url_encryptor.encrypt(
77
+ url_for(params_without_facebook_data.merge(:canvas => false, :only_path => true))
78
+ )
79
+
80
+ redirect_from_iframe(
81
+ facepalm.oauth_client.url_for_oauth_code(
82
+ :permissions => permissions,
83
+ :callback => facepalm_endpoint_url(
84
+ :fb_return_to => ::Rack::Utils.escape(return_code)
85
+ )
86
+ )
87
+ )
88
+
89
+ false
90
+ end
91
+ end
92
+
93
+ # Internally used to encrypt return URL for authentication endpoint
94
+ #
95
+ # @return ActiveSupport::MessageEncryptor
96
+ #
97
+ # @private
98
+ def facepalm_url_encryptor
99
+ @facebook_url_encryptor ||= ActiveSupport::MessageEncryptor.new(facepalm.secret)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,48 @@
1
+ module Facepalm
2
+ module Rails
3
+ module Controller
4
+ module Redirects
5
+ # Overrides ActionController::Base#redirect_to to pass signed_request in flash[]
6
+ def redirect_to(*args)
7
+ flash[:signed_request] = fb_signed_request
8
+
9
+ super(*args)
10
+ end
11
+
12
+ # Redirects user to a definite URL with JavaScript code that overwrites
13
+ # top frame location. Use it to redirect user from within an iframe.
14
+ def redirect_from_iframe(url_options)
15
+ redirect_url = url_options.is_a?(String) ? url_options : url_for(url_options)
16
+
17
+ logger.info "Redirecting from IFRAME to #{ redirect_url }"
18
+
19
+ render(
20
+ :text => iframe_redirect_code(redirect_url),
21
+ :layout => false
22
+ )
23
+ end
24
+
25
+ # Generates HTML and JavaScript code to redirect user with top frame location
26
+ # overwrite
27
+ #
28
+ # @param target_url An URL to redirect the user to
29
+ # @param custom_code A custom HTML code to insert into the result document.
30
+ # Can be used to add OpenGraph tags to redirect page code.
31
+ def iframe_redirect_code(target_url, custom_code = nil)
32
+ %{
33
+ <html><head>
34
+ <script type="text/javascript">
35
+ window.top.location.href = #{ target_url.to_json };
36
+ </script>
37
+ <noscript>
38
+ <meta http-equiv="refresh" content="0;url=#{ target_url }" />
39
+ <meta http-equiv="window-target" content="_top" />
40
+ </noscript>
41
+ #{ custom_code }
42
+ </head></html>
43
+ }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ module Facepalm
2
+ module Rails
3
+ module Controller
4
+ module UrlRewriting
5
+ def self.included(base)
6
+ base.class_eval do
7
+ alias_method_chain :url_for, :facepalm
8
+
9
+ helper_method(:facebook_canvas_page_url, :facebook_callback_url)
10
+ end
11
+ end
12
+
13
+ # A helper to generate an URL of the application canvas page URL
14
+ #
15
+ # @param protocol A request protocol, should be either 'http://' or 'https://'.
16
+ # Defaults to current protocol.
17
+ def facebook_canvas_page_url(protocol = nil)
18
+ facepalm.canvas_page_url(protocol || request.protocol)
19
+ end
20
+
21
+ # A helper to generate an application callback URL
22
+ #
23
+ # @param protocol A request protocol, should be either 'http://' or 'https://'.
24
+ # Defaults to current protocol.
25
+ def facebook_callback_url(protocol = nil)
26
+ facepalm.callback_url(protocol || request.protocol)
27
+ end
28
+
29
+ # Overrides ActionController::Base#urs_for to filter out secure Facebook params
30
+ # and add Facebook Canvas URL if necessary
31
+ def url_for_with_facepalm(options = {})
32
+ if options.is_a?(Hash)
33
+ if options.delete(:canvas) && !options[:host]
34
+ options[:only_path] = true
35
+
36
+ canvas = true
37
+ else
38
+ canvas = false
39
+ end
40
+
41
+ url = url_for_without_facepalm(options.except(:signed_request))
42
+
43
+ canvas ? facebook_canvas_page_url + url : url
44
+ else
45
+ url_for_without_facepalm(options)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,60 @@
1
+ require 'facepalm/rails/controller/oauth_access'
2
+ require 'facepalm/rails/controller/url_rewriting'
3
+ require 'facepalm/rails/controller/redirects'
4
+
5
+ module Facepalm
6
+ module Rails
7
+
8
+ # Rails application controller extension
9
+ module Controller
10
+ def self.included(base)
11
+ base.class_eval do
12
+ include Facepalm::Rails::Controller::OauthAccess
13
+ include Facepalm::Rails::Controller::UrlRewriting
14
+ include Facepalm::Rails::Controller::Redirects
15
+
16
+ # Fix cookie permission issue in IE
17
+ before_filter :normal_cookies_for_ie_in_iframes!
18
+
19
+ helper_method(:facepalm, :fb_signed_request, :current_facebook_user, :params_without_facebook_data)
20
+
21
+ helper Facepalm::Rails::Helpers
22
+ end
23
+ end
24
+
25
+ # Accessor to current application config. Override it in your controller
26
+ # if you need multi-application support or per-request configuration selection.
27
+ def facepalm
28
+ Facepalm::Config.default
29
+ end
30
+
31
+ # Accessor to current facebook user. Returns instance of Facepalm::User
32
+ def current_facebook_user
33
+ @current_facebook_user ||= fetch_current_facebook_user
34
+ end
35
+
36
+ # Accessor to secure cookie set by Facebook
37
+ def fb_cookie
38
+ cookies["fbsr_#{ facepalm.app_id }"]
39
+ end
40
+
41
+ # Accessor to signed request passed either in params or in flash
42
+ def fb_signed_request
43
+ request.env['HTTP_SIGNED_REQUEST'] || flash[:signed_request]
44
+ end
45
+
46
+ # A hash of params passed to this action, excluding secure information
47
+ # passed by Facebook
48
+ def params_without_facebook_data
49
+ params.except(:signed_request)
50
+ end
51
+
52
+ private
53
+
54
+ def fetch_current_facebook_user
55
+ Facepalm::User.from_signed_request(facepalm, fb_signed_request || fb_cookie)
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,106 @@
1
+ module Facepalm
2
+ module Rails
3
+ module Helpers
4
+ module JavascriptHelper
5
+
6
+ # A helper to integrate Facebook Connect to the current page. Generates a
7
+ # JavaScript code that initializes Facebook Javascript client for the
8
+ # current application.
9
+ #
10
+ # @param app_id Facebook App ID of the application. Defaults to value provided by the current config.
11
+ # @param options A hash of options for JavaScript generation. Available options are:
12
+ # :cookie - Enable cookie generation for the application. Default to true.
13
+ # :status - Enable login status check. Defaults to true.
14
+ # :xfbml - Enable XFBML tag parsing. Default to true.
15
+ # :frictionless - Enable frictionless app request delivery. Defaults to true
16
+ # :locale - Locale to use for JavaScript client. Defaults to 'en_US'.
17
+ # :weak_cache - Enable FB JS client cache expiration every minute. Defaults to false.
18
+ # :async - Enable asynchronous FB JS client code load and initialization. Defaults to false.
19
+ # :cache_url - An URL to load custom or cached version of the FB JS client code. Not used by default.
20
+ # @param &block A block of JS code to be inserted in addition to FB client initialization code.
21
+ def fb_connect_js(*args, &block)
22
+ options = args.extract_options!
23
+
24
+ app_id = args.shift || facepalm.app_id
25
+
26
+ options.reverse_merge!(
27
+ :cookie => true,
28
+ :status => true,
29
+ :xfbml => true,
30
+ :frictionless => true,
31
+ :locale => "en_US"
32
+ )
33
+
34
+ extra_js = capture(&block) if block_given?
35
+
36
+ init_js = <<-JAVASCRIPT
37
+ FB.init({
38
+ appId : '#{ app_id }',
39
+ status : #{ options[:status] },
40
+ cookie : #{ options[:cookie] },
41
+ xfbml : #{ options[:xfbml] },
42
+ frictionlessRequests : #{ options[:frictionless] },
43
+ channelUrl : '#{ options[:channel_url] || 'null' }'
44
+ });
45
+ JAVASCRIPT
46
+
47
+ init_js = "FB._https = true; #{ init_js }" if request.ssl?
48
+
49
+ js_url = "connect.facebook.net/#{options[:locale]}/all.js"
50
+ js_url << "?#{Time.now.change(:min => 0, :sec => 0, :usec => 0).to_i}" if options[:weak_cache]
51
+
52
+ if options[:async]
53
+ js = <<-JAVASCRIPT
54
+ window.fbAsyncInit = function() {
55
+ #{init_js}
56
+ #{extra_js}
57
+ };
58
+
59
+ (function() {
60
+ var e = document.createElement('script');
61
+ e.src = document.location.protocol + '//#{ js_url }';
62
+ e.async = true;
63
+ document.getElementById('fb-root').appendChild(e);
64
+ }());
65
+ JAVASCRIPT
66
+
67
+ js = <<-CODE
68
+ <div id="fb-root"></div>
69
+ <script type="text/javascript">#{ js }</script>
70
+ CODE
71
+ else
72
+ js = <<-CODE
73
+ <div id="fb-root"></div>
74
+ <script src="#{ request.protocol }#{ js_url }" type="text/javascript"></script>
75
+ CODE
76
+
77
+ if options[:cache_url]
78
+ js << <<-CODE
79
+ <script type="text/javascript">
80
+ window.FB || document.write(unescape(\"%3Cscript src='#{ options[:cache_url] }' type='text/javascript'%3E%3C/script%3E\"));
81
+ </script>
82
+ CODE
83
+ end
84
+
85
+ js << <<-CODE
86
+ <script type="text/javascript">
87
+ if(typeof FB !== 'undefined'){
88
+ #{init_js}
89
+ #{extra_js}
90
+ }
91
+ </script>
92
+ CODE
93
+ end
94
+
95
+ js = js.html_safe
96
+
97
+ if block_given? && ::Rails::VERSION::STRING.to_i < 3
98
+ concat(js)
99
+ else
100
+ js
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,9 @@
1
+ require 'facepalm/rails/helpers/javascript_helper'
2
+
3
+ module Facepalm
4
+ module Rails
5
+ module Helpers
6
+ include JavascriptHelper
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ module Facepalm
2
+
3
+ # A class for Facebook user
4
+ class User
5
+ class UnsupportedAlgorithm < StandardError; end
6
+ class InvalidSignature < StandardError; end
7
+
8
+ class << self
9
+ # Creates an instance of Facepalm::User using application config and signed_request
10
+ def from_signed_request(config, input)
11
+ return if input.blank?
12
+
13
+ new(parse_signed_request(config, input))
14
+ end
15
+
16
+ # Originally provided directly by Facebook, however this has changed
17
+ # as their concept of crypto changed. For historic purposes, this is their proposal:
18
+ # https://developers.facebook.com/docs/authentication/canvas/encryption_proposal/
19
+ # Currently see https://github.com/facebook/php-sdk/blob/master/src/facebook.php#L758
20
+ # for a more accurate reference implementation strategy.
21
+ def parse_signed_request(config, input)
22
+ encoded_sig, encoded_envelope = input.split('.', 2)
23
+ signature = base64_url_decode(encoded_sig).unpack("H*").first
24
+
25
+ MultiJson.decode(base64_url_decode(encoded_envelope)).tap do |envelope|
26
+ raise UnsupportedAlgorithm.new("Unsupported encryption algorithm: #{ envelope['algorithm'] }") unless envelope['algorithm'] == 'HMAC-SHA256'
27
+
28
+ # now see if the signature is valid (digest, key, data)
29
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, config.secret, encoded_envelope)
30
+
31
+ raise InvalidSignature.new('Invalid request signature') if (signature != hmac)
32
+ end
33
+ end
34
+
35
+ def base64_url_decode(str)
36
+ str += '=' * (4 - str.length.modulo(4))
37
+
38
+ Base64.decode64(str.tr('-_', '+/'))
39
+ end
40
+ end
41
+
42
+
43
+ def initialize(options = {})
44
+ @options = options
45
+ end
46
+
47
+ # Checks if user is authenticated in the application
48
+ def authenticated?
49
+ access_token && !access_token.empty?
50
+ end
51
+
52
+ # Facebook UID
53
+ def uid
54
+ @options['user_id']
55
+ end
56
+
57
+ # The code used for OAuth 2.0
58
+ def oauth_code
59
+ @options['code']
60
+ end
61
+
62
+ # OAuth 2.0 access token generated for this user
63
+ def access_token
64
+ @options['access_token'] || @options['oauth_token']
65
+ end
66
+
67
+ # Token expiration time
68
+ def access_token_expires_at
69
+ Time.at(@options['expires'])
70
+ end
71
+
72
+ # Koala Facebook API client instantiated with user's access token
73
+ def api_client
74
+ @api_client ||= Koala::Facebook::API.new(access_token)
75
+ end
76
+ end
77
+
78
+ end
data/lib/facepalm.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Facepalm
2
+ end
3
+
4
+ # Dependencies
5
+ require 'ie_iframe_cookies'
6
+ require 'koala'
7
+
8
+ require 'facepalm/config'
9
+ require 'facepalm/user'
10
+
11
+ require 'facepalm/rack/post_canvas_middleware'
12
+
13
+ # Rails integration
14
+ require 'facepalm/rails/controller'
15
+ require 'facepalm/rails/helpers'
16
+
17
+ require 'facepalm/engine'
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: facepalm
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Aleksey V. Dmitriev
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-12-28 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ requirement: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ hash: 31
27
+ segments:
28
+ - 0
29
+ - 1
30
+ - 2
31
+ version: 0.1.2
32
+ version_requirements: *id001
33
+ name: ie_iframe_cookies
34
+ prerelease: false
35
+ type: :runtime
36
+ - !ruby/object:Gem::Dependency
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ hash: 29
43
+ segments:
44
+ - 1
45
+ - 2
46
+ - 1
47
+ version: 1.2.1
48
+ version_requirements: *id002
49
+ name: koala
50
+ prerelease: false
51
+ type: :runtime
52
+ description:
53
+ email: rene.dekart@gmail.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - LICENSE
60
+ - README.md
61
+ files:
62
+ - Gemfile
63
+ - Gemfile.lock
64
+ - LICENSE
65
+ - README.md
66
+ - Rakefile
67
+ - VERSION
68
+ - app/.gitkeep
69
+ - app/controllers/facepalm/endpoint_controller.rb
70
+ - config/routes.rb
71
+ - facepalm.gemspec
72
+ - init.rb
73
+ - install
74
+ - lib/facepalm.rb
75
+ - lib/facepalm/config.rb
76
+ - lib/facepalm/engine.rb
77
+ - lib/facepalm/rack/post_canvas_middleware.rb
78
+ - lib/facepalm/rails/controller.rb
79
+ - lib/facepalm/rails/controller/oauth_access.rb
80
+ - lib/facepalm/rails/controller/redirects.rb
81
+ - lib/facepalm/rails/controller/url_rewriting.rb
82
+ - lib/facepalm/rails/helpers.rb
83
+ - lib/facepalm/rails/helpers/javascript_helper.rb
84
+ - lib/facepalm/user.rb
85
+ homepage: http://github.com/dekart/facepalm
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.8.13
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Facebook integration for Rack & Rails application
118
+ test_files: []
119
+