facepalm2 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: df7444ea0447bf5fa7110a3c899a5ce233cf629e
4
+ data.tar.gz: fae1374d10c29e9d34a77e6a1af909e688b9b54e
5
+ SHA512:
6
+ metadata.gz: b3a57d325e731bdb1bd6c2ac64675640971d127c880cf34e0846fec704bdc078e336f8cc3e513bde1e884a39bfaefff21e96ca433689053ecc2ab7e79de8a58f
7
+ data.tar.gz: 7d995775af09763d5cf6aa9ca55c4f1c47b9843233c49733d653bbe35406a5046d2f980cdbd284354e3c3081e7135c58b99d12f534a37679f64f20c05e9c3732
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', '2.3.11'
5
+ gem 'redgreen'
6
+ gem 'rake'
7
+ gem 'jeweler'
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,72 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionmailer (2.3.11)
5
+ actionpack (= 2.3.11)
6
+ actionpack (2.3.11)
7
+ activesupport (= 2.3.11)
8
+ rack (~> 1.1.0)
9
+ activerecord (2.3.11)
10
+ activesupport (= 2.3.11)
11
+ activeresource (2.3.11)
12
+ activesupport (= 2.3.11)
13
+ activesupport (2.3.11)
14
+ addressable (2.3.8)
15
+ builder (3.2.2)
16
+ descendants_tracker (0.0.4)
17
+ thread_safe (~> 0.3, >= 0.3.1)
18
+ faraday (0.9.1)
19
+ multipart-post (>= 1.2, < 3)
20
+ git (1.2.9.1)
21
+ github_api (0.12.3)
22
+ addressable (~> 2.3)
23
+ descendants_tracker (~> 0.0.4)
24
+ faraday (~> 0.8, < 0.10)
25
+ hashie (>= 3.3)
26
+ multi_json (>= 1.7.5, < 2.0)
27
+ nokogiri (~> 1.6.3)
28
+ oauth2
29
+ hashie (3.4.1)
30
+ highline (1.7.1)
31
+ httpauth (0.2.1)
32
+ jeweler (2.0.1)
33
+ builder
34
+ bundler (>= 1.0)
35
+ git (>= 1.2.5)
36
+ github_api
37
+ highline (>= 1.6.15)
38
+ nokogiri (>= 1.5.10)
39
+ rake
40
+ rdoc
41
+ json (1.8.2)
42
+ mini_portile (0.6.2)
43
+ multi_json (1.11.0)
44
+ multipart-post (2.0.0)
45
+ nokogiri (1.6.6.2)
46
+ mini_portile (~> 0.6.0)
47
+ oauth2 (0.6.1)
48
+ faraday (~> 0.7)
49
+ httpauth (~> 0.1)
50
+ multi_json (~> 1.3)
51
+ rack (1.1.6)
52
+ rails (2.3.11)
53
+ actionmailer (= 2.3.11)
54
+ actionpack (= 2.3.11)
55
+ activerecord (= 2.3.11)
56
+ activeresource (= 2.3.11)
57
+ activesupport (= 2.3.11)
58
+ rake (>= 0.8.3)
59
+ rake (10.4.2)
60
+ rdoc (4.2.0)
61
+ json (~> 1.4)
62
+ redgreen (1.2.2)
63
+ thread_safe (0.3.5)
64
+
65
+ PLATFORMS
66
+ ruby
67
+
68
+ DEPENDENCIES
69
+ jeweler
70
+ rails (= 2.3.11)
71
+ rake
72
+ 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 = 'facepalm2'
21
+ gem.summary = "Facebook v2 integration for Rack & Rails application"
22
+ gem.email = "vk.stelmakh@gmail.com"
23
+ gem.homepage = "http://github.com/stelmakh/facepalm"
24
+ gem.authors = ["Aleksey V. Dmitriev", "Volodymyr Stelmakh"]
25
+
26
+ gem.add_dependency "ie_iframe_cookies", '~> 0.1.2'
27
+ gem.add_dependency "koala", '~> 2.0'
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.0.1
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,30 @@
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_description])
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
+ return_url = URI.unescape(params[:fb_return_to].to_s)
15
+ ::Rails.logger.fatal("Return URL: #{return_url}")
16
+ redirect_to(facebook_canvas_page_url + facepalm_url_encryptor.decrypt(return_url))
17
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage
18
+ ::Rails.logger.fatal "ERROR: Failed to decrypt return URL: #{ params[:fb_return_to] }"
19
+
20
+ redirect_to facebook_canvas_page_url
21
+ end
22
+
23
+ false
24
+ else
25
+ raise Facepalm::OAuthException.new('No code returned.')
26
+ end
27
+ end
28
+ end
29
+ end
30
+ 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,65 @@
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
+ # stub: facepalm 0.2.6.b3 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "facepalm"
9
+ s.version = "0.2.6.b3"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
13
+ s.authors = ["Aleksey V. Dmitriev", "Volodymyr Stelmakh"]
14
+ s.date = "2015-04-09"
15
+ s.email = "vk.stelmakh@gmail.com"
16
+ s.extra_rdoc_files = [
17
+ "LICENSE",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "app/.gitkeep",
28
+ "app/controllers/facepalm/endpoint_controller.rb",
29
+ "config/routes.rb",
30
+ "facepalm.gemspec",
31
+ "init.rb",
32
+ "install",
33
+ "lib/facepalm.rb",
34
+ "lib/facepalm/config.rb",
35
+ "lib/facepalm/engine.rb",
36
+ "lib/facepalm/rack/post_canvas_middleware.rb",
37
+ "lib/facepalm/rails/controller.rb",
38
+ "lib/facepalm/rails/controller/oauth_access.rb",
39
+ "lib/facepalm/rails/controller/redirects.rb",
40
+ "lib/facepalm/rails/controller/url_rewriting.rb",
41
+ "lib/facepalm/rails/helpers.rb",
42
+ "lib/facepalm/rails/helpers/javascript_helper.rb",
43
+ "lib/facepalm/rails/helpers/url_helper.rb",
44
+ "lib/facepalm/user.rb"
45
+ ]
46
+ s.homepage = "http://github.com/stelmakh/facepalm"
47
+ s.rubygems_version = "2.2.2"
48
+ s.summary = "Facebook integration for Rack & Rails application"
49
+
50
+ if s.respond_to? :specification_version then
51
+ s.specification_version = 4
52
+
53
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
+ s.add_runtime_dependency(%q<ie_iframe_cookies>, ["~> 0.1.2"])
55
+ s.add_runtime_dependency(%q<koala>, ["~> 2.0"])
56
+ else
57
+ s.add_dependency(%q<ie_iframe_cookies>, ["~> 0.1.2"])
58
+ s.add_dependency(%q<koala>, ["~> 2.0"])
59
+ end
60
+ else
61
+ s.add_dependency(%q<ie_iframe_cookies>, ["~> 0.1.2"])
62
+ s.add_dependency(%q<koala>, ["~> 2.0"])
63
+ end
64
+ end
65
+
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,108 @@
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
+ protected
47
+
48
+ # Requires a given set of permissions in context of the current action.
49
+ # Use it to require permissions in a single action or custom filter.
50
+ #
51
+ # NOTE: Facepalm doesn't check if user provided all required permissions.
52
+ # It only checks if user was authenticated and redirects to permission
53
+ # request page with a given set of permissions.
54
+ #
55
+ # @param permissions An array of permissions to require
56
+ #
57
+ # @return true if user authorized the application, false otehrwise
58
+ #
59
+ # @example
60
+ # class MyController < ApplicationController
61
+ # before_filter :my_custom_filter, :only => :show
62
+ #
63
+ # def my_custom_filter
64
+ # my_custom_condition? and facepalm_require_authentication(:publish_actions)
65
+ # end
66
+ #
67
+ # def index
68
+ # if facepalm_require_authentication(:email)
69
+ # ... do what you need ...
70
+ # end
71
+ # end
72
+ # end
73
+ def facepalm_require_authentication(*permissions)
74
+ if current_facebook_user.try(:authenticated?)
75
+ true
76
+ else
77
+ redirect_to = facepalm.oauth_client.url_for_oauth_code(
78
+ :permissions => permissions,
79
+ :callback => facepalm_endpoint_url(
80
+ :fb_return_to => facepalm_auth_return_code
81
+ )
82
+ )
83
+ ::Rails.logger.fatal("User #{current_facebook_user} is not authenticated\nRedirecting to #{redirect_to}")
84
+ redirect_from_iframe(redirect_to)
85
+
86
+ false
87
+ end
88
+ end
89
+
90
+ # Encrypting return URL to pass it to Facebook
91
+ def facepalm_auth_return_code
92
+ facepalm_url_encryptor.encrypt(
93
+ url_for(params_without_facebook_data.merge(:canvas => false, :only_path => true))
94
+ )
95
+ end
96
+
97
+ # Internally used to encrypt return URL for authentication endpoint
98
+ #
99
+ # @return ActiveSupport::MessageEncryptor
100
+ #
101
+ # @private
102
+ def facepalm_url_encryptor
103
+ @facebook_url_encryptor ||= ActiveSupport::MessageEncryptor.new(facepalm.secret)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,74 @@
1
+ module Facepalm
2
+ module Rails
3
+ module Controller
4
+ module Redirects
5
+ def self.included(base)
6
+ base.class_eval do
7
+ alias_method_chain :redirect_to, :signed_request
8
+ end
9
+ end
10
+
11
+ protected
12
+
13
+ # Overrides ActionController::Base#redirect_to to pass signed_request in flash[]
14
+ def redirect_to_with_signed_request(*args)
15
+ flash[:signed_request] = fb_signed_request if fb_canvas?
16
+
17
+ redirect_to_without_signed_request(*args)
18
+ end
19
+
20
+ # Redirects user to a definite URL with JavaScript code that overwrites
21
+ # top frame location. Use it to redirect user from within an iframe.
22
+ def redirect_from_iframe(url_options)
23
+ redirect_url = url_options.is_a?(String) ? url_options : url_for(url_options)
24
+
25
+ logger.info "Redirecting from IFRAME to #{ redirect_url }"
26
+
27
+ respond_to do |format|
28
+ format.html do
29
+ render(
30
+ :text => iframe_redirect_html_code(redirect_url),
31
+ :layout => false
32
+ )
33
+ end
34
+
35
+ format.js do
36
+ render(
37
+ :text => iframe_redirect_js_code(redirect_url),
38
+ :layout => false
39
+ )
40
+ end
41
+ end
42
+ end
43
+
44
+ # Generates HTML and JavaScript code to redirect user with top frame location
45
+ # overwrite
46
+ #
47
+ # @param target_url An URL to redirect the user to
48
+ # @param custom_code A custom HTML code to insert into the result document.
49
+ # Can be used to add OpenGraph tags to redirect page code.
50
+ def iframe_redirect_html_code(target_url, custom_code = nil)
51
+ %{
52
+ <html><head>
53
+ <script type="text/javascript">
54
+ window.top.location.href = #{ target_url.to_json };
55
+ </script>
56
+ <noscript>
57
+ <meta http-equiv="refresh" content="0;url=#{ target_url }" />
58
+ <meta http-equiv="window-target" content="_top" />
59
+ </noscript>
60
+ #{ custom_code }
61
+ </head></html>
62
+ }
63
+ end
64
+
65
+ # Generates JavaScript code to redirect user
66
+ #
67
+ # @param target_url An URL to redirect the user to
68
+ def iframe_redirect_js_code(target_url)
69
+ "window.top.location.href = #{ target_url.to_json };"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,35 @@
1
+ require 'facepalm/rails/helpers/url_helper'
2
+
3
+ module Facepalm
4
+ module Rails
5
+ module Controller
6
+ module UrlRewriting
7
+ include Facepalm::Rails::Helpers::UrlHelper
8
+
9
+ def self.included(base)
10
+ base.class_eval do
11
+ helper_method(:facebook_canvas_page_url, :facebook_callback_url)
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ # A helper to generate an URL of the application canvas page URL
18
+ #
19
+ # @param protocol A request protocol, should be either 'http://' or 'https://'.
20
+ # Defaults to current protocol.
21
+ def facebook_canvas_page_url(protocol = nil)
22
+ facepalm.canvas_page_url(protocol || request.protocol)
23
+ end
24
+
25
+ # A helper to generate an application callback URL
26
+ #
27
+ # @param protocol A request protocol, should be either 'http://' or 'https://'.
28
+ # Defaults to current protocol.
29
+ def facebook_callback_url(protocol = nil)
30
+ facepalm.callback_url(protocol || request.protocol)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
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, :fb_canvas?)
20
+
21
+ helper Facepalm::Rails::Helpers
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ # Accessor to current application config. Override it in your controller
28
+ # if you need multi-application support or per-request configuration selection.
29
+ def facepalm
30
+ Facepalm::Config.default
31
+ end
32
+
33
+ # Accessor to current facebook user. Returns instance of Facepalm::User
34
+ def current_facebook_user
35
+ @current_facebook_user ||= fetch_current_facebook_user
36
+ end
37
+
38
+ # Accessor to secure cookie set by Facebook
39
+ def fb_cookie
40
+ cookies["fbsr_#{ facepalm.app_id }"]
41
+ end
42
+
43
+ # Accessor to signed request passed either in params or in flash
44
+ def fb_signed_request
45
+ request.env['HTTP_SIGNED_REQUEST'] || flash[:signed_request]
46
+ end
47
+
48
+ # A hash of params passed to this action, excluding secure information
49
+ # passed by Facebook
50
+ def params_without_facebook_data
51
+ params.except(:signed_request)
52
+ end
53
+
54
+ # Did the request come from canvas app
55
+ def fb_canvas?
56
+ request.env['HTTP_SIGNED_REQUEST'].present? || flash[:signed_request].present?
57
+ end
58
+
59
+ private
60
+
61
+ def fetch_current_facebook_user
62
+ Facepalm::User.from_signed_request(facepalm, fb_signed_request || fb_cookie)
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,107 @@
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
+ version : 'v2.0',
40
+ status : #{ options[:status] },
41
+ cookie : #{ options[:cookie] },
42
+ xfbml : #{ options[:xfbml] },
43
+ frictionlessRequests : #{ options[:frictionless] },
44
+ channelUrl : '#{ options[:channel_url] || 'null' }'
45
+ });
46
+ JAVASCRIPT
47
+
48
+ init_js = "FB._https = true; #{ init_js }" if request.ssl?
49
+
50
+ js_url = "connect.facebook.net/#{options[:locale]}/sdk.js"
51
+ js_url << "?#{Time.now.change(:min => 0, :sec => 0, :usec => 0).to_i}" if options[:weak_cache]
52
+
53
+ if options[:async]
54
+ js = <<-JAVASCRIPT
55
+ window.fbAsyncInit = function() {
56
+ #{init_js}
57
+ #{extra_js}
58
+ };
59
+
60
+ (function() {
61
+ var e = document.createElement('script');
62
+ e.src = document.location.protocol + '//#{ js_url }';
63
+ e.async = true;
64
+ document.getElementById('fb-root').appendChild(e);
65
+ }());
66
+ JAVASCRIPT
67
+
68
+ js = <<-CODE
69
+ <div id="fb-root"></div>
70
+ <script type="text/javascript">#{ js }</script>
71
+ CODE
72
+ else
73
+ js = <<-CODE
74
+ <div id="fb-root"></div>
75
+ <script src="#{ request.protocol }#{ js_url }" type="text/javascript"></script>
76
+ CODE
77
+
78
+ if options[:cache_url]
79
+ js << <<-CODE
80
+ <script type="text/javascript">
81
+ window.FB || document.write(unescape(\"%3Cscript src='#{ options[:cache_url] }' type='text/javascript'%3E%3C/script%3E\"));
82
+ </script>
83
+ CODE
84
+ end
85
+
86
+ js << <<-CODE
87
+ <script type="text/javascript">
88
+ if(typeof FB !== 'undefined'){
89
+ #{init_js}
90
+ #{extra_js}
91
+ }
92
+ </script>
93
+ CODE
94
+ end
95
+
96
+ js = js.html_safe
97
+
98
+ if block_given? && ::Rails::VERSION::STRING.to_i < 3
99
+ concat(js)
100
+ else
101
+ js
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,27 @@
1
+ module Facepalm
2
+ module Rails
3
+ module Helpers
4
+ module UrlHelper
5
+ # Overrides UrlHelper#url_for to filter out secure Facebook params
6
+ # and add Facebook Canvas URL if necessary
7
+ def url_for(options = {})
8
+ if options.is_a?(Hash)
9
+ if options.delete(:canvas) && !options[:host]
10
+ options[:only_path] = true
11
+
12
+ canvas = true
13
+ else
14
+ canvas = false
15
+ end
16
+
17
+ url = super(options.except(:signed_request))
18
+
19
+ canvas ? facebook_canvas_page_url + url : url
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ require 'facepalm/rails/helpers/javascript_helper'
2
+ require 'facepalm/rails/helpers/url_helper'
3
+
4
+ module Facepalm
5
+ module Rails
6
+ module Helpers
7
+ include JavascriptHelper
8
+ include UrlHelper
9
+ end
10
+ end
11
+ end
12
+
@@ -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,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: facepalm2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Aleksey V. Dmitriev
8
+ - Volodymyr Stelmakh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-04-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ie_iframe_cookies
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.1.2
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.1.2
28
+ - !ruby/object:Gem::Dependency
29
+ name: koala
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '2.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '2.0'
42
+ description:
43
+ email: vk.stelmakh@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files:
47
+ - LICENSE
48
+ - README.md
49
+ files:
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - LICENSE
53
+ - README.md
54
+ - Rakefile
55
+ - VERSION
56
+ - app/.gitkeep
57
+ - app/controllers/facepalm/endpoint_controller.rb
58
+ - config/routes.rb
59
+ - facepalm.gemspec
60
+ - init.rb
61
+ - install
62
+ - lib/facepalm.rb
63
+ - lib/facepalm/config.rb
64
+ - lib/facepalm/engine.rb
65
+ - lib/facepalm/rack/post_canvas_middleware.rb
66
+ - lib/facepalm/rails/controller.rb
67
+ - lib/facepalm/rails/controller/oauth_access.rb
68
+ - lib/facepalm/rails/controller/redirects.rb
69
+ - lib/facepalm/rails/controller/url_rewriting.rb
70
+ - lib/facepalm/rails/helpers.rb
71
+ - lib/facepalm/rails/helpers/javascript_helper.rb
72
+ - lib/facepalm/rails/helpers/url_helper.rb
73
+ - lib/facepalm/user.rb
74
+ homepage: http://github.com/stelmakh/facepalm
75
+ licenses: []
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Facebook v2 integration for Rack & Rails application
97
+ test_files: []