class OpenID::Consumer::AssociationManager
An object that manages creating and storing associations for an OpenID provider endpoint
Public Class Methods
# File lib/openid/consumer/associationmanager.rb, line 89 def self.create_session(session_type) case session_type when 'no-encryption' NoEncryptionSession.new when 'DH-SHA1' DiffieHellmanSHA1Session.new when 'DH-SHA256' DiffieHellmanSHA256Session.new else raise ArgumentError, "Unknown association session type: " "#{session_type.inspect}" end end
# File lib/openid/consumer/associationmanager.rb, line 103 def initialize(store, server_url, compatibility_mode=false, negotiator=nil) @store = store @server_url = server_url @compatibility_mode = compatibility_mode @negotiator = negotiator || DefaultNegotiator end
Protected Class Methods
# File lib/openid/consumer/associationmanager.rb, line 272 def self.extract_expires_in(message) # expires_in should be a base-10 string. expires_in_str = message.get_arg(OPENID_NS, 'expires_in', NO_DEFAULT) if !(/\A\d+\Z/ =~ expires_in_str) raise ProtocolError, "Invalid expires_in field: #{expires_in_str}" end expires_in_str.to_i end
Public Instance Methods
# File lib/openid/consumer/associationmanager.rb, line 111 def get_association if @store.nil? return nil end assoc = @store.get_association(@server_url) if assoc.nil? || assoc.expires_in <= 0 assoc = negotiate_association if !assoc.nil? @store.store_association(@server_url, assoc) end end return assoc end
# File lib/openid/consumer/associationmanager.rb, line 127 def negotiate_association assoc_type, session_type = @negotiator.get_allowed_type begin return request_association(assoc_type, session_type) rescue ServerError => why supported_types = extract_supported_association_type(why, assoc_type) if !supported_types.nil? # Attempt to create an association from the assoc_type and # session_type that the server told us it supported. assoc_type, session_type = supported_types begin return request_association(assoc_type, session_type) rescue ServerError => why Util.log("Server #{@server_url} refused its suggested " "association type: session_type=#{session_type}, " "assoc_type=#{assoc_type}") return nil end end rescue InvalidOpenIDNamespace Util.log("Server #{@server_url} returned a malformed association " "response. Falling back to check_id mode for this request.") return nil end end
Protected Instance Methods
Create an association request for the given assoc_type and session_type. Returns a pair of the association session object and the request message that will be sent to the server.
# File lib/openid/consumer/associationmanager.rb, line 215 def create_associate_request(assoc_type, session_type) assoc_session = self.class.create_session(session_type) args = { 'mode' => 'associate', 'assoc_type' => assoc_type, } if !@compatibility_mode args['ns'] = OPENID2_NS end # Leave out the session type if we're in compatibility mode # *and* it's no-encryption. if !@compatibility_mode || assoc_session.class.session_type != 'no-encryption' args['session_type'] = assoc_session.class.session_type end args.merge!(assoc_session.get_request) message = Message.from_openid_args(args) return assoc_session, message end
Attempt to extract an association from the response, given the association response message and the established association session.
# File lib/openid/consumer/associationmanager.rb, line 284 def extract_association(assoc_response, assoc_session) # Extract the common fields from the response, raising an # exception if they are not found assoc_type = assoc_response.get_arg(OPENID_NS, 'assoc_type', NO_DEFAULT) assoc_handle = assoc_response.get_arg(OPENID_NS, 'assoc_handle', NO_DEFAULT) expires_in = self.class.extract_expires_in(assoc_response) # OpenID 1 has funny association session behaviour. if assoc_response.is_openid1 session_type = get_openid1_session_type(assoc_response) else session_type = assoc_response.get_arg(OPENID2_NS, 'session_type', NO_DEFAULT) end # Session type mismatch if assoc_session.class.session_type != session_type if (assoc_response.is_openid1 and session_type == 'no-encryption') # In OpenID 1, any association request can result in a # 'no-encryption' association response. Setting # assoc_session to a new no-encryption session should # make the rest of this function work properly for # that case. assoc_session = NoEncryptionSession.new else # Any other mismatch, regardless of protocol version # results in the failure of the association session # altogether. raise ProtocolError, "Session type mismatch. Expected " "#{assoc_session.class.session_type}, got " "#{session_type}" end end # Make sure assoc_type is valid for session_type if !assoc_session.class.allowed_assoc_types.member?(assoc_type) raise ProtocolError, "Unsupported assoc_type for session " "#{assoc_session.class.session_type} " "returned: #{assoc_type}" end # Delegate to the association session to extract the secret # from the response, however is appropriate for that session # type. begin secret = assoc_session.extract_secret(assoc_response) rescue Message::KeyNotFound, ArgumentError => why raise ProtocolError, "Malformed response for " "#{assoc_session.class.session_type} " "session: #{why.message}" end return Association.from_expires_in(expires_in, assoc_handle, secret, assoc_type) end
# File lib/openid/consumer/associationmanager.rb, line 154 def extract_supported_association_type(server_error, assoc_type) # Any error message whose code is not 'unsupported-type' should # be considered a total failure. if (server_error.error_code != 'unsupported-type' or server_error.message.is_openid1) Util.log("Server error when requesting an association from " "#{@server_url}: #{server_error.error_text}") return nil end # The server didn't like the association/session type that we # sent, and it sent us back a message that might tell us how to # handle it. Util.log("Unsupported association type #{assoc_type}: " "#{server_error.error_text}") # Extract the session_type and assoc_type from the error message assoc_type = server_error.message.get_arg(OPENID_NS, 'assoc_type') session_type = server_error.message.get_arg(OPENID_NS, 'session_type') if assoc_type.nil? or session_type.nil? Util.log("Server #{@server_url} responded with unsupported " "association session but did not supply a fallback.") return nil elsif !@negotiator.allowed?(assoc_type, session_type) Util.log("Server sent unsupported session/association type: " "session_type=#{session_type}, assoc_type=#{assoc_type}") return nil else return [assoc_type, session_type] end end
Given an association response message, extract the OpenID 1.X session type. Returns the association type for this message
This function mostly takes care of the 'no-encryption' default behavior in OpenID 1.
If the association type is plain-text, this function will return 'no-encryption'
# File lib/openid/consumer/associationmanager.rb, line 246 def get_openid1_session_type(assoc_response) # If it's an OpenID 1 message, allow session_type to default # to nil (which signifies "no-encryption") session_type = assoc_response.get_arg(OPENID_NS, 'session_type') # Handle the differences between no-encryption association # respones in OpenID 1 and 2: # no-encryption is not really a valid session type for # OpenID 1, but we'll accept it anyway, while issuing a # warning. if session_type == 'no-encryption' Util.log("WARNING: #{@server_url} sent 'no-encryption'" "for OpenID 1.X") # Missing or empty session type is the way to flag a # 'no-encryption' response. Change the session type to # 'no-encryption' so that it can be handled in the same # way as OpenID 2 'no-encryption' respones. elsif session_type == '' || session_type.nil? session_type = 'no-encryption' end return session_type end
Make and process one association request to this endpoint's OP endpoint URL. Returns an association object or nil if the association processing failed. Raises ServerError when the remote OpenID server returns an error.
# File lib/openid/consumer/associationmanager.rb, line 191 def request_association(assoc_type, session_type) assoc_session, args = create_associate_request(assoc_type, session_type) begin response = OpenID.make_kv_post(args, @server_url) return extract_association(response, assoc_session) rescue HTTPStatusError => why Util.log("Got HTTP status error when requesting association: #{why}") return nil rescue Message::KeyNotFound => why Util.log("Missing required parameter in response from " "#{@server_url}: #{why}") return nil rescue ProtocolError => why Util.log("Protocol error processing response from #{@server_url}: " "#{why}") return nil end end