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.is_openid1 || !['checkid_setup', 'checkid_immediate'].include?(mode)) 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