facepalm 0.1.0

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