OpenID for rails app behind Apache

2020-06-30 05:22发布

问题:

I'm trying to integrate simple OpenID authentication through Google accounts. I'm using omniauth gem and on my local development system (Win7, ruby 1.8.7-p302, rails 2.3.8, omniauth 0.1.5) everything works nice.

The problem shows it's face when I deploy it to my hosting (HostGator). The app (mongrel) starts at port 12002 and through HostGator's cPanel it's configured to be rewritten from one of subdomains:

RewriteCond %{HTTP_HOST} ^subdomain.mycompany.com$ [OR]
RewriteCond %{HTTP_HOST} ^www.subdomain.mycompany.com$
RewriteRule ^(.*)$ "http\:\/\/127\.0\.0\.1\:12002\/$1" [P,L]

When browser goes /auth/open_id it's redirected to Google for authorization but it's return_to address is subdomain.mycompany.com:12002 which of course is incorrect because Apache doesn't listen on 12002. I've managed to monkey patch OmniAuth::Strategies::OpenID and Rack::OpenID so they assemble address without port BUT now on the final stage after user access confirmation if falls into:

Routing Error
No route matches "/403.shtml" with {:method=>:get}

I guess the problem is what my site's address is encoded (some kind of hash) at other authorization request parameter to avoid

回答1:

The port issue is because of the the way RACK request computes the port. Here is the patch

require 'rack/utils'
module Rack
  class Request
    def port
      if host_with_port =~ /:(\d+)$/
        $1.to_i
      else
        standard_port
      end
    end

    # Returns the standard \port number for this request's protocol.
    def standard_port
      case protocol
        when 'https://' then 443
        else 80
      end
    end

    # Returns 'https://' if this is an SSL request and 'http://' otherwise.
    def protocol
      ssl? ? 'https://' : 'http://'
    end

    # Is this an SSL request?
    def ssl?
      @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
    end
  end
end

This is the way rails ActionController::Request computes the port.

Hope this helps



回答2:

Well...

The 403 error was because hosting security rules were so strict what Google's OpenID response (long enough to be suspicious) was rejected. Resolved by hosting support.

To resolve the problem with port number within return_to parameter (addressed by freiwillen) I've made the following (monkey path) initializer:

module OmniAuth
  module Strategies
    # OmniAuth strategy for connecting via OpenID. This allows for connection
    # to a wide variety of sites, some of which are listed [on the OpenID website](http://openid.net/get-an-openid/).
    class OpenID
      protected
      def callback_url
        uri = URI.parse(request.url)
        uri.path += '/callback'

        # by KirylP: to overcome hosting subdomain forwarding to rails port        
        uri.port = '' if request.env.has_key? 'HTTP_X_FORWARDED_SERVER'

        uri.to_s
      end
    end
  end
end

module Rack
  class OpenID
    SERVER_PORT_TO_AVOID = 12002

    private
    def realm_url(req)
      url = req.scheme + "://"
      url << req.host

      scheme, port = req.scheme, req.port
      if scheme == "https" && port != 443 ||
          scheme == "http" && port != 80
        url << ":#{port}" if port != SERVER_PORT_TO_AVOID # KirylP
      end

      url
    end
  end
end

module OpenID
  class Consumer
    def complete(query, current_url)
      message = Message.from_post_args(query)

      current_url.sub!(":#{Rack::OpenID::SERVER_PORT_TO_AVOID}", '') # KirylP

      mode = message.get_arg(OPENID_NS, 'mode', 'invalid')
      begin
        meth = method('complete_' + mode)
      rescue NameError
        meth = method(:complete_invalid)
      end
      response = meth.call(message, current_url)
      cleanup_last_requested_endpoint
      if [SUCCESS, CANCEL].member?(response.status)
        cleanup_session
      end
      return response
    end    
  end
end

I hope someone will propose more elegant solution.



回答3:

In the most recent version of Omniauth (0.2.0.beta4) you can specify full_host as a config option and set it to be evaluated at each request, or just use a fixed value.

Example (rails):

ActionController::Dispatcher.middleware.use OmniAuth::Builder do
    store = Rails.env.development? ? OpenID::Store::Filesystem.new("#{Rails.root}/tmp") : nil
    provider :open_id, store, :name => 'google', :identifier => 'https://www.google.com/accounts/o8/id', :scope => 'email'

    if Rails.env.production?
        configure do |config|
            config.full_host = lambda  do |env|
                req = Rack::Request.new(env)
                "#{req.scheme}://#{req.host}"  # req.host strips port number
            end
        end
    end
end