1

バックグラウンド

私のアプリケーションでは、さまざまな目的で 2 つの異なる Facebook アプリに接続する必要があり、そのために以下に示すコードを使用しています。

問題

以下に示すメソッドのようにカスタムcallback_pathfacebook_opts_for_social_sharingを使用すると、コールバック ハンドラ、つまりExternalApiAuthController#create_social_sharing_auth_accountアクションrequest.env['omniauth.auth']で nil が返されます。同様の問題がintridea/omniauthリポジトリで報告されています。

/config/routes.rb

  get '/auth/:provider/callback', to: 'external_api_auth#create'
  get '/auth/:provider/social_sharing/callback', to: 'external_api_auth#create_social_media_platform_account', as: :social_sharing_auth
  get '/auth/failure', to: 'external_api_auth#failure'

/app/controllers/external_api_auth_controller.rb

  class ExternalApiAuthController
    # GET /auth/failure
    def failure
    end

    # GET /auth/:provider/callback
    def create
    end

    # GET /auth/:provider/social_sharing/callback
    def create_social_media_platform_account
    end
  end

/config/initializers/omniauth.rb

    def provider_facebook
      'facebook'
    end

    def facebook_opts
      my_model_obj = MyModelService.find_by_provider_name(provider_facebook)

      return unless my_model_obj.present?

      app_details_hash = my_model_obj.application_details
      client_id = app_details_hash[:client_id]
      client_secret = app_details_hash[:client_secret]

      return if client_id.blank? || client_secret.blank?

      {
        client_id: client_id,
        client_secret: client_secret,
        scope: 'email,manage_pages,publish_pages',
        display: 'popup'
      }
    end

    def facebook_opts_for_social_sharing
      my_model_obj = MyAnotherModelService.find_by_internal_name(provider_facebook)

      return unless my_model_obj.present?

      app_details_hash = my_model_obj.application_details
      client_id = app_details_hash[:client_id]
      client_secret = app_details_hash[:client_secret]

      return if client_id.blank? || client_secret.blank?

      {
        client_id: client_id,
        client_secret: client_secret,
        scope: 'email,manage_pages,publish_pages',
        display: 'popup',
        callback_path: ExternalApiAuthUrl.sharing_auth_callback_path(provider: provider_facebook)
      }
    end

    SETUP_PROC = lambda do |env|
      strategy_instance = env['omniauth.strategy']
      provider_name = provider_name_from_oauth_strategy_class(strategy_instance.class)

      request = Rack::Request.new(env)

      is_social_sharing_auth = false

      auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
      if ExternalApiAuthUrl.is_auth_purpose_sharing?(auth_purpose: auth_purpose)
        is_social_sharing_auth = true
      end

      opts = case provider_name.downcase.underscore
              when 'facebook'
                ( is_social_sharing_auth ? facebook_opts_for_sharing : facebook_opts )
              else
                nil
             end

      if opts.present?
        env['omniauth.strategy'].options.merge!(opts)
      end
    end

    OmniAuth.config.logger = Rails.logger

    OmniAuth.config.on_failure do |env|
      .....
      .....
    end

    Rails.application.config.middleware.use OmniAuth::Builder do
      # Reference: https://github.com/intridea/omniauth/wiki/Setup-Phase
      provider :facebook, setup: SETUP_PROC
    #end

そのコードで起こっていることは、Request Phasecallback_pathの間に正しくピックアップされていることです。ただし、リクエスト フェーズが終了し、OmniAuth::Strategies::OAuth2#request_phase がリダイレクトを開始するとすぐに、OmniAuth::Strategies::Facebook.default オプションのみがOmniAuth::Strategyインスタンス によって使用されます。これらのオプションには(リダイレクトが開始された後)が含まれていないため、次の行を評価する と常に false が返されるため、コールバック フェーズが実行される機会がありません。callback_pathon_callback_path?return callback_call if on_callback_path?

アプローチ1

この制限を回避するために、OmniAuth::Strategies::Facebook.default オプションを送信して、各フェーズで取得されるようにする方法を試しcallback_pathまし 。したがって、 method のように SETUP_PROC のコードを介して渡すのではなく、次の方法で渡しました。つまり、メソッド呼び出しfacebook_opts_for_social_sharingのオプションとして渡しました。OmniAuth::Builder#provider

Rails.application.config.middleware.use OmniAuth::Builder do provider :facebook, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.sharing_auth_callback_path(provider: provider_facebook) end

そして、それを機能させるために、SETUP_PROCを次のように更新しました

    SETUP_PROC = lambda do |env|
      strategy_instance = env['omniauth.strategy']
      provider_name = provider_name_from_oauth_strategy_class(strategy_instance.class)

      request = Rack::Request.new(env)

      is_social_sharing_auth = false

      auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
      if ExternalApiAuthUrl.is_auth_purpose_sharing?(auth_purpose: auth_purpose)
        is_social_sharing_auth = true
      elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
        is_social_sharing_auth = true
      end

      opts = case provider_name.downcase.underscore
              when 'facebook'
                ( is_social_sharing_auth ? facebook_opts_for_sharing : facebook_opts )
              else
                nil
             end

      unless is_social_sharing_auth
        env['omniauth.strategy'].options.delete(:callback_path)
      end

      if opts.present?
        env['omniauth.strategy'].options.merge!(opts)
      end
    end

ただし、これによりカスタム callback_pathシナリオは機能しますが、カスタム callback_path を含むオプションはOmniAuth::Strategyインスタンスで常に使用できるため、デフォルトの callback_path /auth/facebook/callbackシナリオは失敗します。callback_path

アプローチ 2

したがって、アプローチ 1によってもたらされる制限を回避するために、リクエストの path_info と params に基づいて、必要なオプションで戦略ミドルウェアを呼び出すミドルウェアを使用する別のアプローチを試しました。

/app/middleware/omniauth_builder_setup.rb

    class OmniauthBuilderSetup
      def initialize(app)
       @app = app
      end

      def call(env)
        request = Rack::Request.new(env)

        Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup @app: #{@app.inspect}"

        provider_name = provider_name(request.path_info)

        unless provider_name
          status, headers, response = @app.call(env)
          return [status, headers, response]
        end

        is_social_sharing_auth = false

        auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
        if ExternalApiAuthUrl.is_auth_purpose_reviews_social_sharing?(auth_purpose: auth_purpose)
          is_social_sharing_auth = true
        elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
          is_social_sharing_auth = true
        end

        if is_social_sharing_auth
          middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name))
        else
          middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC)
        end

        Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #{middleware_instance.inspect}"

        @app = middleware_instance

        status, headers, response = @app.call(env)

        [status, headers, response]
      end

      private

      def provider_name_regex
        # matches
        #  /auth/facebook
        #  /auth/facebook/callback
        #  /auth/facebook?auth_purpose=social_sharing

        /\A\/auth\/(facebook|twitter)(?:((\/.*)|(\?.+=.+))?)\z/
      end

      def provider_name(path_info)
        match_data = path_info.match(provider_name_regex)

        return if match_data.nil?

        match_data.captures.first
      end

      def omniauth_strategy_middleware(klass, *args, &block)
        if klass.is_a?(Class)
          middleware = klass
        else
          begin
            middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
          rescue NameError
            raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass})."))
          end
        end

        args.last.is_a?(Hash) ? args.push({}.merge(args.pop)) : args.push({})
        middleware.new(middleware, *args, &block)
      end
    end

/config/application.rb

  ....

    config.middleware.use "OmniauthBuilderSetup"
  ....

/config/initializers/omniauth.rb (コメントアウトuse OmniAuth::Builder)

  ....
  ......
  .....

  #Rails.application.config.middleware.use OmniAuth::Builder do
  #  provider :facebook, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.reviews_social_sharing_auth_callback_path(provider: provider_facebook)
  #end

このミドルウェア アプローチでは、デフォルトの callback_path/auth/facebook/callbackとカスタムの callback_pathを使用する場合など、両方のシナリオでコールバック フェーズが開始されます/auth/facebook/social_sharing/callback。しかし、コールバック段階では、次のエラーで失敗します:

      undefined method `call' for OmniAuth::Strategies::Facebook:Class Did you mean? caller

OmniAuth::Strategyにいくつかのログ ステートメントを追加したところ 、次のログが生成されました。

      Started GET "/auth/facebook" for 127.0.0.1 at 2016-07-28 10:28:23 +0530
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<ActionDispatch::Routing::RouteSet:0x000000073a64c8>
      >>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      (facebook) Setup endpoint detected, running now.
      (facebook) Request phase initiated.


      Started GET "/auth/facebook/callback?code=AQDxel76u_UvtTeSHUw3CzMpA98KTI4V_75qhxV5TGD7rdGcyeCX-FS1nrrlo-EAezZXUPdH9cAC5h4c1xlqoIL7UZ2WLDfXHG4GHWZTEGYHzH7QURNSkrjvDoBNWV90E83f_R6RURl1POsq8ZhmQOFD5YGXRxosiVx4Sof8_vqJZ5UT2S5SFbmVLEtaZZacJDqEbWjNKBrYdrZauuqCS91lEw6Lrz5U5rA2eOmmygAiBwso-cnmOuRu-PptwtIbBL5zw5hPOANQskIFHL-lfbobZYBwy_NsY8Nf-HsJauuymSmtfsQ28UaPlkox9vSinqDAHYhW1ltBXrOX_7P4HfBr&state=3831c127892242fb43aaa2ebfe37cac9e0cd2c8dbea06f3e" for 127.0.0.1 at 2016-07-28 10:28:29 +0530
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniAuth::Strategy call!(env) @app OmniAuth::Strategies::Facebook
      >>>>>>>>>>>>> OmniAuth::Strategy call!(env) options #<OmniAuth::Strategy::Options access_token_options=#<OmniAuth::Strategy::Options header_format="OAuth %s" param_name="access_token"> auth_token_params=#<OmniAuth::Strategy::Options> authorize_options=[:scope, :display, :auth_type] authorize_params=#<OmniAuth::Strategy::Options> client_id=nil client_options=#<OmniAuth::Strategy::Options authorize_url="https://www.facebook.com/dialog/oauth" site="https://graph.facebook.com" token_url="oauth/access_token"> client_secret=nil name="facebook" provider_ignores_state=false setup=#<Proc:0x000000065ead70@/jwork/ruby/ror_projects/Reviewgo-JonathanSmith/reviewgo/config/initializers/omniauth.rb:76 (lambda)> skip_info=false token_options=[] token_params=#<OmniAuth::Strategy::Options parse=:query>>
      >>>>>>>>>>>>> OmniAuth::Strategy call!(env) class: OmniAuth::Strategies::Facebook
      >>>>>>>>>>>>>>OmniAuth::Strategy call!(env) current_path: /auth/facebook/callback
      >>>>>>>>>>>>>>OmniAuth::Strategy call!(env) on_callback_path?: true
      (facebook) Setup endpoint detected, running now.
      (facebook) Callback phase initiated.

      NoMethodError (undefined method `call' for OmniAuth::Strategies::Facebook:Class
      Did you mean?  caller):
        app/middleware/omniauth_builder_setup.rb:61:in `call'

ミドルウェアのコールバック フェーズ中に @app がOmniAuth::Strategies::Facebook のインスタンスを保持していることに気付いた場合、コントロールが OmniAuth::Strategy に到達するとすぐに、OmniAuth::Strategy インスタンス内の @app が class を参照しますOmniAuth::Strategies::Facebook

      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniAuth::Strategy call!(env) @app OmniAuth::Strategies::Facebook

ミドルウェアに問題があると確信しています。以前にミドルウェアを使用したことがないため、この @app の概念を理解していません。それを把握するためにウェブ上のいくつかのリソースを参照しようとしましたが、成功しませんでした.

  1. 希望どおりに動作するようにミドルウェアを修正するのを手伝ってくれる人はいますか?

  2. 可能であれば、@app の概念と、@app.call(env) が返す必要があるステータス、ヘッダー、および本文の値を理解できるようにしてください。そうでない場合は、干渉せずにスキップして先に進む必要があります。この動作を達成する方法がわかりません。

PS 過去 2 日間からこの制限を回避するのに苦労しています。ここに記載されているすべての詳細、調査結果、アプローチを使用して、コミュニティの誰かが私の問題を解決するために私を導いてくれることを願っています。

ありがとう。

4

2 に答える 2

1

うおおおおおおおおおおやっと解決しました。ミドルウェアの問題を修正したところ、動作し始めました。この投稿と、ミドルウェアの問題を見つけて修正する際に私を導いてくれたその受け入れられた回答に感謝します。

以下のメソッドのコードを変更する必要があります。以下に示すのは、上記の私の投稿に示されている以前のバージョンです。

  def call(env)
    request = Rack::Request.new(env)

    Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup @app: #{@app.inspect}"

    provider_name = provider_name(request.path_info)

    unless provider_name
      status, headers, response = @app.call(env)
      return [status, headers, response]
    end

    is_social_sharing_auth = false

    auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
    if ExternalApiAuthUrl.is_auth_purpose_reviews_social_sharing?(auth_purpose: auth_purpose)
      is_social_sharing_auth = true
    elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
      is_social_sharing_auth = true
    end

    if is_social_sharing_auth
      middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name))
    else
      middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC)
    end

    Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #{middleware_instance.inspect}"

    @app = middleware_instance

    status, headers, response = @app.call(env)

    [status, headers, response]
  end


  def omniauth_strategy_middleware(klass, *args, &block)
    if klass.is_a?(Class)
      middleware = klass
    else
      begin
        middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
      rescue NameError
        raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass})."))
      end
    end

    args.last.is_a?(Hash) ? args.push({}.merge(args.pop)) : args.push({})
    middleware.new(middleware, *args, &block)
  end

変更されたコード:

  def call(env)
    request = Rack::Request.new(env)

    Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup @app: #{@app.inspect}"

    provider_name = provider_name(request.path_info)

    unless provider_name
      status, headers, response = @app.call(env)
      return [status, headers, response]
    end

    is_social_sharing_auth = false

    auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
    if ExternalApiAuthUrl.is_auth_purpose_reviews_social_sharing?(auth_purpose: auth_purpose)
      is_social_sharing_auth = true
    elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
      is_social_sharing_auth = true
    end

    if is_social_sharing_auth
      middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name))
    else
      middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC)
    end

    Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #{middleware_instance.inspect}"

    status, headers, response = middleware_instance.call(env) # <<<<<--------- Changed here

    [status, headers, response]
  end


  def omniauth_strategy_middleware(klass, *args, &block)
    if klass.is_a?(Class)
      middleware = klass
    else
      begin
        middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
      rescue NameError
        raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass})."))
      end
    end

    args.last.is_a?(Hash) ? args.push({}.merge(args.pop)) : args.push({})
    middleware.new(@app, *args, &block) # <<<<<--------- Changed here
  end

更新:コード全体を含む要旨をhttps://gist.github.com/jiggneshhgohel/4a79aa26cb628533fe132295cffc45b2に作成しました。それが私のような誰かを助け、時間を節約できることを願っています.

ありがとう。

于 2016-07-28T07:01:48.970 に答える