1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90:
91:
92: 93: 94:
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:
110: 111: 112:
113: global $_Auth_OpenID_Request_Modes;
114: $_Auth_OpenID_Request_Modes = array('checkid_setup',
115: 'checkid_immediate');
116:
117: 118: 119:
120: define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');
121:
122: 123: 124:
125: define('Auth_OpenID_ENCODE_URL', 'URL/redirect');
126:
127: 128: 129:
130: define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');
131:
132: 133: 134:
135: function Auth_OpenID_isError($obj, $cls = Auth_OpenID_ServerError)
136: {
137: return ($obj instanceof $cls);
138: }
139:
140: 141: 142: 143: 144: 145: 146:
147: class Auth_OpenID_ServerError {
148: 149: 150:
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:
171: 172: 173: 174:
175: function hasReturnTo()
176: {
177: return $this->getReturnTo() !== null;
178: }
179:
180: 181: 182: 183: 184:
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:
195: 196: 197: 198: 199: 200:
201: function encodeToKVForm()
202: {
203: return Auth_OpenID_KVForm::fromArray(
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: {
216: return Auth_OpenID::autoSubmitHTML(
217: $this->toFormMarkup($form_tag_attrs));
218: }
219:
220: function toMessage()
221: {
222:
223:
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:
241: 242: 243: 244: 245:
246: function whichEncoding()
247: {
248: global $_Auth_OpenID_Request_Modes;
249:
250: if ($this->hasReturnTo()) {
251: if ($this->message->isOpenID2() &&
252: (strlen($this->encodeToURL()) >
253: Auth_OpenID_OPENID1_URL_LIMIT)) {
254: return Auth_OpenID_ENCODE_HTML_FORM;
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)) {
269: return Auth_OpenID_ENCODE_KVFORM;
270: }
271: }
272: return null;
273: }
274:
275: 276: 277:
278: function toString()
279: {
280: if ($this->text) {
281: return $this->text;
282: } else {
283: return get_class($this) . " error";
284: }
285: }
286: }
287:
288: 289: 290: 291: 292: 293:
294: class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError {
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:
307: 308: 309: 310: 311:
312: class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
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:
320: 321: 322: 323: 324:
325: class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
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:
338: 339: 340: 341: 342:
343: class Auth_OpenID_Request {
344: var $mode = null;
345: }
346:
347: 348: 349: 350: 351:
352: class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
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:
404:
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:
425: 426: 427: 428: 429:
430: class Auth_OpenID_PlainTextServerSession {
431: 432: 433: 434:
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: {
441: return new Auth_OpenID_PlainTextServerSession();
442: }
443:
444: function answer($secret)
445: {
446: return array('mac_key' => base64_encode($secret));
447: }
448: }
449:
450: 451: 452: 453: 454:
455: class Auth_OpenID_DiffieHellmanSHA1ServerSession {
456: 457: 458: 459:
460:
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: {
528: $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
529:
530: if (($result instanceof Auth_OpenID_ServerError)) {
531: return $result;
532: } else {
533: list($dh, $consumer_pubkey) = $result;
534: return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh,
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:
551: 552: 553: 554: 555:
556: class Auth_OpenID_DiffieHellmanSHA256ServerSession
557: extends Auth_OpenID_DiffieHellmanSHA1ServerSession {
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: {
565: $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
566:
567: if ($result instanceof Auth_OpenID_ServerError) {
568: return $result;
569: } else {
570: list($dh, $consumer_pubkey) = $result;
571: return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh,
572: $consumer_pubkey);
573: }
574: }
575: }
576:
577: 578: 579: 580: 581:
582: class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
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:
608:
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(
622: Auth_OpenID_AssociateRequest::getSessionClasses(),
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 ($session instanceof 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:
704: 705: 706: 707: 708:
709: class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
710: 711: 712: 713:
714: var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo';
715:
716: 717: 718:
719: var $mode = "checkid_setup";
720:
721: 722: 723:
724: var $immediate = false;
725:
726: 727: 728:
729: var $trust_root = null;
730:
731: 732: 733: 734:
735: var $namespace;
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,
751: $trust_root, $immediate,
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: ($other instanceof 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: 806: 807: 808: 809: 810: 811: 812: 813: 814: 815: 816: 817:
818: function returnToVerified()
819: {
820: $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
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:
868:
869:
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 ($obj instanceof 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:
911:
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:
935: 936: 937: 938: 939: 940: 941: 942: 943: 944: 945: 946: 947: 948: 949: 950: 951: 952: 953: 954: 955: 956: 957: 958: 959: 960: 961: 962: 963: 964: 965: 966: 967: 968: 969: 970: 971: 972: 973:
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(
1078: Auth_OpenID_OPENID_NS,
1079: 'identity',
1080: $response_identity);
1081: if ($this->message->isOpenID2()) {
1082: $response->fields->setArg(
1083: Auth_OpenID_OPENID_NS,
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:
1133:
1134:
1135:
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:
1180: 1181: 1182: 1183: 1184:
1185: class Auth_OpenID_ServerResponse {
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: {
1195: global $_Auth_OpenID_Request_Modes;
1196:
1197: if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
1198: if ($this->fields->isOpenID2() &&
1199: (strlen($this->encodeToURL()) >
1200: Auth_OpenID_OPENID1_URL_LIMIT)) {
1201: return Auth_OpenID_ENCODE_HTML_FORM;
1202: } else {
1203: return Auth_OpenID_ENCODE_URL;
1204: }
1205: } else {
1206: return Auth_OpenID_ENCODE_KVFORM;
1207: }
1208: }
1209:
1210: 1211: 1212: 1213: 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: 1223: 1224:
1225: function toHTML()
1226: {
1227: return Auth_OpenID::autoSubmitHTML($this->toFormMarkup());
1228: }
1229:
1230: 1231: 1232: 1233: 1234: 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:
1264: 1265: 1266: 1267: 1268: 1269:
1270: class Auth_OpenID_WebResponse {
1271: var $code = AUTH_OPENID_HTTP_OK;
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:
1293: 1294: 1295: 1296: 1297: 1298:
1299: class Auth_OpenID_Signatory {
1300:
1301:
1302: var $SECRET_LIFETIME = 1209600;
1303:
1304:
1305:
1306:
1307:
1308: var $normal_key = 'http://localhost/|normal';
1309: var $dumb_key = 'http://localhost/|dumb';
1310:
1311: 1312: 1313:
1314: function Auth_OpenID_Signatory($store)
1315: {
1316:
1317: $this->store = $store;
1318: }
1319:
1320: 1321: 1322: 1323:
1324: function verify($assoc_handle, $message)
1325: {
1326: $assoc = $this->getAssociation($assoc_handle, true);
1327: if (!$assoc) {
1328:
1329:
1330: return false;
1331: }
1332:
1333: return $assoc->checkMessageSignature($message);
1334: }
1335:
1336: 1337: 1338: 1339:
1340: function sign($response)
1341: {
1342: $signed_response = $response;
1343: $assoc_handle = $response->request->assoc_handle;
1344:
1345: if ($assoc_handle) {
1346:
1347: $assoc = $this->getAssociation($assoc_handle, false, false);
1348: if (!$assoc || ($assoc->getExpiresIn() <= 0)) {
1349:
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:
1362: $assoc = $this->createAssociation(true);
1363: }
1364:
1365: $signed_response->fields = $assoc->signMessage(
1366: $signed_response->fields);
1367: return $signed_response;
1368: }
1369:
1370: 1371: 1372:
1373: function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
1374: {
1375: $secret = Auth_OpenID_CryptUtil::getBytes(
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:
1381: $assoc = Auth_OpenID_Association::fromExpiresIn(
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:
1394: 1395: 1396: 1397:
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:
1423: 1424: 1425:
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:
1437: 1438: 1439: 1440: 1441: 1442:
1443: class Auth_OpenID_Encoder {
1444:
1445: var $responseFactory = 'Auth_OpenID_WebResponse';
1446:
1447: 1448: 1449: 1450:
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 ($response instanceof 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:
1472: if(isset($response->code)) {
1473: $wr->code = $response->code;
1474: }
1475: return $wr;
1476: }
1477: }
1478:
1479: 1480: 1481: 1482: 1483:
1484: class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {
1485:
1486: function Auth_OpenID_SigningEncoder($signatory)
1487: {
1488: $this->signatory = $signatory;
1489: }
1490:
1491: 1492: 1493: 1494:
1495: function encode($response)
1496: {
1497:
1498:
1499: if (!$response instanceof 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:
1517: 1518: 1519: 1520: 1521:
1522: class Auth_OpenID_Decoder {
1523:
1524: function Auth_OpenID_Decoder($server)
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:
1536: 1537: 1538: 1539:
1540: function decode($query)
1541: {
1542: if (!$query) {
1543: return null;
1544: }
1545:
1546: $message = Auth_OpenID_Message::fromPostArgs($query);
1547:
1548: if ($message === null) {
1549: 1550: 1551: 1552: 1553: 1554: 1555:
1556: $old_ns = $query['openid.ns'];
1557:
1558: $query['openid.ns'] = Auth_OpenID_OPENID2_NS;
1559: $message = Auth_OpenID_Message::fromPostArgs($query);
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 (!($handlerCls instanceof 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:
1601: 1602: 1603: 1604: 1605:
1606: class Auth_OpenID_EncodingError {
1607: function Auth_OpenID_EncodingError($response)
1608: {
1609: $this->response = $response;
1610: }
1611: }
1612:
1613: 1614: 1615: 1616: 1617:
1618: class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
1619:
1620: }
1621:
1622: 1623: 1624: 1625: 1626: 1627:
1628: class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
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:
1644: 1645: 1646: 1647: 1648: 1649: 1650: 1651: 1652: 1653: 1654: 1655: 1656: 1657: 1658: 1659: 1660: 1661: 1662: 1663: 1664: 1665: 1666: 1667: 1668: 1669: 1670: 1671: 1672: 1673: 1674: 1675: 1676: 1677: 1678: 1679: 1680:
1681: class Auth_OpenID_Server {
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:
1692: 1693: 1694: 1695: 1696: 1697: 1698: 1699: 1700: 1701: 1702:
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:
1712: 1713: 1714:
1715: function openid_check_authentication($request)
1716: {
1717: return $request->answer($this->signatory);
1718: }
1719:
1720: 1721: 1722:
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:
1742: 1743: 1744: 1745:
1746: function encodeResponse($response)
1747: {
1748: return $this->encoder->encode($response);
1749: }
1750:
1751: 1752: 1753: 1754:
1755: function decodeRequest($query=null)
1756: {
1757: if ($query === null) {
1758: $query = Auth_OpenID::getQuery();
1759: }
1760:
1761: return $this->decoder->decode($query);
1762: }
1763: }
1764:
1765:
1766: