187 lines
7.1 KiB
Ruby
187 lines
7.1 KiB
Ruby
require "openid/message"
|
|
require "openid/util"
|
|
|
|
module OpenID
|
|
class Consumer
|
|
# An object that holds the state necessary for generating an
|
|
# OpenID authentication request. This object holds the association
|
|
# with the server and the discovered information with which the
|
|
# request will be made.
|
|
#
|
|
# It is separate from the consumer because you may wish to add
|
|
# things to the request before sending it on its way to the
|
|
# server. It also has serialization options that let you encode
|
|
# the authentication request as a URL or as a form POST.
|
|
class CheckIDRequest
|
|
attr_accessor :return_to_args, :message
|
|
attr_reader :endpoint
|
|
|
|
# Users of this library should not create instances of this
|
|
# class. Instances of this class are created by the library
|
|
# when needed.
|
|
def initialize(assoc, endpoint)
|
|
@assoc = assoc
|
|
@endpoint = endpoint
|
|
@return_to_args = {}
|
|
@message = Message.new(endpoint.preferred_namespace)
|
|
@anonymous = false
|
|
end
|
|
|
|
attr_reader :anonymous
|
|
|
|
# Set whether this request should be made anonymously. If a
|
|
# request is anonymous, the identifier will not be sent in the
|
|
# request. This is only useful if you are making another kind of
|
|
# request with an extension in this request.
|
|
#
|
|
# Anonymous requests are not allowed when the request is made
|
|
# with OpenID 1.
|
|
def anonymous=(is_anonymous)
|
|
if is_anonymous && @message.is_openid1
|
|
raise ArgumentError, ("OpenID1 requests MUST include the "\
|
|
"identifier in the request")
|
|
end
|
|
@anonymous = is_anonymous
|
|
end
|
|
|
|
# Add an object that implements the extension interface for
|
|
# adding arguments to an OpenID message to this checkid request.
|
|
#
|
|
# extension_request: an OpenID::Extension object.
|
|
def add_extension(extension_request)
|
|
extension_request.to_message(@message)
|
|
end
|
|
|
|
# Add an extension argument to this OpenID authentication
|
|
# request. You probably want to use add_extension and the
|
|
# OpenID::Extension interface.
|
|
#
|
|
# Use caution when adding arguments, because they will be
|
|
# URL-escaped and appended to the redirect URL, which can easily
|
|
# get quite long.
|
|
def add_extension_arg(namespace, key, value)
|
|
@message.set_arg(namespace, key, value)
|
|
end
|
|
|
|
# Produce a OpenID::Message representing this request.
|
|
#
|
|
# Not specifying a return_to URL means that the user will not be
|
|
# returned to the site issuing the request upon its completion.
|
|
#
|
|
# If immediate mode is requested, the OpenID provider is to send
|
|
# back a response immediately, useful for behind-the-scenes
|
|
# authentication attempts. Otherwise the OpenID provider may
|
|
# engage the user before providing a response. This is the
|
|
# default case, as the user may need to provide credentials or
|
|
# approve the request before a positive response can be sent.
|
|
def get_message(realm, return_to=nil, immediate=false)
|
|
if !return_to.nil?
|
|
return_to = Util.append_args(return_to, @return_to_args)
|
|
elsif immediate
|
|
raise ArgumentError, ('"return_to" is mandatory when using '\
|
|
'"checkid_immediate"')
|
|
elsif @message.is_openid1
|
|
raise ArgumentError, ('"return_to" is mandatory for OpenID 1 '\
|
|
'requests')
|
|
elsif @return_to_args.empty?
|
|
raise ArgumentError, ('extra "return_to" arguments were specified, '\
|
|
'but no return_to was specified')
|
|
end
|
|
|
|
|
|
message = @message.copy
|
|
|
|
mode = immediate ? 'checkid_immediate' : 'checkid_setup'
|
|
message.set_arg(OPENID_NS, 'mode', mode)
|
|
|
|
realm_key = message.is_openid1 ? 'trust_root' : 'realm'
|
|
message.set_arg(OPENID_NS, realm_key, realm)
|
|
|
|
if !return_to.nil?
|
|
message.set_arg(OPENID_NS, 'return_to', return_to)
|
|
end
|
|
|
|
if not @anonymous
|
|
if @endpoint.is_op_identifier
|
|
# This will never happen when we're in OpenID 1
|
|
# compatibility mode, as long as is_op_identifier()
|
|
# returns false whenever preferred_namespace returns
|
|
# OPENID1_NS.
|
|
claimed_id = request_identity = IDENTIFIER_SELECT
|
|
else
|
|
request_identity = @endpoint.get_local_id
|
|
claimed_id = @endpoint.claimed_id
|
|
end
|
|
|
|
# This is true for both OpenID 1 and 2
|
|
message.set_arg(OPENID_NS, 'identity', request_identity)
|
|
|
|
if message.is_openid2
|
|
message.set_arg(OPENID2_NS, 'claimed_id', claimed_id)
|
|
end
|
|
end
|
|
|
|
if @assoc
|
|
message.set_arg(OPENID_NS, 'assoc_handle', @assoc.handle)
|
|
assoc_log_msg = "with assocication #{@assoc.handle}"
|
|
else
|
|
assoc_log_msg = 'using stateless mode.'
|
|
end
|
|
|
|
Util.log("Generated #{mode} request to #{@endpoint.server_url} "\
|
|
"#{assoc_log_msg}")
|
|
return message
|
|
end
|
|
|
|
# Returns a URL with an encoded OpenID request.
|
|
#
|
|
# The resulting URL is the OpenID provider's endpoint URL with
|
|
# parameters appended as query arguments. You should redirect
|
|
# the user agent to this URL.
|
|
#
|
|
# OpenID 2.0 endpoints also accept POST requests, see
|
|
# 'send_redirect?' and 'form_markup'.
|
|
def redirect_url(realm, return_to=nil, immediate=false)
|
|
message = get_message(realm, return_to, immediate)
|
|
return message.to_url(@endpoint.server_url)
|
|
end
|
|
|
|
# Get html for a form to submit this request to the IDP.
|
|
#
|
|
# form_tag_attrs is a hash of attributes to be added to the form
|
|
# tag. 'accept-charset' and 'enctype' have defaults that can be
|
|
# overridden. If a value is supplied for 'action' or 'method',
|
|
# it will be replaced.
|
|
def form_markup(realm, return_to=nil, immediate=false,
|
|
form_tag_attrs=nil)
|
|
message = get_message(realm, return_to, immediate)
|
|
return message.to_form_markup(@endpoint.server_url, form_tag_attrs)
|
|
end
|
|
|
|
# Get a complete HTML document that autosubmits the request to the IDP
|
|
# with javascript. This method wraps form_markup - see that method's
|
|
# documentation for help with the parameters.
|
|
def html_markup(realm, return_to=nil, immediate=false,
|
|
form_tag_attrs=nil)
|
|
Util.auto_submit_html(form_markup(realm,
|
|
return_to,
|
|
immediate,
|
|
form_tag_attrs))
|
|
end
|
|
|
|
# Should this OpenID authentication request be sent as a HTTP
|
|
# redirect or as a POST (form submission)?
|
|
#
|
|
# This takes the same parameters as redirect_url or form_markup
|
|
def send_redirect?(realm, return_to=nil, immediate=false)
|
|
if @endpoint.compatibility_mode
|
|
return true
|
|
else
|
|
url = redirect_url(realm, return_to, immediate)
|
|
return url.length <= OPENID1_URL_LIMIT
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|