class OpenID::Server::CheckIDRequest

A request to confirm the identity of a user.

This class handles requests for openid modes checkid_immediate and checkid_setup .

Attributes

assoc_handle[RW]

Provided in smart mode requests, a handle for a previously established association. nil for dumb mode requests.

claimed_id[RW]

The claimed identifier. Not present in OpenID 1.x messages.

identity[RW]

The OP-local identifier being checked.

immediate[RW]

Is this an immediate-mode request?

mode[RW]
mode

checkid_immediate or checkid_setup

op_endpoint[RW]
return_to[RW]

The URL to send the user agent back to to reply to this request.

trust_root[RW]

This URL identifies the party making the request, and the user will use that to make her decision about what answer she trusts them to have. Referred to as “realm” in OpenID 2.0.

Public Class Methods

from_message(message, op_endpoint) click to toggle source

Construct me from an OpenID message.

message

An OpenID checkid_* request Message

#op_endpoint

The endpoint URL of the server that this message was sent to.

Raises:

ProtocolError

When not all required parameters are present in the message.

MalformedReturnURL

When the return_to URL is not a URL.

UntrustedReturnURL

When the return_to URL is outside the trust_root.

# File lib/openid/server.rb, line 490
def self.from_message(message, op_endpoint)
  obj = self.allocate
  obj.message = message
  obj.op_endpoint = op_endpoint
  mode = message.get_arg(OPENID_NS, 'mode')
  if mode == "checkid_immediate"
    obj.immediate = true
    obj.mode = "checkid_immediate"
  else
    obj.immediate = false
    obj.mode = "checkid_setup"
  end

  obj.return_to = message.get_arg(OPENID_NS, 'return_to')
  if message.is_openid1 and !obj.return_to
    msg = sprintf("Missing required field 'return_to' from %s",
                  message)
    raise ProtocolError.new(message, msg)
  end

  obj.identity = message.get_arg(OPENID_NS, 'identity')
  obj.claimed_id = message.get_arg(OPENID_NS, 'claimed_id')
  if message.is_openid1()
    if !obj.identity
      s = "OpenID 1 message did not contain openid.identity"
      raise ProtocolError.new(message, s)
    end
  else
    if obj.identity and not obj.claimed_id
      s = ("OpenID 2.0 message contained openid.identity but not " +
           "claimed_id")
      raise ProtocolError.new(message, s)
    elsif obj.claimed_id and not obj.identity
      s = ("OpenID 2.0 message contained openid.claimed_id but not " +
           "identity")
      raise ProtocolError.new(message, s)
    end
  end

  # There's a case for making self.trust_root be a TrustRoot
  # here.  But if TrustRoot isn't currently part of the "public"
  # API, I'm not sure it's worth doing.
  if message.is_openid1
    trust_root_param = 'trust_root'
  else
    trust_root_param = 'realm'
  end
  trust_root = message.get_arg(OPENID_NS, trust_root_param)
  trust_root = obj.return_to if (trust_root.nil? || trust_root.empty?)
  obj.trust_root = trust_root

  if !message.is_openid1 and !obj.return_to and !obj.trust_root
    raise ProtocolError.new(message, "openid.realm required when " +
                            "openid.return_to absent")
  end

  obj.assoc_handle = message.get_arg(OPENID_NS, 'assoc_handle')

  # Using TrustRoot.parse here is a bit misleading, as we're not
  # parsing return_to as a trust root at all.  However, valid
  # URLs are valid trust roots, so we can use this to get an
  # idea if it is a valid URL.  Not all trust roots are valid
  # return_to URLs, however (particularly ones with wildcards),
  # so this is still a little sketchy.
  if obj.return_to and            !TrustRoot::TrustRoot.parse(obj.return_to)
    raise MalformedReturnURL.new(message, obj.return_to)
  end

  # I first thought that checking to see if the return_to is
  # within the trust_root is premature here, a
  # logic-not-decoding thing.  But it was argued that this is
  # really part of data validation.  A request with an invalid
  # trust_root/return_to is broken regardless of application,
  # right?
  if !obj.trust_root_valid()
    raise UntrustedReturnURL.new(message, obj.return_to, obj.trust_root)
  end

  return obj
end
new(identity, return_to, op_endpoint, trust_root=nil, immediate=false, assoc_handle=nil, claimed_id=nil) click to toggle source

These parameters are assigned directly as attributes, see the #CheckIDRequest class documentation for their descriptions.

Raises #MalformedReturnURL when the return_to URL is not a URL.

# File lib/openid/server.rb, line 447
def initialize(identity, return_to, op_endpoint, trust_root=nil,
               immediate=false, assoc_handle=nil, claimed_id=nil)
  @assoc_handle = assoc_handle
  @identity = identity
  @claimed_id = (claimed_id or identity)
  @return_to = return_to
  @trust_root = (trust_root or return_to)
  @op_endpoint = op_endpoint
  @message = nil

  if immediate
    @immediate = true
    @mode = "checkid_immediate"
  else
    @immediate = false
    @mode = "checkid_setup"
  end

  if @return_to and
      !TrustRoot::TrustRoot.parse(@return_to)
    raise MalformedReturnURL.new(nil, @return_to)
  end

  if !trust_root_valid()
    raise UntrustedReturnURL.new(nil, @return_to, @trust_root)
  end
end

Public Instance Methods

answer(allow, server_url=nil, identity=nil, claimed_id=nil) click to toggle source

Respond to this request.

allow

Allow this user to claim this identity, and allow the consumer to have this information?

server_url

DEPRECATED. Passing #op_endpoint to the #Server constructor makes this optional.

When an OpenID 1.x immediate mode request does not succeed, it gets back a URL where the request may be carried out in a not-so-immediate fashion. Pass my URL in here (the fully qualified address of this server's endpoint, i.e. http://example.com/server), and I will use it as a base for the URL for a new request.

Optional for requests where #CheckIDRequest.immediate is false or allow is true.

identity

The OP-local identifier to answer with. Only for use when the relying party requested identifier selection.

#claimed_id

The claimed identifier to answer with, for use with identifier selection in the case where the claimed identifier and the OP-local identifier differ, i.e. when the #claimed_id uses delegation.

If identity is provided but this is not, claimed_id will default to the value of identity. When answering requests that did not ask for identifier selection, the response claimed_id will default to that of the request.

This parameter is new in OpenID 2.0.

Returns an OpenIDResponse object containing a OpenID id_res message.

Raises NoReturnToError if the #return_to is missing.

Version 2.0 deprecates server_url and adds claimed_id.

# File lib/openid/server.rb, line 657
def answer(allow, server_url=nil, identity=nil, claimed_id=nil)
  if !@return_to
    raise NoReturnToError
  end

  if !server_url
    if @message.is_openid2 and !@op_endpoint
      # In other words, that warning I raised in
      # Server.__init__?  You should pay attention to it now.
      raise RuntimeError, ("#{self} should be constructed with "                                  "op_endpoint to respond to OpenID 2.0 "                                  "messages.")
    end

    server_url = @op_endpoint
  end

  if allow
    mode = 'id_res'
  elsif @message.is_openid1
    if @immediate
      mode = 'id_res'
    else
      mode = 'cancel'
    end
  else
    if @immediate
      mode = 'setup_needed'
    else
      mode = 'cancel'
    end
  end

  response = OpenIDResponse.new(self)

  if claimed_id and @message.is_openid1
    raise VersionError, ("claimed_id is new in OpenID 2.0 and not "                                "available for #{@message.get_openid_namespace}")
  end

  if identity and !claimed_id
    claimed_id = identity
  end

  if allow
    if @identity == IDENTIFIER_SELECT
      if !identity
        raise ArgumentError, ("This request uses IdP-driven "                                     "identifier selection.You must supply "                                     "an identifier in the response.")
      end

      response_identity = identity
      response_claimed_id = claimed_id

    elsif @identity
      if identity and (@identity != identity)
        raise ArgumentError, ("Request was for identity #{@identity}, "                                     "cannot reply with identity #{identity}")
      end

      response_identity = @identity
      response_claimed_id = @claimed_id
    else
      if identity
        raise ArgumentError, ("This request specified no identity "                                     "and you supplied #{identity}")
      end
      response_identity = nil
    end

    if @message.is_openid1 and !response_identity
      raise ArgumentError, ("Request was an OpenID 1 request, so "                                   "response must include an identifier.")
    end

    response.fields.update_args(OPENID_NS, {
          'mode' => mode,
          'op_endpoint' => server_url,
          'return_to' => @return_to,
          'response_nonce' => Nonce.mk_nonce(),
          })

    if response_identity
      response.fields.set_arg(OPENID_NS, 'identity', response_identity)
      if @message.is_openid2
        response.fields.set_arg(OPENID_NS,
                                'claimed_id', response_claimed_id)
      end
    end
  else
    response.fields.set_arg(OPENID_NS, 'mode', mode)
    if @immediate
      if @message.is_openid1 and !server_url
        raise ArgumentError, ("setup_url is required for allow=false "                                     "in OpenID 1.x immediate mode.")
      end

      # Make a new request just like me, but with
      # immediate=false.
      setup_request = self.class.new(@identity, @return_to,
                                     @op_endpoint, @trust_root, false,
                                     @assoc_handle, @claimed_id)
      setup_request.message = Message.new(@message.get_openid_namespace)
      setup_url = setup_request.encode_to_url(server_url)
      response.fields.set_arg(OPENID_NS, 'user_setup_url', setup_url)
    end
  end

  return response
end
cancel_url() click to toggle source
# File lib/openid/server.rb, line 804
def cancel_url
  # Get the URL to cancel this request.
  #
  # Useful for creating a "Cancel" button on a web form so that
  # operation can be carried out directly without another trip
  # through the server.
  #
  # (Except you may want to make another trip through the
  # server so that it knows that the user did make a decision.)
  #
  # Returns a URL as a string.
  if !@return_to
    raise NoReturnToError
  end

  if @immediate
    raise ArgumentError.new("Cancel is not an appropriate response to " +
                            "immediate mode requests.")
  end

  response = Message.new(@message.get_openid_namespace)
  response.set_arg(OPENID_NS, 'mode', 'cancel')
  return response.to_url(@return_to)
end
encode_to_url(server_url) click to toggle source
# File lib/openid/server.rb, line 769
def encode_to_url(server_url)
  # Encode this request as a URL to GET.
  #
  # server_url:: The URL of the OpenID server to make this
  #              request of.
  if !@return_to
    raise NoReturnToError
  end

  # Imported from the alternate reality where these classes are
  # used in both the client and server code, so Requests are
  # Encodable too.  That's right, code imported from alternate
  # realities all for the love of you, id_res/user_setup_url.
  q = {'mode' => @mode,
       'identity' => @identity,
       'claimed_id' => @claimed_id,
       'return_to' => @return_to}

  if @trust_root
    if @message.is_openid1
      q['trust_root'] = @trust_root
    else
      q['realm'] = @trust_root
    end
  end

  if @assoc_handle
    q['assoc_handle'] = @assoc_handle
  end

  response = Message.new(@message.get_openid_namespace)
  response.update_args(@message.get_openid_namespace, q)
  return response.to_url(server_url)
end
id_select() click to toggle source

Is the identifier to be selected by the IDP?

# File lib/openid/server.rb, line 573
def id_select
  # So IDPs don't have to import the constant
  return @identity == IDENTIFIER_SELECT
end
return_to_verified() click to toggle source

Does the relying party publish the #return_to URL for this response under the realm? It is up to the provider to set a policy for what kinds of realms should be allowed. This #return_to URL verification reduces vulnerability to data-theft attacks based on open proxies, corss-site-scripting, or open redirectors.

This check should only be performed after making sure that the #return_to URL matches the realm.

Raises DiscoveryFailure if the realm URL does not support Yadis discovery (and so does not support the verification process).

Returns true if the realm publishes a document with the #return_to URL listed

# File lib/openid/server.rb, line 612
def return_to_verified
  return TrustRoot.verify_return_to(@trust_root, @return_to)
end
to_s() click to toggle source
# File lib/openid/server.rb, line 829
def to_s
  return sprintf('<%s id:%s im:%s tr:%s ah:%s>', self.class,
                 @identity,
                 @immediate,
                 @trust_root,
                 @assoc_handle)
end
trust_root_valid() click to toggle source

Is my #return_to under my #trust_root?

# File lib/openid/server.rb, line 579
def trust_root_valid
  if !@trust_root
    return true
  end

  tr = TrustRoot::TrustRoot.parse(@trust_root)
  if !tr
    raise MalformedTrustRoot.new(@message, @trust_root)
  end

  if @return_to
    return tr.validate_url(@return_to)
  else
    return true
  end
end