ILIAS  release_4-3 Revision
 All Data Structures Namespaces Files Functions Variables Groups Pages
Server.php
Go to the documentation of this file.
1 <?php
2 
95 require_once "Auth/OpenID.php";
96 require_once "Auth/OpenID/Association.php";
97 require_once "Auth/OpenID/CryptUtil.php";
98 require_once "Auth/OpenID/BigMath.php";
99 require_once "Auth/OpenID/DiffieHellman.php";
100 require_once "Auth/OpenID/KVForm.php";
101 require_once "Auth/OpenID/TrustRoot.php";
102 require_once "Auth/OpenID/ServerRequest.php";
103 require_once "Auth/OpenID/Message.php";
104 require_once "Auth/OpenID/Nonce.php";
105 
106 define('AUTH_OPENID_HTTP_OK', 200);
107 define('AUTH_OPENID_HTTP_REDIRECT', 302);
108 define('AUTH_OPENID_HTTP_ERROR', 400);
109 
114 $_Auth_OpenID_Request_Modes = array('checkid_setup',
115  'checkid_immediate');
116 
120 define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');
121 
125 define('Auth_OpenID_ENCODE_URL', 'URL/redirect');
126 
130 define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');
131 
135 function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError')
136 {
137  return is_a($obj, $cls);
138 }
139 
151  function Auth_OpenID_ServerError($message = null, $text = null,
152  $reference = null, $contact = null)
153  {
154  $this->message = $message;
155  $this->text = $text;
156  $this->contact = $contact;
157  $this->reference = $reference;
158  }
159 
160  function getReturnTo()
161  {
162  if ($this->message &&
163  $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) {
164  return $this->message->getArg(Auth_OpenID_OPENID_NS,
165  'return_to');
166  } else {
167  return null;
168  }
169  }
170 
175  function hasReturnTo()
176  {
177  return $this->getReturnTo() !== null;
178  }
179 
185  function encodeToURL()
186  {
187  if (!$this->message) {
188  return null;
189  }
190 
191  $msg = $this->toMessage();
192  return $msg->toURL($this->getReturnTo());
193  }
194 
201  function encodeToKVForm()
202  {
204  array('mode' => 'error',
205  'error' => $this->toString()));
206  }
207 
208  function toFormMarkup($form_tag_attrs=null)
209  {
210  $msg = $this->toMessage();
211  return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs);
212  }
213 
214  function toHTML($form_tag_attrs=null)
215  {
217  $this->toFormMarkup($form_tag_attrs));
218  }
219 
220  function toMessage()
221  {
222  // Generate a Message object for sending to the relying party,
223  // after encoding.
224  $namespace = $this->message->getOpenIDNamespace();
225  $reply = new Auth_OpenID_Message($namespace);
226  $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error');
227  $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString());
228 
229  if ($this->contact !== null) {
230  $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact);
231  }
232 
233  if ($this->reference !== null) {
234  $reply->setArg(Auth_OpenID_OPENID_NS, 'reference',
235  $this->reference);
236  }
237 
238  return $reply;
239  }
240 
246  function whichEncoding()
247  {
249 
250  if ($this->hasReturnTo()) {
251  if ($this->message->isOpenID2() &&
252  (strlen($this->encodeToURL()) >
255  } else {
256  return Auth_OpenID_ENCODE_URL;
257  }
258  }
259 
260  if (!$this->message) {
261  return null;
262  }
263 
264  $mode = $this->message->getArg(Auth_OpenID_OPENID_NS,
265  'mode');
266 
267  if ($mode) {
268  if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
270  }
271  }
272  return null;
273  }
274 
278  function toString()
279  {
280  if ($this->text) {
281  return $this->text;
282  } else {
283  return get_class($this) . " error";
284  }
285  }
286 }
287 
295  function Auth_OpenID_NoReturnToError($message = null,
296  $text = "No return_to URL available")
297  {
298  parent::Auth_OpenID_ServerError($message, $text);
299  }
300 
301  function toString()
302  {
303  return "No return_to available";
304  }
305 }
306 
313  function Auth_OpenID_MalformedReturnURL($message, $return_to)
314  {
315  $this->return_to = $return_to;
316  parent::Auth_OpenID_ServerError($message, "malformed return_to URL");
317  }
318 }
319 
326  function Auth_OpenID_MalformedTrustRoot($message = null,
327  $text = "Malformed trust root")
328  {
329  parent::Auth_OpenID_ServerError($message, $text);
330  }
331 
332  function toString()
333  {
334  return "Malformed trust root";
335  }
336 }
337 
344  var $mode = null;
345 }
346 
353  var $mode = "check_authentication";
354  var $invalidate_handle = null;
355 
356  function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed,
357  $invalidate_handle = null)
358  {
359  $this->assoc_handle = $assoc_handle;
360  $this->signed = $signed;
361  if ($invalidate_handle !== null) {
362  $this->invalidate_handle = $invalidate_handle;
363  }
364  $this->namespace = Auth_OpenID_OPENID2_NS;
365  $this->message = null;
366  }
367 
368  static function fromMessage($message, $server=null)
369  {
370  $required_keys = array('assoc_handle', 'sig', 'signed');
371 
372  foreach ($required_keys as $k) {
373  if (!$message->getArg(Auth_OpenID_OPENID_NS, $k)) {
374  return new Auth_OpenID_ServerError($message,
375  sprintf("%s request missing required parameter %s from \
376  query", "check_authentication", $k));
377  }
378  }
379 
380  $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
381  $sig = $message->getArg(Auth_OpenID_OPENID_NS, 'sig');
382 
383  $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
384  $signed_list = explode(",", $signed_list);
385 
386  $signed = $message;
387  if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) {
388  $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
389  }
390 
391  $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed);
392  $result->message = $message;
393  $result->sig = $sig;
394  $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS,
395  'invalidate_handle');
396  return $result;
397  }
398 
399  function answer($signatory)
400  {
401  $is_valid = $signatory->verify($this->assoc_handle, $this->signed);
402 
403  // Now invalidate that assoc_handle so it this checkAuth
404  // message cannot be replayed.
405  $signatory->invalidate($this->assoc_handle, true);
406  $response = new Auth_OpenID_ServerResponse($this);
407 
408  $response->fields->setArg(Auth_OpenID_OPENID_NS,
409  'is_valid',
410  ($is_valid ? "true" : "false"));
411 
412  if ($this->invalidate_handle) {
413  $assoc = $signatory->getAssociation($this->invalidate_handle,
414  false);
415  if (!$assoc) {
416  $response->fields->setArg(Auth_OpenID_OPENID_NS,
417  'invalidate_handle',
418  $this->invalidate_handle);
419  }
420  }
421  return $response;
422  }
423 }
424 
435  var $session_type = 'no-encryption';
436  var $needs_math = false;
437  var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');
438 
439  static function fromMessage($unused_request)
440  {
442  }
443 
444  function answer($secret)
445  {
446  return array('mac_key' => base64_encode($secret));
447  }
448 }
449 
461  var $session_type = 'DH-SHA1';
462  var $needs_math = true;
463  var $allowed_assoc_types = array('HMAC-SHA1');
464  var $hash_func = 'Auth_OpenID_SHA1';
465 
466  function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey)
467  {
468  $this->dh = $dh;
469  $this->consumer_pubkey = $consumer_pubkey;
470  }
471 
472  static function getDH($message)
473  {
474  $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus');
475  $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen');
476 
477  if ((($dh_modulus === null) && ($dh_gen !== null)) ||
478  (($dh_gen === null) && ($dh_modulus !== null))) {
479 
480  if ($dh_modulus === null) {
481  $missing = 'modulus';
482  } else {
483  $missing = 'generator';
484  }
485 
486  return new Auth_OpenID_ServerError($message,
487  'If non-default modulus or generator is '.
488  'supplied, both must be supplied. Missing '.
489  $missing);
490  }
491 
492  $lib = Auth_OpenID_getMathLib();
493 
494  if ($dh_modulus || $dh_gen) {
495  $dh_modulus = $lib->base64ToLong($dh_modulus);
496  $dh_gen = $lib->base64ToLong($dh_gen);
497  if ($lib->cmp($dh_modulus, 0) == 0 ||
498  $lib->cmp($dh_gen, 0) == 0) {
499  return new Auth_OpenID_ServerError(
500  $message, "Failed to parse dh_mod or dh_gen");
501  }
502  $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
503  } else {
504  $dh = new Auth_OpenID_DiffieHellman();
505  }
506 
507  $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS,
508  'dh_consumer_public');
509  if ($consumer_pubkey === null) {
510  return new Auth_OpenID_ServerError($message,
511  'Public key for DH-SHA1 session '.
512  'not found in query');
513  }
514 
515  $consumer_pubkey =
516  $lib->base64ToLong($consumer_pubkey);
517 
518  if ($consumer_pubkey === false) {
519  return new Auth_OpenID_ServerError($message,
520  "dh_consumer_public is not base64");
521  }
522 
523  return array($dh, $consumer_pubkey);
524  }
525 
526  static function fromMessage($message)
527  {
529 
530  if (is_a($result, 'Auth_OpenID_ServerError')) {
531  return $result;
532  } else {
533  list($dh, $consumer_pubkey) = $result;
535  $consumer_pubkey);
536  }
537  }
538 
539  function answer($secret)
540  {
541  $lib = Auth_OpenID_getMathLib();
542  $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret,
543  $this->hash_func);
544  return array(
545  'dh_server_public' =>
546  $lib->longToBase64($this->dh->public),
547  'enc_mac_key' => base64_encode($mac_key));
548  }
549 }
550 
558 
559  var $session_type = 'DH-SHA256';
560  var $hash_func = 'Auth_OpenID_SHA256';
561  var $allowed_assoc_types = array('HMAC-SHA256');
562 
563  static function fromMessage($message)
564  {
566 
567  if (is_a($result, 'Auth_OpenID_ServerError')) {
568  return $result;
569  } else {
570  list($dh, $consumer_pubkey) = $result;
572  $consumer_pubkey);
573  }
574  }
575 }
576 
583  var $mode = "associate";
584 
585  static function getSessionClasses()
586  {
587  return array(
588  'no-encryption' => 'Auth_OpenID_PlainTextServerSession',
589  'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession',
590  'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession');
591  }
592 
593  function Auth_OpenID_AssociateRequest($session, $assoc_type)
594  {
595  $this->session = $session;
596  $this->namespace = Auth_OpenID_OPENID2_NS;
597  $this->assoc_type = $assoc_type;
598  }
599 
600  static function fromMessage($message, $server=null)
601  {
602  if ($message->isOpenID1()) {
603  $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
604  'session_type');
605 
606  if ($session_type == 'no-encryption') {
607  // oidutil.log('Received OpenID 1 request with a no-encryption '
608  // 'assocaition session type. Continuing anyway.')
609  } else if (!$session_type) {
610  $session_type = 'no-encryption';
611  }
612  } else {
613  $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
614  'session_type');
615  if ($session_type === null) {
616  return new Auth_OpenID_ServerError($message,
617  "session_type missing from request");
618  }
619  }
620 
621  $session_class = Auth_OpenID::arrayGet(
623  $session_type);
624 
625  if ($session_class === null) {
626  return new Auth_OpenID_ServerError($message,
627  "Unknown session type " .
628  $session_type);
629  }
630 
631  $session = call_user_func(array($session_class, 'fromMessage'),
632  $message);
633  if (is_a($session, 'Auth_OpenID_ServerError')) {
634  return $session;
635  }
636 
637  $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS,
638  'assoc_type', 'HMAC-SHA1');
639 
640  if (!in_array($assoc_type, $session->allowed_assoc_types)) {
641  $fmt = "Session type %s does not support association type %s";
642  return new Auth_OpenID_ServerError($message,
643  sprintf($fmt, $session_type, $assoc_type));
644  }
645 
646  $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type);
647  $obj->message = $message;
648  $obj->namespace = $message->getOpenIDNamespace();
649  return $obj;
650  }
651 
652  function answer($assoc)
653  {
654  $response = new Auth_OpenID_ServerResponse($this);
655  $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
656  array(
657  'expires_in' => sprintf('%d', $assoc->getExpiresIn()),
658  'assoc_type' => $this->assoc_type,
659  'assoc_handle' => $assoc->handle));
660 
661  $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
662  $this->session->answer($assoc->secret));
663 
664  if (! ($this->session->session_type == 'no-encryption'
665  && $this->message->isOpenID1())) {
666  $response->fields->setArg(Auth_OpenID_OPENID_NS,
667  'session_type',
668  $this->session->session_type);
669  }
670 
671  return $response;
672  }
673 
674  function answerUnsupported($text_message,
675  $preferred_association_type=null,
676  $preferred_session_type=null)
677  {
678  if ($this->message->isOpenID1()) {
679  return new Auth_OpenID_ServerError($this->message);
680  }
681 
682  $response = new Auth_OpenID_ServerResponse($this);
683  $response->fields->setArg(Auth_OpenID_OPENID_NS,
684  'error_code', 'unsupported-type');
685  $response->fields->setArg(Auth_OpenID_OPENID_NS,
686  'error', $text_message);
687 
688  if ($preferred_association_type) {
689  $response->fields->setArg(Auth_OpenID_OPENID_NS,
690  'assoc_type',
691  $preferred_association_type);
692  }
693 
694  if ($preferred_session_type) {
695  $response->fields->setArg(Auth_OpenID_OPENID_NS,
696  'session_type',
697  $preferred_session_type);
698  }
699  $response->code = AUTH_OPENID_HTTP_ERROR;
700  return $response;
701  }
702 }
703 
714  var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo';
715 
719  var $mode = "checkid_setup"; // or "checkid_immediate"
720 
724  var $immediate = false;
725 
729  var $trust_root = null;
730 
736 
737  static function make($message, $identity, $return_to, $trust_root = null,
738  $immediate = false, $assoc_handle = null, $server = null)
739  {
740  if ($server === null) {
741  return new Auth_OpenID_ServerError($message,
742  "server must not be null");
743  }
744 
745  if ($return_to &&
746  !Auth_OpenID_TrustRoot::_parse($return_to)) {
747  return new Auth_OpenID_MalformedReturnURL($message, $return_to);
748  }
749 
750  $r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
752  $assoc_handle, $server);
753 
754  $r->namespace = $message->getOpenIDNamespace();
755  $r->message = $message;
756 
757  if (!$r->trustRootValid()) {
758  return new Auth_OpenID_UntrustedReturnURL($message,
759  $return_to,
760  $trust_root);
761  } else {
762  return $r;
763  }
764  }
765 
766  function Auth_OpenID_CheckIDRequest($identity, $return_to,
767  $trust_root = null, $immediate = false,
768  $assoc_handle = null, $server = null,
769  $claimed_id = null)
770  {
771  $this->namespace = Auth_OpenID_OPENID2_NS;
772  $this->assoc_handle = $assoc_handle;
773  $this->identity = $identity;
774  if ($claimed_id === null) {
775  $this->claimed_id = $identity;
776  } else {
777  $this->claimed_id = $claimed_id;
778  }
779  $this->return_to = $return_to;
780  $this->trust_root = $trust_root;
781  $this->server = $server;
782 
783  if ($immediate) {
784  $this->immediate = true;
785  $this->mode = "checkid_immediate";
786  } else {
787  $this->immediate = false;
788  $this->mode = "checkid_setup";
789  }
790  }
791 
792  function equals($other)
793  {
794  return (
795  (is_a($other, 'Auth_OpenID_CheckIDRequest')) &&
796  ($this->namespace == $other->namespace) &&
797  ($this->assoc_handle == $other->assoc_handle) &&
798  ($this->identity == $other->identity) &&
799  ($this->claimed_id == $other->claimed_id) &&
800  ($this->return_to == $other->return_to) &&
801  ($this->trust_root == $other->trust_root));
802  }
803 
804  /*
805  * Does the relying party publish the return_to URL for this
806  * response under the realm? It is up to the provider to set a
807  * policy for what kinds of realms should be allowed. This
808  * return_to URL verification reduces vulnerability to data-theft
809  * attacks based on open proxies, corss-site-scripting, or open
810  * redirectors.
811  *
812  * This check should only be performed after making sure that the
813  * return_to URL matches the realm.
814  *
815  * @return true if the realm publishes a document with the
816  * return_to URL listed, false if not or if discovery fails
817  */
818  function returnToVerified()
819  {
821  return call_user_func_array($this->verifyReturnTo,
822  array($this->trust_root, $this->return_to, $fetcher));
823  }
824 
825  static function fromMessage($message, $server)
826  {
827  $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
828  $immediate = null;
829 
830  if ($mode == "checkid_immediate") {
831  $immediate = true;
832  $mode = "checkid_immediate";
833  } else {
834  $immediate = false;
835  $mode = "checkid_setup";
836  }
837 
838  $return_to = $message->getArg(Auth_OpenID_OPENID_NS,
839  'return_to');
840 
841  if (($message->isOpenID1()) &&
842  (!$return_to)) {
843  $fmt = "Missing required field 'return_to' from checkid request";
844  return new Auth_OpenID_ServerError($message, $fmt);
845  }
846 
847  $identity = $message->getArg(Auth_OpenID_OPENID_NS,
848  'identity');
849  $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id');
850  if ($message->isOpenID1()) {
851  if ($identity === null) {
852  $s = "OpenID 1 message did not contain openid.identity";
853  return new Auth_OpenID_ServerError($message, $s);
854  }
855  } else {
856  if ($identity && !$claimed_id) {
857  $s = "OpenID 2.0 message contained openid.identity but not " .
858  "claimed_id";
859  return new Auth_OpenID_ServerError($message, $s);
860  } else if ($claimed_id && !$identity) {
861  $s = "OpenID 2.0 message contained openid.claimed_id " .
862  "but not identity";
863  return new Auth_OpenID_ServerError($message, $s);
864  }
865  }
866 
867  // There's a case for making self.trust_root be a TrustRoot
868  // here. But if TrustRoot isn't currently part of the
869  // "public" API, I'm not sure it's worth doing.
870  if ($message->isOpenID1()) {
871  $trust_root_param = 'trust_root';
872  } else {
873  $trust_root_param = 'realm';
874  }
875  $trust_root = $message->getArg(Auth_OpenID_OPENID_NS,
876  $trust_root_param);
877  if (! $trust_root) {
878  $trust_root = $return_to;
879  }
880 
881  if (! $message->isOpenID1() &&
882  ($return_to === null) &&
883  ($trust_root === null)) {
884  return new Auth_OpenID_ServerError($message,
885  "openid.realm required when openid.return_to absent");
886  }
887 
888  $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
889  'assoc_handle');
890 
891  $obj = Auth_OpenID_CheckIDRequest::make($message,
892  $identity,
893  $return_to,
894  $trust_root,
895  $immediate,
896  $assoc_handle,
897  $server);
898 
899  if (is_a($obj, 'Auth_OpenID_ServerError')) {
900  return $obj;
901  }
902 
903  $obj->claimed_id = $claimed_id;
904 
905  return $obj;
906  }
907 
908  function idSelect()
909  {
910  // Is the identifier to be selected by the IDP?
911  // So IDPs don't have to import the constant
912  return $this->identity == Auth_OpenID_IDENTIFIER_SELECT;
913  }
914 
915  function trustRootValid()
916  {
917  if (!$this->trust_root) {
918  return true;
919  }
920 
921  $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
922  if ($tr === false) {
923  return new Auth_OpenID_MalformedTrustRoot($this->message,
924  $this->trust_root);
925  }
926 
927  if ($this->return_to !== null) {
928  return Auth_OpenID_TrustRoot::match($this->trust_root,
929  $this->return_to);
930  } else {
931  return true;
932  }
933  }
934 
974  function answer($allow, $server_url = null, $identity = null,
975  $claimed_id = null)
976  {
977  if (!$this->return_to) {
978  return new Auth_OpenID_NoReturnToError();
979  }
980 
981  if (!$server_url) {
982  if ((!$this->message->isOpenID1()) &&
983  (!$this->server->op_endpoint)) {
984  return new Auth_OpenID_ServerError(null,
985  "server should be constructed with op_endpoint to " .
986  "respond to OpenID 2.0 messages.");
987  }
988 
989  $server_url = $this->server->op_endpoint;
990  }
991 
992  if ($allow) {
993  $mode = 'id_res';
994  } else if ($this->message->isOpenID1()) {
995  if ($this->immediate) {
996  $mode = 'id_res';
997  } else {
998  $mode = 'cancel';
999  }
1000  } else {
1001  if ($this->immediate) {
1002  $mode = 'setup_needed';
1003  } else {
1004  $mode = 'cancel';
1005  }
1006  }
1007 
1008  if (!$this->trustRootValid()) {
1009  return new Auth_OpenID_UntrustedReturnURL(null,
1010  $this->return_to,
1011  $this->trust_root);
1012  }
1013 
1014  $response = new Auth_OpenID_ServerResponse($this);
1015 
1016  if ($claimed_id &&
1017  ($this->message->isOpenID1())) {
1018  return new Auth_OpenID_ServerError(null,
1019  "claimed_id is new in OpenID 2.0 and not " .
1020  "available for ".$this->namespace);
1021  }
1022 
1023  if ($identity && !$claimed_id) {
1024  $claimed_id = $identity;
1025  }
1026 
1027  if ($allow) {
1028 
1029  if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) {
1030  if (!$identity) {
1031  return new Auth_OpenID_ServerError(null,
1032  "This request uses IdP-driven identifier selection. " .
1033  "You must supply an identifier in the response.");
1034  }
1035 
1036  $response_identity = $identity;
1037  $response_claimed_id = $claimed_id;
1038 
1039  } else if ($this->identity) {
1040  if ($identity &&
1041  ($this->identity != $identity)) {
1042  $fmt = "Request was for %s, cannot reply with identity %s";
1043  return new Auth_OpenID_ServerError(null,
1044  sprintf($fmt, $this->identity, $identity));
1045  }
1046 
1047  $response_identity = $this->identity;
1048  $response_claimed_id = $this->claimed_id;
1049  } else {
1050  if ($identity) {
1051  return new Auth_OpenID_ServerError(null,
1052  "This request specified no identity and " .
1053  "you supplied ".$identity);
1054  }
1055 
1056  $response_identity = null;
1057  }
1058 
1059  if (($this->message->isOpenID1()) &&
1060  ($response_identity === null)) {
1061  return new Auth_OpenID_ServerError(null,
1062  "Request was an OpenID 1 request, so response must " .
1063  "include an identifier.");
1064  }
1065 
1066  $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
1067  array('mode' => $mode,
1068  'return_to' => $this->return_to,
1069  'response_nonce' => Auth_OpenID_mkNonce()));
1070 
1071  if (!$this->message->isOpenID1()) {
1072  $response->fields->setArg(Auth_OpenID_OPENID_NS,
1073  'op_endpoint', $server_url);
1074  }
1075 
1076  if ($response_identity !== null) {
1077  $response->fields->setArg(
1079  'identity',
1080  $response_identity);
1081  if ($this->message->isOpenID2()) {
1082  $response->fields->setArg(
1084  'claimed_id',
1085  $response_claimed_id);
1086  }
1087  }
1088 
1089  } else {
1090  $response->fields->setArg(Auth_OpenID_OPENID_NS,
1091  'mode', $mode);
1092 
1093  if ($this->immediate) {
1094  if (($this->message->isOpenID1()) &&
1095  (!$server_url)) {
1096  return new Auth_OpenID_ServerError(null,
1097  'setup_url is required for $allow=false \
1098  in OpenID 1.x immediate mode.');
1099  }
1100 
1101  $setup_request = new Auth_OpenID_CheckIDRequest(
1102  $this->identity,
1103  $this->return_to,
1104  $this->trust_root,
1105  false,
1106  $this->assoc_handle,
1107  $this->server,
1108  $this->claimed_id);
1109  $setup_request->message = $this->message;
1110 
1111  $setup_url = $setup_request->encodeToURL($server_url);
1112 
1113  if ($setup_url === null) {
1114  return new Auth_OpenID_NoReturnToError();
1115  }
1116 
1117  $response->fields->setArg(Auth_OpenID_OPENID_NS,
1118  'user_setup_url',
1119  $setup_url);
1120  }
1121  }
1122 
1123  return $response;
1124  }
1125 
1126  function encodeToURL($server_url)
1127  {
1128  if (!$this->return_to) {
1129  return new Auth_OpenID_NoReturnToError();
1130  }
1131 
1132  // Imported from the alternate reality where these classes are
1133  // used in both the client and server code, so Requests are
1134  // Encodable too. That's right, code imported from alternate
1135  // realities all for the love of you, id_res/user_setup_url.
1136 
1137  $q = array('mode' => $this->mode,
1138  'identity' => $this->identity,
1139  'claimed_id' => $this->claimed_id,
1140  'return_to' => $this->return_to);
1141 
1142  if ($this->trust_root) {
1143  if ($this->message->isOpenID1()) {
1144  $q['trust_root'] = $this->trust_root;
1145  } else {
1146  $q['realm'] = $this->trust_root;
1147  }
1148  }
1149 
1150  if ($this->assoc_handle) {
1151  $q['assoc_handle'] = $this->assoc_handle;
1152  }
1153 
1154  $response = new Auth_OpenID_Message(
1155  $this->message->getOpenIDNamespace());
1156  $response->updateArgs(Auth_OpenID_OPENID_NS, $q);
1157  return $response->toURL($server_url);
1158  }
1159 
1160  function getCancelURL()
1161  {
1162  if (!$this->return_to) {
1163  return new Auth_OpenID_NoReturnToError();
1164  }
1165 
1166  if ($this->immediate) {
1167  return new Auth_OpenID_ServerError(null,
1168  "Cancel is not an appropriate \
1169  response to immediate mode \
1170  requests.");
1171  }
1172 
1173  $response = new Auth_OpenID_Message(
1174  $this->message->getOpenIDNamespace());
1175  $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
1176  return $response->toURL($this->return_to);
1177  }
1178 }
1179 
1186 
1187  function Auth_OpenID_ServerResponse($request)
1188  {
1189  $this->request = $request;
1190  $this->fields = new Auth_OpenID_Message($this->request->namespace);
1191  }
1192 
1193  function whichEncoding()
1194  {
1196 
1197  if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
1198  if ($this->fields->isOpenID2() &&
1199  (strlen($this->encodeToURL()) >
1202  } else {
1203  return Auth_OpenID_ENCODE_URL;
1204  }
1205  } else {
1207  }
1208  }
1209 
1210  /*
1211  * Returns the form markup for this response.
1212  *
1213  * @return str
1214  */
1215  function toFormMarkup($form_tag_attrs=null)
1216  {
1217  return $this->fields->toFormMarkup($this->request->return_to,
1218  $form_tag_attrs);
1219  }
1220 
1221  /*
1222  * Returns an HTML document containing the form markup for this
1223  * response that autosubmits with javascript.
1224  */
1225  function toHTML()
1226  {
1227  return Auth_OpenID::autoSubmitHTML($this->toFormMarkup());
1228  }
1229 
1230  /*
1231  * Returns True if this response's encoding is ENCODE_HTML_FORM.
1232  * Convenience method for server authors.
1233  *
1234  * @return bool
1235  */
1236  function renderAsForm()
1237  {
1238  return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM;
1239  }
1240 
1241 
1242  function encodeToURL()
1243  {
1244  return $this->fields->toURL($this->request->return_to);
1245  }
1246 
1247  function addExtension($extension_response)
1248  {
1249  $extension_response->toMessage($this->fields);
1250  }
1251 
1252  function needsSigning()
1253  {
1254  return $this->fields->getArg(Auth_OpenID_OPENID_NS,
1255  'mode') == 'id_res';
1256  }
1257 
1258  function encodeToKVForm()
1259  {
1260  return $this->fields->toKVForm();
1261  }
1262 }
1263 
1272  var $body = "";
1273 
1274  function Auth_OpenID_WebResponse($code = null, $headers = null,
1275  $body = null)
1276  {
1277  if ($code) {
1278  $this->code = $code;
1279  }
1280 
1281  if ($headers !== null) {
1282  $this->headers = $headers;
1283  } else {
1284  $this->headers = array();
1285  }
1286 
1287  if ($body !== null) {
1288  $this->body = $body;
1289  }
1290  }
1291 }
1292 
1300 
1301  // = 14 * 24 * 60 * 60; # 14 days, in seconds
1302  var $SECRET_LIFETIME = 1209600;
1303 
1304  // keys have a bogus server URL in them because the filestore
1305  // really does expect that key to be a URL. This seems a little
1306  // silly for the server store, since I expect there to be only one
1307  // server URL.
1308  var $normal_key = 'http://localhost/|normal';
1309  var $dumb_key = 'http://localhost/|dumb';
1310 
1314  function Auth_OpenID_Signatory($store)
1315  {
1316  // assert store is not None
1317  $this->store = $store;
1318  }
1319 
1324  function verify($assoc_handle, $message)
1325  {
1326  $assoc = $this->getAssociation($assoc_handle, true);
1327  if (!$assoc) {
1328  // oidutil.log("failed to get assoc with handle %r to verify sig %r"
1329  // % (assoc_handle, sig))
1330  return false;
1331  }
1332 
1333  return $assoc->checkMessageSignature($message);
1334  }
1335 
1340  function sign($response)
1341  {
1342  $signed_response = $response;
1343  $assoc_handle = $response->request->assoc_handle;
1344 
1345  if ($assoc_handle) {
1346  // normal mode
1347  $assoc = $this->getAssociation($assoc_handle, false, false);
1348  if (!$assoc || ($assoc->getExpiresIn() <= 0)) {
1349  // fall back to dumb mode
1350  $signed_response->fields->setArg(Auth_OpenID_OPENID_NS,
1351  'invalidate_handle', $assoc_handle);
1352  $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1');
1353 
1354  if ($assoc && ($assoc->getExpiresIn() <= 0)) {
1355  $this->invalidate($assoc_handle, false);
1356  }
1357 
1358  $assoc = $this->createAssociation(true, $assoc_type);
1359  }
1360  } else {
1361  // dumb mode.
1362  $assoc = $this->createAssociation(true);
1363  }
1364 
1365  $signed_response->fields = $assoc->signMessage(
1366  $signed_response->fields);
1367  return $signed_response;
1368  }
1369 
1373  function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
1374  {
1376  Auth_OpenID_getSecretSize($assoc_type));
1377 
1378  $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
1379  $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);
1380 
1382  $this->SECRET_LIFETIME, $handle, $secret, $assoc_type);
1383 
1384  if ($dumb) {
1385  $key = $this->dumb_key;
1386  } else {
1387  $key = $this->normal_key;
1388  }
1389 
1390  $this->store->storeAssociation($key, $assoc);
1391  return $assoc;
1392  }
1393 
1398  function getAssociation($assoc_handle, $dumb, $check_expiration=true)
1399  {
1400  if ($assoc_handle === null) {
1401  return new Auth_OpenID_ServerError(null,
1402  "assoc_handle must not be null");
1403  }
1404 
1405  if ($dumb) {
1406  $key = $this->dumb_key;
1407  } else {
1408  $key = $this->normal_key;
1409  }
1410 
1411  $assoc = $this->store->getAssociation($key, $assoc_handle);
1412 
1413  if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
1414  if ($check_expiration) {
1415  $this->store->removeAssociation($key, $assoc_handle);
1416  $assoc = null;
1417  }
1418  }
1419 
1420  return $assoc;
1421  }
1422 
1426  function invalidate($assoc_handle, $dumb)
1427  {
1428  if ($dumb) {
1429  $key = $this->dumb_key;
1430  } else {
1431  $key = $this->normal_key;
1432  }
1433  $this->store->removeAssociation($key, $assoc_handle);
1434  }
1435 }
1436 
1444 
1445  var $responseFactory = 'Auth_OpenID_WebResponse';
1446 
1451  function encode($response)
1452  {
1453  $cls = $this->responseFactory;
1454 
1455  $encode_as = $response->whichEncoding();
1456  if ($encode_as == Auth_OpenID_ENCODE_KVFORM) {
1457  $wr = new $cls(null, null, $response->encodeToKVForm());
1458  if (is_a($response, 'Auth_OpenID_ServerError')) {
1459  $wr->code = AUTH_OPENID_HTTP_ERROR;
1460  }
1461  } else if ($encode_as == Auth_OpenID_ENCODE_URL) {
1462  $location = $response->encodeToURL();
1463  $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT,
1464  array('location' => $location));
1465  } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) {
1466  $wr = new $cls(AUTH_OPENID_HTTP_OK, array(),
1467  $response->toHTML());
1468  } else {
1469  return new Auth_OpenID_EncodingError($response);
1470  }
1471  /* Allow the response to carry a custom error code (ex: for Association errors) */
1472  if(isset($response->code)) {
1473  $wr->code = $response->code;
1474  }
1475  return $wr;
1476  }
1477 }
1478 
1485 
1486  function Auth_OpenID_SigningEncoder($signatory)
1487  {
1488  $this->signatory = $signatory;
1489  }
1490 
1495  function encode($response)
1496  {
1497  // the isinstance is a bit of a kludge... it means there isn't
1498  // really an adapter to make the interfaces quite match.
1499  if (!is_a($response, 'Auth_OpenID_ServerError') &&
1500  $response->needsSigning()) {
1501 
1502  if (!$this->signatory) {
1503  return new Auth_OpenID_ServerError(null,
1504  "Must have a store to sign request");
1505  }
1506 
1507  if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) {
1508  return new Auth_OpenID_AlreadySigned($response);
1509  }
1510  $response = $this->signatory->sign($response);
1511  }
1512 
1513  return parent::encode($response);
1514  }
1515 }
1516 
1523 
1525  {
1526  $this->server = $server;
1527 
1528  $this->handlers = array(
1529  'checkid_setup' => 'Auth_OpenID_CheckIDRequest',
1530  'checkid_immediate' => 'Auth_OpenID_CheckIDRequest',
1531  'check_authentication' => 'Auth_OpenID_CheckAuthRequest',
1532  'associate' => 'Auth_OpenID_AssociateRequest'
1533  );
1534  }
1535 
1540  function decode($query)
1541  {
1542  if (!$query) {
1543  return null;
1544  }
1545 
1547 
1548  if ($message === null) {
1549  /*
1550  * It's useful to have a Message attached to a
1551  * ProtocolError, so we override the bad ns value to build
1552  * a Message out of it. Kinda kludgy, since it's made of
1553  * lies, but the parts that aren't lies are more useful
1554  * than a 'None'.
1555  */
1556  $old_ns = $query['openid.ns'];
1557 
1558  $query['openid.ns'] = Auth_OpenID_OPENID2_NS;
1560  return new Auth_OpenID_ServerError(
1561  $message,
1562  sprintf("Invalid OpenID namespace URI: %s", $old_ns));
1563  }
1564 
1565  $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
1566  if (!$mode) {
1567  return new Auth_OpenID_ServerError($message,
1568  "No mode value in message");
1569  }
1570 
1571  if (Auth_OpenID::isFailure($mode)) {
1572  return new Auth_OpenID_ServerError($message,
1573  $mode->message);
1574  }
1575 
1576  $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
1577  $this->defaultDecoder($message));
1578 
1579  if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
1580  return call_user_func_array(array($handlerCls, 'fromMessage'),
1581  array($message, $this->server));
1582  } else {
1583  return $handlerCls;
1584  }
1585  }
1586 
1587  function defaultDecoder($message)
1588  {
1589  $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
1590 
1591  if (Auth_OpenID::isFailure($mode)) {
1592  return new Auth_OpenID_ServerError($message,
1593  $mode->message);
1594  }
1595 
1596  return new Auth_OpenID_ServerError($message,
1597  sprintf("Unrecognized OpenID mode %s", $mode));
1598  }
1599 }
1600 
1607  function Auth_OpenID_EncodingError($response)
1608  {
1609  $this->response = $response;
1610  }
1611 }
1612 
1619  // This response is already signed.
1620 }
1621 
1629  function Auth_OpenID_UntrustedReturnURL($message, $return_to,
1630  $trust_root)
1631  {
1632  parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL");
1633  $this->return_to = $return_to;
1634  $this->trust_root = $trust_root;
1635  }
1636 
1637  function toString()
1638  {
1639  return sprintf("return_to %s not under trust_root %s",
1640  $this->return_to, $this->trust_root);
1641  }
1642 }
1643 
1682  function Auth_OpenID_Server($store, $op_endpoint=null)
1683  {
1684  $this->store = $store;
1685  $this->signatory = new Auth_OpenID_Signatory($this->store);
1686  $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory);
1687  $this->decoder = new Auth_OpenID_Decoder($this);
1688  $this->op_endpoint = $op_endpoint;
1689  $this->negotiator = Auth_OpenID_getDefaultNegotiator();
1690  }
1691 
1703  function handleRequest($request)
1704  {
1705  if (method_exists($this, "openid_" . $request->mode)) {
1706  $handler = array($this, "openid_" . $request->mode);
1707  return call_user_func($handler, &$request);
1708  }
1709  return null;
1710  }
1711 
1715  function openid_check_authentication($request)
1716  {
1717  return $request->answer($this->signatory);
1718  }
1719 
1723  function openid_associate($request)
1724  {
1725  $assoc_type = $request->assoc_type;
1726  $session_type = $request->session->session_type;
1727  if ($this->negotiator->isAllowed($assoc_type, $session_type)) {
1728  $assoc = $this->signatory->createAssociation(false,
1729  $assoc_type);
1730  return $request->answer($assoc);
1731  } else {
1732  $message = sprintf('Association type %s is not supported with '.
1733  'session type %s', $assoc_type, $session_type);
1734  list($preferred_assoc_type, $preferred_session_type) =
1735  $this->negotiator->getAllowedType();
1736  return $request->answerUnsupported($message,
1737  $preferred_assoc_type,
1738  $preferred_session_type);
1739  }
1740  }
1741 
1746  function encodeResponse($response)
1747  {
1748  return $this->encoder->encode($response);
1749  }
1750 
1755  function decodeRequest($query=null)
1756  {
1757  if ($query === null) {
1759  }
1760 
1761  return $this->decoder->decode($query);
1762  }
1763 }
1764 
1765