Codebase list ruby-omniauth-facebook / edc2169 lib / omniauth / strategies / facebook.rb
edc2169

Tree @edc2169 (Download .tar.gz)

facebook.rb @edc2169

2f871a0
0859890
99a3d15
36afea7
c4bc37a
2f871a0
 
 
 
36afea7
0859890
9a7cde8
36afea7
0106bb4
edc2169
 
 
0106bb4
 
 
edc2169
 
0106bb4
36afea7
2da9279
36afea7
8484d7c
36afea7
2200948
81c3e7b
7f923bc
 
81c3e7b
7f923bc
 
3f22129
81c3e7b
 
 
 
 
2990f06
 
81c3e7b
2200948
36afea7
12bd3d6
88f4736
 
 
12bd3d6
36afea7
18d4f92
768ab1d
0175533
 
 
edc2169
 
 
1474e45
edc2169
18d4f92
0106bb4
867ff36
765ed9a
a0036b9
0106bb4
a0036b9
 
0859890
21432a7
0106bb4
36afea7
6ae20dc
 
765ed9a
99a3d15
765ed9a
972ed5e
 
64b1603
eff97bf
972ed5e
99a3d15
0106bb4
 
 
 
36afea7
6ae20dc
 
d4b0934
765ed9a
0959ab1
 
c277322
c07e228
 
 
 
 
5f51095
0959ab1
 
8086931
92d1134
 
 
765ed9a
 
92d1134
 
 
81c3e7b
36afea7
765ed9a
db0d393
36afea7
 
765ed9a
 
36afea7
 
 
 
6ae20dc
765ed9a
36afea7
99a3d15
8086931
765ed9a
36afea7
765ed9a
2d395ee
6afe859
765ed9a
 
99a3d15
 
 
 
765ed9a
 
99a3d15
36afea7
765ed9a
8086931
 
36afea7
81c3e7b
36afea7
81c3e7b
12bd3d6
81c3e7b
 
36afea7
bf2a015
3f22129
7afaacc
edc2169
3f22129
d541116
edc2169
3f22129
 
 
4ab2759
3f22129
 
 
da25a4c
 
093d550
da25a4c
2f871a0
 
 
require 'omniauth/strategies/oauth2'
require 'omniauth/facebook/signed_request'
require 'openssl'
require 'rack/utils'
require 'uri'

module OmniAuth
  module Strategies
    class Facebook < OmniAuth::Strategies::OAuth2
      class NoAuthorizationCodeError < StandardError; end

      DEFAULT_SCOPE = 'email'

      option :client_options, {
        site: 'https://graph.facebook.com/v2.6',
        authorize_url: "https://www.facebook.com/v2.6/dialog/oauth",
        token_url: 'oauth/access_token'
      }

      option :access_token_options, {
        header_format: 'OAuth %s',
        param_name: 'access_token'
      }

      option :authorize_options, [:scope, :display, :auth_type]

      uid { raw_info['id'] }

      info do
        prune!({
          'nickname' => raw_info['username'],
          'email' => raw_info['email'],
          'name' => raw_info['name'],
          'first_name' => raw_info['first_name'],
          'last_name' => raw_info['last_name'],
          'image' => image_url(uid, options),
          'description' => raw_info['bio'],
          'urls' => {
            'Facebook' => raw_info['link'],
            'Website' => raw_info['website']
          },
          'location' => (raw_info['location'] || {})['name'],
          'verified' => raw_info['verified']
        })
      end

      extra do
        hash = {}
        hash['raw_info'] = raw_info unless skip_info?
        prune! hash
      end

      def raw_info
        @raw_info ||= access_token.get('me', info_options).parsed || {}
      end

      def info_options
        params = {appsecret_proof: appsecret_proof}
        params.merge!({fields: (options[:info_fields] || 'name,email')})
        params.merge!({locale: options[:locale]}) if options[:locale]

        { params: params }
      end

      def callback_phase
        with_authorization_code! do
          super
        end
      rescue NoAuthorizationCodeError => e
        fail!(:no_authorization_code, e)
      rescue OmniAuth::Facebook::SignedRequest::UnknownSignatureAlgorithmError => e
        fail!(:unknown_signature_algorithm, e)
      end

      # NOTE If we're using code from the signed request then FB sets the redirect_uri to '' during the authorize
      #      phase and it must match during the access_token phase:
      #      https://github.com/facebook/facebook-php-sdk/blob/master/src/base_facebook.php#L477
      def callback_url
        if @authorization_code_from_signed_request_in_cookie
          ''
        else
          # Fixes regression in omniauth-oauth2 v1.4.0 by https://github.com/intridea/omniauth-oauth2/commit/85fdbe117c2a4400d001a6368cc359d88f40abc7
          options[:callback_url] || (full_host + script_name + callback_path)
        end
      end

      def access_token_options
        options.access_token_options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
      end

      # You can pass +display+, +scope+, or +auth_type+ params to the auth request, if you need to set them dynamically.
      # You can also set these options in the OmniAuth config :authorize_params option.
      #
      # For example: /auth/facebook?display=popup
      def authorize_params
        super.tap do |params|
          %w[display scope auth_type].each do |v|
            if request.params[v]
              params[v.to_sym] = request.params[v]
            end
          end

          params[:scope] ||= DEFAULT_SCOPE
        end
      end

      protected

      def build_access_token
        super.tap do |token|
          token.options.merge!(access_token_options)
        end
      end

      private

      def signed_request_from_cookie
        @signed_request_from_cookie ||= raw_signed_request_from_cookie && OmniAuth::Facebook::SignedRequest.parse(raw_signed_request_from_cookie, client.secret)
      end

      def raw_signed_request_from_cookie
        request.cookies["fbsr_#{client.id}"]
      end

      # Picks the authorization code in order, from:
      #
      # 1. The request 'code' param (manual callback from standard server-side flow)
      # 2. A signed request from cookie (passed from the client during the client-side flow)
      def with_authorization_code!
        if request.params.key?('code')
          yield
        elsif code_from_signed_request = signed_request_from_cookie && signed_request_from_cookie['code']
          request.params['code'] = code_from_signed_request
          @authorization_code_from_signed_request_in_cookie = true
          # NOTE The code from the signed fbsr_XXX cookie is set by the FB JS SDK will confirm that the identity of the
          #      user contained in the signed request matches the user loading the app.
          original_provider_ignores_state = options.provider_ignores_state
          options.provider_ignores_state = true
          begin
            yield
          ensure
            request.params.delete('code')
            @authorization_code_from_signed_request_in_cookie = false
            options.provider_ignores_state = original_provider_ignores_state
          end
        else
          raise NoAuthorizationCodeError, 'must pass either a `code` (via URL or by an `fbsr_XXX` signed request cookie)'
        end
      end

      def prune!(hash)
        hash.delete_if do |_, value|
          prune!(value) if value.is_a?(Hash)
          value.nil? || (value.respond_to?(:empty?) && value.empty?)
        end
      end

      def image_url(uid, options)
        uri_class = options[:secure_image_url] ? URI::HTTPS : URI::HTTP
        site_uri = URI.parse(client.site)
        url = uri_class.build({host: site_uri.host, path: "#{site_uri.path}/#{uid}/picture"})

        query = if options[:image_size].is_a?(String) || options[:image_size].is_a?(Symbol)
          { type: options[:image_size] }
        elsif options[:image_size].is_a?(Hash)
          options[:image_size]
        end
        url.query = Rack::Utils.build_query(query) if query

        url.to_s
      end

      def appsecret_proof
        @appsecret_proof ||= OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, client.secret, access_token.token)
      end
    end
  end
end