Class: OpenID::Consumer::IdResHandler

### Home Classes Methods

### In Files

- lib/openid/consumer/idres.rb

### Parent

Object

### Methods

- ::new
- #check_auth
- #check_for_fields
- #check_nonce
- #check_signature
- #create_check_auth_request
- #discover_and_verify
- #fetch
- #id_res
- #openid_namespace
- #process_check_auth_response
- #server_url
- #signed_fields
- #signed_list
- #verify_discovered_services
- #verify_discovery_results
- #verify_discovery_results_openid1
- #verify_discovery_results_openid2
- #verify_discovery_single
- #verify_return_to
- #verify_return_to_args
- #verify_return_to_base

### Files

- LICENSE

### Class/Module Index

Quicksearch

- OpenID
- OpenID::AX
- OpenID::AX::AXMessage
- OpenID::AX::AttrInfo
- OpenID::AX::Error
- OpenID::AX::FetchRequest
- OpenID::AX::FetchResponse
- OpenID::AX::KeyValueMessage
- OpenID::AX::StoreRequest
- OpenID::AX::StoreResponse
- OpenID::AssertionError
- OpenID::Association
- OpenID::AssociationNegotiator
- OpenID::Consumer
- OpenID::Consumer::AssociationManager
- OpenID::Consumer::CancelResponse
- OpenID::Consumer::CheckIDRequest
- OpenID::Consumer::DiffieHellmanSHA1Session
- OpenID::Consumer::DiffieHellmanSHA256Session
- OpenID::Consumer::DiffieHellmanSession
- OpenID::Consumer::DiscoveredServices
- OpenID::Consumer::DiscoveryManager
- OpenID::Consumer::FailureResponse
- OpenID::Consumer::IdResHandler
- OpenID::Consumer::NoEncryptionSession
- OpenID::Consumer::Response
- OpenID::Consumer::Session
- OpenID::Consumer::SetupNeededResponse
- OpenID::Consumer::SuccessResponse
- OpenID::CryptUtil
- OpenID::DiffieHellman
- OpenID::DiscoveryFailure
- OpenID::Extension
- OpenID::FetchingError
- OpenID::HTTPRedirectLimitReached
- OpenID::HTTPResponse
- OpenID::HTTPStatusError
- OpenID::InvalidOpenIDNamespace
- OpenID::KVFormError
- OpenID::KVPostNetworkError
- OpenID::Message
- OpenID::Message::KeyNotFound
- OpenID::NamespaceAliasRegistrationError
- OpenID::NamespaceMap
- OpenID::Nonce
- OpenID::OAuth
- OpenID::OAuth::Request
- OpenID::OAuth::Response
- OpenID::OpenIDError
- OpenID::OpenIDServiceEndpoint
- OpenID::PAPE
- OpenID::PAPE::Request
- OpenID::PAPE::Response
- OpenID::ProtocolError
- OpenID::RealmVerificationRedirected
- OpenID::SReg
- OpenID::SReg::NamespaceError
- OpenID::SReg::Request
- OpenID::SReg::Response
- OpenID::SSLFetchingError
- OpenID::Server
- OpenID::Server::AlreadySigned
- OpenID::Server::AssociateRequest
- OpenID::Server::BaseServerSession
- OpenID::Server::CheckAuthRequest
- OpenID::Server::CheckIDRequest
- OpenID::Server::Decoder
- OpenID::Server::DiffieHellmanSHA1ServerSession
- OpenID::Server::DiffieHellmanSHA256ServerSession
- OpenID::Server::Encoder
- OpenID::Server::EncodingError
- OpenID::Server::MalformedReturnURL
- OpenID::Server::MalformedTrustRoot
- OpenID::Server::NoReturnToError
- OpenID::Server::OpenIDRequest
- OpenID::Server::OpenIDResponse
- OpenID::Server::PlainTextServerSession
- OpenID::Server::ProtocolError
- OpenID::Server::Server
- OpenID::Server::Signatory
- OpenID::Server::SigningEncoder
- OpenID::Server::UntrustedReturnURL
- OpenID::Server::VersionError
- OpenID::Server::WebResponse
- OpenID::ServerError
- OpenID::StandardFetcher
- OpenID::Store
- OpenID::Store::Filesystem
- OpenID::Store::Interface
- OpenID::Store::Memcache
- OpenID::Store::Memory
- OpenID::TrustRoot
- OpenID::TrustRoot::TrustRoot
- OpenID::TypeURIMismatch
- OpenID::UI
- OpenID::UI::Request
- OpenID::URINorm
- OpenID::UndefinedOpenIDNamespace
- OpenID::Util
- OpenID::Yadis
- OpenID::Yadis::BasicServiceEndpoint
- OpenID::Yadis::CompoundFilter
- OpenID::Yadis::DiscoveryResult
- OpenID::Yadis::TransformFilterMaker
- OpenID::Yadis::XRDSError
- OpenID::Yadis::XRDSFraud
- OpenID::Yadis::XRI
- OpenID::Yadis::XRI::ProxyResolver
- OpenID::Yadis::XRI::XRIHTTPError
- HMAC
- HMAC::Base
- HMAC::SHA1
- HMAC::SHA256
- HMAC::SHA384
- HMAC::SHA512
- Net
- Net::HTTP
- HTMLComment
- HTMLTag
- HTMLText
- HTMLToken
- HTMLTokenizer
- HTMLTokenizerError
- Object

No matching classes.

# OpenID::Consumer::IdResHandler

Handles an openid.mode=id_res response. This object is instantiated and used by the Consumer.

### Attributes

endpoint[R]
message[R]

### Public Class Methods

new(message, current_url, store=nil, endpoint=nil) click to toggle source

    # File lib/openid/consumer/idres.rb, line 51
    def initialize(message, current_url, store=nil, endpoint=nil)
      @store = store # Fer the nonce and invalidate_handle
      @message = message
      @endpoint = endpoint
      @current_url = current_url
      @signed_list = nil

      # Start the verification process
      id_res
    end

### Public Instance Methods

signed_fields() click to toggle source

    # File lib/openid/consumer/idres.rb, line 62
    def signed_fields
      signed_list.map {|x| 'openid.' + x}
    end

### Protected Instance Methods

check_auth() click to toggle source

    # File lib/openid/consumer/idres.rb, line 228
    def check_auth
      Util.log("Using 'check_authentication' with #{server_url}")
      begin
        request = create_check_auth_request
      rescue Message::KeyNotFound => why
        raise ProtocolError, "Could not generate 'check_authentication' "                                "request: #{why.message}"
      end

      response = OpenID.make_kv_post(request, server_url)

      process_check_auth_response(response)
    end

check_for_fields() click to toggle source

    # File lib/openid/consumer/idres.rb, line 104
    def check_for_fields
      # XXX: if a field is missing, we should not have to explicitly
      # check that it's present, just make sure that the fields are
      # actually being used by the rest of the code in
      # tests. Although, which fields are signed does need to be
      # checked somewhere.
      basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']
      basic_sig_fields = ['return_to', 'identity']

      case openid_namespace
      when OPENID2_NS
        require_fields = basic_fields + ['op_endpoint']
        require_sigs = basic_sig_fields +
          ['response_nonce', 'claimed_id', 'assoc_handle', 'op_endpoint']
      when OPENID1_NS, OPENID11_NS
        require_fields = basic_fields + ['identity']
        require_sigs = basic_sig_fields
      else
        raise RuntimeError, "check_for_fields doesn't know about "                               "namespace #{openid_namespace.inspect}"
      end

      require_fields.each do |field|
        if !@message.has_key?(OPENID_NS, field)
          raise ProtocolError, "Missing required field #{field}"
        end
      end

      require_sigs.each do |field|
        # Field is present and not in signed list
        if @message.has_key?(OPENID_NS, field) && !signed_list.member?(field)
          raise ProtocolError, "#{field.inspect} not signed"
        end
      end
    end

check_nonce() click to toggle source

    # File lib/openid/consumer/idres.rb, line 277
    def check_nonce
      case openid_namespace
      when OPENID1_NS, OPENID11_NS
        nonce =
          @message.get_arg(BARE_NS, Consumer.openid1_return_to_nonce_name)

        # We generated the nonce, so it uses the empty string as the
        # server URL
        server_url = ''
      when OPENID2_NS
        nonce = @message.get_arg(OPENID2_NS, 'response_nonce')
        server_url = self.server_url
      else
        raise StandardError, 'Not reached'
      end

      if nonce.nil?
        raise ProtocolError, 'Nonce missing from response'
      end

      begin
        time, extra = Nonce.split_nonce(nonce)
      rescue ArgumentError
        raise ProtocolError, "Malformed nonce: #{nonce.inspect}"
      end

      if !@store.nil? && !@store.use_nonce(server_url, time, extra)
        raise ProtocolError, ("Nonce already used or out of range: "                                "#{nonce.inspect}")
      end
    end

check_signature() click to toggle source

Raises ProtocolError if the signature is bad

    # File lib/openid/consumer/idres.rb, line 205
    def check_signature
      if @store.nil?
        assoc = nil
      else
        assoc = @store.get_association(server_url, fetch('assoc_handle'))
      end

      if assoc.nil?
        check_auth
      else
        if assoc.expires_in <= 0
          # XXX: It might be a good idea sometimes to re-start the
          # authentication with a new association. Doing it
          # automatically opens the possibility for
          # denial-of-service by a server that just returns expired
          # associations (or really short-lived associations)
          raise ProtocolError, "Association with #{server_url} expired"
        elsif !assoc.check_message_signature(@message)
          raise ProtocolError, "Bad signature in response from #{server_url}"
        end
      end
    end

create_check_auth_request() click to toggle source

    # File lib/openid/consumer/idres.rb, line 242
    def create_check_auth_request
      signed_list = @message.get_arg(OPENID_NS, 'signed', NO_DEFAULT).split(',')

      # check that we got all the signed arguments
      signed_list.each {|k|
        @message.get_aliased_arg(k, NO_DEFAULT)
      }

      ca_message = @message.copy
      ca_message.set_arg(OPENID_NS, 'mode', 'check_authentication')

      return ca_message
    end

discover_and_verify(claimed_id, to_match_endpoints) click to toggle source

Given an endpoint object created from the information in an OpenID response, perform discovery and verify the discovery results, returning the matching endpoint that is the result of doing that discovery.

    # File lib/openid/consumer/idres.rb, line 418
    def discover_and_verify(claimed_id, to_match_endpoints)
      Util.log("Performing discovery on #{claimed_id}")
      _, services = OpenID.discover(claimed_id)
      if services.length == 0
        # XXX: this might want to be something other than
        # ProtocolError. In Python, it's DiscoveryFailure
        raise ProtocolError, ("No OpenID information found at "                                 "#{claimed_id}")
      end
      verify_discovered_services(claimed_id, services, to_match_endpoints)
    end

fetch(field, default=NO_DEFAULT) click to toggle source

    # File lib/openid/consumer/idres.rb, line 88
    def fetch(field, default=NO_DEFAULT)
      @message.get_arg(OPENID_NS, field, default)
    end

id_res() click to toggle source

This method will raise ProtocolError unless the request is a valid id_res response. Once it has been verified, the methods 'endpoint', 'message', and 'signed_fields' contain the verified information.

    # File lib/openid/consumer/idres.rb, line 72
    def id_res
      check_for_fields
      verify_return_to
      verify_discovery_results
      check_signature
      check_nonce
    end

openid_namespace() click to toggle source

    # File lib/openid/consumer/idres.rb, line 84
    def openid_namespace
      @message.get_openid_namespace
    end

process_check_auth_response(response) click to toggle source

Process the response message from a check_authentication request, invalidating associations if requested.

    # File lib/openid/consumer/idres.rb, line 258
    def process_check_auth_response(response)
      is_valid = response.get_arg(OPENID_NS, 'is_valid', 'false')

      invalidate_handle = response.get_arg(OPENID_NS, 'invalidate_handle')
      if !invalidate_handle.nil?
        Util.log("Received 'invalidate_handle' from server #{server_url}")
        if @store.nil?
          Util.log('Unexpectedly got "invalidate_handle" without a store!')
        else
          @store.remove_association(server_url, invalidate_handle)
        end
      end

      if is_valid != 'true'
        raise ProtocolError, ("Server #{server_url} responds that the "                                 "'check_authentication' call is not valid")
      end
    end

server_url() click to toggle source

    # File lib/openid/consumer/idres.rb, line 80
    def server_url
      @endpoint.nil? ? nil : @endpoint.server_url
    end

signed_list() click to toggle source

    # File lib/openid/consumer/idres.rb, line 92
    def signed_list
      if @signed_list.nil?
        signed_list_str = fetch('signed', nil)
        if signed_list_str.nil?
          raise ProtocolError, 'Response missing signed list'
        end

        @signed_list = signed_list_str.split(',', -1)
      end
      @signed_list
    end

verify_discovered_services(claimed_id, services, to_match_endpoints) click to toggle source

    # File lib/openid/consumer/idres.rb, line 431
    def verify_discovered_services(claimed_id, services, to_match_endpoints)
      # Search the services resulting from discovery to find one
      # that matches the information from the assertion
      failure_messages = []
      for endpoint in services
        for to_match_endpoint in to_match_endpoints
          begin
            verify_discovery_single(endpoint, to_match_endpoint)
          rescue ProtocolError => why
            failure_messages << why.message
          else
            # It matches, so discover verification has
            # succeeded. Return this endpoint.
            @endpoint = endpoint
            return
          end
        end
      end

      Util.log("Discovery verification failure for #{claimed_id}")
      failure_messages.each do |failure_message|
        Util.log(" * Endpoint mismatch: " + failure_message)
      end

      # XXX: is DiscoveryFailure in Python OpenID
      raise ProtocolError, ("No matching endpoint found after "                               "discovering #{claimed_id}")
    end

verify_discovery_results() click to toggle source

    # File lib/openid/consumer/idres.rb, line 309
    def verify_discovery_results
      begin
        case openid_namespace
        when OPENID1_NS, OPENID11_NS
          verify_discovery_results_openid1
        when OPENID2_NS
          verify_discovery_results_openid2
        else
          raise StandardError, "Not reached: #{openid_namespace}"
        end
      rescue Message::KeyNotFound => why
        raise ProtocolError, "Missing required field: #{why.message}"
      end
    end

verify_discovery_results_openid1() click to toggle source

    # File lib/openid/consumer/idres.rb, line 367
    def verify_discovery_results_openid1
      claimed_id =
        @message.get_arg(BARE_NS, Consumer.openid1_return_to_claimed_id_name)

      if claimed_id.nil?
        if @endpoint.nil?
          raise ProtocolError, ("When using OpenID 1, the claimed ID must "                                   "be supplied, either by passing it through "                                   "as a return_to parameter or by using a "                                   "session, and supplied to the IdResHandler "                                   "when it is constructed.")
        else
          claimed_id = @endpoint.claimed_id
        end
      end

      to_match = OpenIDServiceEndpoint.new
      to_match.type_uris = [OPENID_1_1_TYPE]
      to_match.local_id = fetch('identity')
      # Restore delegate information from the initiation phase
      to_match.claimed_id = claimed_id

      to_match_1_0 = to_match.dup
      to_match_1_0.type_uris = [OPENID_1_0_TYPE]

      if !@endpoint.nil?
        begin
          begin
            verify_discovery_single(@endpoint, to_match)
          rescue TypeURIMismatch
            verify_discovery_single(@endpoint, to_match_1_0)
          end
        rescue ProtocolError => why
          Util.log('Error attempting to use stored discovery information: ' +
                   why.message)
          Util.log('Attempting discovery to verify endpoint')
        else
          return @endpoint
        end
      end

      # Either no endpoint was supplied or OpenID 1.x verification
      # of the information that's in the message failed on that
      # endpoint.
      discover_and_verify(to_match.claimed_id, [to_match, to_match_1_0])
    end

verify_discovery_results_openid2() click to toggle source

    # File lib/openid/consumer/idres.rb, line 324
    def verify_discovery_results_openid2
      to_match = OpenIDServiceEndpoint.new
      to_match.type_uris = [OPENID_2_0_TYPE]
      to_match.claimed_id = fetch('claimed_id', nil)
      to_match.local_id = fetch('identity', nil)
      to_match.server_url = fetch('op_endpoint')

      if to_match.claimed_id.nil? && !to_match.local_id.nil?
        raise ProtocolError, ('openid.identity is present without '                                 'openid.claimed_id')
      elsif !to_match.claimed_id.nil? && to_match.local_id.nil?
        raise ProtocolError, ('openid.claimed_id is present without '                                 'openid.identity')

      # This is a response without identifiers, so there's really no
      # checking that we can do, so return an endpoint that's for
      # the specified `openid.op_endpoint'
      elsif to_match.claimed_id.nil?
        @endpoint =
          OpenIDServiceEndpoint.from_op_endpoint_url(to_match.server_url)
        return
      end

      if @endpoint.nil?
        Util.log('No pre-discovered information supplied')
        discover_and_verify(to_match.claimed_id, [to_match])
      else
        begin
          verify_discovery_single(@endpoint, to_match)
        rescue ProtocolError => why
          Util.log("Error attempting to use stored discovery "                      "information: #{why.message}")
          Util.log("Attempting discovery to verify endpoint")
          discover_and_verify(to_match.claimed_id, [to_match])
        end
      end

      if @endpoint.claimed_id != to_match.claimed_id
        @endpoint = @endpoint.dup
        @endpoint.claimed_id = to_match.claimed_id
      end
    end

verify_discovery_single(endpoint, to_match) click to toggle source

    # File lib/openid/consumer/idres.rb, line 460
    def verify_discovery_single(endpoint, to_match)
      # Every type URI that's in the to_match endpoint has to be
      # present in the discovered endpoint.
      for type_uri in to_match.type_uris
        if !endpoint.uses_extension(type_uri)
          raise TypeURIMismatch.new(type_uri, endpoint)
        end
      end

      # Fragments do not influence discovery, so we can't compare a
      # claimed identifier with a fragment to discovered information.
      defragged_claimed_id =
        case Yadis::XRI.identifier_scheme(to_match.claimed_id)
        when :xri
          to_match.claimed_id
        when :uri
          begin
            parsed = URI.parse(to_match.claimed_id)
          rescue URI::InvalidURIError
            to_match.claimed_id
          else
            parsed.fragment = nil
            parsed.to_s
          end
        else
          raise StandardError, 'Not reached'
        end

      if defragged_claimed_id != endpoint.claimed_id
        raise ProtocolError, ("Claimed ID does not match (different "                                 "subjects!), Expected "                                 "#{defragged_claimed_id}, got "                                 "#{endpoint.claimed_id}")
      end

      if to_match.get_local_id != endpoint.get_local_id
        raise ProtocolError, ("local_id mismatch. Expected "                                 "#{to_match.get_local_id}, got "                                 "#{endpoint.get_local_id}")
      end

      # If the server URL is nil, this must be an OpenID 1
      # response, because op_endpoint is a required parameter in
      # OpenID 2. In that case, we don't actually care what the
      # discovered server_url is, because signature checking or
      # check_auth should take care of that check for us.
      if to_match.server_url.nil?
        if to_match.preferred_namespace != OPENID1_NS
          raise StandardError,
          "The code calling this must ensure that OpenID 2 "             "responses have a non-none `openid.op_endpoint' and "             "that it is set as the `server_url' attribute of the "             "`to_match' endpoint."
        end
      elsif to_match.server_url != endpoint.server_url
        raise ProtocolError, ("OP Endpoint mismatch. Expected"                                 "#{to_match.server_url}, got "                                 "#{endpoint.server_url}")
      end
    end

verify_return_to() click to toggle source

    # File lib/openid/consumer/idres.rb, line 140
    def verify_return_to
      begin
        msg_return_to = URI.parse(URINorm::urinorm(fetch('return_to')))
      rescue URI::InvalidURIError
        raise ProtocolError, ("return_to is not a valid URI")
      end

      verify_return_to_args(msg_return_to)
      if !@current_url.nil?
        verify_return_to_base(msg_return_to)
      end
    end

verify_return_to_args(msg_return_to) click to toggle source

    # File lib/openid/consumer/idres.rb, line 153
    def verify_return_to_args(msg_return_to)
      return_to_parsed_query = {}
      if !msg_return_to.query.nil?
        CGI.parse(msg_return_to.query).each_pair do |k, vs|
          return_to_parsed_query[k] = vs[0]
        end
      end
      query = @message.to_post_args
      return_to_parsed_query.each_pair do |rt_key, rt_val|
        msg_val = query[rt_key]
        if msg_val.nil? && !rt_val.nil?
          raise ProtocolError, "Message missing return_to argument '#{rt_key}'"
        elsif msg_val != rt_val
          raise ProtocolError, ("Parameter '#{rt_key}' value "                                   "#{msg_val.inspect} does not match "                                   "return_to's value #{rt_val.inspect}")
        end
      end
      @message.get_args(BARE_NS).each_pair do |bare_key, bare_val|
        rt_val = return_to_parsed_query[bare_key]
        if not return_to_parsed_query.has_key? bare_key
          # This may be caused by your web framework throwing extra
          # entries in to your parameters hash that were not GET or
          # POST parameters.  For example, Rails has been known to
          # add "controller" and "action" keys; another server adds
          # at least a "format" key.
          raise ProtocolError, ("Unexpected parameter (not on return_to): "                                   "'#{bare_key}'=#{rt_val.inspect})")
        end
        if rt_val != bare_val
          raise ProtocolError, ("Parameter '#{bare_key}' value "                                   "#{bare_val.inspect} does not match "                                   "return_to's value #{rt_val.inspect}")
        end
      end
    end

verify_return_to_base(msg_return_to) click to toggle source

    # File lib/openid/consumer/idres.rb, line 190
    def verify_return_to_base(msg_return_to)
      begin
        app_parsed = URI.parse(URINorm::urinorm(@current_url))
      rescue URI::InvalidURIError
        raise ProtocolError, "current_url is not a valid URI: #{@current_url}"
      end

      [:scheme, :host, :port, :path].each do |meth|
        if msg_return_to.send(meth) != app_parsed.send(meth)
          raise ProtocolError, "return_to #{meth.to_s} does not match"
        end
      end
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.