Subversion Repositories Applications.annuaire

Rev

Rev 42 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
?>