summaryrefslogtreecommitdiffstats
path: root/Auth/OpenID/Consumer.php
diff options
context:
space:
mode:
Diffstat (limited to 'Auth/OpenID/Consumer.php')
-rw-r--r--Auth/OpenID/Consumer.php286
1 files changed, 215 insertions, 71 deletions
diff --git a/Auth/OpenID/Consumer.php b/Auth/OpenID/Consumer.php
index ba3ffc9..3c8ccb6 100644
--- a/Auth/OpenID/Consumer.php
+++ b/Auth/OpenID/Consumer.php
@@ -402,10 +402,13 @@ class Auth_OpenID_Consumer {
}
}
-class Auth_OpenID_DiffieHellmanConsumerSession {
+class Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
var $session_type = 'DH-SHA1';
+ var $hash_func = 'Auth_OpenID_SHA1';
+ var $secret_size = 20;
+ var $allowed_assoc_types = array('HMAC-SHA1');
- function Auth_OpenID_DiffieHellmanConsumerSession($dh = null)
+ function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh = null)
{
if ($dh === null) {
$dh = new Auth_OpenID_DiffieHellman();
@@ -420,14 +423,14 @@ class Auth_OpenID_DiffieHellmanConsumerSession {
$cpub = $math->longToBase64($this->dh->public);
- $args = array('openid.dh_consumer_public' => $cpub);
+ $args = array('dh_consumer_public' => $cpub);
if (!$this->dh->usingDefaultValues()) {
$args = array_merge($args, array(
- 'openid.dh_modulus' =>
+ 'dh_modulus' =>
$math->longToBase64($this->dh->mod),
- 'openid.dh_gen' =>
- $math->longToBase64($this->dh->gen)));
+ 'dh_gen' =>
+ $math->longToBase64($this->dh->gen)));
}
return $args;
@@ -446,17 +449,19 @@ class Auth_OpenID_DiffieHellmanConsumerSession {
}
$math =& Auth_OpenID_getMathLib();
+
$spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS,
'dh_server_public'));
$enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
'enc_mac_key'));
- return $this->dh->xorSecret($spub, $enc_mac_key);
+ return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func);
}
}
class Auth_OpenID_PlainTextConsumerSession {
- var $session_type = null;
+ var $session_type = 'no-encryption';
+ var $allowed_assoc_types = array('HMAC-SHA1');
function getRequest()
{
@@ -514,9 +519,16 @@ class Auth_OpenID_GenericConsumer {
function Auth_OpenID_GenericConsumer(&$store)
{
$this->store =& $store;
+ $this->negotiator =& Auth_OpenID_getDefaultNegotiator();
$this->_use_assocs = !($this->store && $this->store->isDumb());
$this->fetcher = Services_Yadis_Yadis::getHTTPFetcher();
+
+ $this->session_types = array(
+ 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession',
+ // 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession',
+ 'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession'
+ );
}
function begin($service_endpoint)
@@ -813,109 +825,241 @@ class Auth_OpenID_GenericConsumer {
if (($assoc === null) ||
($assoc->getExpiresIn() <= 0)) {
- $parts = $this->_createAssociateRequest($endpoint->server_url);
+ $assoc = $this->_negotiateAssociation($endpoint);
+ if ($assoc !== null) {
+ $this->store->storeAssociation($endpoint->server_url,
+ $assoc);
+ }
+ }
+
+ return $assoc;
+ }
- if ($parts === null) {
+ function _negotiateAssociation($endpoint)
+ {
+ # Get our preferred session/association type from the negotiatior.
+ list($assoc_type, $session_type) = $this->negotiator->getAllowedType();
+
+ $assoc = $this->_requestAssociation(
+ $endpoint, $assoc_type, $session_type);
+
+ if (is_a($assoc, 'Auth_OpenID_ServerError')) {
+ // Any error message whose code is not 'unsupported-type'
+ // should be considered a total failure.
+ if (($why->error_code != 'unsupported-type') ||
+ ($why->message->isOpenID1())) {
+ // oidutil.log(
+ // 'Server error when requesting an association from %r: %s'
+ // % (endpoint.server_url, why.error_text))
return null;
}
- list($assoc_session, $message) = $parts;
+ // 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.
+ // oidutil.log(
+ // 'Unsupported association type %s: %s' % (assoc_type,
+ // why.error_text,))
- $response = $this->_makeKVPost($message, $endpoint->server_url);
+ // Extract the session_type and assoc_type from the
+ // error message
+ $assoc_type = $why->message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_type');
- if ($response === null) {
- $assoc = null;
+ $session_type = $why->message->getArg(Auth_OpenID_OPENID_NS,
+ 'session_type');
+
+ if (($assoc_type === null) || ($session_type === null)) {
+ // oidutil.log('Server responded with unsupported association '
+ // 'session but did not supply a fallback.')
+ return null;
+ } else if (!$self->negotiator->isAllowed($assoc_type,
+ $session_type)) {
+ // fmt = ('Server sent unsupported session/association type: '
+ // 'session_type=%s, assoc_type=%s')
+ // oidutil.log(fmt % (session_type, assoc_type))
+ return null;
} else {
- $assoc = $this->_parseAssociation($response, $assoc_session,
- $endpoint->server_url);
+ // Attempt to create an association from the assoc_type
+ // and session_type that the server told us it
+ // supported.
+ $assoc = $self->_requestAssociation(
+ $endpoint, $assoc_type, $session_type);
+
+ if (is_a($assoc, 'Auth_OpenID_ServerError')) {
+ // Do not keep trying, since it rejected the
+ // association type that it told us to use.
+ // oidutil.log('Server %s refused its suggested association
+ // 'type: session_type=%s, assoc_type=%s'
+ // % (endpoint.server_url, session_type,
+ // assoc_type))
+ return null;
+ } else {
+ return $assoc;
+ }
}
}
return $assoc;
}
- function _createAssociateRequest($server_url)
+ function _requestAssociation($endpoint, $assoc_type, $session_type)
{
- $parts = parse_url($server_url);
+ list($assoc_session, $args) = $this->_createAssociateRequest(
+ $endpoint, $assoc_type, $session_type);
- if ($parts === false) {
- return null;
- }
+ $response = $this->_makeKVPost($args, $endpoint->server_url);
- if (array_key_exists('scheme', $parts)) {
- $proto = $parts['scheme'];
- } else {
- $proto = 'http';
+ if ($response === null) {
+ // oidutil.log('openid.associate request failed: %s' % (why[0],))
+ return null;
}
- if ($proto == 'https' || defined('Auth_OpenID_NO_MATH_SUPPORT')) {
- $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
- } else {
- $assoc_session = new Auth_OpenID_DiffieHellmanConsumerSession();
- }
+ return $this->_extractAssociation($response, $assoc_session);
+ }
- $args = array(
- 'openid.mode' => 'associate',
- 'openid.assoc_type' => 'HMAC-SHA1');
+ function _extractAssociation($assoc_response, $assoc_session)
+ {
+ // Extract the common fields from the response, raising an
+ // exception if they are not found
+ $assoc_type = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'assoc_type',
+ Auth_OpenID_NO_DEFAULT);
+
+ $assoc_handle = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'assoc_handle',
+ Auth_OpenID_NO_DEFAULT);
+
+ // expires_in is a base-10 string. The Python parsing will
+ // accept literals that have whitespace around them and will
+ // accept negative values. Neither of these are really in-spec,
+ // but we think it's OK to accept them.
+ $expires_in_str = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'expires_in',
+ Auth_OpenID_NO_DEFAULT);
- if ($assoc_session->session_type !== null) {
- $args['openid.session_type'] = $assoc_session->session_type;
+ $expires_in = intval($expires_in_str);
+ if ($expires_in === false) {
+ // raise ProtocolError('Invalid expires_in field: %s' % (why[0],))
+ return null;
}
- $args = array_merge($args, $assoc_session->getRequest());
- $msg = Auth_OpenID_Message::fromPostArgs($args);
-
- return array($assoc_session, $msg);
- }
+ // OpenID 1 has funny association session behaviour.
+ if ($assoc_response->isOpenID1()) {
+ $session_type = $this->_getOpenID1SessionType($assoc_response);
+ } else {
+ $session_type = $assoc_response->getArg(
+ Auth_OpenID_OPENID2_NS, 'session_type',
+ Auth_OpenID_NO_DEFAULT);
+ }
- /**
- * @access private
- */
- function _parseAssociation($results, $assoc_session, $server_url)
- {
- $required_keys = array('assoc_type', 'assoc_handle',
- 'expires_in');
+ // Session type mismatch
+ if ($assoc_session->session_type != $session_type) {
+ if ($assoc_response->isOpenID1() &&
+ ($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 = new Auth_OpenID_PlainTextConsumerSession();
+ } else {
+ // Any other mismatch, regardless of protocol version
+ // results in the failure of the association session
+ // altogether.
- foreach ($required_keys as $key) {
- if (!$results->hasKey(Auth_OpenID_OPENID_NS, $key)) {
+ // fmt = 'Session type mismatch. Expected %r, got %r'
+ // message = fmt % (assoc_session.session_type, session_type)
+ // raise ProtocolError(message)
return null;
}
}
- $assoc_type = $results->getArg(Auth_OpenID_OPENID_NS, 'assoc_type');
- $assoc_handle = $results->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
- $expires_in_str = $results->getArg(Auth_OpenID_OPENID_NS, 'expires_in');
-
- if ($assoc_type != 'HMAC-SHA1') {
+ // Make sure assoc_type is valid for session_type
+ if (!in_array($assoc_type, $assoc_session->allowed_assoc_types)) {
+ // fmt = 'Unsupported assoc_type for session %s returned: %s'
+ // raise ProtocolError(fmt % (assoc_session.session_type,
+ // assoc_type))
return null;
}
- $expires_in = intval($expires_in_str);
+ // Delegate to the association session to extract the secret
+ // from the response, however is appropriate for that session
+ // type.
+ $secret = $assoc_session->extractSecret($assoc_response);
- if ($expires_in <= 0) {
+ if ($secret === null) {
+ // fmt = 'Malformed response for %s session: %s'
+ // raise ProtocolError(fmt % (assoc_session.session_type, why[0]))
return null;
}
- $session_type = $results->getArg(Auth_OpenID_OPENID_NS, 'session_type');
- if ($session_type != $assoc_session->session_type) {
- if ($session_type === null) {
- $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
- } else {
- return null;
- }
- }
+ return Auth_OpenID_Association::fromExpiresIn(
+ $expires_in, $assoc_handle, $secret, $assoc_type);
+ }
- $secret = $assoc_session->extractSecret($results);
+ function _createAssociateRequest($endpoint, $assoc_type, $session_type)
+ {
+ $session_type_class = $this->session_types[$session_type];
+ $assoc_session = new $session_type_class();
- if (!$secret) {
- return null;
+ $args = array(
+ 'mode' => 'associate',
+ 'assoc_type' => $assoc_type);
+
+ if (!$endpoint->compatibilityMode()) {
+ $args['ns'] = Auth_OpenID_OPENID2_NS;
}
- $assoc = Auth_OpenID_Association::fromExpiresIn(
- $expires_in, $assoc_handle, $secret, $assoc_type);
- $this->store->storeAssociation($server_url, $assoc);
+ // Leave out the session type if we're in compatibility mode
+ // *and* it's no-encryption.
+ if ((!$endpoint->compatibilityMode()) ||
+ ($assoc_session->session_type != 'no-encryption')) {
+ $args['session_type'] = $assoc_session->session_type;
+ }
- return $assoc;
+ $args = array_merge($args, $assoc_session->getRequest());
+ $message = Auth_OpenID_Message::fromOpenIDArgs($args);
+ return array($assoc_session, $message);
+ }
+
+ // Given an association response message, extract the OpenID 1.X
+ // session type.
+ //
+ // 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'
+ //
+ // @returns: The association type for this message
+ // @rtype: str
+ //
+ // @raises: KeyError, if the session_type field is absent.
+ function _getOpenID1SessionType($assoc_response)
+ {
+ // If it's an OpenID 1 message, allow session_type to default
+ // to None (which signifies "no-encryption")
+ $session_type = $assoc_response->getArg(Auth_OpenID_OPENID1_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') {
+ // oidutil.log('WARNING: OpenID server sent "no-encryption"'
+ // 'for OpenID 1.X')
+ } else if (($session_type == '') || ($session_type === null)) {
+ // 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.
+ $session_type = 'no-encryption';
+ }
+
+ return $session_type;
}
}