00001 <?php
00002
00013 require_once "Auth/OpenID/Extension.php";
00014 require_once "Auth/OpenID/Message.php";
00015 require_once "Auth/OpenID/TrustRoot.php";
00016
00017 define('Auth_OpenID_AX_NS_URI',
00018 'http://openid.net/srv/ax/1.0');
00019
00020
00021
00022 define('Auth_OpenID_AX_UNLIMITED_VALUES', 'unlimited');
00023
00024
00025
00026 define('Auth_OpenID_AX_MINIMUM_SUPPORTED_ALIAS_LENGTH', 32);
00027
00033 class Auth_OpenID_AX {
00041 function isError($thing)
00042 {
00043 return is_a($thing, 'Auth_OpenID_AX_Error');
00044 }
00045 }
00046
00051 function Auth_OpenID_AX_checkAlias($alias)
00052 {
00053 if (strpos($alias, ',') !== false) {
00054 return new Auth_OpenID_AX_Error(sprintf(
00055 "Alias %s must not contain comma", $alias));
00056 }
00057 if (strpos($alias, '.') !== false) {
00058 return new Auth_OpenID_AX_Error(sprintf(
00059 "Alias %s must not contain period", $alias));
00060 }
00061
00062 return true;
00063 }
00064
00071 class Auth_OpenID_AX_Error {
00072 function Auth_OpenID_AX_Error($message=null)
00073 {
00074 $this->message = $message;
00075 }
00076 }
00077
00084 class Auth_OpenID_AX_Message extends Auth_OpenID_Extension {
00089 var $ns_alias = 'ax';
00090
00095 var $mode = null;
00096
00097 var $ns_uri = Auth_OpenID_AX_NS_URI;
00098
00106 function _checkMode($ax_args)
00107 {
00108 $mode = Auth_OpenID::arrayGet($ax_args, 'mode');
00109 if ($mode != $this->mode) {
00110 return new Auth_OpenID_AX_Error(
00111 sprintf(
00112 "Expected mode '%s'; got '%s'",
00113 $this->mode, $mode));
00114 }
00115
00116 return true;
00117 }
00118
00126 function _newArgs()
00127 {
00128 return array('mode' => $this->mode);
00129 }
00130 }
00131
00139 class Auth_OpenID_AX_AttrInfo {
00154 function Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
00155 $alias)
00156 {
00162 $this->required = $required;
00163
00168 $this->count = $count;
00169
00177 $this->type_uri = $type_uri;
00178
00187 $this->alias = $alias;
00188 }
00189
00194 function make($type_uri, $count=1, $required=false,
00195 $alias=null)
00196 {
00197 if ($alias !== null) {
00198 $result = Auth_OpenID_AX_checkAlias($alias);
00199
00200 if (Auth_OpenID_AX::isError($result)) {
00201 return $result;
00202 }
00203 }
00204
00205 return new Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
00206 $alias);
00207 }
00208
00216 function wantsUnlimitedValues()
00217 {
00218 return $this->count === Auth_OpenID_AX_UNLIMITED_VALUES;
00219 }
00220 }
00221
00238 function Auth_OpenID_AX_toTypeURIs(&$namespace_map, $alias_list_s)
00239 {
00240 $uris = array();
00241
00242 if ($alias_list_s) {
00243 foreach (explode(',', $alias_list_s) as $alias) {
00244 $type_uri = $namespace_map->getNamespaceURI($alias);
00245 if ($type_uri === null) {
00246
00247
00248 return new Auth_OpenID_AX_Error(
00249 sprintf('No type is defined for attribute name %s',
00250 $alias)
00251 );
00252 } else {
00253 $uris[] = $type_uri;
00254 }
00255 }
00256 }
00257
00258 return $uris;
00259 }
00260
00268 class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message {
00269
00270 var $mode = 'fetch_request';
00271
00272 function Auth_OpenID_AX_FetchRequest($update_url=null)
00273 {
00278 $this->requested_attributes = array();
00279
00285 $this->update_url = $update_url;
00286 }
00287
00295 function add($attribute)
00296 {
00297 if ($this->contains($attribute->type_uri)) {
00298 return new Auth_OpenID_AX_Error(
00299 sprintf("The attribute %s has already been requested",
00300 $attribute->type_uri));
00301 }
00302
00303 $this->requested_attributes[$attribute->type_uri] = $attribute;
00304
00305 return true;
00306 }
00307
00313 function getExtensionArgs()
00314 {
00315 $aliases = new Auth_OpenID_NamespaceMap();
00316
00317 $required = array();
00318 $if_available = array();
00319
00320 $ax_args = $this->_newArgs();
00321
00322 foreach ($this->requested_attributes as $type_uri => $attribute) {
00323 if ($attribute->alias === null) {
00324 $alias = $aliases->add($type_uri);
00325 } else {
00326 $alias = $aliases->addAlias($type_uri, $attribute->alias);
00327
00328 if ($alias === null) {
00329 return new Auth_OpenID_AX_Error(
00330 sprintf("Could not add alias %s for URI %s",
00331 $attribute->alias, $type_uri
00332 ));
00333 }
00334 }
00335
00336 if ($attribute->required) {
00337 $required[] = $alias;
00338 } else {
00339 $if_available[] = $alias;
00340 }
00341
00342 if ($attribute->count != 1) {
00343 $ax_args['count.' . $alias] = strval($attribute->count);
00344 }
00345
00346 $ax_args['type.' . $alias] = $type_uri;
00347 }
00348
00349 if ($required) {
00350 $ax_args['required'] = implode(',', $required);
00351 }
00352
00353 if ($if_available) {
00354 $ax_args['if_available'] = implode(',', $if_available);
00355 }
00356
00357 return $ax_args;
00358 }
00359
00367 function getRequiredAttrs()
00368 {
00369 $required = array();
00370 foreach ($this->requested_attributes as $type_uri => $attribute) {
00371 if ($attribute->required) {
00372 $required[] = $type_uri;
00373 }
00374 }
00375
00376 return $required;
00377 }
00378
00389 function &fromOpenIDRequest($request)
00390 {
00391 $m = $request->message;
00392 $obj = new Auth_OpenID_AX_FetchRequest();
00393 $ax_args = $m->getArgs($obj->ns_uri);
00394
00395 $result = $obj->parseExtensionArgs($ax_args);
00396
00397 if (Auth_OpenID_AX::isError($result)) {
00398 return $result;
00399 }
00400
00401 if ($obj->update_url) {
00402
00403
00404 $realm = $m->getArg(Auth_OpenID_OPENID_NS, 'realm',
00405 $m->getArg(
00406 Auth_OpenID_OPENID_NS,
00407 'return_to'));
00408
00409 if (!$realm) {
00410 $obj = new Auth_OpenID_AX_Error(
00411 sprintf("Cannot validate update_url %s " .
00412 "against absent realm", $obj->update_url));
00413 } else if (!Auth_OpenID_TrustRoot::match($realm,
00414 $obj->update_url)) {
00415 $obj = new Auth_OpenID_AX_Error(
00416 sprintf("Update URL %s failed validation against realm %s",
00417 $obj->update_url, $realm));
00418 }
00419 }
00420
00421 return $obj;
00422 }
00423
00432 function parseExtensionArgs($ax_args)
00433 {
00434 $result = $this->_checkMode($ax_args);
00435 if (Auth_OpenID_AX::isError($result)) {
00436 return $result;
00437 }
00438
00439 $aliases = new Auth_OpenID_NamespaceMap();
00440
00441 foreach ($ax_args as $key => $value) {
00442 if (strpos($key, 'type.') === 0) {
00443 $alias = substr($key, 5);
00444 $type_uri = $value;
00445
00446 $alias = $aliases->addAlias($type_uri, $alias);
00447
00448 if ($alias === null) {
00449 return new Auth_OpenID_AX_Error(
00450 sprintf("Could not add alias %s for URI %s",
00451 $alias, $type_uri)
00452 );
00453 }
00454
00455 $count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias);
00456 if ($count_s) {
00457 $count = Auth_OpenID::intval($count_s);
00458 if (($count === false) &&
00459 ($count_s === Auth_OpenID_AX_UNLIMITED_VALUES)) {
00460 $count = $count_s;
00461 }
00462 } else {
00463 $count = 1;
00464 }
00465
00466 if ($count === false) {
00467 return new Auth_OpenID_AX_Error(
00468 sprintf("Integer value expected for %s, got %s",
00469 'count.' . $alias, $count_s));
00470 }
00471
00472 $attrinfo = Auth_OpenID_AX_AttrInfo::make($type_uri, $count,
00473 false, $alias);
00474
00475 if (Auth_OpenID_AX::isError($attrinfo)) {
00476 return $attrinfo;
00477 }
00478
00479 $this->add($attrinfo);
00480 }
00481 }
00482
00483 $required = Auth_OpenID_AX_toTypeURIs($aliases,
00484 Auth_OpenID::arrayGet($ax_args, 'required'));
00485
00486 foreach ($required as $type_uri) {
00487 $attrib =& $this->requested_attributes[$type_uri];
00488 $attrib->required = true;
00489 }
00490
00491 $if_available = Auth_OpenID_AX_toTypeURIs($aliases,
00492 Auth_OpenID::arrayGet($ax_args, 'if_available'));
00493
00494 $all_type_uris = array_merge($required, $if_available);
00495
00496 foreach ($aliases->iterNamespaceURIs() as $type_uri) {
00497 if (!in_array($type_uri, $all_type_uris)) {
00498 return new Auth_OpenID_AX_Error(
00499 sprintf('Type URI %s was in the request but not ' .
00500 'present in "required" or "if_available"',
00501 $type_uri));
00502
00503 }
00504 }
00505
00506 $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
00507
00508 return true;
00509 }
00510
00515 function iterAttrs()
00516 {
00517 return array_values($this->requested_attributes);
00518 }
00519
00520 function iterTypes()
00521 {
00522 return array_keys($this->requested_attributes);
00523 }
00524
00528 function contains($type_uri)
00529 {
00530 return in_array($type_uri, $this->iterTypes());
00531 }
00532 }
00533
00541 class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message {
00542
00543 function Auth_OpenID_AX_KeyValueMessage()
00544 {
00545 $this->data = array();
00546 }
00547
00559 function addValue($type_uri, $value)
00560 {
00561 if (!array_key_exists($type_uri, $this->data)) {
00562 $this->data[$type_uri] = array();
00563 }
00564
00565 $values =& $this->data[$type_uri];
00566 $values[] = $value;
00567 }
00568
00576 function setValues($type_uri, &$values)
00577 {
00578 $this->data[$type_uri] =& $values;
00579 }
00580
00590 function _getExtensionKVArgs(&$aliases)
00591 {
00592 if ($aliases === null) {
00593 $aliases = new Auth_OpenID_NamespaceMap();
00594 }
00595
00596 $ax_args = array();
00597
00598 foreach ($this->data as $type_uri => $values) {
00599 $alias = $aliases->add($type_uri);
00600
00601 $ax_args['type.' . $alias] = $type_uri;
00602 $ax_args['count.' . $alias] = strval(count($values));
00603
00604 foreach ($values as $i => $value) {
00605 $key = sprintf('value.%s.%d', $alias, $i + 1);
00606 $ax_args[$key] = $value;
00607 }
00608 }
00609
00610 return $ax_args;
00611 }
00612
00621 function parseExtensionArgs($ax_args)
00622 {
00623 $result = $this->_checkMode($ax_args);
00624 if (Auth_OpenID_AX::isError($result)) {
00625 return $result;
00626 }
00627
00628 $aliases = new Auth_OpenID_NamespaceMap();
00629
00630 foreach ($ax_args as $key => $value) {
00631 if (strpos($key, 'type.') === 0) {
00632 $type_uri = $value;
00633 $alias = substr($key, 5);
00634
00635 $result = Auth_OpenID_AX_checkAlias($alias);
00636
00637 if (Auth_OpenID_AX::isError($result)) {
00638 return $result;
00639 }
00640
00641 $alias = $aliases->addAlias($type_uri, $alias);
00642
00643 if ($alias === null) {
00644 return new Auth_OpenID_AX_Error(
00645 sprintf("Could not add alias %s for URI %s",
00646 $alias, $type_uri)
00647 );
00648 }
00649 }
00650 }
00651
00652 foreach ($aliases->iteritems() as $pair) {
00653 list($type_uri, $alias) = $pair;
00654
00655 if (array_key_exists('count.' . $alias, $ax_args)) {
00656
00657 $count_key = 'count.' . $alias;
00658 $count_s = $ax_args[$count_key];
00659
00660 $count = Auth_OpenID::intval($count_s);
00661
00662 if ($count === false) {
00663 return new Auth_OpenID_AX_Error(
00664 sprintf("Integer value expected for %s, got %s",
00665 'count. %s' . $alias, $count_s,
00666 Auth_OpenID_AX_UNLIMITED_VALUES)
00667 );
00668 }
00669
00670 $values = array();
00671 for ($i = 1; $i < $count + 1; $i++) {
00672 $value_key = sprintf('value.%s.%d', $alias, $i);
00673
00674 if (!array_key_exists($value_key, $ax_args)) {
00675 return new Auth_OpenID_AX_Error(
00676 sprintf(
00677 "No value found for key %s",
00678 $value_key));
00679 }
00680
00681 $value = $ax_args[$value_key];
00682 $values[] = $value;
00683 }
00684 } else {
00685 $key = 'value.' . $alias;
00686
00687 if (!array_key_exists($key, $ax_args)) {
00688 return new Auth_OpenID_AX_Error(
00689 sprintf(
00690 "No value found for key %s",
00691 $key));
00692 }
00693
00694 $value = $ax_args['value.' . $alias];
00695
00696 if ($value == '') {
00697 $values = array();
00698 } else {
00699 $values = array($value);
00700 }
00701 }
00702
00703 $this->data[$type_uri] = $values;
00704 }
00705
00706 return true;
00707 }
00708
00722 function getSingle($type_uri, $default=null)
00723 {
00724 $values = Auth_OpenID::arrayGet($this->data, $type_uri);
00725 if (!$values) {
00726 return $default;
00727 } else if (count($values) == 1) {
00728 return $values[0];
00729 } else {
00730 return new Auth_OpenID_AX_Error(
00731 sprintf('More than one value present for %s',
00732 $type_uri)
00733 );
00734 }
00735 }
00736
00753 function get($type_uri)
00754 {
00755 if (array_key_exists($type_uri, $this->data)) {
00756 return $this->data[$type_uri];
00757 } else {
00758 return new Auth_OpenID_AX_Error(
00759 sprintf("Type URI %s not found in response",
00760 $type_uri)
00761 );
00762 }
00763 }
00764
00775 function count($type_uri)
00776 {
00777 if (array_key_exists($type_uri, $this->data)) {
00778 return count($this->get($type_uri));
00779 } else {
00780 return new Auth_OpenID_AX_Error(
00781 sprintf("Type URI %s not found in response",
00782 $type_uri)
00783 );
00784 }
00785 }
00786 }
00787
00793 class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage {
00794 var $mode = 'fetch_response';
00795
00796 function Auth_OpenID_AX_FetchResponse($update_url=null)
00797 {
00798 $this->Auth_OpenID_AX_KeyValueMessage();
00799 $this->update_url = $update_url;
00800 }
00801
00810 function getExtensionArgs($request=null)
00811 {
00812 $aliases = new Auth_OpenID_NamespaceMap();
00813
00814 $zero_value_types = array();
00815
00816 if ($request !== null) {
00817
00818
00819
00820
00821
00822 foreach ($this->data as $type_uri => $unused) {
00823 if (!$request->contains($type_uri)) {
00824 return new Auth_OpenID_AX_Error(
00825 sprintf("Response attribute not present in request: %s",
00826 $type_uri)
00827 );
00828 }
00829 }
00830
00831 foreach ($request->iterAttrs() as $attr_info) {
00832
00833
00834 if ($attr_info->alias === null) {
00835 $aliases->add($attr_info->type_uri);
00836 } else {
00837 $alias = $aliases->addAlias($attr_info->type_uri,
00838 $attr_info->alias);
00839
00840 if ($alias === null) {
00841 return new Auth_OpenID_AX_Error(
00842 sprintf("Could not add alias %s for URI %s",
00843 $attr_info->alias, $attr_info->type_uri)
00844 );
00845 }
00846 }
00847
00848 if (array_key_exists($attr_info->type_uri, $this->data)) {
00849 $values = $this->data[$attr_info->type_uri];
00850 } else {
00851 $values = array();
00852 $zero_value_types[] = $attr_info;
00853 }
00854
00855 if (($attr_info->count != Auth_OpenID_AX_UNLIMITED_VALUES) &&
00856 ($attr_info->count < count($values))) {
00857 return new Auth_OpenID_AX_Error(
00858 sprintf("More than the number of requested values " .
00859 "were specified for %s",
00860 $attr_info->type_uri)
00861 );
00862 }
00863 }
00864 }
00865
00866 $kv_args = $this->_getExtensionKVArgs($aliases);
00867
00868
00869
00870 $ax_args = $this->_newArgs();
00871
00872
00873
00874 foreach ($zero_value_types as $attr_info) {
00875 $alias = $aliases->getAlias($attr_info->type_uri);
00876 $kv_args['type.' . $alias] = $attr_info->type_uri;
00877 $kv_args['count.' . $alias] = '0';
00878 }
00879
00880 $update_url = null;
00881 if ($request) {
00882 $update_url = $request->update_url;
00883 } else {
00884 $update_url = $this->update_url;
00885 }
00886
00887 if ($update_url) {
00888 $ax_args['update_url'] = $update_url;
00889 }
00890
00891 Auth_OpenID::update(&$ax_args, $kv_args);
00892
00893 return $ax_args;
00894 }
00895
00900 function parseExtensionArgs($ax_args)
00901 {
00902 $result = parent::parseExtensionArgs($ax_args);
00903
00904 if (Auth_OpenID_AX::isError($result)) {
00905 return $result;
00906 }
00907
00908 $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
00909
00910 return true;
00911 }
00912
00925 function fromSuccessResponse($success_response, $signed=true)
00926 {
00927 $obj = new Auth_OpenID_AX_FetchResponse();
00928 if ($signed) {
00929 $ax_args = $success_response->getSignedNS($obj->ns_uri);
00930 } else {
00931 $ax_args = $success_response->message->getArgs($obj->ns_uri);
00932 }
00933 if ($ax_args === null || Auth_OpenID::isFailure($ax_args) ||
00934 sizeof($ax_args) == 0) {
00935 return null;
00936 }
00937
00938 $result = $obj->parseExtensionArgs($ax_args);
00939 if (Auth_OpenID_AX::isError($result)) {
00940 #XXX log me
00941 return null;
00942 }
00943 return $obj;
00944 }
00945 }
00946
00952 class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage {
00953 var $mode = 'store_request';
00954
00959 function getExtensionArgs($aliases=null)
00960 {
00961 $ax_args = $this->_newArgs();
00962 $kv_args = $this->_getExtensionKVArgs($aliases);
00963 Auth_OpenID::update(&$ax_args, $kv_args);
00964 return $ax_args;
00965 }
00966 }
00967
00975 class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message {
00976 var $SUCCESS_MODE = 'store_response_success';
00977 var $FAILURE_MODE = 'store_response_failure';
00978
00983 function &make($succeeded=true, $error_message=null)
00984 {
00985 if (($succeeded) && ($error_message !== null)) {
00986 return new Auth_OpenID_AX_Error('An error message may only be '.
00987 'included in a failing fetch response');
00988 }
00989
00990 return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message);
00991 }
00992
00993 function Auth_OpenID_AX_StoreResponse($succeeded=true, $error_message=null)
00994 {
00995 if ($succeeded) {
00996 $this->mode = $this->SUCCESS_MODE;
00997 } else {
00998 $this->mode = $this->FAILURE_MODE;
00999 }
01000
01001 $this->error_message = $error_message;
01002 }
01003
01007 function succeeded()
01008 {
01009 return $this->mode == $this->SUCCESS_MODE;
01010 }
01011
01012 function getExtensionArgs()
01013 {
01014 $ax_args = $this->_newArgs();
01015 if ((!$this->succeeded()) && $this->error_message) {
01016 $ax_args['error'] = $this->error_message;
01017 }
01018
01019 return $ax_args;
01020 }
01021 }
01022
01023 ?>