Source code for file /openid/Auth/OpenID/Server.php
Documentation is available at Server.php
* OpenID server protocol and logic.
* 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
* 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.
* 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
* (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.
* // 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')
* 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
* 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.
* LICENSE: See the COPYING file included in this distribution.
* @copyright 2005 Janrain, Inc.
* @license http://www.gnu.org/copyleft/lesser.html LGPL
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;
$_Auth_OpenID_Request_Modes =
array('checkid_setup',
$_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')
* 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.
function Auth_OpenID_ServerError($query =
null, $message =
null)
$this->message =
$message;
* Returns the return_to URL for the request which caused this
global $_Auth_OpenID_OpenID_Prefix;
'return_to', $this->query);
* Encodes this error's response as a URL suitable for
* redirection. If the response has no return_to, another
* Auth_OpenID_ServerError is returned.
global $_Auth_OpenID_OpenID_Prefix;
$return_to =
Auth_OpenID::arrayGet($this->query,
$_Auth_OpenID_OpenID_Prefix .
return Auth_OpenID::appendArgs($return_to,
array('openid.mode' =>
'error',
* 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.
* 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.
global $_Auth_OpenID_Encode_Url,
$_Auth_OpenID_Encode_Kvform,
$_Auth_OpenID_Request_Modes;
return $_Auth_OpenID_Encode_Url;
$mode =
Auth_OpenID::arrayGet($this->query, 'openid.mode');
if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
return $_Auth_OpenID_Encode_Kvform;
* Returns this error message.
* An error indicating that the return_to URL is malformed.
$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.
return "Malformed trust root";
* The base class for all server request classes.
class Auth_OpenID_Request {
* A request to verify the validity of a previous response.
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;
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) {
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);
foreach ($signed_list as $field) {
// 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
$value =
$query[$_Auth_OpenID_OpenID_Prefix .
$field];
sprintf("Couldn't find signed field %r in query %s",
$signed_pairs[] =
array($field, $value);
$result =
new Auth_OpenID_CheckAuthRequest($assoc_handle, $sig,
$result->invalidate_handle =
Auth_OpenID::arrayGet($query,
$_Auth_OpenID_OpenID_Prefix .
'invalidate_handle');
function answer(&$signatory)
$is_valid =
$signatory->verify($this->assoc_handle, $this->sig,
// 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,
$response->fields['invalidate_handle'] =
$this->invalidate_handle;
* An object that knows how to handle association requests with no
* An object that knows how to handle association requests with
* the Diffie-Hellman session type.
$this->consumer_pubkey =
$consumer_pubkey;
$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) {
'If non-default modulus or generator is '.
'supplied, both must be supplied. 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) {
$query, "Failed to parse dh_mod or dh_gen");
$dh =
new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
$dh =
new Auth_OpenID_DiffieHellman();
$consumer_pubkey =
Auth_OpenID::arrayGet($query,
'openid.dh_consumer_public');
if ($consumer_pubkey ===
null) {
'Public key for DH-SHA1 session '.
$lib->base64ToLong($consumer_pubkey);
if ($consumer_pubkey ===
false) {
"dh_consumer_public is not base64");
$lib =
& Auth_OpenID_getMathLib();
$mac_key =
$this->dh->xorSecret($this->consumer_pubkey, $secret);
$lib->longToBase64($this->dh->public),
* A request to associate with the server.
class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
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 =
$query[$_Auth_OpenID_OpenID_Prefix .
"Unknown session type $session_type");
$session_cls =
$session_classes[$session_type];
if (($session ===
null) ||
(_isError($session))) {
"Error parsing $session_type session");
return new Auth_OpenID_AssociateRequest($session);
$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;
* A request to confirm the identity of a user.
class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
var $mode =
"checkid_setup"; // or "checkid_immediate"
function make($query, $identity, $return_to, $trust_root =
null,
$immediate =
false, $assoc_handle =
null)
$r =
new Auth_OpenID_CheckIDRequest($identity, $return_to,
if (!$r->trustRootValid()) {
function Auth_OpenID_CheckIDRequest($identity, $return_to,
$trust_root =
null, $immediate =
false,
$this->identity =
$identity;
$this->return_to =
$return_to;
$this->trust_root =
$trust_root;
$this->assoc_handle =
$assoc_handle;
$this->mode =
"checkid_immediate";
$this->immediate =
false;
$this->mode =
"checkid_setup";
function fromQuery($query)
global $_Auth_OpenID_OpenID_Prefix;
$mode =
$query[$_Auth_OpenID_OpenID_Prefix .
'mode'];
if ($mode ==
"checkid_immediate") {
$mode =
"checkid_immediate";
$required =
array('identity',
$optional =
array('trust_root',
foreach ($required as $field) {
$value =
$query[$_Auth_OpenID_OpenID_Prefix .
$field];
sprintf("Missing required field %s from request",
$values[$field] =
$value;
foreach ($optional as $field) {
$value =
$query[$_Auth_OpenID_OpenID_Prefix.
$field];
$values[$field] =
$value;
$obj =
Auth_OpenID_CheckIDRequest::make($query,
Auth_OpenID::arrayGet($values,
if (is_a($obj, 'Auth_OpenID_ServerError')) {
if (Auth_OpenID::arrayGet($values, 'assoc_handle')) {
$obj->assoc_handle =
$values['assoc_handle'];
function trustRootValid()
if (!$this->trust_root) {
function answer($allow, $server_url =
null)
if ($allow ||
$this->immediate) {
$response =
new Auth_OpenID_CheckIDResponse($this, $mode);
$response->fields['identity'] =
$this->identity;
$response->fields['return_to'] =
$this->return_to;
if (!$this->trustRootValid()) {
$response->signed =
array();
'setup_url is required for $allow=false \
$setup_request =
& new Auth_OpenID_CheckIDRequest(
$setup_url =
$setup_request->encodeToURL($server_url);
$response->fields['user_setup_url'] =
$setup_url;
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);
$q['trust_root'] =
$this->trust_root;
if ($this->assoc_handle) {
$q['assoc_handle'] =
$this->assoc_handle;
foreach ($q as $k =>
$v) {
$_q[$_Auth_OpenID_OpenID_Prefix .
$k] =
$v;
return Auth_OpenID::appendArgs($server_url, $_q);
global $_Auth_OpenID_OpenID_Prefix;
"Cancel is not an appropriate \
response to immediate mode \
return Auth_OpenID::appendArgs($this->return_to,
array($_Auth_OpenID_OpenID_Prefix .
'mode' =>
* This class encapsulates the response to an OpenID server request.
class Auth_OpenID_ServerResponse {
function Auth_OpenID_ServerResponse($request)
$this->request =
$request;
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;
return $_Auth_OpenID_Encode_Kvform;
global $_Auth_OpenID_OpenID_Prefix;
foreach ($this->fields as $k =>
$v) {
$fields[$_Auth_OpenID_OpenID_Prefix .
$k] =
$v;
return Auth_OpenID::appendArgs($this->request->return_to, $fields);
function encodeToKVForm()
* A response to a checkid request.
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;
array_push($this->signed, 'mode', 'identity', 'return_to');
function addField($namespace, $key, $value, $signed =
true)
$key =
sprintf('%s.%s', $namespace, $key);
$this->fields[$key] =
$value;
if ($signed &&
!in_array($key, $this->signed)) {
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
var $code =
AUTH_OPENID_HTTP_OK;
$this->headers =
$headers;
$this->headers =
array();
* Responsible for the signature of query data and the verification of
* OpenID signature values.
// = 14 * 24 * 60 * 60; # 14 days, in seconds
// 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
* Create a new signatory using a given store.
// assert store is not None
* Verify, using a given association handle, a signature with
* signed key-value pairs from an HTTP request.
function verify($assoc_handle, $sig, $signed_pairs)
// oidutil.log("failed to get assoc with handle %r to verify sig %r"
// % (assoc_handle, sig))
return $sig ==
$expected_sig;
* Given a response, sign the fields in the response's 'signed'
* list, and insert the signature into the response.
$signed_response =
$response;
$assoc_handle =
$response->request->assoc_handle;
// fall back to dumb mode
$signed_response->fields['invalidate_handle'] =
$assoc_handle;
$signed_response->fields['assoc_handle'] =
$assoc->handle;
$assoc->addSignature($signed_response->signed,
$signed_response->fields, '');
* Make a new association.
$this->store->storeAssociation($key, $assoc);
* Given an association handle, get the association from the
* store, or return a ServerError or null if something goes wrong.
if ($assoc_handle ===
null) {
"assoc_handle must not be null");
$assoc =
$this->store->getAssociation($key, $assoc_handle);
if (($assoc !==
null) &&
($assoc->getExpiresIn() <=
0)) {
$this->store->removeAssociation($key, $assoc_handle);
* Invalidate a given association handle.
$this->store->removeAssociation($key, $assoc_handle);
* Encode an Auth_OpenID_Response to an Auth_OpenID_WebResponse.
* Encode an Auth_OpenID_Response and return an
* Auth_OpenID_WebResponse.
global $_Auth_OpenID_Encode_Kvform,
$_Auth_OpenID_Encode_Url;
$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')) {
} else if ($encode_as ==
$_Auth_OpenID_Encode_Url) {
$location =
$response->encodeToURL();
array('location' =>
$location));
* Returns true if the given response needs a signature.
function needsSigning($response)
return (in_array($response->request->mode, array('checkid_setup',
* An encoder which also takes care of signing fields when required.
$this->signatory =
& $signatory;
* Sign an Auth_OpenID_Response and return an
* Auth_OpenID_WebResponse.
// 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)) {
"Must have a store to sign request");
$response =
$this->signatory->sign($response);
return parent::encode($response);
* Decode an incoming Auth_OpenID_WebResponse into an
global $_Auth_OpenID_OpenID_Prefix;
$this->prefix =
$_Auth_OpenID_OpenID_Prefix;
'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.
foreach ($query as $k =>
$v) {
if (strpos($k, $this->prefix) ===
0) {
$mode =
Auth_OpenID::arrayGet($myquery, $this->prefix .
'mode');
sprintf("No %s mode found in query", $this->prefix));
$handlerCls =
Auth_OpenID::arrayGet($this->handlers, $mode,
if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
$mode =
$query[$this->prefix .
'mode'];
sprintf("No decoder for mode %s", $mode));
* An error that indicates an encoding problem occurred.
$this->response =
& $response;
* An error that indicates that a response was already signed.
// This response is already signed.
* An error that indicates that the given return_to is not under the
global $_Auth_OpenID_OpenID_Prefix;
$_Auth_OpenID_OpenID_Prefix .
'return_to' =>
$return_to,
$_Auth_OpenID_OpenID_Prefix .
'trust_root' =>
$trust_root);
parent::Auth_OpenID_ServerError($query);
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.
* 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.
$handler =
array($this, "openid_" .
$request->mode);
* The callback for 'check_authentication' messages.
function openid_check_authentication(&$request)
return $request->answer($this->signatory);
* The callback for 'associate' messages.
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.
return $this->encoder->encode($response);
* Decodes a query args array into the appropriate
* Auth_OpenID_Request object.
return $this->decoder->decode($query);