diff options
Diffstat (limited to 'vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb')
-rw-r--r-- | vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb new file mode 100644 index 000000000..afe025a00 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb @@ -0,0 +1,395 @@ +require "openid/consumer/idres.rb" +require "openid/consumer/checkid_request.rb" +require "openid/consumer/associationmanager.rb" +require "openid/consumer/responses.rb" +require "openid/consumer/discovery_manager" +require "openid/consumer/discovery" +require "openid/message" +require "openid/yadis/discovery" +require "openid/store/nonce" + +module OpenID + # OpenID support for Relying Parties (aka Consumers). + # + # This module documents the main interface with the OpenID consumer + # library. The only part of the library which has to be used and + # isn't documented in full here is the store required to create an + # Consumer instance. + # + # = OVERVIEW + # + # The OpenID identity verification process most commonly uses the + # following steps, as visible to the user of this library: + # + # 1. The user enters their OpenID into a field on the consumer's + # site, and hits a login button. + # + # 2. The consumer site discovers the user's OpenID provider using + # the Yadis protocol. + # + # 3. The consumer site sends the browser a redirect to the OpenID + # provider. This is the authentication request as described in + # the OpenID specification. + # + # 4. The OpenID provider's site sends the browser a redirect back to + # the consumer site. This redirect contains the provider's + # response to the authentication request. + # + # The most important part of the flow to note is the consumer's site + # must handle two separate HTTP requests in order to perform the + # full identity check. + # + # = LIBRARY DESIGN + # + # This consumer library is designed with that flow in mind. The + # goal is to make it as easy as possible to perform the above steps + # securely. + # + # At a high level, there are two important parts in the consumer + # library. The first important part is this module, which contains + # the interface to actually use this library. The second is + # openid/store/interface.rb, which describes the interface to use if + # you need to create a custom method for storing the state this + # library needs to maintain between requests. + # + # In general, the second part is less important for users of the + # library to know about, as several implementations are provided + # which cover a wide variety of situations in which consumers may + # use the library. + # + # The Consumer class has methods corresponding to the actions + # necessary in each of steps 2, 3, and 4 described in the overview. + # Use of this library should be as easy as creating an Consumer + # instance and calling the methods appropriate for the action the + # site wants to take. + # + # This library automatically detects which version of the OpenID + # protocol should be used for a transaction and constructs the + # proper requests and responses. Users of this library do not need + # to worry about supporting multiple protocol versions; the library + # supports them implicitly. Depending on the version of the + # protocol in use, the OpenID transaction may be more secure. See + # the OpenID specifications for more information. + # + # = SESSIONS, STORES, AND STATELESS MODE + # + # The Consumer object keeps track of two types of state: + # + # 1. State of the user's current authentication attempt. Things + # like the identity URL, the list of endpoints discovered for + # that URL, and in case where some endpoints are unreachable, the + # list of endpoints already tried. This state needs to be held + # from Consumer.begin() to Consumer.complete(), but it is only + # applicable to a single session with a single user agent, and at + # the end of the authentication process (i.e. when an OP replies + # with either <tt>id_res</tt>. or <tt>cancel</tt> it may be + # discarded. + # + # 2. State of relationships with servers, i.e. shared secrets + # (associations) with servers and nonces seen on signed messages. + # This information should persist from one session to the next + # and should not be bound to a particular user-agent. + # + # These two types of storage are reflected in the first two + # arguments of Consumer's constructor, <tt>session</tt> and + # <tt>store</tt>. <tt>session</tt> is a dict-like object and we + # hope your web framework provides you with one of these bound to + # the user agent. <tt>store</tt> is an instance of Store. + # + # Since the store does hold secrets shared between your application + # and the OpenID provider, you should be careful about how you use + # it in a shared hosting environment. If the filesystem or database + # permissions of your web host allow strangers to read from them, do + # not store your data there! If you have no safe place to store + # your data, construct your consumer with nil for the store, and it + # will operate only in stateless mode. Stateless mode may be + # slower, put more load on the OpenID provider, and trusts the + # provider to keep you safe from replay attacks. + # + # Several store implementation are provided, and the interface is + # fully documented so that custom stores can be used as well. See + # the documentation for the Consumer class for more information on + # the interface for stores. The implementations that are provided + # allow the consumer site to store the necessary data in several + # different ways, including several SQL databases and normal files + # on disk. + # + # = IMMEDIATE MODE + # + # In the flow described above, the user may need to confirm to the + # OpenID provider that it's ok to disclose his or her identity. The + # provider may draw pages asking for information from the user + # before it redirects the browser back to the consumer's site. This + # is generally transparent to the consumer site, so it is typically + # ignored as an implementation detail. + # + # There can be times, however, where the consumer site wants to get + # a response immediately. When this is the case, the consumer can + # put the library in immediate mode. In immediate mode, there is an + # extra response possible from the server, which is essentially the + # server reporting that it doesn't have enough information to answer + # the question yet. + # + # = USING THIS LIBRARY + # + # Integrating this library into an application is usually a + # relatively straightforward process. The process should basically + # follow this plan: + # + # Add an OpenID login field somewhere on your site. When an OpenID + # is entered in that field and the form is submitted, it should make + # a request to the your site which includes that OpenID URL. + # + # First, the application should instantiate a Consumer with a + # session for per-user state and store for shared state using the + # store of choice. + # + # Next, the application should call the <tt>begin</tt> method of + # Consumer instance. This method takes the OpenID URL as entered by + # the user. The <tt>begin</tt> method returns a CheckIDRequest + # object. + # + # Next, the application should call the redirect_url method on the + # CheckIDRequest object. The parameter <tt>return_to</tt> is the + # URL that the OpenID server will send the user back to after + # attempting to verify his or her identity. The <tt>realm</tt> + # parameter is the URL (or URL pattern) that identifies your web + # site to the user when he or she is authorizing it. Send a + # redirect to the resulting URL to the user's browser. + # + # That's the first half of the authentication process. The second + # half of the process is done after the user's OpenID Provider sends + # the user's browser a redirect back to your site to complete their + # login. + # + # When that happens, the user will contact your site at the URL + # given as the <tt>return_to</tt> URL to the redirect_url call made + # above. The request will have several query parameters added to + # the URL by the OpenID provider as the information necessary to + # finish the request. + # + # Get a Consumer instance with the same session and store as before + # and call its complete() method, passing in all the received query + # arguments and URL currently being handled. + # + # There are multiple possible return types possible from that + # method. These indicate the whether or not the login was + # successful, and include any additional information appropriate for + # their type. + class Consumer + attr_accessor :session_key_prefix + + # Initialize a Consumer instance. + # + # You should create a new instance of the Consumer object with + # every HTTP request that handles OpenID transactions. + # + # session: the session object to use to store request information. + # The session should behave like a hash. + # + # store: an object that implements the interface in Store. + def initialize(session, store) + @session = session + @store = store + @session_key_prefix = 'OpenID::Consumer::' + end + + # Start the OpenID authentication process. See steps 1-2 in the + # overview for the Consumer class. + # + # user_url: Identity URL given by the user. This method performs a + # textual transformation of the URL to try and make sure it is + # normalized. For example, a user_url of example.com will be + # normalized to http://example.com/ normalizing and resolving any + # redirects the server might issue. + # + # anonymous: A boolean value. Whether to make an anonymous + # request of the OpenID provider. Such a request does not ask for + # an authorization assertion for an OpenID identifier, but may be + # used with extensions to pass other data. e.g. "I don't care who + # you are, but I'd like to know your time zone." + # + # Returns a CheckIDRequest object containing the discovered + # information, with a method for building a redirect URL to the + # server, as described in step 3 of the overview. This object may + # also be used to add extension arguments to the request, using + # its add_extension_arg method. + # + # Raises DiscoveryFailure when no OpenID server can be found for + # this URL. + def begin(openid_identifier, anonymous=false) + manager = discovery_manager(openid_identifier) + service = manager.get_next_service(&method(:discover)) + + if service.nil? + raise DiscoveryFailure.new("No usable OpenID services were found "\ + "for #{openid_identifier.inspect}", nil) + else + begin_without_discovery(service, anonymous) + end + end + + # Start OpenID verification without doing OpenID server + # discovery. This method is used internally by Consumer.begin() + # after discovery is performed, and exists to provide an interface + # for library users needing to perform their own discovery. + # + # service: an OpenID service endpoint descriptor. This object and + # factories for it are found in the openid/consumer/discovery.rb + # module. + # + # Returns an OpenID authentication request object. + def begin_without_discovery(service, anonymous) + assoc = association_manager(service).get_association + checkid_request = CheckIDRequest.new(assoc, service) + checkid_request.anonymous = anonymous + + if service.compatibility_mode + rt_args = checkid_request.return_to_args + rt_args[Consumer.openid1_return_to_nonce_name] = Nonce.mk_nonce + rt_args[Consumer.openid1_return_to_claimed_id_name] = + service.claimed_id + end + + self.last_requested_endpoint = service + return checkid_request + end + + # Called to interpret the server's response to an OpenID + # request. It is called in step 4 of the flow described in the + # Consumer overview. + # + # query: A hash of the query parameters for this HTTP request. + # Note that in rails, this is <b>not</b> <tt>params</tt> but + # <tt>params.reject{|k,v|request.path_parameters[k]}</tt> + # because <tt>controller</tt> and <tt>action</tt> and other + # "path parameters" are included in params. + # + # current_url: Extract the URL of the current request from your + # application's web request framework and specify it here to have it + # checked against the openid.return_to value in the response. Do not + # just pass <tt>args['openid.return_to']</tt> here; that will defeat the + # purpose of this check. (See OpenID Authentication 2.0 section 11.1.) + # + # If the return_to URL check fails, the status of the completion will be + # FAILURE. + + # + # Returns a subclass of Response. The type of response is + # indicated by the status attribute, which will be one of + # SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED. + def complete(query, current_url) + message = Message.from_post_args(query) + 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 + + protected + + def session_get(name) + @session[session_key(name)] + end + + def session_set(name, val) + @session[session_key(name)] = val + end + + def session_key(suffix) + @session_key_prefix + suffix + end + + def last_requested_endpoint + session_get('last_requested_endpoint') + end + + def last_requested_endpoint=(endpoint) + session_set('last_requested_endpoint', endpoint) + end + + def cleanup_last_requested_endpoint + @session[session_key('last_requested_endpoint')] = nil + end + + def discovery_manager(openid_identifier) + DiscoveryManager.new(@session, openid_identifier, @session_key_prefix) + end + + def cleanup_session + discovery_manager(nil).cleanup(true) + end + + + def discover(identifier) + OpenID.discover(identifier) + end + + def negotiator + DefaultNegotiator + end + + def association_manager(service) + AssociationManager.new(@store, service.server_url, + service.compatibility_mode, negotiator) + end + + def handle_idres(message, current_url) + IdResHandler.new(message, current_url, @store, last_requested_endpoint) + end + + def complete_invalid(message, unused_return_to) + mode = message.get_arg(OPENID_NS, 'mode', '<No mode set>') + return FailureResponse.new(last_requested_endpoint, + "Invalid openid.mode: #{mode}") + end + + def complete_cancel(unused_message, unused_return_to) + return CancelResponse.new(last_requested_endpoint) + end + + def complete_error(message, unused_return_to) + error = message.get_arg(OPENID_NS, 'error') + contact = message.get_arg(OPENID_NS, 'contact') + reference = message.get_arg(OPENID_NS, 'reference') + + return FailureResponse.new(last_requested_endpoint, + error, contact, reference) + end + + def complete_setup_needed(message, unused_return_to) + if message.is_openid1 + return complete_invalid(message, nil) + else + setup_url = message.get_arg(OPENID2_NS, 'user_setup_url') + return SetupNeededResponse.new(last_requested_endpoint, setup_url) + end + end + + def complete_id_res(message, current_url) + if message.is_openid1 + setup_url = message.get_arg(OPENID1_NS, 'user_setup_url') + if !setup_url.nil? + return SetupNeededResponse.new(last_requested_endpoint, setup_url) + end + end + + begin + idres = handle_idres(message, current_url) + rescue OpenIDError => why + return FailureResponse.new(last_requested_endpoint, why.message) + else + return SuccessResponse.new(idres.endpoint, message, + idres.signed_fields) + end + end + end +end |