summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortailor <cygnus@janrain.com>2006-04-19 22:08:15 +0000
committertailor <cygnus@janrain.com>2006-04-19 22:08:15 +0000
commitf00e38a4f9984ba432a73aa1c791ff7ad196f781 (patch)
treec6f79d4304e1a27b8c1a1b3d5d9a9edfa270e251
parent4cb1c79600ea5d6d977c487c78195c06e20652b2 (diff)
downloadphp-openid-f00e38a4f9984ba432a73aa1c791ff7ad196f781.zip
php-openid-f00e38a4f9984ba432a73aa1c791ff7ad196f781.tar.gz
php-openid-f00e38a4f9984ba432a73aa1c791ff7ad196f781.tar.bz2
[project @ Added new server port and unit tests]
-rw-r--r--Auth/OpenID/Server.php1152
-rw-r--r--Auth/OpenID/ServerRequest.php119
-rw-r--r--Tests/Auth/OpenID/Server.php1327
3 files changed, 1940 insertions, 658 deletions
diff --git a/Auth/OpenID/Server.php b/Auth/OpenID/Server.php
index a89a4e1..f20edfe 100644
--- a/Auth/OpenID/Server.php
+++ b/Auth/OpenID/Server.php
@@ -20,432 +20,886 @@
require_once "Auth/OpenID.php";
require_once "Auth/OpenID/Association.php";
require_once "Auth/OpenID/CryptUtil.php";
+require_once "Auth/OpenID/BigMath.php";
require_once "Auth/OpenID/DiffieHellman.php";
require_once "Auth/OpenID/KVForm.php";
require_once "Auth/OpenID/TrustRoot.php";
require_once "Auth/OpenID/ServerRequest.php";
-/**
- * An object that implements the OpenID protocol for a single URL.
- *
- * Use this object by calling getOpenIDResponse when you get any
- * request for the server URL.
- *
- * @package OpenID
- */
-class Auth_OpenID_Server {
+define('AUTH_OPENID_HTTP_OK', 200);
+define('AUTH_OPENID_HTTP_REDIRECT', 302);
+define('AUTH_OPENID_HTTP_ERROR', 400);
- /**
- * A store implementing the interface in Auth/OpenID/Interface.php
- */
- var $store;
-
- /**
- * The URL of the server that this instance represents.
- */
- var $server_url;
-
- /**
- * The server URL with a namespace indicating that this
- * association is a shared association.
- *
- * @access private
- */
- var $_normal_key;
-
- /**
- * The server URL with a namespace indicating that this
- * association is a private (dumb-mode) association.
- *
- * @access private
- */
- var $_dumb_key;
-
- /**
- * How long an association should be valid for (in seconds)
- */
- var $association_lifetime = 1209600; // 14 days, in seconds
-
- /**
- * Constructor.
- *
- * @param string $server_url The URL of the OpenID server
- *
- * @param mixed $store The association store for this
- * instance. See {@link Auth_OpenID_OpenIDStore}.
- */
- function Auth_OpenID_Server($server_url, $store)
- {
- $this->server_url = $server_url;
- $this->store =& $store;
+global $_Auth_OpenID_Request_Modes,
+ $_Auth_OpenID_OpenID_Prefix,
+ $_Auth_OpenID_Encode_Kvform,
+ $_Auth_OpenID_Encode_Url;
+
+$_Auth_OpenID_Request_Modes = array('checkid_setup',
+ 'checkid_immediate');
+$_Auth_OpenID_OpenID_Prefix = "openid.";
+$_Auth_OpenID_Encode_Kvform = array('kfvorm');
+$_Auth_OpenID_Encode_Url = array('URL/redirect');
+
+function _isError($obj, $cls = 'Auth_OpenID_ServerError')
+{
+ return is_a($obj, $cls);
+}
+
+class Auth_OpenID_ServerError {
+ function Auth_OpenID_ServerError($query = null, $message = null)
+ {
+ $this->message = $message;
+ $this->query = $query;
+ }
+
+ function hasReturnTo()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+ return array_key_exists($_Auth_OpenID_OpenID_Prefix . 'return_to',
+ $this->query);
+ }
- $this->_normal_key = $server_url . '|normal';
- $this->_dumb_key = $server_url . '|dumb';
- }
-
- /**
- * This is the initial entry point for a server URL.
- *
- * @param mixed $is_authorized: the name of a callback to use for
- * determining if a given identity URL should be authorized. It
- * will be called with the identity URL and the trust_root for
- * this request.
- *
- * @param string $method The HTTP method of the current
- * request. If omitted, $_SERVER['HTTP_METHOD'] will be used.
- *
- * @param array $args The arguments parsed from the request. If
- * omitted, the arguments in the environment will be used.
- *
- * @return array $array A pair of elements in which the first is a
- * status code and the meaning of the second depends on the
- * status. See the status codes defined in this file for
- * information about each response.
- */
- function getOpenIDResponse($is_authorized=false, $method=null, $args=null)
- {
- if (!isset($method)) {
- $method = $_SERVER['REQUEST_METHOD'];
- }
-
- switch ($method) {
-
- case 'GET':
- // Convert anything that starts with openid_ to openid.
- if ($args === null) {
- $args = Auth_OpenID::fixArgs($_GET);
+ function encodeToURL()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+ $return_to = Auth_OpenID::arrayGet($this->query,
+ $_Auth_OpenID_OpenID_Prefix .
+ 'return_to');
+ if (!$return_to) {
+ return new Auth_OpenID_ServerError(null, "no return_to URL");
+ }
+
+ return Auth_OpenID::appendArgs($return_to,
+ array('openid.mode' => 'error',
+ 'error' => $this->toString()));
+ }
+
+ function encodeToKVForm()
+ {
+ return Auth_OpenID_KVForm::fromArray(
+ array('mode' => 'error',
+ 'error' => $this->toString()));
+ }
+
+ function whichEncoding()
+ {
+ global $_Auth_OpenID_Encode_Url,
+ $_Auth_OpenID_Encode_Kvform,
+ $_Auth_OpenID_Request_Modes;
+
+ if ($this->hasReturnTo()) {
+ return $_Auth_OpenID_Encode_Url;
+ }
+
+ $mode = Auth_OpenID::arrayGet($this->query, 'openid.mode');
+
+ if ($mode) {
+ if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
+ return $_Auth_OpenID_Encode_Kvform;
}
- $request = new Auth_OpenID_ServerRequest($this->server_url, $args);
- return $request->retry(&$this, $is_authorized);
+ }
+ return null;
+ }
+
+ function toString()
+ {
+ if ($this->message) {
+ return $this->message;
+ } else {
+ return get_class($this) . " error";
+ }
+ }
+}
+
+class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
+ function Auth_OpenID_MalformedReturnURL($query, $return_to)
+ {
+ $this->return_to = $return_to;
+ parent::Auth_OpenID_ServerError($query, "malformed return_to URL");
+ }
+}
+
+class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
+ function toString()
+ {
+ return "Malformed trust root";
+ }
+}
+
+class Auth_OpenID_Request {
+ var $mode = null;
+}
+
+class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
+ var $mode = "check_authentication";
+ var $invalidate_handle = null;
+
+ function Auth_OpenID_CheckAuthRequest($assoc_handle, $sig, $signed,
+ $invalidate_handle = null)
+ {
+ $this->assoc_handle = $assoc_handle;
+ $this->sig = $sig;
+ $this->signed = $signed;
+ if ($invalidate_handle !== null) {
+ $this->invalidate_handle = $invalidate_handle;
+ }
+ }
+
+ function fromQuery($query)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
- case 'POST':
- if ($args === null) {
- $args = Auth_OpenID::fixArgs($_POST);
+ $required_keys = array('assoc_handle', 'sig', 'signed');
+
+ foreach ($required_keys as $k) {
+ if (!array_key_exists($_Auth_OpenID_OpenID_Prefix . $k,
+ $query)) {
+ return new Auth_OpenID_ServerError($query,
+ sprintf("%s request missing required parameter %s from \
+ query", $this->mode, $k));
}
- $mode = $args['openid.mode'];
- switch ($mode) {
+ }
- case 'associate':
- return $this->associate($args);
+ $assoc_handle = $query[$_Auth_OpenID_OpenID_Prefix . 'assoc_handle'];
+ $sig = $query[$_Auth_OpenID_OpenID_Prefix . 'sig'];
+ $signed_list = $query[$_Auth_OpenID_OpenID_Prefix . 'signed'];
+
+ $signed_list = explode(",", $signed_list);
+ $signed_pairs = array();
+
+ foreach ($signed_list as $field) {
+ if ($field == 'mode') {
+ // XXX KLUDGE HAX WEB PROTOCoL BR0KENNN
+ //
+ // openid.mode is currently check_authentication
+ // because that's the mode of this request. But the
+ // signature was made on something with a different
+ // openid.mode.
+ $value = "id_res";
+ } else {
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
+ $query)) {
+ $value = $query[$_Auth_OpenID_OpenID_Prefix . $field];
+ } else {
+ return new Auth_OpenID_ServerError($query,
+ sprintf("Couldn't find signed field %r in query %s",
+ $field));
+ }
+ }
+ $signed_pairs[] = array($field, $value);
+ }
- case 'check_authentication':
- return $this->checkAuthentication($args);
+ return new Auth_OpenID_CheckAuthRequest($assoc_handle, $sig,
+ $signed_pairs);
+ }
- default:
- $err = "Invalid openid.mode ($mode) for a POST request";
- return $this->postError($err);
+ function answer(&$signatory)
+ {
+ $is_valid = $signatory->verify($this->assoc_handle, $this->sig,
+ $this->signed);
+
+ // Now invalidate that assoc_handle so it this checkAuth
+ // message cannot be replayed.
+ $signatory->invalidate($this->assoc_handle, true);
+ $response = new Auth_OpenID_ServerResponse($this);
+ $response->fields['is_valid'] = $is_valid ? "true" : "false";
+
+ if ($this->invalidate_handle) {
+ $assoc = $signatory->getAssociation($this->invalidate_handle,
+ false);
+ if (!$assoc) {
+ $response->fields['invalidate_handle'] =
+ $this->invalidate_handle;
}
+ }
+ return $response;
+ }
+}
- default:
- $err = "HTTP method $method is not part of OpenID";
- return array(Auth_OpenID_LOCAL_ERROR, $err);
+class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
+ var $mode = "associate";
+ var $session_type = 'plaintext';
+ var $assoc_type = 'HMAC-SHA1';
+
+ function fromQuery($query)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ // FIXME: Missing dh_modulus and dh_gen options.
+ $obj = new Auth_OpenID_AssociateRequest();
+
+ $session_type = null;
+
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix . 'session_type',
+ $query)) {
+ $session_type = $query[$_Auth_OpenID_OpenID_Prefix .
+ 'session_type'];
}
+
+ if ($session_type) {
+ $obj->session_type = $session_type;
+
+ if ($session_type == 'DH-SHA1') {
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix .
+ 'dh_consumer_public', $query)) {
+
+ # Auth_OpenID_getMathLib()
+ $lib =& Auth_OpenID_getMathLib();
+
+ $obj->pubkey = $lib->base64ToLong(
+ $query[$_Auth_OpenID_OpenID_Prefix .
+ 'dh_consumer_public']);
+ } else {
+ return new Auth_OpenID_ServerError($query,
+ "Public key for DH-SHA1 session not found in query");
+ }
+ }
+ }
+
+ return $obj;
}
- /**
- * @access private
- *
- * @param object $request The Auth_OpenID_ServerRequest
- * object representing this request.
- *
- * @param bool $authorized Whether the user making this request is
- * capable of approving this authorization request.
- */
- function getAuthResponse(&$request, $authorized)
+ function answer($assoc)
{
- $identity = $request->getIdentityURL();
- if (!isset($identity)) {
- return $this->getError($request, 'No identity specified');
+ $ml =& Auth_OpenID_getMathLib();
+ $response = new Auth_OpenID_ServerResponse($this);
+
+ $response->fields = array('expires_in' => $assoc->getExpiresIn(),
+ 'assoc_type' => 'HMAC-SHA1',
+ 'assoc_handle' => $assoc->handle);
+
+ if ($this->session_type == 'DH-SHA1') {
+ // XXX - get dh_modulus and dh_gen
+ $dh = new Auth_OpenID_DiffieHellman();
+ $mac_key = $dh->xorSecret($this->pubkey, $assoc->secret);
+ $response->fields['session_type'] = $this->session_type;
+ $response->fields['dh_server_public'] =
+ $ml->longToBase64($dh->public);
+ $response->fields['enc_mac_key'] = base64_encode($mac_key);
+ } else if ($this->session_type == 'plaintext') {
+ $response->fields['mac_key'] = base64_encode($assoc->secret);
+ } else {
+ // XXX - kablooie
}
- list($status, $info) = $this->_checkTrustRoot(&$request);
- if (!$status) {
- return $this->getError($request, $info);
+ return $response;
+ }
+}
+
+class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
+ var $mode = "checkid_setup"; // or "checkid_immediate"
+ var $immediate = false;
+ var $trust_root = null;
+
+ function make($query, $identity, $return_to, $trust_root = null,
+ $immediate = false, $assoc_handle = null)
+ {
+ if (!Auth_OpenID_TrustRoot::_parse($return_to)) {
+ return new Auth_OpenID_MalformedReturnURL($query, $return_to);
+ }
+
+ return new Auth_OpenID_CheckIDRequest($identity, $return_to,
+ $trust_root, $immediate,
+ $assoc_handle);
+ }
+
+ function Auth_OpenID_CheckIDRequest($identity, $return_to,
+ $trust_root = null, $immediate = false,
+ $assoc_handle = null)
+ {
+ $this->identity = $identity;
+ $this->return_to = $return_to;
+ $this->trust_root = $trust_root;
+ $this->assoc_handle = $assoc_handle;
+
+ if ($immediate) {
+ $this->immediate = true;
+ $this->mode = "checkid_immediate";
} else {
- $return_to = $info;
+ $this->immediate = false;
+ $this->mode = "checkid_setup";
}
+ }
+
+ function fromQuery($query)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ $mode = $query[$_Auth_OpenID_OpenID_Prefix . 'mode'];
+ $immediate = null;
- if (!$authorized) {
- return $this->_getAuthNotAuthorized(&$request, $return_to);
+ if ($mode == "checkid_immediate") {
+ $immediate = true;
+ $mode = "checkid_immediate";
} else {
- return $this->_getAuthAuthorized(&$request, $return_to);
- }
- }
-
- /**
- * Return whether the return_to URL matches the trust_root for
- * this request.
- *
- * @access private
- */
- function _checkTrustRoot(&$request)
- {
- $return_to = $request->getReturnTo();
- if (!isset($return_to)) {
- return array(false, 'No return_to URL specified');
- }
-
- $trust_root = $request->getTrustRoot();
- if (isset($trust_root) &&
- !Auth_OpenID_TrustRoot::match($trust_root, $return_to)) {
- return array(false, 'Trust root does not match');
- }
- return array(true, $return_to);
- }
-
- /**
- * @access private
- */
- function _getAuthNotAuthorized(&$request, $return_to)
- {
- $mode = $request->getMode();
- switch ($mode) {
- case 'checkid_immediate':
- // Build a URL that is just the URL that came here
- // with the mode changed from checkid_immediate to
- // checkid_setup.
- $args = $request->args;
- $args['openid.mode'] = 'checkid_setup';
- $setup_url = Auth_OpenID::appendArgs($this->server_url, $args);
-
- // Return to the consumer, instructing it that the user
- // needs to do something in order to verify his identity.
- $rargs = array(
- 'openid.mode' => 'id_res',
- 'openid.user_setup_url' => $setup_url
- );
-
- $redir_url = Auth_OpenID::appendArgs($return_to, $rargs);
- return array(Auth_OpenID_REDIRECT, $redir_url);
-
- case 'checkid_setup':
- // Return to the application indicating that the user
- // needs to authenticate.
- return array(Auth_OpenID_DO_AUTH, &$request);
-
- default:
- $err = sprintf('invalid openid.mode (%s) for GET requests', $mode);
- return $this->getError($request, $err);
- }
- }
-
- /**
- * @access private
- */
- function _getAuthAuthorized(&$request, $return_to)
- {
- $mode = $request->getMode();
- switch ($mode) {
- case 'checkid_immediate':
- case 'checkid_setup':
- break;
- default:
- $err = sprintf('invalid openid.mode (%s) for GET requests', $mode);
- return $this->getError($request, $err);
- }
-
- $reply = array('openid.mode' => 'id_res',
- 'openid.return_to' => $return_to,
- 'openid.identity' => $request->getIdentityURL()
- );
-
- $assoc = null;
- $assoc_handle = @$request->args['openid.assoc_handle'];
- if (isset($assoc_handle)) {
- $key = $this->_normal_key;
- $assoc = $this->store->getAssociation($key, $assoc_handle);
-
- // fall back to dumb mode if assoc_handle not found,
- // and send the consumer an invalidate_handle message
- if (!isset($assoc) || $assoc->getExpiresIn() <= 0) {
- $assoc = null;
- $this->store->removeAssociation($key, $assoc_handle);
- $reply['openid.invalidate_handle'] = $assoc_handle;
+ $immediate = false;
+ $mode = "checkid_setup";
+ }
+
+ $required = array('identity',
+ 'return_to');
+
+ $optional = array('trust_root',
+ 'assoc_handle');
+
+ $values = array();
+
+ foreach ($required as $field) {
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
+ $query)) {
+ $value = $query[$_Auth_OpenID_OpenID_Prefix . $field];
+ } else {
+ return new Auth_OpenID_ServerError($query,
+ sprintf("Missing required field %s from request",
+ $field));
}
+ $values[$field] = $value;
}
- // Use dumb mode if there is no association.
- if ($assoc === null) {
- $assoc = $this->createAssociation('HMAC-SHA1');
- $this->store->storeAssociation($this->_dumb_key, $assoc);
+ foreach ($optional as $field) {
+ $value = null;
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
+ $query)) {
+ $value = $query[$_Auth_OpenID_OpenID_Prefix. $field];
+ }
+ if ($value) {
+ $values[$field] = $value;
+ }
}
- $reply['openid.assoc_handle'] = $assoc->handle;
- $signed_fields = array('mode', 'identity', 'return_to');
- $assoc->addSignature($signed_fields, &$reply);
- $redir_url = Auth_OpenID::appendArgs($return_to, $reply);
- return array(Auth_OpenID_REDIRECT, $redir_url);
+ if (!Auth_OpenID_TrustRoot::_parse($values['return_to'])) {
+ return new Auth_OpenID_MalformedReturnURL($query,
+ $values['return_to']);
+ }
+
+ $obj = Auth_OpenID_CheckIDRequest::make($query,
+ $values['identity'],
+ $values['return_to'],
+ Auth_OpenID::arrayGet($values,
+ 'trust_root', null),
+ $immediate);
+
+ if (Auth_OpenID::arrayGet($values, 'assoc_handle')) {
+ $obj->assoc_handle = $values['assoc_handle'];
+ }
+
+ return $obj;
}
- /**
- * Perform an openid.mode=associate query
- *
- * @access private
- */
- function associate($query)
+ function trustRootValid()
{
- $reply = array();
+ if (!$this->trust_root) {
+ return true;
+ }
- $assoc_type = @$query['openid.assoc_type'];
- if (!isset($assoc_type)) {
- $assoc_type = 'HMAC-SHA1';
+ $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
+ if ($tr === false) {
+ return new Auth_OpenID_MalformedTrustRoot(null, $this->trust_root);
}
- $assoc = $this->createAssociation($assoc_type);
- if (!isset($assoc)) {
- $fmt = 'unable to create an association for type %s';
- return $this->postError(sprinft($fmt, $assoc_type));
+ return Auth_OpenID_TrustRoot::match($this->trust_root,
+ $this->return_to);
+ }
+
+ function answer($allow, $server_url = null)
+ {
+ if ($allow || $this->immediate) {
+ $mode = 'id_res';
+ } else {
+ $mode = 'cancel';
}
- $this->store->storeAssociation($this->_normal_key, $assoc);
+ $response = new Auth_OpenID_CheckIDResponse($this, $mode);
- if (isset($assoc_type)) {
- $reply['assoc_type'] = $assoc_type;
+ if ($allow) {
+ $response->fields['identity'] = $this->identity;
+ $response->fields['return_to'] = $this->return_to;
+ if (!$this->trustRootValid()) {
+ return new Auth_OpenID_UntrustedReturnURL($this->return_to,
+ $this->trust_root);
+ }
+ } else {
+ $response->signed = array();
+ if ($this->immediate) {
+ if (!$server_url) {
+ return new Auth_OpenID_ServerError(null,
+ 'setup_url is required for $allow=false \
+ in immediate mode.');
+ }
+
+ $setup_request =& new Auth_OpenID_CheckIDRequest(
+ $this->identity,
+ $this->return_to,
+ $this->trust_root,
+ false,
+ $this->assoc_handle);
+
+ $setup_url = $setup_request->encodeToURL($server_url);
+
+ $response->fields['user_setup_url'] = $setup_url;
+ }
}
- $reply['assoc_handle'] = $assoc->handle;
- $reply['expires_in'] = strval($assoc->getExpiresIn());
- if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
- $session_type = null;
+ return $response;
+ }
+
+ function encodeToURL($server_url)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ // Imported from the alternate reality where these classes are
+ // used in both the client and server code, so Requests are
+ // Encodable too. That's right, code imported from alternate
+ // realities all for the love of you, id_res/user_setup_url.
+
+ $q = array('mode' => $this->mode,
+ 'identity' => $this->identity,
+ 'return_to' => $this->return_to);
+
+ if ($this->trust_root) {
+ $q['trust_root'] = $this->trust_root;
+ }
+
+ if ($this->assoc_handle) {
+ $q['assoc_handle'] = $this->assoc_handle;
+ }
+
+ $_q = array();
+
+ foreach ($q as $k => $v) {
+ $_q[$_Auth_OpenID_OpenID_Prefix . $k] = $v;
+ }
+
+ return Auth_OpenID::appendArgs($server_url, $_q);
+ }
+
+ function getCancelURL()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ if ($this->immediate) {
+ return new Auth_OpenID_ServerError(null,
+ "Cancel is not an appropriate \
+ response to immediate mode \
+ requests.");
+ }
+
+ return Auth_OpenID::appendArgs($this->return_to,
+ array($_Auth_OpenID_OpenID_Prefix . 'mode' =>
+ 'cancel'));
+ }
+}
+
+class Auth_OpenID_ServerResponse {
+
+ function Auth_OpenID_ServerResponse($request)
+ {
+ $this->request = $request;
+ $this->fields = array();
+ }
+
+ function whichEncoding()
+ {
+ global $_Auth_OpenID_Encode_Kvform,
+ $_Auth_OpenID_Request_Modes,
+ $_Auth_OpenID_Encode_Url;
+
+ if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
+ return $_Auth_OpenID_Encode_Url;
} else {
- $session_type = @$query['openid.session_type'];
+ return $_Auth_OpenID_Encode_Kvform;
}
+ }
+
+ function encodeToURL()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ $fields = array();
- switch ($session_type) {
- case 'DH-SHA1':
- $sess_reply = Auth_OpenID_DiffieHellman::
- serverAssociate($query, $assoc->secret);
- break;
- case null:
- $sess_reply = array('mac_key' => base64_encode($assoc->secret));
- break;
- default:
- $sess_reply = false;
+ foreach ($this->fields as $k => $v) {
+ $fields[$_Auth_OpenID_OpenID_Prefix . $k] = $v;
}
- if ($sess_reply === false) {
- $msg = "Association session (type $session_type) failed";
- return $this->postError($msg);
+ return Auth_OpenID::appendArgs($this->request->return_to, $fields);
+ }
+
+ function encodeToKVForm()
+ {
+ return Auth_OpenID_KVForm::fromArray($this->fields);
+ }
+}
+
+class Auth_OpenID_CheckIDResponse extends Auth_OpenID_ServerResponse {
+
+ function Auth_OpenID_CheckIDResponse(&$request, $mode = 'id_res')
+ {
+ parent::Auth_OpenID_ServerResponse(&$request);
+ $this->fields['mode'] = $mode;
+ $this->signed = array();
+
+ if ($mode == 'id_res') {
+ array_push($this->signed, 'mode', 'identity', 'return_to');
}
+ }
- $reply = array_merge($reply, $sess_reply);
- $reply_kv = Auth_OpenID_KVForm::fromArray($reply);
- return array(Auth_OpenID_REMOTE_OK, $reply_kv);
+ function addField($namespace, $key, $value, $signed = true)
+ {
+ if ($namespace) {
+ $key = sprintf('%s.%s', $namespace, $key);
+ }
+ $this->fields[$key] = $value;
+ if ($signed && !in_array($key, $this->signed)) {
+ $this->signed[] = $key;
+ }
}
- /**
- * Perform an openid.mode=check_authentication request
- *
- * @access private
- */
- function checkAuthentication($args)
+ function addFields($namespace, $fields, $signed = true)
{
- $handle = $args['openid.assoc_handle'];
- if (!isset($handle)) {
- return $this->postError('Missing openid.assoc_handle');
+ foreach ($fields as $k => $v) {
+ $this->addField($namespace, $k, $v, $signed);
}
+ }
- $store =& $this->store;
- $assoc = $store->getAssociation($this->_dumb_key, $handle);
- $reply = array('is_valid' => 'false');
- if ($assoc !== null && $assoc->getExpiresIn() > 0) {
- $signed = $args['openid.signed'];
- if (!isset($signed)) {
- return $this->postError('Missing openid.signed');
- }
+ function update($namespace, $other)
+ {
+ $namespaced_fields = array();
- $sig = $args['openid.sig'];
- if (!isset($sig)) {
- return $this->postError('Missing openid.sig');
- }
+ foreach ($other->fields as $k => $v) {
+ $name = sprintf('%s.%s', $namespace, $k);
- $to_verify = $args;
- $to_verify['openid.mode'] = 'id_res';
- $fields = explode(',', trim($signed));
- $tv_sig = $assoc->signDict($fields, $to_verify);
-
- if ($tv_sig == $sig) {
- $normal_key = $this->_normal_key;
- $store->removeAssociation($normal_key, $assoc->handle);
- $reply['is_valid'] = 'true';
-
- $inv_handle = @$args['openid.invalidate_handle'];
- if (isset($inv_handle)) {
- $assoc = $store->getAssociation($normal_key, $inv_handle);
- if (!isset($assoc)) {
- $reply['invalidate_handle'] = $inv_handle;
- }
- }
- }
- } elseif ($assoc !== null) {
- $store->removeAssociation($this->_dumb_key, $assoc_handle);
+ $namespaced_fields[$name] = $v;
}
- $kv = Auth_OpenID_KVForm::fromArray($reply);
- return array(Auth_OpenID_REMOTE_OK, $kv);
+ $this->fields = array_merge($this->fields, $namespaced_fields);
+ $this->signed = array_merge($this->signed, $other->signed);
}
+}
+
+class Auth_OpenID_WebResponse {
+ var $code = AUTH_OPENID_HTTP_OK;
+ var $body = "";
- /**
- * Create a new association and store it
- *
- * @access private
- */
- function createAssociation($assoc_type)
+ function Auth_OpenID_WebResponse($code = null, $headers = null,
+ $body = null)
{
- if ($assoc_type == 'HMAC-SHA1') {
- $secret = Auth_OpenID_CryptUtil::getBytes(20);
+ if ($code) {
+ $this->code = $code;
+ }
+
+ if ($headers !== null) {
+ $this->headers = $headers;
} else {
- // XXX: log
+ $this->headers = array();
+ }
+
+ if ($body !== null) {
+ $this->body = $body;
+ }
+ }
+}
+
+class Auth_OpenID_Signatory {
+
+ // = 14 * 24 * 60 * 60; # 14 days, in seconds
+ var $SECRET_LIFETIME = 1209600;
+
+ // keys have a bogus server URL in them because the filestore
+ // really does expect that key to be a URL. This seems a little
+ // silly for the server store, since I expect there to be only one
+ // server URL.
+ var $normal_key = 'http://localhost/|normal';
+ var $dumb_key = 'http://localhost/|dumb';
+
+ function Auth_OpenID_Signatory(&$store)
+ {
+ // assert store is not None
+ $this->store =& $store;
+ }
+
+ function verify($assoc_handle, $sig, $signed_pairs)
+ {
+ $assoc = $this->getAssociation($assoc_handle, true);
+ if (!$assoc) {
+ // oidutil.log("failed to get assoc with handle %r to verify sig %r"
+ // % (assoc_handle, sig))
return false;
}
+ $expected_sig = base64_encode($assoc->sign($signed_pairs));
+
+ return $sig == $expected_sig;
+ }
+
+ function sign($response)
+ {
+ $signed_response = $response;
+ $assoc_handle = $response->request->assoc_handle;
+
+ if ($assoc_handle) {
+ // normal mode
+ $assoc = $this->getAssociation($assoc_handle, false);
+ if (!$assoc) {
+ // fall back to dumb mode
+ $signed_response->fields['invalidate_handle'] = $assoc_handle;
+ $assoc = $this->createAssociation(true);
+ }
+ } else {
+ // dumb mode.
+ $assoc = $this->createAssociation(true);
+ }
+
+ $signed_response->fields['assoc_handle'] = $assoc->handle;
+ $assoc->addSignature($signed_response->signed,
+ $signed_response->fields, '');
+ return $signed_response;
+ }
+
+ function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
+ {
+ $secret = Auth_OpenID_CryptUtil::getBytes(20);
$uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
- $handle = sprintf('{%s}{%x}{%s}', $assoc_type, time(), $uniq);
+ $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);
- $ltime = $this->association_lifetime;
- $assoc = Auth_OpenID_Association::
- fromExpiresIn($ltime, $handle, $secret, $assoc_type);
+ $assoc = Auth_OpenID_Association::fromExpiresIn(
+ $this->SECRET_LIFETIME, $handle, $secret, $assoc_type);
+ if ($dumb) {
+ $key = $this->dumb_key;
+ } else {
+ $key = $this->normal_key;
+ }
+
+ $this->store->storeAssociation($key, $assoc);
return $assoc;
}
- /**
- * Return an error response for GET requests
- *
- * @access private
- */
- function getError($request, $msg)
- {
- $args = $request->args;
- $return_to = @$args['openid.return_to'];
- if (isset($return_to)) {
- $err = array(
- 'openid.mode' => 'error',
- 'openid.error' => $msg
- );
- $redir_url = Auth_OpenID::appendArgs($return_to, $err);
- return array(Auth_OpenID_REDIRECT, $redir_url);
+ function getAssociation($assoc_handle, $dumb)
+ {
+ if ($assoc_handle === null) {
+ return new Auth_OpenID_ServerError(null,
+ "assoc_handle must not be null");
+ }
+
+ if ($dumb) {
+ $key = $this->dumb_key;
} else {
- foreach (array_keys($args) as $k) {
- if (preg_match('/^openid\./', $k)) {
- return array(Auth_OpenID_LOCAL_ERROR, $msg);
- }
+ $key = $this->normal_key;
+ }
+
+ $assoc = $this->store->getAssociation($key, $assoc_handle);
+
+ if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
+ $this->store->removeAssociation($key, $assoc_handle);
+ $assoc = null;
+ }
+
+ return $assoc;
+ }
+
+ function invalidate($assoc_handle, $dumb)
+ {
+ if ($dumb) {
+ $key = $this->dumb_key;
+ } else {
+ $key = $this->normal_key;
+ }
+ $this->store->removeAssociation($key, $assoc_handle);
+ }
+}
+
+class Auth_OpenID_Encoder {
+
+ var $responseFactory = 'Auth_OpenID_WebResponse';
+
+ function encode(&$response)
+ {
+ global $_Auth_OpenID_Encode_Kvform,
+ $_Auth_OpenID_Encode_Url;
+
+ $cls = $this->responseFactory;
+
+ $encode_as = $response->whichEncoding();
+ if ($encode_as == $_Auth_OpenID_Encode_Kvform) {
+ $wr = new $cls(null, null, $response->encodeToKVForm());
+ if (is_a($response, 'Auth_OpenID_ServerError')) {
+ $wr->code = AUTH_OPENID_HTTP_ERROR;
}
+ } else if ($encode_as == $_Auth_OpenID_Encode_Url) {
+ $location = $response->encodeToURL();
+ $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT,
+ array('location' => $location));
+ } else {
+ return new Auth_OpenID_EncodingError(&$response);
+ }
+ return $wr;
+ }
+}
+
+function needsSigning($response)
+{
+ return (in_array($response->request->mode, array('checkid_setup',
+ 'checkid_immediate')) &&
+ $response->signed);
+}
+
+class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {
+
+ function Auth_OpenID_SigningEncoder(&$signatory)
+ {
+ $this->signatory =& $signatory;
+ }
+
+ function encode(&$response)
+ {
+ // the isinstance is a bit of a kludge... it means there isn't
+ // really an adapter to make the interfaces quite match.
+ if (!is_a($response, 'Auth_OpenID_ServerError') &&
+ needsSigning($response)) {
+
+ if (!$this->signatory) {
+ return new Auth_OpenID_ServerError(null,
+ "Must have a store to sign request");
+ }
+ if (array_key_exists('sig', $response->fields)) {
+ return new Auth_OpenID_AlreadySigned($response);
+ }
+ $response = $this->signatory->sign($response);
+ }
+ return parent::encode($response);
+ }
+}
+
+class Auth_OpenID_Decoder {
+
+ function Auth_OpenID_Decoder()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+ $this->prefix = $_Auth_OpenID_OpenID_Prefix;
+
+ $this->handlers = array(
+ 'checkid_setup' => 'Auth_OpenID_CheckIDRequest',
+ 'checkid_immediate' => 'Auth_OpenID_CheckIDRequest',
+ 'check_authentication' => 'Auth_OpenID_CheckAuthRequest',
+ 'associate' => 'Auth_OpenID_AssociateRequest'
+ );
+ }
- return array(Auth_OpenID_DO_ABOUT, null);
+ function decode($query)
+ {
+ if (!$query) {
+ return null;
+ }
+
+ $myquery = array();
+
+ foreach ($query as $k => $v) {
+ if (strpos($k, $this->prefix) === 0) {
+ $myquery[$k] = $v;
+ }
}
+
+ if (!$myquery) {
+ return null;
+ }
+
+ $mode = Auth_OpenID::arrayGet($myquery, $this->prefix . 'mode');
+ if (!$mode) {
+ return new Auth_OpenID_ServerError($query,
+ sprintf("No %smode found in query", $this->prefix));
+ }
+
+ $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
+ $this->defaultDecoder($query));
+
+ if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
+ return call_user_func_array(array($handlerCls, 'fromQuery'),
+ array($query));
+ } else {
+ return $handlerCls;
+ }
+ }
+
+ function defaultDecoder($query)
+ {
+ $mode = $query[$this->prefix . 'mode'];
+ return new Auth_OpenID_ServerError($query,
+ sprintf("No decoder for mode %s", $mode));
+ }
+}
+
+class Auth_OpenID_EncodingError {
+ function Auth_OpenID_EncodingError(&$response)
+ {
+ $this->response =& $response;
+ }
+}
+
+class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
+ // This response is already signed.
+}
+
+class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
+ function Auth_OpenID_UntrustedReturnURL($return_to, $trust_root)
+ {
+ $this->return_to = $return_to;
+ $this->trust_root = $trust_root;
+ }
+
+ function toString()
+ {
+ return sprintf("return_to %s not under trust_root %s", $this->return_to,
+ $this->trust_root);
+ }
+}
+
+/**
+ * An object that implements the OpenID protocol for a single URL.
+ *
+ * Use this object by calling getOpenIDResponse when you get any
+ * request for the server URL.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Server {
+ function Auth_OpenID_Server(&$store)
+ {
+ $this->store =& $store;
+ $this->signatory =& new Auth_OpenID_Signatory($this->store);
+ $this->encoder =& new Auth_OpenID_Encoder($this->signatory);
+ $this->decoder =& new Auth_OpenID_Decoder();
+ }
+
+ function handleRequest($request)
+ {
+ if (method_exists($this, "openid_" . $request->mode)) {
+ $handler = "openid_" . $request->mode;
+ return $handler($request);
+ }
+ return null;
+ }
+
+ function openid_check_authentication(&$request)
+ {
+ return $request->answer($this->signatory);
+ }
+
+ function openid_associate(&$request)
+ {
+ $assoc = $this->signatory->createAssociation(false);
+ return $request->answer($assoc);
+ }
+
+ function encodeResponse(&$response)
+ {
+ return $this->encoder->encode($response);
}
- /**
- * Return an error response for POST requests
- *
- * @access private
- */
- function postError($msg)
+ function decodeRequest(&$query)
{
- $kv = Auth_OpenID_KVForm::fromArray(array('error' => $msg));
- return array(Auth_OpenID_REMOTE_ERROR, $kv);
+ return $this->decoder->decode($query);
}
}
-?> \ No newline at end of file
+?>
diff --git a/Auth/OpenID/ServerRequest.php b/Auth/OpenID/ServerRequest.php
index ff34b6b..0072894 100644
--- a/Auth/OpenID/ServerRequest.php
+++ b/Auth/OpenID/ServerRequest.php
@@ -28,124 +28,9 @@ require_once "Auth/OpenID.php";
* @package OpenID
*/
class Auth_OpenID_ServerRequest {
- /**
- * The arguments for this request
- */
- var $args;
-
- /**
- * The URL of the server for this request
- */
- var $server_url;
-
- /**
- * Constructor
- *
- * @internal This is private because the library user should not
- * have to make instances of this class.
- *
- * @access private
- *
- * @param string $server_url The openid.server URL for the server
- * that goes with this request.
- *
- * @param array $args The query arguments for this request
- */
- function Auth_OpenID_ServerRequest($server_url, $args)
- {
- $this->server_url = $server_url;
- $this->args = $args;
- }
-
- /**
- * @access private
- */
- function getMode()
- {
- return $this->args['openid.mode'];
- }
-
- /**
- * Get the identity URL that is being checked
- */
- function getIdentityURL()
- {
- return @$this->args['openid.identity'];
- }
-
- /**
- * Get the return_to URL for the consumer that initiated this request.
- *
- * @return string $return_to The return_to URL for the consumer
- */
- function getReturnTo()
- {
- return @$this->args['openid.return_to'];
- }
-
- /**
- * Get a cancel response for this URL
- *
- * @return array $response The status code and data
- */
- function cancel()
+ function Auth_OpenID_ServerRequest()
{
- return array(Auth_OpenID_REDIRECT, $this->getCancelURL());
- }
-
- /**
- * Return a cancel URL for this request
- */
- function getCancelURL()
- {
- $cancel_args = array('openid.mode' => 'cancel');
- $return_to = $this->args['openid.return_to'];
- return Auth_OpenID::appendArgs($return_to, $cancel_args);
- }
-
- /**
- * Get a URL that will initiate this request again.
- */
- function getRetryURL()
- {
- return Auth_OpenID::appendArgs($this->server_url, $this->args);
- }
-
- /**
- * Return the trust_root for this request
- */
- function getTrustRoot()
- {
- if (isset($this->args['openid.trust_root'])) {
- return $this->args['openid.trust_root'];
- } else {
- return @$this->args['openid.return_to'];
- }
- }
-
- /**
- * Attempt to authenticate again, given a server and
- * authentication checking function.
- *
- * @param object $server An instance of {@link Auth_OpenID_Server}
- *
- * @param mixed $is_authorized The callback to use to determine
- * whether the current user can authorize this request.
- */
- function retry(&$server, $is_authorized)
- {
- $trust_root = $this->getTrustRoot();
- $identity_url = $this->getIdentityURL();
-
- // If there is no return_to or trust_root or there is no
- // identity_url, then it's impossible to continue.
- if (isset($identity_url) && isset($trust_root) && $is_authorized) {
- $authorized = $is_authorized($identity_url, $trust_root);
- } else {
- $authorized = false;
- }
-
- return $server->getAuthResponse(&$this, $authorized);
+ $this->mode = null;
}
}
diff --git a/Tests/Auth/OpenID/Server.php b/Tests/Auth/OpenID/Server.php
index 9bbe29f..393dc4c 100644
--- a/Tests/Auth/OpenID/Server.php
+++ b/Tests/Auth/OpenID/Server.php
@@ -7,297 +7,1240 @@
require_once "PHPUnit.php";
require_once "Tests/Auth/OpenID/MemStore.php";
require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/DiffieHellman.php";
require_once "Auth/OpenID/Server.php";
+function arrayToString($arr)
+{
+ $s = "Array(";
+
+ $parts = array();
+ foreach ($arr as $k => $v) {
+ if (is_array($v)) {
+ $v = arrayToString($v);
+ }
+ $parts[] = sprintf("%s => %s", $k, $v);
+ }
+
+ $s .= implode(", ", $parts);
+ $s .= ")";
+
+ return $s;
+}
+
function _Auth_OpenID_NotAuthorized()
{
return false;
}
-class Tests_Auth_OpenID_Server extends PHPUnit_TestCase {
+class Tests_Auth_OpenID_Test_ServerError extends PHPUnit_TestCase {
+ function test_browserWithReturnTo()
+ {
+ $return_to = "http://rp.unittest/consumer";
+ // will be a ProtocolError raised by Decode or CheckIDRequest.answer
+ $args = array(
+ 'openid.mode' => 'monkeydance',
+ 'openid.identity' => 'http://wagu.unittest/',
+ 'openid.return_to' => $return_to);
+ $e = new Auth_OpenID_ServerError($args, "plucky");
+ $this->assertTrue($e->hasReturnTo());
+ $expected_args = array(
+ 'openid.mode' => 'error',
+ 'error' => 'plucky');
+
+ $encoded = $e->encodeToURL();
+ if (_isError($encoded)) {
+ $this->fail($encoded->toString());
+ return;
+ }
+
+ list($rt_base, $_result_args) = explode("?", $e->encodeToURL(), 2);
+ $result_args = array();
+ parse_str($_result_args, $result_args);
+ $result_args = Auth_OpenID::fixArgs($result_args);
+
+ $this->assertEquals($result_args, $expected_args);
+ }
+
+ function test_noReturnTo()
+ {
+ // will be a ProtocolError raised by Decode or CheckIDRequest.answer
+ $args = array(
+ 'openid.mode' => 'zebradance',
+ 'openid.identity' => 'http://wagu.unittest/');
+
+ $e = new Auth_OpenID_ServerError($args, "waffles");
+ $this->assertFalse($e->hasReturnTo());
+ $expected = "error:waffles\nmode:error\n";
+ $this->assertEquals($e->encodeToKVForm(), $expected);
+ }
+}
+
+class Tests_Auth_OpenID_Test_Decode extends PHPUnit_TestCase {
function setUp()
{
- $this->sv_url = 'http://id.server.url/';
- $this->id_url = 'http://foo.com/';
- $this->rt_url = 'http://return.to/rt';
- $this->tr_url = 'http://return.to/';
- $this->noauth = '_Auth_OpenID_NotAuthorized';
+ $this->id_url = "http://decoder.am.unittest/";
+ $this->rt_url = "http://rp.unittest/foobot/?qux=zam";
+ $this->tr_url = "http://rp.unittest/";
+ $this->assoc_handle = "{assoc}{handle}";
+ $this->decoder = new Auth_OpenID_Decoder();
+ }
- $this->store = new Tests_Auth_OpenID_MemStore();
- $this->server =& new Auth_OpenID_Server($this->sv_url, &$this->store);
+ function test_none()
+ {
+ $args = array();
+ $r = $this->decoder->decode($args);
+ $this->assertEquals($r, null);
}
- function _parseRedirResp($ret)
+ function test_irrelevant()
{
- list($status, $redir) = $ret;
- if ($status != Auth_OpenID_REDIRECT) {
- $this->fail("Bad status: $status");
- return false;
+ $args = array(
+ 'pony' => 'spotted',
+ 'sreg.mutant_power' => 'decaffinator');
+
+ $r = $this->decoder->decode($args);
+
+ $this->assertTrue($r === null);
+ }
+
+ function test_bad()
+ {
+ $args = array(
+ 'openid.mode' => 'twos-compliment',
+ 'openid.pants' => 'zippered');
+
+ // Be sure that decoding the args returns an error.
+ $result = $this->decoder->decode($args);
+
+ $this->assertTrue(_isError($result));
+ }
+
+ function test_checkidImmediate()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_immediate',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.trust_root' => $this->tr_url,
+ # should be ignored
+ 'openid.some.extension' => 'junk');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckIDRequest'));
+ $this->assertEquals($r->mode, "checkid_immediate");
+ $this->assertEquals($r->immediate, true);
+ $this->assertEquals($r->identity, $this->id_url);
+ $this->assertEquals($r->trust_root, $this->tr_url);
+ $this->assertEquals($r->return_to, $this->rt_url);
+ $this->assertEquals($r->assoc_handle, $this->assoc_handle);
+ }
+
+ function test_checkidSetup()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.trust_root' => $this->tr_url);
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckIDRequest'));
+ $this->assertEquals($r->mode, "checkid_setup");
+ $this->assertEquals($r->immediate, false);
+ $this->assertEquals($r->identity, $this->id_url);
+ $this->assertEquals($r->trust_root, $this->tr_url);
+ $this->assertEquals($r->return_to, $this->rt_url);
+ }
+
+ function test_checkidSetupNoIdentity()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_setup',
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.trust_root' => $this->tr_url);
+
+ $result = $this->decoder->decode($args);
+ if (_isError($result)) {
+ $this->assertTrue($result->query);
+ } else {
+ $this->fail(sprintf("Expected Auth_OpenID_Error, instead " .
+ "returned with %s", gettype($result)));
+ }
+ }
+
+ function test_checkidSetupNoReturn()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.trust_root' => $this->tr_url);
+
+ $result = $this->decoder->decode($args);
+ if (!_isError($result)) {
+ $this->fail("Expected Auth_OpenID_Error");
}
+ }
- list($base, $query_str) = explode('?', $redir, 2);
+ function test_checkidSetupBadReturn()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => 'not a url');
- $query = array();
- parse_str($query_str, $query);
- $query = Auth_OpenID::fixArgs($query);
- return array($base, $query);
+ $result = $this->decoder->decode($args);;
+ if (_isError($result)) {
+ $this->assertTrue($result->query);
+ } else {
+ $this->fail(sprintf("Expected ProtocolError, instead " .
+ "returned with %s", gettype($result)));
+ }
+ }
+
+ function test_checkAuth()
+ {
+ $args = array(
+ 'openid.mode' => 'check_authentication',
+ 'openid.assoc_handle' => '{dumb}{handle}',
+ 'openid.sig' => 'sigblob',
+ 'openid.signed' => 'foo,bar,mode',
+ 'openid.foo' => 'signedval1',
+ 'openid.bar' => 'signedval2',
+ 'openid.baz' => 'unsigned');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckAuthRequest'));
+ $this->assertEquals($r->mode, 'check_authentication');
+ $this->assertEquals($r->sig, 'sigblob');
+ $this->assertEquals($r->signed, array(
+ array('foo', 'signedval1'),
+ array('bar', 'signedval2'),
+ array('mode', 'id_res')));
+
+ // XXX: and invalidate_handle, which is optional
+ // XXX: test error cases (missing required fields,
+ // missing fields that are in the signed list).
}
- function test_getWithReturnToError()
+ function test_associateDH()
{
$args = array(
- 'openid.mode' => 'monkeydance',
- 'openid.identity' => $this->id_url,
- 'openid.return_to' => $this->rt_url,
- );
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => "Rzup9265tw==");
- $ret = $this->server->getOpenIDResponse($this->noauth, 'GET', $args);
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_AssociateRequest'));
+ $this->assertEquals($r->mode, "associate");
+ $this->assertEquals($r->session_type, "DH-SHA1");
+ $this->assertEquals($r->assoc_type, "HMAC-SHA1");
+ $this->assertTrue($r->pubkey);
+ }
- list($rt_base, $resultArgs) = $this->_parseRedirResp($ret);
+ function test_associateDHMissingKey()
+ {
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1');
- $this->assertEquals($this->rt_url, $rt_base);
- $this->assertEquals('error', $resultArgs['openid.mode']);
- if (!array_key_exists('openid.error', $resultArgs)) {
- $dump = var_export($resultArgs, true);
- $msg = sprintf("no openid.error in %s", $dump);
- $this->fail($msg);
+ // Using DH-SHA1 without supplying dh_consumer_public is an error.
+ $result = $this->decoder->decode($args);
+ if (!_isError($result)) {
+ $this->fail(sprintf("Expected Auth_OpenID_Error, got %s",
+ gettype($result)));
}
}
- function test_getBadArgsError()
+ function test_associatePlain()
+ {
+ $args = array('openid.mode' => 'associate');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_AssociateRequest'));
+ $this->assertEquals($r->mode, "associate");
+ $this->assertEquals($r->session_type, "plaintext");
+ $this->assertEquals($r->assoc_type, "HMAC-SHA1");
+ }
+
+ function test_nomode()
{
$args = array(
- 'openid.mode' => 'zebradance',
- 'openid.identity' => $this->id_url,
- );
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => "my public keeey");
- list($status, $info) = $this->server->getOpenIDResponse(
- $this->noauth, 'GET', $args);
+ $result = $this->decoder->decode($args);
+ if (!_isError($result)) {
+ $this->fail(sprintf("Expected Auth_OpenID_Error",
+ gettype($result)));
+ }
+ }
+}
- $this->assertEquals(Auth_OpenID_LOCAL_ERROR, $status);
- $this->assertTrue($info);
+class Tests_Auth_OpenID_Test_Encode extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->encoder = new Auth_OpenID_Encoder();
+ $this->encode = $this->encoder;
}
- function test_getNoArgsError()
+ function test_id_res()
{
- list($status, $info) = $this->server->getOpenIDResponse(
- $this->noauth, 'GET', array());
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/',
+ 'http://burr.unittest/999',
+ false);
+
+ $response = new Auth_OpenID_CheckIDResponse($request);
+ $response->fields = array(
+ 'mode' => 'id_res',
+ 'identity' => $request->identity,
+ 'return_to' => $request->return_to);
+
+ $webresponse = $this->encoder->encode($response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location',
+ $webresponse->headers));
- $this->assertEquals(Auth_OpenID_DO_ABOUT, $status);
+ $location = $webresponse->headers['location'];
+ $this->assertTrue(strpos($location, $request->return_to) === 0);
+ // "%s does not start with %s" % ($location,
+ // $request->return_to));
+
+ $parsed = parse_url($location);
+ $query = array();
+ parse_str($parsed['query'], $query);
+ $query = Auth_OpenID::fixArgs($query);
+
+ $expected = array();
+
+ foreach ($response->fields as $k => $v) {
+ $expected['openid.' . $k] = $v;
+ }
+
+ $this->assertEquals($query, $expected);
+ }
+
+ function test_cancel()
+ {
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/',
+ 'http://burr.unittest/999',
+ false);
+
+ $response = new Auth_OpenID_CheckIDResponse($request);
+ $response->fields = array('mode' => 'cancel');
+
+ $webresponse = $this->encoder->encode($response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location', $webresponse->headers));
+ }
+
+ function test_assocReply()
+ {
+ $request = new Auth_OpenID_AssociateRequest();
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = array('assoc_handle' => "every-zig");
+ $webresponse = $this->encoder->encode($response);
+ $body = "assoc_handle:every-zig\n";
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_OK);
+ $this->assertEquals($webresponse->headers, array());
+ $this->assertEquals($webresponse->body, $body);
+ }
+
+ function test_checkauthReply()
+ {
+ $request = new Auth_OpenID_CheckAuthRequest('a_sock_monkey',
+ 'siggggg',
+ array());
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = array(
+ 'is_valid' => 'true',
+ 'invalidate_handle' => 'xXxX:xXXx');
+
+ $body = "invalidate_handle:xXxX:xXXx\nis_valid:true\n";
+ $webresponse = $this->encoder->encode($response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_OK);
+ $this->assertEquals($webresponse->headers, array());
+ $this->assertEquals($webresponse->body, $body);
+ }
+
+ function test_unencodableError()
+ {
+ $args = array('openid.identity' => 'http://limu.unittest/');
+
+ $e = new Auth_OpenID_ServerError($args, "wet paint");
+
+ $result = $this->encoder->encode($e);
+ if (!_isError($result, 'Auth_OpenID_EncodingError')) {
+ $this->fail(sprintf("Expected Auth_OpenID_ServerError, got %s",
+ gettype($result)));
+ }
}
- function test_postError()
+ function test_encodableError()
{
$args = array(
- 'openid.mode' => 'pandadance',
- 'openid.identity' => $this->id_url,
- );
+ 'openid.mode' => 'associate',
+ 'openid.identity' => 'http://limu.unittest/');
+
+ $body="error:snoot\nmode:error\n";
+ $err = new Auth_OpenID_ServerError($args, "snoot");
+ $webresponse = $this->encoder->encode($err);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_ERROR);
+ $this->assertEquals($webresponse->headers, array());
+ $this->assertEquals($webresponse->body, $body);
+ }
+}
+
+class Tests_Auth_OpenID_SigningEncode extends PHPUnit_TestCase {
+ function setUp()
+ {
+ // Use filestore here instead of memstore
+ $this->store = new Tests_Auth_OpenID_MemStore();
+
+ $this->request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/',
+ 'http://burr.unittest/999',
+ false);
+
+ $this->response = new Auth_OpenID_CheckIDResponse($this->request);
+ $this->response->fields = array(
+ 'mode' => 'id_res',
+ 'identity' => $this->request->identity,
+ 'return_to' => $this->request->return_to);
+
+ $this->signatory = new Auth_OpenID_Signatory($this->store);
+ $this->dumb_key = $this->signatory->dumb_key;
+ $this->normal_key = $this->signatory->normal_key;
- list($status, $info) = $this->server->getOpenIDResponse(
- $this->noauth, 'POST', $args);
+ $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory);
+ }
+
+ function test_idres()
+ {
+ $assoc_handle = '{bicycle}{shed}';
+ $this->store->storeAssociation(
+ $this->normal_key,
+ Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1'));
+ $this->request->assoc_handle = $assoc_handle;
+ $webresponse = $this->encoder->encode($this->response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location',
+ $webresponse->headers));
+
+ $location = $webresponse->headers['location'];
+ $parsed = parse_url($location);
+ $query = array();
+ parse_str($parsed['query'], $query);
+ $query = Auth_OpenID::fixArgs($query);
- $this->assertEquals(Auth_OpenID_REMOTE_ERROR, $status);
- $resultArgs = Auth_OpenID_KVForm::toArray($info);
- $this->assertTrue(array_key_exists('error', $resultArgs));
+ $this->assertTrue(array_key_exists('openid.sig', $query));
+ $this->assertTrue(array_key_exists('openid.assoc_handle', $query));
+ $this->assertTrue(array_key_exists('openid.signed', $query));
}
- function assertKeyExists($key, $ary)
+ function test_idresDumb()
{
- $this->assertTrue(array_key_exists($key, $ary),
- "Failed to find $key in $ary");
+ $webresponse = $this->encoder->encode($this->response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location', $webresponse->headers));
+
+ $location = $webresponse->headers['location'];
+ $parsed = parse_url($location);
+ $query = array();
+ parse_str($parsed['query'], $query);
+ $query = Auth_OpenID::fixArgs($query);
+ $this->assertTrue(array_key_exists('openid.sig', $query));
+ $this->assertTrue(array_key_exists('openid.assoc_handle', $query));
+ $this->assertTrue(array_key_exists('openid.signed', $query));
}
- function assertKeyAbsent($key, $ary)
+ function test_forgotStore()
{
- $this->assertFalse(array_key_exists($key, $ary),
- "Unexpectedly found $key in $ary");
+ $this->encoder->signatory = null;
+ $result = $this->encoder->encode($this->response);
+ if (!is_a($result, 'Auth_OpenID_ServerError')) {
+ $this->fail(sprintf("Expected Auth_OpenID_ServerError, got %s",
+ gettype($result)));
+ }
}
- function test_associatePlain()
+ function test_cancel()
{
- list($status, $info) = $this->server->associate(array());
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/',
+ 'http://burr.unittest/999',
+ false);
- $this->assertEquals(Auth_OpenID_REMOTE_OK, $status);
- $ra = Auth_OpenID_KVForm::toArray($info);
- $this->assertEquals('HMAC-SHA1', $ra['assoc_type']);
- $this->assertKeyAbsent('session_type', $ra);
- $this->assertKeyExists('assoc_handle', $ra);
- $this->assertKeyExists('mac_key', $ra);
- $exp = (integer)$ra['expires_in'];
- $this->assertTrue($exp > 0);
+ $response = new Auth_OpenID_CheckIDResponse($request, 'cancel');
+ $webresponse = $this->encoder->encode($response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location', $webresponse->headers));
+ $location = $webresponse->headers['location'];
+ $parsed = parse_url($location);
+ $query = array();
+ parse_str($parsed['query'], $query);
+ $query = Auth_OpenID::fixArgs($query);
+ $this->assertFalse(array_key_exists('openid.sig', $query));
+ }
+
+ function test_assocReply()
+ {
+ $request = new Auth_OpenID_AssociateRequest();
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = array('assoc_handle' => "every-zig");
+ $webresponse = $this->encoder->encode($response);
+ $body = "assoc_handle:every-zig\n";
+
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_OK);
+ $this->assertEquals($webresponse->headers, array());
+ $this->assertEquals($webresponse->body, $body);
+ }
+
+ function test_alreadySigned()
+ {
+ $this->response->fields['sig'] = 'priorSig==';
+ $result = $this->encoder->encode($this->response);
+ if (!is_a($result, 'Auth_OpenID_AlreadySigned')) {
+ $this->fail(sprintf("Expected Auth_OpenID_AlreadySigned " .
+ "instance, got %s", gettype($result)));
+ }
+ }
+}
+
+class Tests_Auth_OpenID_CheckID extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->request = new Auth_OpenID_CheckIDRequest(
+ 'http://bambam.unittest/',
+ 'http://bar.unittest/999',
+ 'http://bar.unittest/',
+ false);
}
- function test_associateDHdefaults()
+ function test_trustRootInvalid()
{
- if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $this->request->trust_root = "http://foo.unittest/17";
+ $this->request->return_to = "http://foo.unittest/39";
+ $this->assertFalse($this->request->trustRootValid());
+ }
+
+ function test_trustRootValid()
+ {
+ $this->request->trust_root = "http://foo.unittest/";
+ $this->request->return_to = "http://foo.unittest/39";
+ $this->assertTrue($this->request->trustRootValid());
+ }
+
+ function test_answerToInvalidRoot()
+ {
+ $this->request->trust_root = "http://foo.unittest/17";
+ $this->request->return_to = "http://foo.unittest/39";
+ $result = $this->request->answer(true);
+ if (!is_a($result, 'Auth_OpenID_UntrustedReturnURL')) {
+ $this->fail(sprintf("Expected Auth_OpenID_UntrustedReturnURL, " .
+ "got %s", gettype($result)));
+ }
+ $this->assertTrue($this->request->answer(false));
+ }
+
+ function test_answerAllow()
+ {
+ $answer = $this->request->answer(true);
+
+ if (_isError($answer)) {
+ $this->fail($answer->toString());
return;
}
- $dh = new Auth_OpenID_DiffieHellman();
- $args = $dh->getAssocArgs();
- list($status, $info) = $this->server->associate($args);
- $this->assertEquals(Auth_OpenID_REMOTE_OK, $status);
-
- $ra = Auth_OpenID_KVForm::toArray($info);
- $this->assertEquals('HMAC-SHA1', $ra['assoc_type']);
- $this->assertEquals('DH-SHA1', $ra['session_type']);
- $this->assertKeyExists('assoc_handle', $ra);
- $this->assertKeyExists('dh_server_public', $ra);
- $this->assertKeyAbsent('mac_key', $ra);
- $exp = (integer)$ra['expires_in'];
- $this->assertTrue($exp > 0);
- $secret = $dh->consumerFinish($ra);
- $this->assertEquals('string', gettype($secret));
- $this->assertTrue(strlen($secret) > 0);
- }
-
- function test_associateDHnoKey()
- {
- $args = array('openid.session_type' => 'DH-SHA1');
- list($status, $info) = $this->server->associate($args);
- if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
- $this->assertEquals(Auth_OpenID_REMOTE_OK, $status);
- $ra = Auth_OpenID_KVForm::toArray($info);
- $this->assertEquals('HMAC-SHA1', $ra['assoc_type']);
- $this->assertKeyExists('assoc_handle', $ra);
- $this->assertKeyExists('mac_key', $ra);
- $exp = (integer)$ra['expires_in'];
- $this->assertTrue($exp > 0);
+ $this->assertEquals($answer->request, $this->request);
+ $this->assertEquals($answer->fields, array(
+ 'mode' => 'id_res',
+ 'identity' => $this->request->identity,
+ 'return_to' => $this->request->return_to));
+
+ $this->assertEquals($answer->signed,
+ array("mode", "identity", "return_to"));
+ }
+
+ function test_answerAllowNoTrustRoot()
+ {
+ $this->request->trust_root = null;
+ $answer = $this->request->answer(true);
+ $this->assertEquals($answer->request, $this->request);
+ $this->assertEquals($answer->fields, array(
+ 'mode' => 'id_res',
+ 'identity' => $this->request->identity,
+ 'return_to' => $this->request->return_to));
+
+ $this->assertEquals($answer->signed,
+ array("mode", "identity", "return_to"));
+ }
+
+ function test_answerImmediateDeny()
+ {
+ $this->request->mode = 'checkid_immediate';
+ $this->request->immediate = true;
+ $server_url = "http://setup-url.unittest/";
+ $answer = $this->request->answer(false, $server_url);
+ $this->assertEquals($answer->request, $this->request);
+ $this->assertEquals(count($answer->fields), 2);
+ $this->assertEquals(Auth_OpenID::arrayGet($answer->fields, 'mode'),
+ 'id_res');
+ $this->assertTrue(strpos(Auth_OpenID::arrayGet($answer->fields,
+ 'user_setup_url'),
+ $server_url) == 0);
+
+ $this->assertEquals($answer->signed, array());
+ }
+
+ function test_answerSetupDeny()
+ {
+ $answer = $this->request->answer(false);
+ $this->assertEquals($answer->fields, array('mode' => 'cancel'));
+ $this->assertEquals($answer->signed, array());
+ }
+
+ function test_getCancelURL()
+ {
+ $url = $this->request->getCancelURL();
+ $expected = $this->request->return_to . '?openid.mode=cancel';
+ $this->assertEquals($url, $expected);
+ }
+
+ function test_getCancelURLimmed()
+ {
+ $this->request->mode = 'checkid_immediate';
+ $this->request->immediate = true;
+ $result = $this->request->getCancelURL();
+ if (!is_a($result, 'Auth_OpenID_ServerError')) {
+ $this->fail(sprintf("Expected Auth_OpenID_ServerError, got %s",
+ gettype($result)));
+ }
+ }
+}
+
+class Tests_Auth_OpenID_CheckIDExtension extends PHPUnit_TestCase {
+
+ function setUp()
+ {
+ $this->request = new Auth_OpenID_CheckIDRequest(
+ 'http://bambam.unittest/',
+ 'http://bar.unittest/',
+ 'http://bar.unittest/999',
+ false);
+
+ $this->response = new Auth_OpenID_CheckIDResponse($this->request);
+ $this->response->fields['blue'] = 'star';
+ }
+
+ function test_addField()
+ {
+ $namespace = 'mj12';
+ $this->response->addField($namespace, 'bright', 'potato');
+ $this->assertEquals($this->response->fields,
+ array('blue' => 'star',
+ 'mode' => 'id_res',
+ 'mj12.bright' => 'potato'));
+ $this->assertEquals($this->response->signed,
+ array('mode', 'identity', 'return_to',
+ 'mj12.bright'));
+ }
+
+ function test_addFieldUnsigned()
+ {
+ $namespace = 'mj12';
+ $this->response->addField($namespace, 'dull', 'lemon', false);
+ $this->assertEquals($this->response->fields,
+ array('blue' => 'star',
+ 'mode' => 'id_res',
+ 'mj12.dull' => 'lemon'));
+ $this->assertEquals($this->response->signed,
+ array('mode', 'identity', 'return_to'));
+ }
+
+ function test_addFields()
+ {
+ $namespace = 'mi5';
+ $this->response->addFields($namespace, array('tangy' => 'suspenders',
+ 'bravo' => 'inclusion'));
+ $this->assertEquals($this->response->fields,
+ array('blue' => 'star',
+ 'mode' => 'id_res',
+ 'mi5.tangy' => 'suspenders',
+ 'mi5.bravo' => 'inclusion'));
+ $this->assertEquals($this->response->signed,
+ array('mode', 'identity', 'return_to',
+ 'mi5.tangy', 'mi5.bravo'));
+ }
+
+ function test_addFieldsUnsigned()
+ {
+ $namespace = 'mi5';
+ $this->response->addFields($namespace, array('strange' => 'conditioner',
+ 'elemental' => 'blender'),
+ false);
+ $this->assertEquals($this->response->fields,
+ array('blue' => 'star',
+ 'mode' => 'id_res',
+ 'mi5.strange' => 'conditioner',
+ 'mi5.elemental' => 'blender'));
+ $this->assertEquals($this->response->signed,
+ array('mode', 'identity', 'return_to'));
+ }
+
+ function test_update()
+ {
+ $eresponse = new Auth_OpenID_ServerResponse(null);
+ $eresponse->fields = array('shape' => 'heart',
+ 'content' => 'strings,wire');
+ $eresponse->signed = array('content');
+ $this->response->update('box', $eresponse);
+ $this->assertEquals($this->response->fields,
+ array('blue' => 'star',
+ 'mode' => 'id_res',
+ 'box.shape' => 'heart',
+ 'box.content' => 'strings,wire'));
+ $this->assertEquals($this->response->signed,
+ array('mode', 'identity', 'return_to', 'content'));
+ }
+}
+
+class _MockSignatory {
+ var $isValid = true;
+
+ function _MockSignatory($assoc)
+ {
+ $this->assocs = array($assoc);
+ }
+
+ function verify($assoc_handle, $sig, $signed_pairs)
+ {
+ if (!$sig) {
+ return false;
+ }
+
+ if (!is_array($signed_pairs)) {
+ return false;
+ }
+
+ if (in_array(array(true, $assoc_handle), $this->assocs)) {
+ return $this->isValid;
} else {
- $this->assertEquals(Auth_OpenID_REMOTE_ERROR, $status);
- $ra = Auth_OpenID_KVForm::toArray($info);
- $this->assertKeyExists('error', $ra);
+ return false;
}
}
- function _buildURL($base, $query)
+ function getAssociation($assoc_handle, $dumb)
{
- $result = $base;
- $div = '?';
- foreach ($query as $k => $v) {
- $result .= sprintf("%s%s=%s", $div, urlencode($k), urlencode($v));
- $div = '&';
+ if (in_array(array($dumb, $assoc_handle), $this->assocs)) {
+ // This isn't a valid implementation for many uses of this
+ // function, mind you.
+ return true;
+ } else {
+ return null;
}
- return $result;
}
- function _startAuth($mode, $authorized)
+ function invalidate($assoc_handle, $dumb)
{
- $args = array(
- 'openid.mode' => $mode,
- 'openid.identity' => $this->id_url,
- 'openid.return_to' => $this->rt_url,
- );
- $ainfo = new Auth_OpenID_ServerRequest($this->sv_url, $args);
- return $this->server->getAuthResponse(&$ainfo, $authorized);
+ if (in_array(array($dumb, $assoc_handle), $this->assocs)) {
+ $i = 0;
+ foreach ($this->assocs as $pair) {
+ if ($pair == array($dumb, $assoc_handle)) {
+ unset($this->assocs[$i]);
+ break;
+ }
+ $i++;
+ }
+ }
}
+}
- function test_checkIdImmediateFailure()
+class Tests_Auth_OpenID_CheckAuth extends PHPUnit_TestCase {
+ function setUp()
{
- $ret = $this->_startAuth('checkid_immediate', false);
- list($base, $query) = $this->_parseRedirResp($ret);
+ $this->assoc_handle = 'mooooooooo';
+ $this->request = new Auth_OpenID_CheckAuthRequest(
+ $this->assoc_handle, 'signarture',
+ array(array('one', 'alpha'),
+ array('two', 'beta')));
- $setup_args = array('openid.identity' => $this->id_url,
- 'openid.mode' => 'checkid_setup',
- 'openid.return_to' => $this->rt_url,
- );
- $setup_url = $this->_buildURL($this->sv_url, $setup_args);
+ $this->signatory = new _MockSignatory(array(true, $this->assoc_handle));
+ }
- $eargs = array('openid.mode' => 'id_res',
- 'openid.user_setup_url' => $setup_url);
+ function test_valid()
+ {
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields, array('is_valid' => 'true'));
+ $this->assertEquals($r->request, $this->request);
+ }
- $this->assertEquals($eargs, $query);
- $this->assertEquals($this->rt_url, $base);
+ function test_invalid()
+ {
+ $this->signatory->isValid = false;
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields, array('is_valid' => 'false'));
}
- function _checkIDGood($mode)
+ function test_replay()
{
- $ret = $this->_startAuth($mode, true);
- list($base, $query) = $this->_parseRedirResp($ret);
- $this->assertEquals($base, $this->rt_url);
- $this->assertEquals($query['openid.mode'], 'id_res');
- $this->assertEquals($query['openid.identity'], $this->id_url);
- $this->assertEquals($query['openid.return_to'], $this->rt_url);
- $this->assertEquals('mode,identity,return_to', $query['openid.signed']);
+ $r = $this->request->answer($this->signatory);
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields, array('is_valid' => 'false'));
+ }
- $assoc = $this->store->getAssociation($this->server->_dumb_key,
- $query['openid.assoc_handle']);
- $this->assertNotNull($assoc);
- $expected = $assoc->sign(array('mode' => 'id_res',
- 'identity' => $this->id_url,
- 'return_to' => $this->rt_url,
- ));
- $expected64 = base64_encode($expected);
- $this->assertEquals($expected64, $query['openid.sig']);
+ function test_invalidatehandle()
+ {
+ $this->request->invalidate_handle = "bogusHandle";
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields,
+ array('is_valid' => 'true',
+ 'invalidate_handle' => "bogusHandle"));
+ $this->assertEquals($r->request, $this->request);
}
- function test_checkIdImmediate()
+ function test_invalidatehandleNo()
{
- $this->_checkIDGood('checkid_immediate');
+ $assoc_handle = 'goodhandle';
+ $this->signatory->assocs[] = array(false, 'goodhandle');
+ $this->request->invalidate_handle = $assoc_handle;
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields, array('is_valid' => 'true'));
}
+}
+
+class Tests_Auth_OpenID_Associate extends PHPUnit_TestCase {
+ // TODO: test DH with non-default values for modulus and gen.
+ // (important to do because we actually had it broken for a
+ // while.)
- function test_checkIdSetup()
+ function setUp()
{
- $this->_checkIDGood('checkid_setup');
+ $this->request = new Auth_OpenID_AssociateRequest();
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->signatory = new Auth_OpenID_Signatory($this->store);
+ $this->assoc = $this->signatory->createAssociation(false);
}
- function test_checkIdSetupNeedAuth()
+ function test_dh()
{
- $args = array(
- 'openid.mode' => 'checkid_setup',
- 'openid.identity' => $this->id_url,
- 'openid.return_to' => $this->rt_url,
- 'openid.trust_root' => $this->tr_url,
- );
+ $dh = new Auth_OpenID_DiffieHellman();
+ $ml =& Auth_OpenID_getMathLib();
+
+ $this->request->session_type = 'DH-SHA1';
+ $this->request->pubkey = $dh->public;
+ $response = $this->request->answer($this->assoc);
+ $this->assertEquals(
+ Auth_OpenID::arrayGet($response->fields, "assoc_type"),
+ "HMAC-SHA1");
+
+ $this->assertEquals(
+ Auth_OpenID::arrayGet($response->fields, "assoc_handle"),
+ $this->assoc->handle);
+
+ $this->assertFalse(
+ Auth_OpenID::arrayGet($response->fields, "mac_key"));
+
+ $this->assertEquals(
+ Auth_OpenID::arrayGet($response->fields, "session_type"),
+ "DH-SHA1");
+
+ $this->assertTrue(
+ Auth_OpenID::arrayGet($response->fields, "enc_mac_key"));
+
+ $this->assertTrue(
+ Auth_OpenID::arrayGet($response->fields,
+ "dh_server_public"));
+
+ $enc_key = base64_decode(
+ Auth_OpenID::arrayGet($response->fields, "enc_mac_key"));
+
+ $spub = $ml->base64ToLong(
+ Auth_OpenID::arrayGet($response->fields,
+ "dh_server_public"));
+
+ $secret = $dh->xorSecret($spub, $enc_key);
+
+ $this->assertEquals($secret, $this->assoc->secret);
+ }
+
+ function test_plaintext()
+ {
+ $response = $this->request->answer($this->assoc);
+
+ $this->assertEquals(
+ Auth_OpenID::arrayGet($response->fields, "assoc_type"),
+ "HMAC-SHA1");
+
+ $this->assertEquals(
+ Auth_OpenID::arrayGet($response->fields, "assoc_handle"),
+ $this->assoc->handle);
+
+ $this->assertEquals(
+ Auth_OpenID::arrayGet($response->fields, "expires_in"),
+ sprintf("%d", $this->signatory->SECRET_LIFETIME));
+
+ $this->assertEquals(
+ Auth_OpenID::arrayGet($response->fields, "mac_key"),
+ base64_encode($this->assoc->secret));
+
+ $this->assertFalse(Auth_OpenID::arrayGet($response->fields,
+ "session_type"));
+
+ $this->assertFalse(Auth_OpenID::arrayGet($response->fields,
+ "enc_mac_key"));
+
+ $this->assertFalse(Auth_OpenID::arrayGet($response->fields,
+ "dh_server_public"));
+ }
+}
+
+class Counter {
+ function Counter()
+ {
+ $this->count = 0;
+ }
+
+ function inc()
+ {
+ $this->count += 1;
+ }
+}
+
+class Tests_Auth_OpenID_ServerTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->server = new Auth_OpenID_Server($this->store);
+ }
+
+ /*
+ * Leaving this test out because PHP doesn't really support this
+ * kind of runtime modification.
+ *
+ function test_dispatch()
+ {
+ $monkeycalled =& new Counter();
+ function monkeyDo(request):
+ monkeycalled.inc();
+ r = server.OpenIDResponse(request);
+ return r;
+ $this->server.openid_monkeymode = monkeyDo;
+ request = server.OpenIDRequest();
+ request.mode = "monkeymode";
+ webresult = $this->server.handleRequest(request);
+ $this->assertEquals(monkeycalled.count, 1);
+ */
+
+ function test_associate()
+ {
+ $request = new Auth_OpenID_AssociateRequest();
+ $response = $this->server->openid_associate($request);
+ $this->assertTrue(array_key_exists('assoc_handle',
+ $response->fields));
+ }
+
+ function test_checkAuth()
+ {
+ $request = new Auth_OpenID_CheckAuthRequest('arrrrrf',
+ '0x3999', array());
- $ainfo = new Auth_OpenID_ServerRequest($this->sv_url, $args);
- list($status, $info) = $this->server->getAuthResponse(&$ainfo, false);
- $this->assertEquals(Auth_OpenID_DO_AUTH, $status);
- $this->assertEquals($this->tr_url, $info->getTrustRoot());
- $this->assertEquals($this->id_url, $info->getIdentityURL());
+ $response = $this->server->openid_check_authentication($request);
+ $this->assertTrue(array_key_exists('is_valid',
+ $response->fields));
}
+}
- function test_checkIdSetupCancel()
+class Tests_Auth_OpenID_Signatory extends PHPUnit_TestCase {
+ function setUp()
{
- list($status, $info) = $this->_startAuth('checkid_setup', false);
- $this->assertEquals(Auth_OpenID_DO_AUTH, $status);
- list($base, $query) = $this->_parseRedirResp($info->cancel());
- $this->assertEquals($this->rt_url, $base);
- $this->assertEquals('cancel', $query['openid.mode']);
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->signatory = new Auth_OpenID_Signatory($this->store);
+ $this->dumb_key = $this->signatory->dumb_key;
+ $this->normal_key = $this->signatory->normal_key;
}
- function _setupCheckAuth()
+ function test_sign()
{
- $ret = $this->_startAuth('checkid_immediate', true);
- list($base, $query) = $this->_parseRedirResp($ret);
- $this->assertEquals($base, $this->rt_url);
- $query['openid.mode'] = 'check_authentication';
- return $query;
+ $request = new Auth_OpenID_ServerRequest();
+ $assoc_handle = '{assoc}{lookatme}';
+ $this->store->storeAssociation(
+ $this->normal_key,
+ Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1'));
+ $request->assoc_handle = $assoc_handle;
+ $response = new Auth_OpenID_CheckIDResponse($request);
+ $response->fields = array(
+ 'foo' => 'amsigned',
+ 'bar' => 'notsigned',
+ 'azu' => 'alsosigned');
+
+ $response->signed = array('foo', 'azu');
+ $sresponse = $this->signatory->sign($response);
+
+ $this->assertEquals(Auth_OpenID::arrayGet($sresponse->fields,
+ 'assoc_handle'),
+ $assoc_handle);
+
+ $this->assertEquals(Auth_OpenID::arrayGet($sresponse->fields, 'signed'),
+ 'foo,azu');
+
+ $this->assertTrue(Auth_OpenID::arrayGet($sresponse->fields, 'sig'));
+ }
+
+ function test_signDumb()
+ {
+ $request = new Auth_OpenID_ServerRequest();
+ $request->assoc_handle = null;
+ $response = new Auth_OpenID_CheckIDResponse($request);
+ $response->fields = array(
+ 'foo' => 'amsigned',
+ 'bar' => 'notsigned',
+ 'azu' => 'alsosigned');
+
+ $response->signed = array('foo', 'azu');
+ $sresponse = $this->signatory->sign($response);
+
+ $assoc_handle = Auth_OpenID::arrayGet($sresponse->fields,
+ 'assoc_handle');
+
+ $this->assertTrue($assoc_handle);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+
+ $this->assertTrue($assoc);
+ $this->assertEquals(Auth_OpenID::arrayGet($sresponse->fields, 'signed'),
+ 'foo,azu');
+ $this->assertTrue(Auth_OpenID::arrayGet($sresponse->fields, 'sig'));
}
- function test_checkAuthentication()
+ function test_signExpired()
{
- $args = $this->_setupCheckAuth();
- list($status, $info) = $this->server->checkAuthentication($args);
- $this->assertEquals(Auth_OpenID_REMOTE_OK, $status);
- $this->assertEquals("is_valid:true\n", $info);
+ $request = new Auth_OpenID_ServerRequest();
+ $assoc_handle = '{assoc}{lookatme}';
+ $this->store->storeAssociation(
+ $this->normal_key,
+ Auth_OpenID_Association::fromExpiresIn(-10, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1'));
+ $this->assertTrue($this->store->getAssociation($this->normal_key,
+ $assoc_handle));
+
+ $request->assoc_handle = $assoc_handle;
+ $response = new Auth_OpenID_CheckIDResponse($request);
+ $response->fields = array(
+ 'foo' => 'amsigned',
+ 'bar' => 'notsigned',
+ 'azu' => 'alsosigned');
+
+ $response->signed = array('foo', 'azu');
+ $sresponse = $this->signatory->sign($response);
+
+ $new_assoc_handle = Auth_OpenID::arrayGet($sresponse->fields,
+ 'assoc_handle');
+ $this->assertTrue($new_assoc_handle);
+ $this->assertFalse($new_assoc_handle == $assoc_handle);
+
+ $this->assertEquals(Auth_OpenID::arrayGet($sresponse->fields,
+ 'invalidate_handle'),
+ $assoc_handle);
+
+ $this->assertEquals(Auth_OpenID::arrayGet($sresponse->fields, 'signed'),
+ 'foo,azu');
+ $this->assertTrue(Auth_OpenID::arrayGet($sresponse->fields, 'sig'));
+
+ // make sure the expired association is gone
+ $this->assertFalse($this->store->getAssociation($this->normal_key,
+ $assoc_handle));
+
+ // make sure the new key is a dumb mode association
+ $this->assertTrue($this->store->getAssociation($this->dumb_key,
+ $new_assoc_handle));
+
+ $this->assertFalse($this->store->getAssociation($this->normal_key,
+ $new_assoc_handle));
}
- function test_checkAuthenticationFailSig()
+ function test_signInvalidHandle()
{
- $args = $this->_setupCheckAuth();
- $args['openid.sig'] = str_rot13($args['openid.sig']);
- list($status, $info) = $this->server->checkAuthentication($args);
- $this->assertEquals(Auth_OpenID_REMOTE_OK, $status);
- $this->assertEquals("is_valid:false\n", $info);
+ $request = new Auth_OpenID_ServerRequest();
+ $assoc_handle = '{bogus-assoc}{notvalid}';
+
+ $request->assoc_handle = $assoc_handle;
+ $response = new Auth_OpenID_CheckIDResponse($request);
+ $response->fields = array(
+ 'foo' => 'amsigned',
+ 'bar' => 'notsigned',
+ 'azu' => 'alsosigned');
+
+ $response->signed = array('foo', 'azu');
+ $sresponse = $this->signatory->sign($response);
+
+ $new_assoc_handle = Auth_OpenID::arrayGet($sresponse->fields,
+ 'assoc_handle');
+
+ $this->assertTrue($new_assoc_handle);
+ $this->assertFalse($new_assoc_handle == $assoc_handle);
+
+ $this->assertEquals(Auth_OpenID::arrayGet($sresponse->fields,
+ 'invalidate_handle'),
+ $assoc_handle);
+
+ $this->assertEquals(Auth_OpenID::arrayGet($sresponse->fields, 'signed'),
+ 'foo,azu');
+ $this->assertTrue(Auth_OpenID::arrayGet($sresponse->fields, 'sig'));
+
+ // make sure the new key is a dumb mode association
+ $this->assertTrue($this->store->getAssociation($this->dumb_key,
+ $new_assoc_handle));
+
+ $this->assertFalse($this->store->getAssociation($this->normal_key,
+ $new_assoc_handle));
}
- function test_checkAuthenticationFailHandle()
+ function test_verify()
{
- $args = $this->_setupCheckAuth();
- $args['openid.assoc_handle'] = 'a bad handle';
- list($status, $info) = $this->server->checkAuthentication($args);
- $this->assertEquals(Auth_OpenID_REMOTE_OK, $status);
- $this->assertEquals("is_valid:false\n", $info);
+ $assoc_handle = '{vroom}{zoom}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation($this->dumb_key, $assoc);
+
+ $signed_pairs = array(array('foo', 'bar'),
+ array('apple', 'orange'));
+
+ $sig = "Ylu0KcIR7PvNegB/K41KpnRgJl0=";
+ $verified = $this->signatory->verify($assoc_handle, $sig,
+ $signed_pairs);
+ $this->assertTrue($verified);
+ }
+
+ function test_verifyBadSig()
+ {
+ $assoc_handle = '{vroom}{zoom}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation($this->dumb_key, $assoc);
+
+ $signed_pairs = array(array('foo', 'bar'),
+ array('apple', 'orange'));
+
+ $sig = str_rot13("Ylu0KcIR7PvNegB/K41KpnRgJl0=");
+ $verified = $this->signatory->verify($assoc_handle, $sig,
+ $signed_pairs);
+
+ $this->assertFalse($verified);
+ }
+
+ function test_verifyBadHandle()
+ {
+ $assoc_handle = '{vroom}{zoom}';
+ $signed_pairs = array(array('foo', 'bar'),
+ array('apple', 'orange'));
+
+ $sig = "Ylu0KcIR7PvNegB/K41KpnRgJl0=";
+ $verified = $this->signatory->verify($assoc_handle, $sig,
+ $signed_pairs);
+ $this->assertFalse($verified);
+ }
+
+ function test_getAssoc()
+ {
+ $assoc_handle = $this->makeAssoc(true);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertTrue($assoc);
+ $this->assertEquals($assoc->handle, $assoc_handle);
+ }
+
+ function test_getAssocExpired()
+ {
+ $assoc_handle = $this->makeAssoc(true, -10);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertFalse($assoc);
+ }
+
+ function test_getAssocInvalid()
+ {
+ $ah = 'no-such-handle';
+ $this->assertEquals(
+ $this->signatory->getAssociation($ah, false), null);
+ }
+
+ function test_getAssocDumbVsNormal()
+ {
+ $assoc_handle = $this->makeAssoc(true);
+ $this->assertEquals(
+ $this->signatory->getAssociation($assoc_handle, false), null);
+ }
+
+ function test_createAssociation()
+ {
+ $assoc = $this->signatory->createAssociation(false);
+ $this->assertTrue($this->signatory->getAssociation($assoc->handle,
+ false));
+ }
+
+ function makeAssoc($dumb, $lifetime = 60)
+ {
+ $assoc_handle = '{bling}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(
+ $lifetime, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation((($dumb) ? $this->dumb_key :
+ $this->normal_key), $assoc);
+ return $assoc_handle;
+ }
+
+ function test_invalidate()
+ {
+ $assoc_handle = '-squash-';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation($this->dumb_key, $assoc);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertTrue($assoc);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertTrue($assoc);
+ $this->signatory->invalidate($assoc_handle, true);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertFalse($assoc);
+ }
+}
+
+class Tests_Auth_OpenID_Server extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return "Tests_Auth_OpenID_Server";
+ }
+
+ function Tests_Auth_OpenID_Server()
+ {
+ $this->addTestSuite('Tests_Auth_OpenID_Signatory');
+ $this->addTestSuite('Tests_Auth_OpenID_ServerTest');
+ $this->addTestSuite('Tests_Auth_OpenID_Associate');
+ $this->addTestSuite('Tests_Auth_OpenID_CheckAuth');
+ $this->addTestSuite('Tests_Auth_OpenID_CheckIDExtension');
+ $this->addTestSuite('Tests_Auth_OpenID_CheckAuth');
+ $this->addTestSuite('Tests_Auth_OpenID_SigningEncode');
+ $this->addTestSuite('Tests_Auth_OpenID_Test_Encode');
+ $this->addTestSuite('Tests_Auth_OpenID_Test_Decode');
+ $this->addTestSuite('Tests_Auth_OpenID_Test_ServerError');
+ $this->addTestSuite('Tests_Auth_OpenID_CheckID');
}
}
+
+?> \ No newline at end of file