Rev 42 | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?php
/**
* OpenID server protocol and logic.
*
* Overview
*
* An OpenID server must perform three tasks:
*
* 1. Examine the incoming request to determine its nature and validity.
* 2. Make a decision about how to respond to this request.
* 3. Format the response according to the protocol.
*
* The first and last of these tasks may performed by the
* 'decodeRequest' and 'encodeResponse' methods of the
* Auth_OpenID_Server object. Who gets to do the intermediate task --
* deciding how to respond to the request -- will depend on what type
* of request it is.
*
* If it's a request to authenticate a user (a 'checkid_setup' or
* 'checkid_immediate' request), you need to decide if you will assert
* that this user may claim the identity in question. Exactly how you
* do that is a matter of application policy, but it generally
* involves making sure the user has an account with your system and
* is logged in, checking to see if that identity is hers to claim,
* and verifying with the user that she does consent to releasing that
* information to the party making the request.
*
* Examine the properties of the Auth_OpenID_CheckIDRequest object,
* and if and when you've come to a decision, form a response by
* calling Auth_OpenID_CheckIDRequest::answer.
*
* Other types of requests relate to establishing associations between
* client and server and verifing the authenticity of previous
* communications. Auth_OpenID_Server contains all the logic and data
* necessary to respond to such requests; just pass it to
* Auth_OpenID_Server::handleRequest.
*
* OpenID Extensions
*
* Do you want to provide other information for your users in addition
* to authentication? Version 1.2 of the OpenID protocol allows
* consumers to add extensions to their requests. For example, with
* sites using the Simple Registration
* Extension
* (http://www.openidenabled.com/openid/simple-registration-extension/),
* a user can agree to have their nickname and e-mail address sent to
* a site when they sign up.
*
* Since extensions do not change the way OpenID authentication works,
* code to handle extension requests may be completely separate from
* the Auth_OpenID_Request class here. But you'll likely want data
* sent back by your extension to be signed. Auth_OpenID_Response
* provides methods with which you can add data to it which can be
* signed with the other data in the OpenID signature.
*
* For example:
*
* // when request is a checkid_* request
* response = request.answer(True)
* // this will a signed 'openid.sreg.timezone' parameter to the response
* response.addField('sreg', 'timezone', 'America/Los_Angeles')
*
* Stores
*
* The OpenID server needs to maintain state between requests in order
* to function. Its mechanism for doing this is called a store. The
* store interface is defined in Interface.php. Additionally, several
* concrete store implementations are provided, so that most sites
* won't need to implement a custom store. For a store backed by flat
* files on disk, see Auth_OpenID_FileStore. For stores based on
* MySQL, SQLite, or PostgreSQL, see the Auth_OpenID_SQLStore
* subclasses.
*
* Upgrading
*
* The keys by which a server looks up associations in its store have
* changed in version 1.2 of this library. If your store has entries
* created from version 1.0 code, you should empty it.
*
* PHP versions 4 and 5
*
* LICENSE: See the COPYING file included in this distribution.
*
* @package OpenID
* @author JanRain, Inc. <openid@janrain.com>
* @copyright 2005 Janrain, Inc.
* @license http://www.gnu.org/copyleft/lesser.html LGPL
*/
/**
* Required imports
*/
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";
define('AUTH_OPENID_HTTP_OK', 200);
define('AUTH_OPENID_HTTP_REDIRECT', 302);
define('AUTH_OPENID_HTTP_ERROR', 400);
global $_Auth_OpenID_Request_Modes,
$_Auth_OpenID_OpenID_Prefix,
$_Auth_OpenID_Encode_Kvform,
$_Auth_OpenID_Encode_Url;
/**
* @access private
*/
$_Auth_OpenID_Request_Modes = array('checkid_setup',
'checkid_immediate');
/**
* @access private
*/
$_Auth_OpenID_OpenID_Prefix = "openid.";
/**
* @access private
*/
$_Auth_OpenID_Encode_Kvform = array('kfvorm');
/**
* @access private
*/
$_Auth_OpenID_Encode_Url = array('URL/redirect');
/**
* @access private
*/
function _isError($obj, $cls = 'Auth_OpenID_ServerError')
{
return is_a($obj, $cls);
}
/**
* An error class which gets instantiated and returned whenever an
* OpenID protocol error occurs. Be prepared to use this in place of
* an ordinary server response.
*
* @package OpenID
*/
class Auth_OpenID_ServerError {
/**
* @access private
*/
function Auth_OpenID_ServerError($query = null, $message = null)
{
$this->message = $message;
$this->query = $query;
}
/**
* Returns the return_to URL for the request which caused this
* error.
*/
function hasReturnTo()
{
global $_Auth_OpenID_OpenID_Prefix;
if ($this->query) {
return array_key_exists($_Auth_OpenID_OpenID_Prefix .
'return_to', $this->query);
} else {
return false;
}
}
/**
* Encodes this error's response as a URL suitable for
* redirection. If the response has no return_to, another
* Auth_OpenID_ServerError is returned.
*/
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',
'openid.error' => $this->toString()));
}
/**
* Encodes the response to key-value form. This is a
* machine-readable format used to respond to messages which came
* directly from the consumer and not through the user-agent. See
* the OpenID specification.
*/
function encodeToKVForm()
{
return Auth_OpenID_KVForm::fromArray(
array('mode' => 'error',
'error' => $this->toString()));
}
/**
* Returns one of $_Auth_OpenID_Encode_Url,
* $_Auth_OpenID_Encode_Kvform, or null, depending on the type of
* encoding expected for this error's payload.
*/
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;
}
}
return null;
}
/**
* Returns this error message.
*/
function toString()
{
if ($this->message) {
return $this->message;
} else {
return get_class($this) . " error";
}
}
}
/**
* An error indicating that the return_to URL is malformed.
*
* @package OpenID
*/
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");
}
}
/**
* This error is returned when the trust_root value is malformed.
*
* @package OpenID
*/
class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
function toString()
{
return "Malformed trust root";
}
}
/**
* The base class for all server request classes.
*
* @access private
* @package OpenID
*/
class Auth_OpenID_Request {
var $mode = null;
}
/**
* A request to verify the validity of a previous response.
*
* @access private
* @package OpenID
*/
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;
$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", "check_authentication", $k));
}
}
$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, var_export($query, true)));
}
}
$signed_pairs[] = array($field, $value);
}
$result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $sig,
$signed_pairs);
$result->invalidate_handle = Auth_OpenID::arrayGet($query,
$_Auth_OpenID_OpenID_Prefix . 'invalidate_handle');
return $result;
}
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;
}
}
class Auth_OpenID_PlainTextServerSession {
/**
* An object that knows how to handle association requests with no
* session type.
*/
var $session_type = 'plaintext';
function fromQuery($unused_request)
{
return new Auth_OpenID_PlainTextServerSession();
}
function answer($secret)
{
return array('mac_key' => base64_encode($secret));
}
}
class Auth_OpenID_DiffieHellmanServerSession {
/**
* An object that knows how to handle association requests with
* the Diffie-Hellman session type.
*/
var $session_type = 'DH-SHA1';
function Auth_OpenID_DiffieHellmanServerSession($dh, $consumer_pubkey)
{
$this->dh = $dh;
$this->consumer_pubkey = $consumer_pubkey;
}
function fromQuery($query)
{
$dh_modulus = Auth_OpenID::arrayGet($query, 'openid.dh_modulus');
$dh_gen = Auth_OpenID::arrayGet($query, 'openid.dh_gen');
if ((($dh_modulus === null) && ($dh_gen !== null)) ||
(($dh_gen === null) && ($dh_modulus !== null))) {
if ($dh_modulus === null) {
$missing = 'modulus';
} else {
$missing = 'generator';
}
return new Auth_OpenID_ServerError(
'If non-default modulus or generator is '.
'supplied, both must be supplied. Missing '.
$missing);
}
$lib =& Auth_OpenID_getMathLib();
if ($dh_modulus || $dh_gen) {
$dh_modulus = $lib->base64ToLong($dh_modulus);
$dh_gen = $lib->base64ToLong($dh_gen);
if ($lib->cmp($dh_modulus, 0) == 0 ||
$lib->cmp($dh_gen, 0) == 0) {
return new Auth_OpenID_ServerError(
$query, "Failed to parse dh_mod or dh_gen");
}
$dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
} else {
$dh = new Auth_OpenID_DiffieHellman();
}
$consumer_pubkey = Auth_OpenID::arrayGet($query,
'openid.dh_consumer_public');
if ($consumer_pubkey === null) {
return new Auth_OpenID_ServerError(
'Public key for DH-SHA1 session '.
'not found in query');
}
$consumer_pubkey =
$lib->base64ToLong($consumer_pubkey);
if ($consumer_pubkey === false) {
return new Auth_OpenID_ServerError($query,
"dh_consumer_public is not base64");
}
return new Auth_OpenID_DiffieHellmanServerSession($dh,
$consumer_pubkey);
}
function answer($secret)
{
$lib =& Auth_OpenID_getMathLib();
$mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret);
return array(
'dh_server_public' =>
$lib->longToBase64($this->dh->public),
'enc_mac_key' => base64_encode($mac_key));
}
}
/**
* A request to associate with the server.
*
* @access private
* @package OpenID
*/
class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
var $mode = "associate";
var $assoc_type = 'HMAC-SHA1';
function Auth_OpenID_AssociateRequest(&$session)
{
$this->session =& $session;
}
function fromQuery($query)
{
global $_Auth_OpenID_OpenID_Prefix;
$session_classes = array(
'DH-SHA1' => 'Auth_OpenID_DiffieHellmanServerSession',
null => 'Auth_OpenID_PlainTextServerSession');
$session_type = null;
if (array_key_exists($_Auth_OpenID_OpenID_Prefix . 'session_type',
$query)) {
$session_type = $query[$_Auth_OpenID_OpenID_Prefix .
'session_type'];
}
if (!array_key_exists($session_type, $session_classes)) {
return new Auth_OpenID_ServerError($query,
"Unknown session type $session_type");
}
$session_cls = $session_classes[$session_type];
$session = call_user_func_array(array($session_cls, 'fromQuery'),
array($query));
if (($session === null) || (_isError($session))) {
return new Auth_OpenID_ServerError($query,
"Error parsing $session_type session");
}
return new Auth_OpenID_AssociateRequest($session);
}
function answer($assoc)
{
$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);
$r = $this->session->answer($assoc->secret);
foreach ($r as $k => $v) {
$response->fields[$k] = $v;
}
if ($this->session->session_type != 'plaintext') {
$response->fields['session_type'] = $this->session->session_type;
}
return $response;
}
}
/**
* A request to confirm the identity of a user.
*
* @access private
* @package OpenID
*/
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);
}
$r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
$trust_root, $immediate,
$assoc_handle);
if (!$r->trustRootValid()) {
return new Auth_OpenID_UntrustedReturnURL($return_to,
$trust_root);
} else {
return $r;
}
}
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 {
$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 ($mode == "checkid_immediate") {
$immediate = true;
$mode = "checkid_immediate";
} else {
$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;
}
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;
}
}
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 (is_a($obj, 'Auth_OpenID_ServerError')) {
return $obj;
}
if (Auth_OpenID::arrayGet($values, 'assoc_handle')) {
$obj->assoc_handle = $values['assoc_handle'];
}
return $obj;
}
function trustRootValid()
{
if (!$this->trust_root) {
return true;
}
$tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
if ($tr === false) {
return new Auth_OpenID_MalformedTrustRoot(null, $this->trust_root);
}
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';
}
$response = new Auth_OpenID_CheckIDResponse($this, $mode);
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;
}
}
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'));
}
}
/**
* This class encapsulates the response to an OpenID server request.
*
* @access private
* @package OpenID
*/
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 {
return $_Auth_OpenID_Encode_Kvform;
}
}
function encodeToURL()
{
global $_Auth_OpenID_OpenID_Prefix;
$fields = array();
foreach ($this->fields as $k => $v) {
$fields[$_Auth_OpenID_OpenID_Prefix . $k] = $v;
}
return Auth_OpenID::appendArgs($this->request->return_to, $fields);
}
function encodeToKVForm()
{
return Auth_OpenID_KVForm::fromArray($this->fields);
}
}
/**
* A response to a checkid request.
*
* @access private
* @package OpenID
*/
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');
}
}
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;
}
}
function addFields($namespace, $fields, $signed = true)
{
foreach ($fields as $k => $v) {
$this->addField($namespace, $k, $v, $signed);
}
}
function update($namespace, $other)
{
$namespaced_fields = array();
foreach ($other->fields as $k => $v) {
$name = sprintf('%s.%s', $namespace, $k);
$namespaced_fields[$name] = $v;
}
$this->fields = array_merge($this->fields, $namespaced_fields);
$this->signed = array_merge($this->signed, $other->signed);
}
}
/**
* A web-capable response object which you can use to generate a
* user-agent response.
*
* @package OpenID
*/
class Auth_OpenID_WebResponse {
var $code = AUTH_OPENID_HTTP_OK;
var $body = "";
function Auth_OpenID_WebResponse($code = null, $headers = null,
$body = null)
{
if ($code) {
$this->code = $code;
}
if ($headers !== null) {
$this->headers = $headers;
} else {
$this->headers = array();
}
if ($body !== null) {
$this->body = $body;
}
}
}
/**
* Responsible for the signature of query data and the verification of
* OpenID signature values.
*
* @package OpenID
*/
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';
/**
* Create a new signatory using a given store.
*/
function Auth_OpenID_Signatory(&$store)
{
// assert store is not None
$this->store =& $store;
}
/**
* Verify, using a given association handle, a signature with
* signed key-value pairs from an HTTP request.
*/
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;
}
/**
* Given a response, sign the fields in the response's 'signed'
* list, and insert the signature into the response.
*/
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;
}
/**
* Make a new association.
*/
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, intval(time()), $uniq);
$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;
}
/**
* Given an association handle, get the association from the
* store, or return a ServerError or null if something goes wrong.
*/
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 {
$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;
}
/**
* Invalidate a given association handle.
*/
function invalidate($assoc_handle, $dumb)
{
if ($dumb) {
$key = $this->dumb_key;
} else {
$key = $this->normal_key;
}
$this->store->removeAssociation($key, $assoc_handle);
}
}
/**
* Encode an Auth_OpenID_Response to an Auth_OpenID_WebResponse.
*
* @package OpenID
*/
class Auth_OpenID_Encoder {
var $responseFactory = 'Auth_OpenID_WebResponse';
/**
* Encode an Auth_OpenID_Response and return an
* 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;
}
}
/**
* Returns true if the given response needs a signature.
*
* @access private
*/
function needsSigning($response)
{
return (in_array($response->request->mode, array('checkid_setup',
'checkid_immediate')) &&
$response->signed);
}
/**
* An encoder which also takes care of signing fields when required.
*
* @package OpenID
*/
class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {
function Auth_OpenID_SigningEncoder(&$signatory)
{
$this->signatory =& $signatory;
}
/**
* Sign an Auth_OpenID_Response and return an
* Auth_OpenID_WebResponse.
*/
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);
}
}
/**
* Decode an incoming Auth_OpenID_WebResponse into an
* Auth_OpenID_Request.
*
* @package OpenID
*/
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'
);
}
/**
* Given an HTTP query in an array (key-value pairs), decode it
* into an Auth_OpenID_Request object.
*/
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 %s mode 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));
}
}
/**
* An error that indicates an encoding problem occurred.
*
* @package OpenID
*/
class Auth_OpenID_EncodingError {
function Auth_OpenID_EncodingError(&$response)
{
$this->response =& $response;
}
}
/**
* An error that indicates that a response was already signed.
*
* @package OpenID
*/
class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
// This response is already signed.
}
/**
* An error that indicates that the given return_to is not under the
* given trust_root.
*
* @package OpenID
*/
class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
function Auth_OpenID_UntrustedReturnURL($return_to, $trust_root)
{
global $_Auth_OpenID_OpenID_Prefix;
$query = array(
$_Auth_OpenID_OpenID_Prefix . 'return_to' => $return_to,
$_Auth_OpenID_OpenID_Prefix . 'trust_root' => $trust_root);
parent::Auth_OpenID_ServerError($query);
}
function toString()
{
global $_Auth_OpenID_OpenID_Prefix;
$return_to = $this->query[$_Auth_OpenID_OpenID_Prefix . 'return_to'];
$trust_root = $this->query[$_Auth_OpenID_OpenID_Prefix . 'trust_root'];
return sprintf("return_to %s not under trust_root %s",
$return_to, $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_SigningEncoder($this->signatory);
$this->decoder =& new Auth_OpenID_Decoder();
}
/**
* Handle a request. Given an Auth_OpenID_Request object, call
* the appropriate Auth_OpenID_Server method to process the
* request and generate a response.
*
* @param Auth_OpenID_Request $request An Auth_OpenID_Request
* returned by Auth_OpenID_Server::decodeRequest.
*
* @return Auth_OpenID_Response $response A response object
* capable of generating a user-agent reply.
*/
function handleRequest($request)
{
if (method_exists($this, "openid_" . $request->mode)) {
$handler = array($this, "openid_" . $request->mode);
return call_user_func($handler, $request);
}
return null;
}
/**
* The callback for 'check_authentication' messages.
*
* @access private
*/
function openid_check_authentication(&$request)
{
return $request->answer($this->signatory);
}
/**
* The callback for 'associate' messages.
*
* @access private
*/
function openid_associate(&$request)
{
$assoc = $this->signatory->createAssociation(false);
return $request->answer($assoc);
}
/**
* Encodes as response in the appropriate format suitable for
* sending to the user agent.
*/
function encodeResponse(&$response)
{
return $this->encoder->encode($response);
}
/**
* Decodes a query args array into the appropriate
* Auth_OpenID_Request object.
*/
function decodeRequest(&$query)
{
return $this->decoder->decode($query);
}
}
?>