| 42 |
aurelien |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
/**
|
|
|
4 |
* OpenID server protocol and logic.
|
|
|
5 |
*
|
|
|
6 |
* Overview
|
|
|
7 |
*
|
|
|
8 |
* An OpenID server must perform three tasks:
|
|
|
9 |
*
|
|
|
10 |
* 1. Examine the incoming request to determine its nature and validity.
|
|
|
11 |
* 2. Make a decision about how to respond to this request.
|
|
|
12 |
* 3. Format the response according to the protocol.
|
|
|
13 |
*
|
|
|
14 |
* The first and last of these tasks may performed by the
|
|
|
15 |
* 'decodeRequest' and 'encodeResponse' methods of the
|
|
|
16 |
* Auth_OpenID_Server object. Who gets to do the intermediate task --
|
|
|
17 |
* deciding how to respond to the request -- will depend on what type
|
|
|
18 |
* of request it is.
|
|
|
19 |
*
|
|
|
20 |
* If it's a request to authenticate a user (a 'checkid_setup' or
|
|
|
21 |
* 'checkid_immediate' request), you need to decide if you will assert
|
|
|
22 |
* that this user may claim the identity in question. Exactly how you
|
|
|
23 |
* do that is a matter of application policy, but it generally
|
|
|
24 |
* involves making sure the user has an account with your system and
|
|
|
25 |
* is logged in, checking to see if that identity is hers to claim,
|
|
|
26 |
* and verifying with the user that she does consent to releasing that
|
|
|
27 |
* information to the party making the request.
|
|
|
28 |
*
|
|
|
29 |
* Examine the properties of the Auth_OpenID_CheckIDRequest object,
|
|
|
30 |
* and if and when you've come to a decision, form a response by
|
|
|
31 |
* calling Auth_OpenID_CheckIDRequest::answer.
|
|
|
32 |
*
|
|
|
33 |
* Other types of requests relate to establishing associations between
|
|
|
34 |
* client and server and verifing the authenticity of previous
|
|
|
35 |
* communications. Auth_OpenID_Server contains all the logic and data
|
|
|
36 |
* necessary to respond to such requests; just pass it to
|
|
|
37 |
* Auth_OpenID_Server::handleRequest.
|
|
|
38 |
*
|
|
|
39 |
* OpenID Extensions
|
|
|
40 |
*
|
|
|
41 |
* Do you want to provide other information for your users in addition
|
|
|
42 |
* to authentication? Version 1.2 of the OpenID protocol allows
|
|
|
43 |
* consumers to add extensions to their requests. For example, with
|
|
|
44 |
* sites using the Simple Registration
|
|
|
45 |
* Extension
|
|
|
46 |
* (http://www.openidenabled.com/openid/simple-registration-extension/),
|
|
|
47 |
* a user can agree to have their nickname and e-mail address sent to
|
|
|
48 |
* a site when they sign up.
|
|
|
49 |
*
|
|
|
50 |
* Since extensions do not change the way OpenID authentication works,
|
|
|
51 |
* code to handle extension requests may be completely separate from
|
|
|
52 |
* the Auth_OpenID_Request class here. But you'll likely want data
|
|
|
53 |
* sent back by your extension to be signed. Auth_OpenID_Response
|
|
|
54 |
* provides methods with which you can add data to it which can be
|
|
|
55 |
* signed with the other data in the OpenID signature.
|
|
|
56 |
*
|
|
|
57 |
* For example:
|
|
|
58 |
*
|
|
|
59 |
* // when request is a checkid_* request
|
|
|
60 |
* response = request.answer(True)
|
|
|
61 |
* // this will a signed 'openid.sreg.timezone' parameter to the response
|
|
|
62 |
* response.addField('sreg', 'timezone', 'America/Los_Angeles')
|
|
|
63 |
*
|
|
|
64 |
* Stores
|
|
|
65 |
*
|
|
|
66 |
* The OpenID server needs to maintain state between requests in order
|
|
|
67 |
* to function. Its mechanism for doing this is called a store. The
|
|
|
68 |
* store interface is defined in Interface.php. Additionally, several
|
|
|
69 |
* concrete store implementations are provided, so that most sites
|
|
|
70 |
* won't need to implement a custom store. For a store backed by flat
|
|
|
71 |
* files on disk, see Auth_OpenID_FileStore. For stores based on
|
|
|
72 |
* MySQL, SQLite, or PostgreSQL, see the Auth_OpenID_SQLStore
|
|
|
73 |
* subclasses.
|
|
|
74 |
*
|
|
|
75 |
* Upgrading
|
|
|
76 |
*
|
|
|
77 |
* The keys by which a server looks up associations in its store have
|
|
|
78 |
* changed in version 1.2 of this library. If your store has entries
|
|
|
79 |
* created from version 1.0 code, you should empty it.
|
|
|
80 |
*
|
|
|
81 |
* PHP versions 4 and 5
|
|
|
82 |
*
|
|
|
83 |
* LICENSE: See the COPYING file included in this distribution.
|
|
|
84 |
*
|
|
|
85 |
* @package OpenID
|
|
|
86 |
* @author JanRain, Inc. <openid@janrain.com>
|
|
|
87 |
* @copyright 2005 Janrain, Inc.
|
|
|
88 |
* @license http://www.gnu.org/copyleft/lesser.html LGPL
|
|
|
89 |
*/
|
|
|
90 |
|
|
|
91 |
/**
|
|
|
92 |
* Required imports
|
|
|
93 |
*/
|
|
|
94 |
require_once "Auth/OpenID.php";
|
|
|
95 |
require_once "Auth/OpenID/Association.php";
|
|
|
96 |
require_once "Auth/OpenID/CryptUtil.php";
|
|
|
97 |
require_once "Auth/OpenID/BigMath.php";
|
|
|
98 |
require_once "Auth/OpenID/DiffieHellman.php";
|
|
|
99 |
require_once "Auth/OpenID/KVForm.php";
|
|
|
100 |
require_once "Auth/OpenID/TrustRoot.php";
|
|
|
101 |
require_once "Auth/OpenID/ServerRequest.php";
|
|
|
102 |
|
|
|
103 |
define('AUTH_OPENID_HTTP_OK', 200);
|
|
|
104 |
define('AUTH_OPENID_HTTP_REDIRECT', 302);
|
|
|
105 |
define('AUTH_OPENID_HTTP_ERROR', 400);
|
|
|
106 |
|
|
|
107 |
global $_Auth_OpenID_Request_Modes,
|
|
|
108 |
$_Auth_OpenID_OpenID_Prefix,
|
|
|
109 |
$_Auth_OpenID_Encode_Kvform,
|
|
|
110 |
$_Auth_OpenID_Encode_Url;
|
|
|
111 |
|
|
|
112 |
/**
|
|
|
113 |
* @access private
|
|
|
114 |
*/
|
|
|
115 |
$_Auth_OpenID_Request_Modes = array('checkid_setup',
|
|
|
116 |
'checkid_immediate');
|
|
|
117 |
|
|
|
118 |
/**
|
|
|
119 |
* @access private
|
|
|
120 |
*/
|
|
|
121 |
$_Auth_OpenID_OpenID_Prefix = "openid.";
|
|
|
122 |
|
|
|
123 |
/**
|
|
|
124 |
* @access private
|
|
|
125 |
*/
|
|
|
126 |
$_Auth_OpenID_Encode_Kvform = array('kfvorm');
|
|
|
127 |
|
|
|
128 |
/**
|
|
|
129 |
* @access private
|
|
|
130 |
*/
|
|
|
131 |
$_Auth_OpenID_Encode_Url = array('URL/redirect');
|
|
|
132 |
|
|
|
133 |
/**
|
|
|
134 |
* @access private
|
|
|
135 |
*/
|
|
|
136 |
function _isError($obj, $cls = 'Auth_OpenID_ServerError')
|
|
|
137 |
{
|
|
|
138 |
return is_a($obj, $cls);
|
|
|
139 |
}
|
|
|
140 |
|
|
|
141 |
/**
|
|
|
142 |
* An error class which gets instantiated and returned whenever an
|
|
|
143 |
* OpenID protocol error occurs. Be prepared to use this in place of
|
|
|
144 |
* an ordinary server response.
|
|
|
145 |
*
|
|
|
146 |
* @package OpenID
|
|
|
147 |
*/
|
|
|
148 |
class Auth_OpenID_ServerError {
|
|
|
149 |
/**
|
|
|
150 |
* @access private
|
|
|
151 |
*/
|
|
|
152 |
function Auth_OpenID_ServerError($query = null, $message = null)
|
|
|
153 |
{
|
|
|
154 |
$this->message = $message;
|
|
|
155 |
$this->query = $query;
|
|
|
156 |
}
|
|
|
157 |
|
|
|
158 |
/**
|
|
|
159 |
* Returns the return_to URL for the request which caused this
|
|
|
160 |
* error.
|
|
|
161 |
*/
|
|
|
162 |
function hasReturnTo()
|
|
|
163 |
{
|
|
|
164 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
165 |
if ($this->query) {
|
|
|
166 |
return array_key_exists($_Auth_OpenID_OpenID_Prefix .
|
|
|
167 |
'return_to', $this->query);
|
|
|
168 |
} else {
|
|
|
169 |
return false;
|
|
|
170 |
}
|
|
|
171 |
}
|
|
|
172 |
|
|
|
173 |
/**
|
|
|
174 |
* Encodes this error's response as a URL suitable for
|
|
|
175 |
* redirection. If the response has no return_to, another
|
|
|
176 |
* Auth_OpenID_ServerError is returned.
|
|
|
177 |
*/
|
|
|
178 |
function encodeToURL()
|
|
|
179 |
{
|
|
|
180 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
181 |
$return_to = Auth_OpenID::arrayGet($this->query,
|
|
|
182 |
$_Auth_OpenID_OpenID_Prefix .
|
|
|
183 |
'return_to');
|
|
|
184 |
if (!$return_to) {
|
|
|
185 |
return new Auth_OpenID_ServerError(null, "no return_to URL");
|
|
|
186 |
}
|
|
|
187 |
|
|
|
188 |
return Auth_OpenID::appendArgs($return_to,
|
|
|
189 |
array('openid.mode' => 'error',
|
|
|
190 |
'openid.error' => $this->toString()));
|
|
|
191 |
}
|
|
|
192 |
|
|
|
193 |
/**
|
|
|
194 |
* Encodes the response to key-value form. This is a
|
|
|
195 |
* machine-readable format used to respond to messages which came
|
|
|
196 |
* directly from the consumer and not through the user-agent. See
|
|
|
197 |
* the OpenID specification.
|
|
|
198 |
*/
|
|
|
199 |
function encodeToKVForm()
|
|
|
200 |
{
|
|
|
201 |
return Auth_OpenID_KVForm::fromArray(
|
|
|
202 |
array('mode' => 'error',
|
|
|
203 |
'error' => $this->toString()));
|
|
|
204 |
}
|
|
|
205 |
|
|
|
206 |
/**
|
|
|
207 |
* Returns one of $_Auth_OpenID_Encode_Url,
|
|
|
208 |
* $_Auth_OpenID_Encode_Kvform, or null, depending on the type of
|
|
|
209 |
* encoding expected for this error's payload.
|
|
|
210 |
*/
|
|
|
211 |
function whichEncoding()
|
|
|
212 |
{
|
|
|
213 |
global $_Auth_OpenID_Encode_Url,
|
|
|
214 |
$_Auth_OpenID_Encode_Kvform,
|
|
|
215 |
$_Auth_OpenID_Request_Modes;
|
|
|
216 |
|
|
|
217 |
if ($this->hasReturnTo()) {
|
|
|
218 |
return $_Auth_OpenID_Encode_Url;
|
|
|
219 |
}
|
|
|
220 |
|
|
|
221 |
$mode = Auth_OpenID::arrayGet($this->query, 'openid.mode');
|
|
|
222 |
|
|
|
223 |
if ($mode) {
|
|
|
224 |
if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
|
|
|
225 |
return $_Auth_OpenID_Encode_Kvform;
|
|
|
226 |
}
|
|
|
227 |
}
|
|
|
228 |
return null;
|
|
|
229 |
}
|
|
|
230 |
|
|
|
231 |
/**
|
|
|
232 |
* Returns this error message.
|
|
|
233 |
*/
|
|
|
234 |
function toString()
|
|
|
235 |
{
|
|
|
236 |
if ($this->message) {
|
|
|
237 |
return $this->message;
|
|
|
238 |
} else {
|
|
|
239 |
return get_class($this) . " error";
|
|
|
240 |
}
|
|
|
241 |
}
|
|
|
242 |
}
|
|
|
243 |
|
|
|
244 |
/**
|
|
|
245 |
* An error indicating that the return_to URL is malformed.
|
|
|
246 |
*
|
|
|
247 |
* @package OpenID
|
|
|
248 |
*/
|
|
|
249 |
class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
|
|
|
250 |
function Auth_OpenID_MalformedReturnURL($query, $return_to)
|
|
|
251 |
{
|
|
|
252 |
$this->return_to = $return_to;
|
|
|
253 |
parent::Auth_OpenID_ServerError($query, "malformed return_to URL");
|
|
|
254 |
}
|
|
|
255 |
}
|
|
|
256 |
|
|
|
257 |
/**
|
|
|
258 |
* This error is returned when the trust_root value is malformed.
|
|
|
259 |
*
|
|
|
260 |
* @package OpenID
|
|
|
261 |
*/
|
|
|
262 |
class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
|
|
|
263 |
function toString()
|
|
|
264 |
{
|
|
|
265 |
return "Malformed trust root";
|
|
|
266 |
}
|
|
|
267 |
}
|
|
|
268 |
|
|
|
269 |
/**
|
|
|
270 |
* The base class for all server request classes.
|
|
|
271 |
*
|
|
|
272 |
* @access private
|
|
|
273 |
* @package OpenID
|
|
|
274 |
*/
|
|
|
275 |
class Auth_OpenID_Request {
|
|
|
276 |
var $mode = null;
|
|
|
277 |
}
|
|
|
278 |
|
|
|
279 |
/**
|
|
|
280 |
* A request to verify the validity of a previous response.
|
|
|
281 |
*
|
|
|
282 |
* @access private
|
|
|
283 |
* @package OpenID
|
|
|
284 |
*/
|
|
|
285 |
class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
|
|
|
286 |
var $mode = "check_authentication";
|
|
|
287 |
var $invalidate_handle = null;
|
|
|
288 |
|
|
|
289 |
function Auth_OpenID_CheckAuthRequest($assoc_handle, $sig, $signed,
|
|
|
290 |
$invalidate_handle = null)
|
|
|
291 |
{
|
|
|
292 |
$this->assoc_handle = $assoc_handle;
|
|
|
293 |
$this->sig = $sig;
|
|
|
294 |
$this->signed = $signed;
|
|
|
295 |
if ($invalidate_handle !== null) {
|
|
|
296 |
$this->invalidate_handle = $invalidate_handle;
|
|
|
297 |
}
|
|
|
298 |
}
|
|
|
299 |
|
|
|
300 |
function fromQuery($query)
|
|
|
301 |
{
|
|
|
302 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
303 |
|
|
|
304 |
$required_keys = array('assoc_handle', 'sig', 'signed');
|
|
|
305 |
|
|
|
306 |
foreach ($required_keys as $k) {
|
|
|
307 |
if (!array_key_exists($_Auth_OpenID_OpenID_Prefix . $k,
|
|
|
308 |
$query)) {
|
|
|
309 |
return new Auth_OpenID_ServerError($query,
|
|
|
310 |
sprintf("%s request missing required parameter %s from \
|
|
|
311 |
query", "check_authentication", $k));
|
|
|
312 |
}
|
|
|
313 |
}
|
|
|
314 |
|
|
|
315 |
$assoc_handle = $query[$_Auth_OpenID_OpenID_Prefix . 'assoc_handle'];
|
|
|
316 |
$sig = $query[$_Auth_OpenID_OpenID_Prefix . 'sig'];
|
|
|
317 |
$signed_list = $query[$_Auth_OpenID_OpenID_Prefix . 'signed'];
|
|
|
318 |
|
|
|
319 |
$signed_list = explode(",", $signed_list);
|
|
|
320 |
$signed_pairs = array();
|
|
|
321 |
|
|
|
322 |
foreach ($signed_list as $field) {
|
|
|
323 |
if ($field == 'mode') {
|
|
|
324 |
// XXX KLUDGE HAX WEB PROTOCoL BR0KENNN
|
|
|
325 |
//
|
|
|
326 |
// openid.mode is currently check_authentication
|
|
|
327 |
// because that's the mode of this request. But the
|
|
|
328 |
// signature was made on something with a different
|
|
|
329 |
// openid.mode.
|
|
|
330 |
$value = "id_res";
|
|
|
331 |
} else {
|
|
|
332 |
if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
|
|
|
333 |
$query)) {
|
|
|
334 |
$value = $query[$_Auth_OpenID_OpenID_Prefix . $field];
|
|
|
335 |
} else {
|
|
|
336 |
return new Auth_OpenID_ServerError($query,
|
|
|
337 |
sprintf("Couldn't find signed field %r in query %s",
|
|
|
338 |
$field, var_export($query, true)));
|
|
|
339 |
}
|
|
|
340 |
}
|
|
|
341 |
$signed_pairs[] = array($field, $value);
|
|
|
342 |
}
|
|
|
343 |
|
|
|
344 |
$result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $sig,
|
|
|
345 |
$signed_pairs);
|
|
|
346 |
$result->invalidate_handle = Auth_OpenID::arrayGet($query,
|
|
|
347 |
$_Auth_OpenID_OpenID_Prefix . 'invalidate_handle');
|
|
|
348 |
return $result;
|
|
|
349 |
}
|
|
|
350 |
|
|
|
351 |
function answer(&$signatory)
|
|
|
352 |
{
|
|
|
353 |
$is_valid = $signatory->verify($this->assoc_handle, $this->sig,
|
|
|
354 |
$this->signed);
|
|
|
355 |
|
|
|
356 |
// Now invalidate that assoc_handle so it this checkAuth
|
|
|
357 |
// message cannot be replayed.
|
|
|
358 |
$signatory->invalidate($this->assoc_handle, true);
|
|
|
359 |
$response = new Auth_OpenID_ServerResponse($this);
|
|
|
360 |
$response->fields['is_valid'] = $is_valid ? "true" : "false";
|
|
|
361 |
|
|
|
362 |
if ($this->invalidate_handle) {
|
|
|
363 |
$assoc = $signatory->getAssociation($this->invalidate_handle,
|
|
|
364 |
false);
|
|
|
365 |
if (!$assoc) {
|
|
|
366 |
$response->fields['invalidate_handle'] =
|
|
|
367 |
$this->invalidate_handle;
|
|
|
368 |
}
|
|
|
369 |
}
|
|
|
370 |
return $response;
|
|
|
371 |
}
|
|
|
372 |
}
|
|
|
373 |
|
|
|
374 |
class Auth_OpenID_PlainTextServerSession {
|
|
|
375 |
/**
|
|
|
376 |
* An object that knows how to handle association requests with no
|
|
|
377 |
* session type.
|
|
|
378 |
*/
|
|
|
379 |
var $session_type = 'plaintext';
|
|
|
380 |
|
|
|
381 |
function fromQuery($unused_request)
|
|
|
382 |
{
|
|
|
383 |
return new Auth_OpenID_PlainTextServerSession();
|
|
|
384 |
}
|
|
|
385 |
|
|
|
386 |
function answer($secret)
|
|
|
387 |
{
|
|
|
388 |
return array('mac_key' => base64_encode($secret));
|
|
|
389 |
}
|
|
|
390 |
}
|
|
|
391 |
|
|
|
392 |
class Auth_OpenID_DiffieHellmanServerSession {
|
|
|
393 |
/**
|
|
|
394 |
* An object that knows how to handle association requests with
|
|
|
395 |
* the Diffie-Hellman session type.
|
|
|
396 |
*/
|
|
|
397 |
|
|
|
398 |
var $session_type = 'DH-SHA1';
|
|
|
399 |
|
|
|
400 |
function Auth_OpenID_DiffieHellmanServerSession($dh, $consumer_pubkey)
|
|
|
401 |
{
|
|
|
402 |
$this->dh = $dh;
|
|
|
403 |
$this->consumer_pubkey = $consumer_pubkey;
|
|
|
404 |
}
|
|
|
405 |
|
|
|
406 |
function fromQuery($query)
|
|
|
407 |
{
|
|
|
408 |
$dh_modulus = Auth_OpenID::arrayGet($query, 'openid.dh_modulus');
|
|
|
409 |
$dh_gen = Auth_OpenID::arrayGet($query, 'openid.dh_gen');
|
|
|
410 |
|
|
|
411 |
if ((($dh_modulus === null) && ($dh_gen !== null)) ||
|
|
|
412 |
(($dh_gen === null) && ($dh_modulus !== null))) {
|
|
|
413 |
|
|
|
414 |
if ($dh_modulus === null) {
|
|
|
415 |
$missing = 'modulus';
|
|
|
416 |
} else {
|
|
|
417 |
$missing = 'generator';
|
|
|
418 |
}
|
|
|
419 |
|
|
|
420 |
return new Auth_OpenID_ServerError(
|
|
|
421 |
'If non-default modulus or generator is '.
|
|
|
422 |
'supplied, both must be supplied. Missing '.
|
|
|
423 |
$missing);
|
|
|
424 |
}
|
|
|
425 |
|
|
|
426 |
$lib =& Auth_OpenID_getMathLib();
|
|
|
427 |
|
|
|
428 |
if ($dh_modulus || $dh_gen) {
|
|
|
429 |
$dh_modulus = $lib->base64ToLong($dh_modulus);
|
|
|
430 |
$dh_gen = $lib->base64ToLong($dh_gen);
|
|
|
431 |
if ($lib->cmp($dh_modulus, 0) == 0 ||
|
|
|
432 |
$lib->cmp($dh_gen, 0) == 0) {
|
|
|
433 |
return new Auth_OpenID_ServerError(
|
|
|
434 |
$query, "Failed to parse dh_mod or dh_gen");
|
|
|
435 |
}
|
|
|
436 |
$dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
|
|
|
437 |
} else {
|
|
|
438 |
$dh = new Auth_OpenID_DiffieHellman();
|
|
|
439 |
}
|
|
|
440 |
|
|
|
441 |
$consumer_pubkey = Auth_OpenID::arrayGet($query,
|
|
|
442 |
'openid.dh_consumer_public');
|
|
|
443 |
if ($consumer_pubkey === null) {
|
|
|
444 |
return new Auth_OpenID_ServerError(
|
|
|
445 |
'Public key for DH-SHA1 session '.
|
|
|
446 |
'not found in query');
|
|
|
447 |
}
|
|
|
448 |
|
|
|
449 |
$consumer_pubkey =
|
|
|
450 |
$lib->base64ToLong($consumer_pubkey);
|
|
|
451 |
|
|
|
452 |
if ($consumer_pubkey === false) {
|
|
|
453 |
return new Auth_OpenID_ServerError($query,
|
|
|
454 |
"dh_consumer_public is not base64");
|
|
|
455 |
}
|
|
|
456 |
|
|
|
457 |
return new Auth_OpenID_DiffieHellmanServerSession($dh,
|
|
|
458 |
$consumer_pubkey);
|
|
|
459 |
}
|
|
|
460 |
|
|
|
461 |
function answer($secret)
|
|
|
462 |
{
|
|
|
463 |
$lib =& Auth_OpenID_getMathLib();
|
|
|
464 |
$mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret);
|
|
|
465 |
return array(
|
|
|
466 |
'dh_server_public' =>
|
|
|
467 |
$lib->longToBase64($this->dh->public),
|
|
|
468 |
'enc_mac_key' => base64_encode($mac_key));
|
|
|
469 |
}
|
|
|
470 |
}
|
|
|
471 |
|
|
|
472 |
/**
|
|
|
473 |
* A request to associate with the server.
|
|
|
474 |
*
|
|
|
475 |
* @access private
|
|
|
476 |
* @package OpenID
|
|
|
477 |
*/
|
|
|
478 |
class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
|
|
|
479 |
var $mode = "associate";
|
|
|
480 |
var $assoc_type = 'HMAC-SHA1';
|
|
|
481 |
|
|
|
482 |
function Auth_OpenID_AssociateRequest(&$session)
|
|
|
483 |
{
|
|
|
484 |
$this->session =& $session;
|
|
|
485 |
}
|
|
|
486 |
|
|
|
487 |
function fromQuery($query)
|
|
|
488 |
{
|
|
|
489 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
490 |
|
|
|
491 |
$session_classes = array(
|
|
|
492 |
'DH-SHA1' => 'Auth_OpenID_DiffieHellmanServerSession',
|
|
|
493 |
null => 'Auth_OpenID_PlainTextServerSession');
|
|
|
494 |
|
|
|
495 |
$session_type = null;
|
|
|
496 |
|
|
|
497 |
if (array_key_exists($_Auth_OpenID_OpenID_Prefix . 'session_type',
|
|
|
498 |
$query)) {
|
|
|
499 |
$session_type = $query[$_Auth_OpenID_OpenID_Prefix .
|
|
|
500 |
'session_type'];
|
|
|
501 |
}
|
|
|
502 |
|
|
|
503 |
if (!array_key_exists($session_type, $session_classes)) {
|
|
|
504 |
return new Auth_OpenID_ServerError($query,
|
|
|
505 |
"Unknown session type $session_type");
|
|
|
506 |
}
|
|
|
507 |
|
|
|
508 |
$session_cls = $session_classes[$session_type];
|
|
|
509 |
$session = call_user_func_array(array($session_cls, 'fromQuery'),
|
|
|
510 |
array($query));
|
|
|
511 |
|
|
|
512 |
if (($session === null) || (_isError($session))) {
|
|
|
513 |
return new Auth_OpenID_ServerError($query,
|
|
|
514 |
"Error parsing $session_type session");
|
|
|
515 |
}
|
|
|
516 |
|
|
|
517 |
return new Auth_OpenID_AssociateRequest($session);
|
|
|
518 |
}
|
|
|
519 |
|
|
|
520 |
function answer($assoc)
|
|
|
521 |
{
|
|
|
522 |
$ml =& Auth_OpenID_getMathLib();
|
|
|
523 |
$response = new Auth_OpenID_ServerResponse($this);
|
|
|
524 |
|
|
|
525 |
$response->fields = array('expires_in' => $assoc->getExpiresIn(),
|
|
|
526 |
'assoc_type' => 'HMAC-SHA1',
|
|
|
527 |
'assoc_handle' => $assoc->handle);
|
|
|
528 |
|
|
|
529 |
$r = $this->session->answer($assoc->secret);
|
|
|
530 |
foreach ($r as $k => $v) {
|
|
|
531 |
$response->fields[$k] = $v;
|
|
|
532 |
}
|
|
|
533 |
|
|
|
534 |
if ($this->session->session_type != 'plaintext') {
|
|
|
535 |
$response->fields['session_type'] = $this->session->session_type;
|
|
|
536 |
}
|
|
|
537 |
|
|
|
538 |
return $response;
|
|
|
539 |
}
|
|
|
540 |
}
|
|
|
541 |
|
|
|
542 |
/**
|
|
|
543 |
* A request to confirm the identity of a user.
|
|
|
544 |
*
|
|
|
545 |
* @access private
|
|
|
546 |
* @package OpenID
|
|
|
547 |
*/
|
|
|
548 |
class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
|
|
|
549 |
var $mode = "checkid_setup"; // or "checkid_immediate"
|
|
|
550 |
var $immediate = false;
|
|
|
551 |
var $trust_root = null;
|
|
|
552 |
|
|
|
553 |
function make($query, $identity, $return_to, $trust_root = null,
|
|
|
554 |
$immediate = false, $assoc_handle = null)
|
|
|
555 |
{
|
|
|
556 |
if (!Auth_OpenID_TrustRoot::_parse($return_to)) {
|
|
|
557 |
return new Auth_OpenID_MalformedReturnURL($query, $return_to);
|
|
|
558 |
}
|
|
|
559 |
|
|
|
560 |
$r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
|
|
|
561 |
$trust_root, $immediate,
|
|
|
562 |
$assoc_handle);
|
|
|
563 |
|
|
|
564 |
if (!$r->trustRootValid()) {
|
|
|
565 |
return new Auth_OpenID_UntrustedReturnURL($return_to,
|
|
|
566 |
$trust_root);
|
|
|
567 |
} else {
|
|
|
568 |
return $r;
|
|
|
569 |
}
|
|
|
570 |
}
|
|
|
571 |
|
|
|
572 |
function Auth_OpenID_CheckIDRequest($identity, $return_to,
|
|
|
573 |
$trust_root = null, $immediate = false,
|
|
|
574 |
$assoc_handle = null)
|
|
|
575 |
{
|
|
|
576 |
$this->identity = $identity;
|
|
|
577 |
$this->return_to = $return_to;
|
|
|
578 |
$this->trust_root = $trust_root;
|
|
|
579 |
$this->assoc_handle = $assoc_handle;
|
|
|
580 |
|
|
|
581 |
if ($immediate) {
|
|
|
582 |
$this->immediate = true;
|
|
|
583 |
$this->mode = "checkid_immediate";
|
|
|
584 |
} else {
|
|
|
585 |
$this->immediate = false;
|
|
|
586 |
$this->mode = "checkid_setup";
|
|
|
587 |
}
|
|
|
588 |
}
|
|
|
589 |
|
|
|
590 |
function fromQuery($query)
|
|
|
591 |
{
|
|
|
592 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
593 |
|
|
|
594 |
$mode = $query[$_Auth_OpenID_OpenID_Prefix . 'mode'];
|
|
|
595 |
$immediate = null;
|
|
|
596 |
|
|
|
597 |
if ($mode == "checkid_immediate") {
|
|
|
598 |
$immediate = true;
|
|
|
599 |
$mode = "checkid_immediate";
|
|
|
600 |
} else {
|
|
|
601 |
$immediate = false;
|
|
|
602 |
$mode = "checkid_setup";
|
|
|
603 |
}
|
|
|
604 |
|
|
|
605 |
$required = array('identity',
|
|
|
606 |
'return_to');
|
|
|
607 |
|
|
|
608 |
$optional = array('trust_root',
|
|
|
609 |
'assoc_handle');
|
|
|
610 |
|
|
|
611 |
$values = array();
|
|
|
612 |
|
|
|
613 |
foreach ($required as $field) {
|
|
|
614 |
if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
|
|
|
615 |
$query)) {
|
|
|
616 |
$value = $query[$_Auth_OpenID_OpenID_Prefix . $field];
|
|
|
617 |
} else {
|
|
|
618 |
return new Auth_OpenID_ServerError($query,
|
|
|
619 |
sprintf("Missing required field %s from request",
|
|
|
620 |
$field));
|
|
|
621 |
}
|
|
|
622 |
$values[$field] = $value;
|
|
|
623 |
}
|
|
|
624 |
|
|
|
625 |
foreach ($optional as $field) {
|
|
|
626 |
$value = null;
|
|
|
627 |
if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
|
|
|
628 |
$query)) {
|
|
|
629 |
$value = $query[$_Auth_OpenID_OpenID_Prefix. $field];
|
|
|
630 |
}
|
|
|
631 |
if ($value) {
|
|
|
632 |
$values[$field] = $value;
|
|
|
633 |
}
|
|
|
634 |
}
|
|
|
635 |
|
|
|
636 |
if (!Auth_OpenID_TrustRoot::_parse($values['return_to'])) {
|
|
|
637 |
return new Auth_OpenID_MalformedReturnURL($query,
|
|
|
638 |
$values['return_to']);
|
|
|
639 |
}
|
|
|
640 |
|
|
|
641 |
$obj = Auth_OpenID_CheckIDRequest::make($query,
|
|
|
642 |
$values['identity'],
|
|
|
643 |
$values['return_to'],
|
|
|
644 |
Auth_OpenID::arrayGet($values,
|
|
|
645 |
'trust_root', null),
|
|
|
646 |
$immediate);
|
|
|
647 |
|
|
|
648 |
if (is_a($obj, 'Auth_OpenID_ServerError')) {
|
|
|
649 |
return $obj;
|
|
|
650 |
}
|
|
|
651 |
|
|
|
652 |
if (Auth_OpenID::arrayGet($values, 'assoc_handle')) {
|
|
|
653 |
$obj->assoc_handle = $values['assoc_handle'];
|
|
|
654 |
}
|
|
|
655 |
|
|
|
656 |
return $obj;
|
|
|
657 |
}
|
|
|
658 |
|
|
|
659 |
function trustRootValid()
|
|
|
660 |
{
|
|
|
661 |
if (!$this->trust_root) {
|
|
|
662 |
return true;
|
|
|
663 |
}
|
|
|
664 |
|
|
|
665 |
$tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
|
|
|
666 |
if ($tr === false) {
|
|
|
667 |
return new Auth_OpenID_MalformedTrustRoot(null, $this->trust_root);
|
|
|
668 |
}
|
|
|
669 |
|
|
|
670 |
return Auth_OpenID_TrustRoot::match($this->trust_root,
|
|
|
671 |
$this->return_to);
|
|
|
672 |
}
|
|
|
673 |
|
|
|
674 |
function answer($allow, $server_url = null)
|
|
|
675 |
{
|
|
|
676 |
if ($allow || $this->immediate) {
|
|
|
677 |
$mode = 'id_res';
|
|
|
678 |
} else {
|
|
|
679 |
$mode = 'cancel';
|
|
|
680 |
}
|
|
|
681 |
|
|
|
682 |
$response = new Auth_OpenID_CheckIDResponse($this, $mode);
|
|
|
683 |
|
|
|
684 |
if ($allow) {
|
|
|
685 |
$response->fields['identity'] = $this->identity;
|
|
|
686 |
$response->fields['return_to'] = $this->return_to;
|
|
|
687 |
if (!$this->trustRootValid()) {
|
|
|
688 |
return new Auth_OpenID_UntrustedReturnURL($this->return_to,
|
|
|
689 |
$this->trust_root);
|
|
|
690 |
}
|
|
|
691 |
} else {
|
|
|
692 |
$response->signed = array();
|
|
|
693 |
if ($this->immediate) {
|
|
|
694 |
if (!$server_url) {
|
|
|
695 |
return new Auth_OpenID_ServerError(null,
|
|
|
696 |
'setup_url is required for $allow=false \
|
|
|
697 |
in immediate mode.');
|
|
|
698 |
}
|
|
|
699 |
|
|
|
700 |
$setup_request =& new Auth_OpenID_CheckIDRequest(
|
|
|
701 |
$this->identity,
|
|
|
702 |
$this->return_to,
|
|
|
703 |
$this->trust_root,
|
|
|
704 |
false,
|
|
|
705 |
$this->assoc_handle);
|
|
|
706 |
|
|
|
707 |
$setup_url = $setup_request->encodeToURL($server_url);
|
|
|
708 |
|
|
|
709 |
$response->fields['user_setup_url'] = $setup_url;
|
|
|
710 |
}
|
|
|
711 |
}
|
|
|
712 |
|
|
|
713 |
return $response;
|
|
|
714 |
}
|
|
|
715 |
|
|
|
716 |
function encodeToURL($server_url)
|
|
|
717 |
{
|
|
|
718 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
719 |
|
|
|
720 |
// Imported from the alternate reality where these classes are
|
|
|
721 |
// used in both the client and server code, so Requests are
|
|
|
722 |
// Encodable too. That's right, code imported from alternate
|
|
|
723 |
// realities all for the love of you, id_res/user_setup_url.
|
|
|
724 |
|
|
|
725 |
$q = array('mode' => $this->mode,
|
|
|
726 |
'identity' => $this->identity,
|
|
|
727 |
'return_to' => $this->return_to);
|
|
|
728 |
|
|
|
729 |
if ($this->trust_root) {
|
|
|
730 |
$q['trust_root'] = $this->trust_root;
|
|
|
731 |
}
|
|
|
732 |
|
|
|
733 |
if ($this->assoc_handle) {
|
|
|
734 |
$q['assoc_handle'] = $this->assoc_handle;
|
|
|
735 |
}
|
|
|
736 |
|
|
|
737 |
$_q = array();
|
|
|
738 |
|
|
|
739 |
foreach ($q as $k => $v) {
|
|
|
740 |
$_q[$_Auth_OpenID_OpenID_Prefix . $k] = $v;
|
|
|
741 |
}
|
|
|
742 |
|
|
|
743 |
return Auth_OpenID::appendArgs($server_url, $_q);
|
|
|
744 |
}
|
|
|
745 |
|
|
|
746 |
function getCancelURL()
|
|
|
747 |
{
|
|
|
748 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
749 |
|
|
|
750 |
if ($this->immediate) {
|
|
|
751 |
return new Auth_OpenID_ServerError(null,
|
|
|
752 |
"Cancel is not an appropriate \
|
|
|
753 |
response to immediate mode \
|
|
|
754 |
requests.");
|
|
|
755 |
}
|
|
|
756 |
|
|
|
757 |
return Auth_OpenID::appendArgs($this->return_to,
|
|
|
758 |
array($_Auth_OpenID_OpenID_Prefix . 'mode' =>
|
|
|
759 |
'cancel'));
|
|
|
760 |
}
|
|
|
761 |
}
|
|
|
762 |
|
|
|
763 |
/**
|
|
|
764 |
* This class encapsulates the response to an OpenID server request.
|
|
|
765 |
*
|
|
|
766 |
* @access private
|
|
|
767 |
* @package OpenID
|
|
|
768 |
*/
|
|
|
769 |
class Auth_OpenID_ServerResponse {
|
|
|
770 |
|
|
|
771 |
function Auth_OpenID_ServerResponse($request)
|
|
|
772 |
{
|
|
|
773 |
$this->request = $request;
|
|
|
774 |
$this->fields = array();
|
|
|
775 |
}
|
|
|
776 |
|
|
|
777 |
function whichEncoding()
|
|
|
778 |
{
|
|
|
779 |
global $_Auth_OpenID_Encode_Kvform,
|
|
|
780 |
$_Auth_OpenID_Request_Modes,
|
|
|
781 |
$_Auth_OpenID_Encode_Url;
|
|
|
782 |
|
|
|
783 |
if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
|
|
|
784 |
return $_Auth_OpenID_Encode_Url;
|
|
|
785 |
} else {
|
|
|
786 |
return $_Auth_OpenID_Encode_Kvform;
|
|
|
787 |
}
|
|
|
788 |
}
|
|
|
789 |
|
|
|
790 |
function encodeToURL()
|
|
|
791 |
{
|
|
|
792 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
793 |
|
|
|
794 |
$fields = array();
|
|
|
795 |
|
|
|
796 |
foreach ($this->fields as $k => $v) {
|
|
|
797 |
$fields[$_Auth_OpenID_OpenID_Prefix . $k] = $v;
|
|
|
798 |
}
|
|
|
799 |
|
|
|
800 |
return Auth_OpenID::appendArgs($this->request->return_to, $fields);
|
|
|
801 |
}
|
|
|
802 |
|
|
|
803 |
function encodeToKVForm()
|
|
|
804 |
{
|
|
|
805 |
return Auth_OpenID_KVForm::fromArray($this->fields);
|
|
|
806 |
}
|
|
|
807 |
}
|
|
|
808 |
|
|
|
809 |
/**
|
|
|
810 |
* A response to a checkid request.
|
|
|
811 |
*
|
|
|
812 |
* @access private
|
|
|
813 |
* @package OpenID
|
|
|
814 |
*/
|
|
|
815 |
class Auth_OpenID_CheckIDResponse extends Auth_OpenID_ServerResponse {
|
|
|
816 |
|
|
|
817 |
function Auth_OpenID_CheckIDResponse(&$request, $mode = 'id_res')
|
|
|
818 |
{
|
|
|
819 |
parent::Auth_OpenID_ServerResponse($request);
|
|
|
820 |
$this->fields['mode'] = $mode;
|
|
|
821 |
$this->signed = array();
|
|
|
822 |
|
|
|
823 |
if ($mode == 'id_res') {
|
|
|
824 |
array_push($this->signed, 'mode', 'identity', 'return_to');
|
|
|
825 |
}
|
|
|
826 |
}
|
|
|
827 |
|
|
|
828 |
function addField($namespace, $key, $value, $signed = true)
|
|
|
829 |
{
|
|
|
830 |
if ($namespace) {
|
|
|
831 |
$key = sprintf('%s.%s', $namespace, $key);
|
|
|
832 |
}
|
|
|
833 |
$this->fields[$key] = $value;
|
|
|
834 |
if ($signed && !in_array($key, $this->signed)) {
|
|
|
835 |
$this->signed[] = $key;
|
|
|
836 |
}
|
|
|
837 |
}
|
|
|
838 |
|
|
|
839 |
function addFields($namespace, $fields, $signed = true)
|
|
|
840 |
{
|
|
|
841 |
foreach ($fields as $k => $v) {
|
|
|
842 |
$this->addField($namespace, $k, $v, $signed);
|
|
|
843 |
}
|
|
|
844 |
}
|
|
|
845 |
|
|
|
846 |
function update($namespace, $other)
|
|
|
847 |
{
|
|
|
848 |
$namespaced_fields = array();
|
|
|
849 |
|
|
|
850 |
foreach ($other->fields as $k => $v) {
|
|
|
851 |
$name = sprintf('%s.%s', $namespace, $k);
|
|
|
852 |
|
|
|
853 |
$namespaced_fields[$name] = $v;
|
|
|
854 |
}
|
|
|
855 |
|
|
|
856 |
$this->fields = array_merge($this->fields, $namespaced_fields);
|
|
|
857 |
$this->signed = array_merge($this->signed, $other->signed);
|
|
|
858 |
}
|
|
|
859 |
}
|
|
|
860 |
|
|
|
861 |
/**
|
|
|
862 |
* A web-capable response object which you can use to generate a
|
|
|
863 |
* user-agent response.
|
|
|
864 |
*
|
|
|
865 |
* @package OpenID
|
|
|
866 |
*/
|
|
|
867 |
class Auth_OpenID_WebResponse {
|
|
|
868 |
var $code = AUTH_OPENID_HTTP_OK;
|
|
|
869 |
var $body = "";
|
|
|
870 |
|
|
|
871 |
function Auth_OpenID_WebResponse($code = null, $headers = null,
|
|
|
872 |
$body = null)
|
|
|
873 |
{
|
|
|
874 |
if ($code) {
|
|
|
875 |
$this->code = $code;
|
|
|
876 |
}
|
|
|
877 |
|
|
|
878 |
if ($headers !== null) {
|
|
|
879 |
$this->headers = $headers;
|
|
|
880 |
} else {
|
|
|
881 |
$this->headers = array();
|
|
|
882 |
}
|
|
|
883 |
|
|
|
884 |
if ($body !== null) {
|
|
|
885 |
$this->body = $body;
|
|
|
886 |
}
|
|
|
887 |
}
|
|
|
888 |
}
|
|
|
889 |
|
|
|
890 |
/**
|
|
|
891 |
* Responsible for the signature of query data and the verification of
|
|
|
892 |
* OpenID signature values.
|
|
|
893 |
*
|
|
|
894 |
* @package OpenID
|
|
|
895 |
*/
|
|
|
896 |
class Auth_OpenID_Signatory {
|
|
|
897 |
|
|
|
898 |
// = 14 * 24 * 60 * 60; # 14 days, in seconds
|
|
|
899 |
var $SECRET_LIFETIME = 1209600;
|
|
|
900 |
|
|
|
901 |
// keys have a bogus server URL in them because the filestore
|
|
|
902 |
// really does expect that key to be a URL. This seems a little
|
|
|
903 |
// silly for the server store, since I expect there to be only one
|
|
|
904 |
// server URL.
|
|
|
905 |
var $normal_key = 'http://localhost/|normal';
|
|
|
906 |
var $dumb_key = 'http://localhost/|dumb';
|
|
|
907 |
|
|
|
908 |
/**
|
|
|
909 |
* Create a new signatory using a given store.
|
|
|
910 |
*/
|
|
|
911 |
function Auth_OpenID_Signatory(&$store)
|
|
|
912 |
{
|
|
|
913 |
// assert store is not None
|
|
|
914 |
$this->store =& $store;
|
|
|
915 |
}
|
|
|
916 |
|
|
|
917 |
/**
|
|
|
918 |
* Verify, using a given association handle, a signature with
|
|
|
919 |
* signed key-value pairs from an HTTP request.
|
|
|
920 |
*/
|
|
|
921 |
function verify($assoc_handle, $sig, $signed_pairs)
|
|
|
922 |
{
|
|
|
923 |
$assoc = $this->getAssociation($assoc_handle, true);
|
|
|
924 |
if (!$assoc) {
|
|
|
925 |
// oidutil.log("failed to get assoc with handle %r to verify sig %r"
|
|
|
926 |
// % (assoc_handle, sig))
|
|
|
927 |
return false;
|
|
|
928 |
}
|
|
|
929 |
|
|
|
930 |
$expected_sig = base64_encode($assoc->sign($signed_pairs));
|
|
|
931 |
|
|
|
932 |
return $sig == $expected_sig;
|
|
|
933 |
}
|
|
|
934 |
|
|
|
935 |
/**
|
|
|
936 |
* Given a response, sign the fields in the response's 'signed'
|
|
|
937 |
* list, and insert the signature into the response.
|
|
|
938 |
*/
|
|
|
939 |
function sign($response)
|
|
|
940 |
{
|
|
|
941 |
$signed_response = $response;
|
|
|
942 |
$assoc_handle = $response->request->assoc_handle;
|
|
|
943 |
|
|
|
944 |
if ($assoc_handle) {
|
|
|
945 |
// normal mode
|
|
|
946 |
$assoc = $this->getAssociation($assoc_handle, false);
|
|
|
947 |
if (!$assoc) {
|
|
|
948 |
// fall back to dumb mode
|
|
|
949 |
$signed_response->fields['invalidate_handle'] = $assoc_handle;
|
|
|
950 |
$assoc = $this->createAssociation(true);
|
|
|
951 |
}
|
|
|
952 |
} else {
|
|
|
953 |
// dumb mode.
|
|
|
954 |
$assoc = $this->createAssociation(true);
|
|
|
955 |
}
|
|
|
956 |
|
|
|
957 |
$signed_response->fields['assoc_handle'] = $assoc->handle;
|
|
|
958 |
$assoc->addSignature($signed_response->signed,
|
|
|
959 |
$signed_response->fields, '');
|
|
|
960 |
return $signed_response;
|
|
|
961 |
}
|
|
|
962 |
|
|
|
963 |
/**
|
|
|
964 |
* Make a new association.
|
|
|
965 |
*/
|
|
|
966 |
function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
|
|
|
967 |
{
|
|
|
968 |
$secret = Auth_OpenID_CryptUtil::getBytes(20);
|
|
|
969 |
$uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
|
|
|
970 |
$handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);
|
|
|
971 |
|
|
|
972 |
$assoc = Auth_OpenID_Association::fromExpiresIn(
|
|
|
973 |
$this->SECRET_LIFETIME, $handle, $secret, $assoc_type);
|
|
|
974 |
|
|
|
975 |
if ($dumb) {
|
|
|
976 |
$key = $this->dumb_key;
|
|
|
977 |
} else {
|
|
|
978 |
$key = $this->normal_key;
|
|
|
979 |
}
|
|
|
980 |
|
|
|
981 |
$this->store->storeAssociation($key, $assoc);
|
|
|
982 |
return $assoc;
|
|
|
983 |
}
|
|
|
984 |
|
|
|
985 |
/**
|
|
|
986 |
* Given an association handle, get the association from the
|
|
|
987 |
* store, or return a ServerError or null if something goes wrong.
|
|
|
988 |
*/
|
|
|
989 |
function getAssociation($assoc_handle, $dumb)
|
|
|
990 |
{
|
|
|
991 |
if ($assoc_handle === null) {
|
|
|
992 |
return new Auth_OpenID_ServerError(null,
|
|
|
993 |
"assoc_handle must not be null");
|
|
|
994 |
}
|
|
|
995 |
|
|
|
996 |
if ($dumb) {
|
|
|
997 |
$key = $this->dumb_key;
|
|
|
998 |
} else {
|
|
|
999 |
$key = $this->normal_key;
|
|
|
1000 |
}
|
|
|
1001 |
|
|
|
1002 |
$assoc = $this->store->getAssociation($key, $assoc_handle);
|
|
|
1003 |
|
|
|
1004 |
if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
|
|
|
1005 |
$this->store->removeAssociation($key, $assoc_handle);
|
|
|
1006 |
$assoc = null;
|
|
|
1007 |
}
|
|
|
1008 |
|
|
|
1009 |
return $assoc;
|
|
|
1010 |
}
|
|
|
1011 |
|
|
|
1012 |
/**
|
|
|
1013 |
* Invalidate a given association handle.
|
|
|
1014 |
*/
|
|
|
1015 |
function invalidate($assoc_handle, $dumb)
|
|
|
1016 |
{
|
|
|
1017 |
if ($dumb) {
|
|
|
1018 |
$key = $this->dumb_key;
|
|
|
1019 |
} else {
|
|
|
1020 |
$key = $this->normal_key;
|
|
|
1021 |
}
|
|
|
1022 |
$this->store->removeAssociation($key, $assoc_handle);
|
|
|
1023 |
}
|
|
|
1024 |
}
|
|
|
1025 |
|
|
|
1026 |
/**
|
|
|
1027 |
* Encode an Auth_OpenID_Response to an Auth_OpenID_WebResponse.
|
|
|
1028 |
*
|
|
|
1029 |
* @package OpenID
|
|
|
1030 |
*/
|
|
|
1031 |
class Auth_OpenID_Encoder {
|
|
|
1032 |
|
|
|
1033 |
var $responseFactory = 'Auth_OpenID_WebResponse';
|
|
|
1034 |
|
|
|
1035 |
/**
|
|
|
1036 |
* Encode an Auth_OpenID_Response and return an
|
|
|
1037 |
* Auth_OpenID_WebResponse.
|
|
|
1038 |
*/
|
|
|
1039 |
function encode(&$response)
|
|
|
1040 |
{
|
|
|
1041 |
global $_Auth_OpenID_Encode_Kvform,
|
|
|
1042 |
$_Auth_OpenID_Encode_Url;
|
|
|
1043 |
|
|
|
1044 |
$cls = $this->responseFactory;
|
|
|
1045 |
|
|
|
1046 |
$encode_as = $response->whichEncoding();
|
|
|
1047 |
if ($encode_as == $_Auth_OpenID_Encode_Kvform) {
|
|
|
1048 |
$wr = new $cls(null, null, $response->encodeToKVForm());
|
|
|
1049 |
if (is_a($response, 'Auth_OpenID_ServerError')) {
|
|
|
1050 |
$wr->code = AUTH_OPENID_HTTP_ERROR;
|
|
|
1051 |
}
|
|
|
1052 |
} else if ($encode_as == $_Auth_OpenID_Encode_Url) {
|
|
|
1053 |
$location = $response->encodeToURL();
|
|
|
1054 |
$wr = new $cls(AUTH_OPENID_HTTP_REDIRECT,
|
|
|
1055 |
array('location' => $location));
|
|
|
1056 |
} else {
|
|
|
1057 |
return new Auth_OpenID_EncodingError($response);
|
|
|
1058 |
}
|
|
|
1059 |
return $wr;
|
|
|
1060 |
}
|
|
|
1061 |
}
|
|
|
1062 |
|
|
|
1063 |
/**
|
|
|
1064 |
* Returns true if the given response needs a signature.
|
|
|
1065 |
*
|
|
|
1066 |
* @access private
|
|
|
1067 |
*/
|
|
|
1068 |
function needsSigning($response)
|
|
|
1069 |
{
|
|
|
1070 |
return (in_array($response->request->mode, array('checkid_setup',
|
|
|
1071 |
'checkid_immediate')) &&
|
|
|
1072 |
$response->signed);
|
|
|
1073 |
}
|
|
|
1074 |
|
|
|
1075 |
/**
|
|
|
1076 |
* An encoder which also takes care of signing fields when required.
|
|
|
1077 |
*
|
|
|
1078 |
* @package OpenID
|
|
|
1079 |
*/
|
|
|
1080 |
class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {
|
|
|
1081 |
|
|
|
1082 |
function Auth_OpenID_SigningEncoder(&$signatory)
|
|
|
1083 |
{
|
|
|
1084 |
$this->signatory =& $signatory;
|
|
|
1085 |
}
|
|
|
1086 |
|
|
|
1087 |
/**
|
|
|
1088 |
* Sign an Auth_OpenID_Response and return an
|
|
|
1089 |
* Auth_OpenID_WebResponse.
|
|
|
1090 |
*/
|
|
|
1091 |
function encode(&$response)
|
|
|
1092 |
{
|
|
|
1093 |
// the isinstance is a bit of a kludge... it means there isn't
|
|
|
1094 |
// really an adapter to make the interfaces quite match.
|
|
|
1095 |
if (!is_a($response, 'Auth_OpenID_ServerError') &&
|
|
|
1096 |
needsSigning($response)) {
|
|
|
1097 |
|
|
|
1098 |
if (!$this->signatory) {
|
|
|
1099 |
return new Auth_OpenID_ServerError(null,
|
|
|
1100 |
"Must have a store to sign request");
|
|
|
1101 |
}
|
|
|
1102 |
if (array_key_exists('sig', $response->fields)) {
|
|
|
1103 |
return new Auth_OpenID_AlreadySigned($response);
|
|
|
1104 |
}
|
|
|
1105 |
$response = $this->signatory->sign($response);
|
|
|
1106 |
}
|
|
|
1107 |
return parent::encode($response);
|
|
|
1108 |
}
|
|
|
1109 |
}
|
|
|
1110 |
|
|
|
1111 |
/**
|
|
|
1112 |
* Decode an incoming Auth_OpenID_WebResponse into an
|
|
|
1113 |
* Auth_OpenID_Request.
|
|
|
1114 |
*
|
|
|
1115 |
* @package OpenID
|
|
|
1116 |
*/
|
|
|
1117 |
class Auth_OpenID_Decoder {
|
|
|
1118 |
|
|
|
1119 |
function Auth_OpenID_Decoder()
|
|
|
1120 |
{
|
|
|
1121 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
1122 |
$this->prefix = $_Auth_OpenID_OpenID_Prefix;
|
|
|
1123 |
|
|
|
1124 |
$this->handlers = array(
|
|
|
1125 |
'checkid_setup' => 'Auth_OpenID_CheckIDRequest',
|
|
|
1126 |
'checkid_immediate' => 'Auth_OpenID_CheckIDRequest',
|
|
|
1127 |
'check_authentication' => 'Auth_OpenID_CheckAuthRequest',
|
|
|
1128 |
'associate' => 'Auth_OpenID_AssociateRequest'
|
|
|
1129 |
);
|
|
|
1130 |
}
|
|
|
1131 |
|
|
|
1132 |
/**
|
|
|
1133 |
* Given an HTTP query in an array (key-value pairs), decode it
|
|
|
1134 |
* into an Auth_OpenID_Request object.
|
|
|
1135 |
*/
|
|
|
1136 |
function decode($query)
|
|
|
1137 |
{
|
|
|
1138 |
if (!$query) {
|
|
|
1139 |
return null;
|
|
|
1140 |
}
|
|
|
1141 |
|
|
|
1142 |
$myquery = array();
|
|
|
1143 |
|
|
|
1144 |
foreach ($query as $k => $v) {
|
|
|
1145 |
if (strpos($k, $this->prefix) === 0) {
|
|
|
1146 |
$myquery[$k] = $v;
|
|
|
1147 |
}
|
|
|
1148 |
}
|
|
|
1149 |
|
|
|
1150 |
if (!$myquery) {
|
|
|
1151 |
return null;
|
|
|
1152 |
}
|
|
|
1153 |
|
|
|
1154 |
$mode = Auth_OpenID::arrayGet($myquery, $this->prefix . 'mode');
|
|
|
1155 |
if (!$mode) {
|
|
|
1156 |
return new Auth_OpenID_ServerError($query,
|
|
|
1157 |
sprintf("No %s mode found in query", $this->prefix));
|
|
|
1158 |
}
|
|
|
1159 |
|
|
|
1160 |
$handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
|
|
|
1161 |
$this->defaultDecoder($query));
|
|
|
1162 |
|
|
|
1163 |
if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
|
|
|
1164 |
return call_user_func_array(array($handlerCls, 'fromQuery'),
|
|
|
1165 |
array($query));
|
|
|
1166 |
} else {
|
|
|
1167 |
return $handlerCls;
|
|
|
1168 |
}
|
|
|
1169 |
}
|
|
|
1170 |
|
|
|
1171 |
function defaultDecoder($query)
|
|
|
1172 |
{
|
|
|
1173 |
$mode = $query[$this->prefix . 'mode'];
|
|
|
1174 |
return new Auth_OpenID_ServerError($query,
|
|
|
1175 |
sprintf("No decoder for mode %s", $mode));
|
|
|
1176 |
}
|
|
|
1177 |
}
|
|
|
1178 |
|
|
|
1179 |
/**
|
|
|
1180 |
* An error that indicates an encoding problem occurred.
|
|
|
1181 |
*
|
|
|
1182 |
* @package OpenID
|
|
|
1183 |
*/
|
|
|
1184 |
class Auth_OpenID_EncodingError {
|
|
|
1185 |
function Auth_OpenID_EncodingError(&$response)
|
|
|
1186 |
{
|
|
|
1187 |
$this->response =& $response;
|
|
|
1188 |
}
|
|
|
1189 |
}
|
|
|
1190 |
|
|
|
1191 |
/**
|
|
|
1192 |
* An error that indicates that a response was already signed.
|
|
|
1193 |
*
|
|
|
1194 |
* @package OpenID
|
|
|
1195 |
*/
|
|
|
1196 |
class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
|
|
|
1197 |
// This response is already signed.
|
|
|
1198 |
}
|
|
|
1199 |
|
|
|
1200 |
/**
|
|
|
1201 |
* An error that indicates that the given return_to is not under the
|
|
|
1202 |
* given trust_root.
|
|
|
1203 |
*
|
|
|
1204 |
* @package OpenID
|
|
|
1205 |
*/
|
|
|
1206 |
class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
|
|
|
1207 |
function Auth_OpenID_UntrustedReturnURL($return_to, $trust_root)
|
|
|
1208 |
{
|
|
|
1209 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
1210 |
|
|
|
1211 |
$query = array(
|
|
|
1212 |
$_Auth_OpenID_OpenID_Prefix . 'return_to' => $return_to,
|
|
|
1213 |
$_Auth_OpenID_OpenID_Prefix . 'trust_root' => $trust_root);
|
|
|
1214 |
|
|
|
1215 |
parent::Auth_OpenID_ServerError($query);
|
|
|
1216 |
}
|
|
|
1217 |
|
|
|
1218 |
function toString()
|
|
|
1219 |
{
|
|
|
1220 |
global $_Auth_OpenID_OpenID_Prefix;
|
|
|
1221 |
|
|
|
1222 |
$return_to = $this->query[$_Auth_OpenID_OpenID_Prefix . 'return_to'];
|
|
|
1223 |
$trust_root = $this->query[$_Auth_OpenID_OpenID_Prefix . 'trust_root'];
|
|
|
1224 |
|
|
|
1225 |
return sprintf("return_to %s not under trust_root %s",
|
|
|
1226 |
$return_to, $trust_root);
|
|
|
1227 |
}
|
|
|
1228 |
}
|
|
|
1229 |
|
|
|
1230 |
/**
|
|
|
1231 |
* An object that implements the OpenID protocol for a single URL.
|
|
|
1232 |
*
|
|
|
1233 |
* Use this object by calling getOpenIDResponse when you get any
|
|
|
1234 |
* request for the server URL.
|
|
|
1235 |
*
|
|
|
1236 |
* @package OpenID
|
|
|
1237 |
*/
|
|
|
1238 |
class Auth_OpenID_Server {
|
|
|
1239 |
function Auth_OpenID_Server(&$store)
|
|
|
1240 |
{
|
|
|
1241 |
$this->store =& $store;
|
|
|
1242 |
$this->signatory =& new Auth_OpenID_Signatory($this->store);
|
|
|
1243 |
$this->encoder =& new Auth_OpenID_SigningEncoder($this->signatory);
|
|
|
1244 |
$this->decoder =& new Auth_OpenID_Decoder();
|
|
|
1245 |
}
|
|
|
1246 |
|
|
|
1247 |
/**
|
|
|
1248 |
* Handle a request. Given an Auth_OpenID_Request object, call
|
|
|
1249 |
* the appropriate Auth_OpenID_Server method to process the
|
|
|
1250 |
* request and generate a response.
|
|
|
1251 |
*
|
|
|
1252 |
* @param Auth_OpenID_Request $request An Auth_OpenID_Request
|
|
|
1253 |
* returned by Auth_OpenID_Server::decodeRequest.
|
|
|
1254 |
*
|
|
|
1255 |
* @return Auth_OpenID_Response $response A response object
|
|
|
1256 |
* capable of generating a user-agent reply.
|
|
|
1257 |
*/
|
|
|
1258 |
function handleRequest($request)
|
|
|
1259 |
{
|
|
|
1260 |
if (method_exists($this, "openid_" . $request->mode)) {
|
|
|
1261 |
$handler = array($this, "openid_" . $request->mode);
|
|
|
1262 |
return call_user_func($handler, $request);
|
|
|
1263 |
}
|
|
|
1264 |
return null;
|
|
|
1265 |
}
|
|
|
1266 |
|
|
|
1267 |
/**
|
|
|
1268 |
* The callback for 'check_authentication' messages.
|
|
|
1269 |
*
|
|
|
1270 |
* @access private
|
|
|
1271 |
*/
|
|
|
1272 |
function openid_check_authentication(&$request)
|
|
|
1273 |
{
|
|
|
1274 |
return $request->answer($this->signatory);
|
|
|
1275 |
}
|
|
|
1276 |
|
|
|
1277 |
/**
|
|
|
1278 |
* The callback for 'associate' messages.
|
|
|
1279 |
*
|
|
|
1280 |
* @access private
|
|
|
1281 |
*/
|
|
|
1282 |
function openid_associate(&$request)
|
|
|
1283 |
{
|
|
|
1284 |
$assoc = $this->signatory->createAssociation(false);
|
|
|
1285 |
return $request->answer($assoc);
|
|
|
1286 |
}
|
|
|
1287 |
|
|
|
1288 |
/**
|
|
|
1289 |
* Encodes as response in the appropriate format suitable for
|
|
|
1290 |
* sending to the user agent.
|
|
|
1291 |
*/
|
|
|
1292 |
function encodeResponse(&$response)
|
|
|
1293 |
{
|
|
|
1294 |
return $this->encoder->encode($response);
|
|
|
1295 |
}
|
|
|
1296 |
|
|
|
1297 |
/**
|
|
|
1298 |
* Decodes a query args array into the appropriate
|
|
|
1299 |
* Auth_OpenID_Request object.
|
|
|
1300 |
*/
|
|
|
1301 |
function decodeRequest(&$query)
|
|
|
1302 |
{
|
|
|
1303 |
return $this->decoder->decode($query);
|
|
|
1304 |
}
|
|
|
1305 |
}
|
|
|
1306 |
|
|
|
1307 |
?>
|