AX.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. <?php
  2. /**
  3. * Implements the OpenID attribute exchange specification, version 1.0
  4. * as of svn revision 370 from openid.net svn.
  5. *
  6. * @package OpenID
  7. */
  8. /**
  9. * Require utility classes and functions for the consumer.
  10. */
  11. require_once "Auth/OpenID/Extension.php";
  12. require_once "Auth/OpenID/Message.php";
  13. require_once "Auth/OpenID/TrustRoot.php";
  14. define('Auth_OpenID_AX_NS_URI',
  15. 'http://openid.net/srv/ax/1.0');
  16. // Use this as the 'count' value for an attribute in a FetchRequest to
  17. // ask for as many values as the OP can provide.
  18. define('Auth_OpenID_AX_UNLIMITED_VALUES', 'unlimited');
  19. // Minimum supported alias length in characters. Here for
  20. // completeness.
  21. define('Auth_OpenID_AX_MINIMUM_SUPPORTED_ALIAS_LENGTH', 32);
  22. /**
  23. * AX utility class.
  24. *
  25. * @package OpenID
  26. */
  27. class Auth_OpenID_AX {
  28. /**
  29. * @param mixed $thing Any object which may be an
  30. * Auth_OpenID_AX_Error object.
  31. *
  32. * @return bool true if $thing is an Auth_OpenID_AX_Error; false
  33. * if not.
  34. */
  35. static function isError($thing)
  36. {
  37. return is_a($thing, 'Auth_OpenID_AX_Error');
  38. }
  39. }
  40. /**
  41. * Check an alias for invalid characters; raise AXError if any are
  42. * found. Return None if the alias is valid.
  43. */
  44. function Auth_OpenID_AX_checkAlias($alias)
  45. {
  46. if (strpos($alias, ',') !== false) {
  47. return new Auth_OpenID_AX_Error(sprintf(
  48. "Alias %s must not contain comma", $alias));
  49. }
  50. if (strpos($alias, '.') !== false) {
  51. return new Auth_OpenID_AX_Error(sprintf(
  52. "Alias %s must not contain period", $alias));
  53. }
  54. return true;
  55. }
  56. /**
  57. * Results from data that does not meet the attribute exchange 1.0
  58. * specification
  59. *
  60. * @package OpenID
  61. */
  62. class Auth_OpenID_AX_Error {
  63. function Auth_OpenID_AX_Error($message=null)
  64. {
  65. $this->message = $message;
  66. }
  67. }
  68. /**
  69. * Abstract class containing common code for attribute exchange
  70. * messages.
  71. *
  72. * @package OpenID
  73. */
  74. class Auth_OpenID_AX_Message extends Auth_OpenID_Extension {
  75. /**
  76. * ns_alias: The preferred namespace alias for attribute exchange
  77. * messages
  78. */
  79. var $ns_alias = 'ax';
  80. /**
  81. * mode: The type of this attribute exchange message. This must be
  82. * overridden in subclasses.
  83. */
  84. var $mode = null;
  85. var $ns_uri = Auth_OpenID_AX_NS_URI;
  86. /**
  87. * Return Auth_OpenID_AX_Error if the mode in the attribute
  88. * exchange arguments does not match what is expected for this
  89. * class; true otherwise.
  90. *
  91. * @access private
  92. */
  93. function _checkMode($ax_args)
  94. {
  95. $mode = Auth_OpenID::arrayGet($ax_args, 'mode');
  96. if ($mode != $this->mode) {
  97. return new Auth_OpenID_AX_Error(
  98. sprintf(
  99. "Expected mode '%s'; got '%s'",
  100. $this->mode, $mode));
  101. }
  102. return true;
  103. }
  104. /**
  105. * Return a set of attribute exchange arguments containing the
  106. * basic information that must be in every attribute exchange
  107. * message.
  108. *
  109. * @access private
  110. */
  111. function _newArgs()
  112. {
  113. return array('mode' => $this->mode);
  114. }
  115. }
  116. /**
  117. * Represents a single attribute in an attribute exchange
  118. * request. This should be added to an AXRequest object in order to
  119. * request the attribute.
  120. *
  121. * @package OpenID
  122. */
  123. class Auth_OpenID_AX_AttrInfo {
  124. /**
  125. * Construct an attribute information object. Do not call this
  126. * directly; call make(...) instead.
  127. *
  128. * @param string $type_uri The type URI for this attribute.
  129. *
  130. * @param int $count The number of values of this type to request.
  131. *
  132. * @param bool $required Whether the attribute will be marked as
  133. * required in the request.
  134. *
  135. * @param string $alias The name that should be given to this
  136. * attribute in the request.
  137. */
  138. function Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
  139. $alias)
  140. {
  141. /**
  142. * required: Whether the attribute will be marked as required
  143. * when presented to the subject of the attribute exchange
  144. * request.
  145. */
  146. $this->required = $required;
  147. /**
  148. * count: How many values of this type to request from the
  149. * subject. Defaults to one.
  150. */
  151. $this->count = $count;
  152. /**
  153. * type_uri: The identifier that determines what the attribute
  154. * represents and how it is serialized. For example, one type
  155. * URI representing dates could represent a Unix timestamp in
  156. * base 10 and another could represent a human-readable
  157. * string.
  158. */
  159. $this->type_uri = $type_uri;
  160. /**
  161. * alias: The name that should be given to this attribute in
  162. * the request. If it is not supplied, a generic name will be
  163. * assigned. For example, if you want to call a Unix timestamp
  164. * value 'tstamp', set its alias to that value. If two
  165. * attributes in the same message request to use the same
  166. * alias, the request will fail to be generated.
  167. */
  168. $this->alias = $alias;
  169. }
  170. /**
  171. * Construct an attribute information object. For parameter
  172. * details, see the constructor.
  173. */
  174. static function make($type_uri, $count=1, $required=false,
  175. $alias=null)
  176. {
  177. if ($alias !== null) {
  178. $result = Auth_OpenID_AX_checkAlias($alias);
  179. if (Auth_OpenID_AX::isError($result)) {
  180. return $result;
  181. }
  182. }
  183. return new Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
  184. $alias);
  185. }
  186. /**
  187. * When processing a request for this attribute, the OP should
  188. * call this method to determine whether all available attribute
  189. * values were requested. If self.count == UNLIMITED_VALUES, this
  190. * returns True. Otherwise this returns False, in which case
  191. * self.count is an integer.
  192. */
  193. function wantsUnlimitedValues()
  194. {
  195. return $this->count === Auth_OpenID_AX_UNLIMITED_VALUES;
  196. }
  197. }
  198. /**
  199. * Given a namespace mapping and a string containing a comma-separated
  200. * list of namespace aliases, return a list of type URIs that
  201. * correspond to those aliases.
  202. *
  203. * @param $namespace_map The mapping from namespace URI to alias
  204. * @param $alias_list_s The string containing the comma-separated
  205. * list of aliases. May also be None for convenience.
  206. *
  207. * @return $seq The list of namespace URIs that corresponds to the
  208. * supplied list of aliases. If the string was zero-length or None, an
  209. * empty list will be returned.
  210. *
  211. * return null If an alias is present in the list of aliases but
  212. * is not present in the namespace map.
  213. */
  214. function Auth_OpenID_AX_toTypeURIs($namespace_map, $alias_list_s)
  215. {
  216. $uris = array();
  217. if ($alias_list_s) {
  218. foreach (explode(',', $alias_list_s) as $alias) {
  219. $type_uri = $namespace_map->getNamespaceURI($alias);
  220. if ($type_uri === null) {
  221. // raise KeyError(
  222. // 'No type is defined for attribute name %r' % (alias,))
  223. return new Auth_OpenID_AX_Error(
  224. sprintf('No type is defined for attribute name %s',
  225. $alias)
  226. );
  227. } else {
  228. $uris[] = $type_uri;
  229. }
  230. }
  231. }
  232. return $uris;
  233. }
  234. /**
  235. * An attribute exchange 'fetch_request' message. This message is sent
  236. * by a relying party when it wishes to obtain attributes about the
  237. * subject of an OpenID authentication request.
  238. *
  239. * @package OpenID
  240. */
  241. class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message {
  242. var $mode = 'fetch_request';
  243. function Auth_OpenID_AX_FetchRequest($update_url=null)
  244. {
  245. /**
  246. * requested_attributes: The attributes that have been
  247. * requested thus far, indexed by the type URI.
  248. */
  249. $this->requested_attributes = array();
  250. /**
  251. * update_url: A URL that will accept responses for this
  252. * attribute exchange request, even in the absence of the user
  253. * who made this request.
  254. */
  255. $this->update_url = $update_url;
  256. }
  257. /**
  258. * Add an attribute to this attribute exchange request.
  259. *
  260. * @param attribute: The attribute that is being requested
  261. * @return true on success, false when the requested attribute is
  262. * already present in this fetch request.
  263. */
  264. function add($attribute)
  265. {
  266. if ($this->contains($attribute->type_uri)) {
  267. return new Auth_OpenID_AX_Error(
  268. sprintf("The attribute %s has already been requested",
  269. $attribute->type_uri));
  270. }
  271. $this->requested_attributes[$attribute->type_uri] = $attribute;
  272. return true;
  273. }
  274. /**
  275. * Get the serialized form of this attribute fetch request.
  276. *
  277. * @returns Auth_OpenID_AX_FetchRequest The fetch request message parameters
  278. */
  279. function getExtensionArgs()
  280. {
  281. $aliases = new Auth_OpenID_NamespaceMap();
  282. $required = array();
  283. $if_available = array();
  284. $ax_args = $this->_newArgs();
  285. foreach ($this->requested_attributes as $type_uri => $attribute) {
  286. if ($attribute->alias === null) {
  287. $alias = $aliases->add($type_uri);
  288. } else {
  289. $alias = $aliases->addAlias($type_uri, $attribute->alias);
  290. if ($alias === null) {
  291. return new Auth_OpenID_AX_Error(
  292. sprintf("Could not add alias %s for URI %s",
  293. $attribute->alias, $type_uri
  294. ));
  295. }
  296. }
  297. if ($attribute->required) {
  298. $required[] = $alias;
  299. } else {
  300. $if_available[] = $alias;
  301. }
  302. if ($attribute->count != 1) {
  303. $ax_args['count.' . $alias] = strval($attribute->count);
  304. }
  305. $ax_args['type.' . $alias] = $type_uri;
  306. }
  307. if ($required) {
  308. $ax_args['required'] = implode(',', $required);
  309. }
  310. if ($if_available) {
  311. $ax_args['if_available'] = implode(',', $if_available);
  312. }
  313. return $ax_args;
  314. }
  315. /**
  316. * Get the type URIs for all attributes that have been marked as
  317. * required.
  318. *
  319. * @return A list of the type URIs for attributes that have been
  320. * marked as required.
  321. */
  322. function getRequiredAttrs()
  323. {
  324. $required = array();
  325. foreach ($this->requested_attributes as $type_uri => $attribute) {
  326. if ($attribute->required) {
  327. $required[] = $type_uri;
  328. }
  329. }
  330. return $required;
  331. }
  332. /**
  333. * Extract a FetchRequest from an OpenID message
  334. *
  335. * @param request: The OpenID request containing the attribute
  336. * fetch request
  337. *
  338. * @returns mixed An Auth_OpenID_AX_Error or the
  339. * Auth_OpenID_AX_FetchRequest extracted from the request message if
  340. * successful
  341. */
  342. static function fromOpenIDRequest($request)
  343. {
  344. $m = $request->message;
  345. $obj = new Auth_OpenID_AX_FetchRequest();
  346. $ax_args = $m->getArgs($obj->ns_uri);
  347. $result = $obj->parseExtensionArgs($ax_args);
  348. if (Auth_OpenID_AX::isError($result)) {
  349. return $result;
  350. }
  351. if ($obj->update_url) {
  352. // Update URL must match the openid.realm of the
  353. // underlying OpenID 2 message.
  354. $realm = $m->getArg(Auth_OpenID_OPENID_NS, 'realm',
  355. $m->getArg(
  356. Auth_OpenID_OPENID_NS,
  357. 'return_to'));
  358. if (!$realm) {
  359. $obj = new Auth_OpenID_AX_Error(
  360. sprintf("Cannot validate update_url %s " .
  361. "against absent realm", $obj->update_url));
  362. } else if (!Auth_OpenID_TrustRoot::match($realm,
  363. $obj->update_url)) {
  364. $obj = new Auth_OpenID_AX_Error(
  365. sprintf("Update URL %s failed validation against realm %s",
  366. $obj->update_url, $realm));
  367. }
  368. }
  369. return $obj;
  370. }
  371. /**
  372. * Given attribute exchange arguments, populate this FetchRequest.
  373. *
  374. * @return $result Auth_OpenID_AX_Error if the data to be parsed
  375. * does not follow the attribute exchange specification. At least
  376. * when 'if_available' or 'required' is not specified for a
  377. * particular attribute type. Returns true otherwise.
  378. */
  379. function parseExtensionArgs($ax_args)
  380. {
  381. $result = $this->_checkMode($ax_args);
  382. if (Auth_OpenID_AX::isError($result)) {
  383. return $result;
  384. }
  385. $aliases = new Auth_OpenID_NamespaceMap();
  386. foreach ($ax_args as $key => $value) {
  387. if (strpos($key, 'type.') === 0) {
  388. $alias = substr($key, 5);
  389. $type_uri = $value;
  390. $alias = $aliases->addAlias($type_uri, $alias);
  391. if ($alias === null) {
  392. return new Auth_OpenID_AX_Error(
  393. sprintf("Could not add alias %s for URI %s",
  394. $alias, $type_uri)
  395. );
  396. }
  397. $count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias);
  398. if ($count_s) {
  399. $count = Auth_OpenID::intval($count_s);
  400. if (($count === false) &&
  401. ($count_s === Auth_OpenID_AX_UNLIMITED_VALUES)) {
  402. $count = $count_s;
  403. }
  404. } else {
  405. $count = 1;
  406. }
  407. if ($count === false) {
  408. return new Auth_OpenID_AX_Error(
  409. sprintf("Integer value expected for %s, got %s",
  410. 'count.' . $alias, $count_s));
  411. }
  412. $attrinfo = Auth_OpenID_AX_AttrInfo::make($type_uri, $count,
  413. false, $alias);
  414. if (Auth_OpenID_AX::isError($attrinfo)) {
  415. return $attrinfo;
  416. }
  417. $this->add($attrinfo);
  418. }
  419. }
  420. $required = Auth_OpenID_AX_toTypeURIs($aliases,
  421. Auth_OpenID::arrayGet($ax_args, 'required'));
  422. foreach ($required as $type_uri) {
  423. $attrib = $this->requested_attributes[$type_uri];
  424. $attrib->required = true;
  425. }
  426. $if_available = Auth_OpenID_AX_toTypeURIs($aliases,
  427. Auth_OpenID::arrayGet($ax_args, 'if_available'));
  428. $all_type_uris = array_merge($required, $if_available);
  429. foreach ($aliases->iterNamespaceURIs() as $type_uri) {
  430. if (!in_array($type_uri, $all_type_uris)) {
  431. return new Auth_OpenID_AX_Error(
  432. sprintf('Type URI %s was in the request but not ' .
  433. 'present in "required" or "if_available"',
  434. $type_uri));
  435. }
  436. }
  437. $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
  438. return true;
  439. }
  440. /**
  441. * Iterate over the AttrInfo objects that are contained in this
  442. * fetch_request.
  443. */
  444. function iterAttrs()
  445. {
  446. return array_values($this->requested_attributes);
  447. }
  448. function iterTypes()
  449. {
  450. return array_keys($this->requested_attributes);
  451. }
  452. /**
  453. * Is the given type URI present in this fetch_request?
  454. */
  455. function contains($type_uri)
  456. {
  457. return in_array($type_uri, $this->iterTypes());
  458. }
  459. }
  460. /**
  461. * An abstract class that implements a message that has attribute keys
  462. * and values. It contains the common code between fetch_response and
  463. * store_request.
  464. *
  465. * @package OpenID
  466. */
  467. class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message {
  468. function Auth_OpenID_AX_KeyValueMessage()
  469. {
  470. $this->data = array();
  471. }
  472. /**
  473. * Add a single value for the given attribute type to the
  474. * message. If there are already values specified for this type,
  475. * this value will be sent in addition to the values already
  476. * specified.
  477. *
  478. * @param type_uri: The URI for the attribute
  479. * @param value: The value to add to the response to the relying
  480. * party for this attribute
  481. * @return null
  482. */
  483. function addValue($type_uri, $value)
  484. {
  485. if (!array_key_exists($type_uri, $this->data)) {
  486. $this->data[$type_uri] = array();
  487. }
  488. $values =& $this->data[$type_uri];
  489. $values[] = $value;
  490. }
  491. /**
  492. * Set the values for the given attribute type. This replaces any
  493. * values that have already been set for this attribute.
  494. *
  495. * @param type_uri: The URI for the attribute
  496. * @param values: A list of values to send for this attribute.
  497. */
  498. function setValues($type_uri, &$values)
  499. {
  500. $this->data[$type_uri] =& $values;
  501. }
  502. /**
  503. * Get the extension arguments for the key/value pairs contained
  504. * in this message.
  505. *
  506. * @param aliases: An alias mapping. Set to None if you don't care
  507. * about the aliases for this request.
  508. *
  509. * @access private
  510. */
  511. function _getExtensionKVArgs($aliases)
  512. {
  513. if ($aliases === null) {
  514. $aliases = new Auth_OpenID_NamespaceMap();
  515. }
  516. $ax_args = array();
  517. foreach ($this->data as $type_uri => $values) {
  518. $alias = $aliases->add($type_uri);
  519. $ax_args['type.' . $alias] = $type_uri;
  520. $ax_args['count.' . $alias] = strval(count($values));
  521. foreach ($values as $i => $value) {
  522. $key = sprintf('value.%s.%d', $alias, $i + 1);
  523. $ax_args[$key] = $value;
  524. }
  525. }
  526. return $ax_args;
  527. }
  528. /**
  529. * Parse attribute exchange key/value arguments into this object.
  530. *
  531. * @param ax_args: The attribute exchange fetch_response
  532. * arguments, with namespacing removed.
  533. *
  534. * @return Auth_OpenID_AX_Error or true
  535. */
  536. function parseExtensionArgs($ax_args)
  537. {
  538. $result = $this->_checkMode($ax_args);
  539. if (Auth_OpenID_AX::isError($result)) {
  540. return $result;
  541. }
  542. $aliases = new Auth_OpenID_NamespaceMap();
  543. foreach ($ax_args as $key => $value) {
  544. if (strpos($key, 'type.') === 0) {
  545. $type_uri = $value;
  546. $alias = substr($key, 5);
  547. $result = Auth_OpenID_AX_checkAlias($alias);
  548. if (Auth_OpenID_AX::isError($result)) {
  549. return $result;
  550. }
  551. $alias = $aliases->addAlias($type_uri, $alias);
  552. if ($alias === null) {
  553. return new Auth_OpenID_AX_Error(
  554. sprintf("Could not add alias %s for URI %s",
  555. $alias, $type_uri)
  556. );
  557. }
  558. }
  559. }
  560. foreach ($aliases->iteritems() as $pair) {
  561. list($type_uri, $alias) = $pair;
  562. if (array_key_exists('count.' . $alias, $ax_args) && ($ax_args['count.' . $alias] !== Auth_OpenID_AX_UNLIMITED_VALUES)) {
  563. $count_key = 'count.' . $alias;
  564. $count_s = $ax_args[$count_key];
  565. $count = Auth_OpenID::intval($count_s);
  566. if ($count === false) {
  567. return new Auth_OpenID_AX_Error(
  568. sprintf("Integer value expected for %s, got %s",
  569. 'count. %s' . $alias, $count_s,
  570. Auth_OpenID_AX_UNLIMITED_VALUES)
  571. );
  572. }
  573. $values = array();
  574. for ($i = 1; $i < $count + 1; $i++) {
  575. $value_key = sprintf('value.%s.%d', $alias, $i);
  576. if (!array_key_exists($value_key, $ax_args)) {
  577. return new Auth_OpenID_AX_Error(
  578. sprintf(
  579. "No value found for key %s",
  580. $value_key));
  581. }
  582. $value = $ax_args[$value_key];
  583. $values[] = $value;
  584. }
  585. } else {
  586. $key = 'value.' . $alias;
  587. if (!array_key_exists($key, $ax_args)) {
  588. return new Auth_OpenID_AX_Error(
  589. sprintf(
  590. "No value found for key %s",
  591. $key));
  592. }
  593. $value = $ax_args['value.' . $alias];
  594. if ($value == '') {
  595. $values = array();
  596. } else {
  597. $values = array($value);
  598. }
  599. }
  600. $this->data[$type_uri] = $values;
  601. }
  602. return true;
  603. }
  604. /**
  605. * Get a single value for an attribute. If no value was sent for
  606. * this attribute, use the supplied default. If there is more than
  607. * one value for this attribute, this method will fail.
  608. *
  609. * @param type_uri: The URI for the attribute
  610. * @param default: The value to return if the attribute was not
  611. * sent in the fetch_response.
  612. *
  613. * @return $value Auth_OpenID_AX_Error on failure or the value of
  614. * the attribute in the fetch_response message, or the default
  615. * supplied
  616. */
  617. function getSingle($type_uri, $default=null)
  618. {
  619. $values = Auth_OpenID::arrayGet($this->data, $type_uri);
  620. if (!$values) {
  621. return $default;
  622. } else if (count($values) == 1) {
  623. return $values[0];
  624. } else {
  625. return new Auth_OpenID_AX_Error(
  626. sprintf('More than one value present for %s',
  627. $type_uri)
  628. );
  629. }
  630. }
  631. /**
  632. * Get the list of values for this attribute in the
  633. * fetch_response.
  634. *
  635. * XXX: what to do if the values are not present? default
  636. * parameter? this is funny because it's always supposed to return
  637. * a list, so the default may break that, though it's provided by
  638. * the user's code, so it might be okay. If no default is
  639. * supplied, should the return be None or []?
  640. *
  641. * @param type_uri: The URI of the attribute
  642. *
  643. * @return $values The list of values for this attribute in the
  644. * response. May be an empty list. If the attribute was not sent
  645. * in the response, returns Auth_OpenID_AX_Error.
  646. */
  647. function get($type_uri)
  648. {
  649. if (array_key_exists($type_uri, $this->data)) {
  650. return $this->data[$type_uri];
  651. } else {
  652. return new Auth_OpenID_AX_Error(
  653. sprintf("Type URI %s not found in response",
  654. $type_uri)
  655. );
  656. }
  657. }
  658. /**
  659. * Get the number of responses for a particular attribute in this
  660. * fetch_response message.
  661. *
  662. * @param type_uri: The URI of the attribute
  663. *
  664. * @returns int The number of values sent for this attribute. If
  665. * the attribute was not sent in the response, returns
  666. * Auth_OpenID_AX_Error.
  667. */
  668. function count($type_uri)
  669. {
  670. if (array_key_exists($type_uri, $this->data)) {
  671. return count($this->get($type_uri));
  672. } else {
  673. return new Auth_OpenID_AX_Error(
  674. sprintf("Type URI %s not found in response",
  675. $type_uri)
  676. );
  677. }
  678. }
  679. }
  680. /**
  681. * A fetch_response attribute exchange message.
  682. *
  683. * @package OpenID
  684. */
  685. class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage {
  686. var $mode = 'fetch_response';
  687. function Auth_OpenID_AX_FetchResponse($update_url=null)
  688. {
  689. $this->Auth_OpenID_AX_KeyValueMessage();
  690. $this->update_url = $update_url;
  691. }
  692. /**
  693. * Serialize this object into arguments in the attribute exchange
  694. * namespace
  695. *
  696. * @return $args The dictionary of unqualified attribute exchange
  697. * arguments that represent this fetch_response, or
  698. * Auth_OpenID_AX_Error on error.
  699. */
  700. function getExtensionArgs($request=null)
  701. {
  702. $aliases = new Auth_OpenID_NamespaceMap();
  703. $zero_value_types = array();
  704. if ($request !== null) {
  705. // Validate the data in the context of the request (the
  706. // same attributes should be present in each, and the
  707. // counts in the response must be no more than the counts
  708. // in the request)
  709. foreach ($this->data as $type_uri => $unused) {
  710. if (!$request->contains($type_uri)) {
  711. return new Auth_OpenID_AX_Error(
  712. sprintf("Response attribute not present in request: %s",
  713. $type_uri)
  714. );
  715. }
  716. }
  717. foreach ($request->iterAttrs() as $attr_info) {
  718. // Copy the aliases from the request so that reading
  719. // the response in light of the request is easier
  720. if ($attr_info->alias === null) {
  721. $aliases->add($attr_info->type_uri);
  722. } else {
  723. $alias = $aliases->addAlias($attr_info->type_uri,
  724. $attr_info->alias);
  725. if ($alias === null) {
  726. return new Auth_OpenID_AX_Error(
  727. sprintf("Could not add alias %s for URI %s",
  728. $attr_info->alias, $attr_info->type_uri)
  729. );
  730. }
  731. }
  732. if (array_key_exists($attr_info->type_uri, $this->data)) {
  733. $values = $this->data[$attr_info->type_uri];
  734. } else {
  735. $values = array();
  736. $zero_value_types[] = $attr_info;
  737. }
  738. if (($attr_info->count != Auth_OpenID_AX_UNLIMITED_VALUES) &&
  739. ($attr_info->count < count($values))) {
  740. return new Auth_OpenID_AX_Error(
  741. sprintf("More than the number of requested values " .
  742. "were specified for %s",
  743. $attr_info->type_uri)
  744. );
  745. }
  746. }
  747. }
  748. $kv_args = $this->_getExtensionKVArgs($aliases);
  749. // Add the KV args into the response with the args that are
  750. // unique to the fetch_response
  751. $ax_args = $this->_newArgs();
  752. // For each requested attribute, put its type/alias and count
  753. // into the response even if no data were returned.
  754. foreach ($zero_value_types as $attr_info) {
  755. $alias = $aliases->getAlias($attr_info->type_uri);
  756. $kv_args['type.' . $alias] = $attr_info->type_uri;
  757. $kv_args['count.' . $alias] = '0';
  758. }
  759. $update_url = null;
  760. if ($request) {
  761. $update_url = $request->update_url;
  762. } else {
  763. $update_url = $this->update_url;
  764. }
  765. if ($update_url) {
  766. $ax_args['update_url'] = $update_url;
  767. }
  768. Auth_OpenID::update($ax_args, $kv_args);
  769. return $ax_args;
  770. }
  771. /**
  772. * @return $result Auth_OpenID_AX_Error on failure or true on
  773. * success.
  774. */
  775. function parseExtensionArgs($ax_args)
  776. {
  777. $result = parent::parseExtensionArgs($ax_args);
  778. if (Auth_OpenID_AX::isError($result)) {
  779. return $result;
  780. }
  781. $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
  782. return true;
  783. }
  784. /**
  785. * Construct a FetchResponse object from an OpenID library
  786. * SuccessResponse object.
  787. *
  788. * @param success_response: A successful id_res response object
  789. *
  790. * @param signed: Whether non-signed args should be processsed. If
  791. * True (the default), only signed arguments will be processsed.
  792. *
  793. * @return $response A FetchResponse containing the data from the
  794. * OpenID message
  795. */
  796. static function fromSuccessResponse($success_response, $signed=true)
  797. {
  798. $obj = new Auth_OpenID_AX_FetchResponse();
  799. if ($signed) {
  800. $ax_args = $success_response->getSignedNS($obj->ns_uri);
  801. } else {
  802. $ax_args = $success_response->message->getArgs($obj->ns_uri);
  803. }
  804. if ($ax_args === null || Auth_OpenID::isFailure($ax_args) ||
  805. sizeof($ax_args) == 0) {
  806. return null;
  807. }
  808. $result = $obj->parseExtensionArgs($ax_args);
  809. if (Auth_OpenID_AX::isError($result)) {
  810. #XXX log me
  811. return null;
  812. }
  813. return $obj;
  814. }
  815. }
  816. /**
  817. * A store request attribute exchange message representation.
  818. *
  819. * @package OpenID
  820. */
  821. class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage {
  822. var $mode = 'store_request';
  823. /**
  824. * @param array $aliases The namespace aliases to use when making
  825. * this store response. Leave as None to use defaults.
  826. */
  827. function getExtensionArgs($aliases=null)
  828. {
  829. $ax_args = $this->_newArgs();
  830. $kv_args = $this->_getExtensionKVArgs($aliases);
  831. Auth_OpenID::update($ax_args, $kv_args);
  832. return $ax_args;
  833. }
  834. }
  835. /**
  836. * An indication that the store request was processed along with this
  837. * OpenID transaction. Use make(), NOT the constructor, to create
  838. * response objects.
  839. *
  840. * @package OpenID
  841. */
  842. class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message {
  843. var $SUCCESS_MODE = 'store_response_success';
  844. var $FAILURE_MODE = 'store_response_failure';
  845. /**
  846. * Returns Auth_OpenID_AX_Error on error or an
  847. * Auth_OpenID_AX_StoreResponse object on success.
  848. */
  849. function make($succeeded=true, $error_message=null)
  850. {
  851. if (($succeeded) && ($error_message !== null)) {
  852. return new Auth_OpenID_AX_Error('An error message may only be '.
  853. 'included in a failing fetch response');
  854. }
  855. return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message);
  856. }
  857. function Auth_OpenID_AX_StoreResponse($succeeded=true, $error_message=null)
  858. {
  859. if ($succeeded) {
  860. $this->mode = $this->SUCCESS_MODE;
  861. } else {
  862. $this->mode = $this->FAILURE_MODE;
  863. }
  864. $this->error_message = $error_message;
  865. }
  866. /**
  867. * Was this response a success response?
  868. */
  869. function succeeded()
  870. {
  871. return $this->mode == $this->SUCCESS_MODE;
  872. }
  873. function getExtensionArgs()
  874. {
  875. $ax_args = $this->_newArgs();
  876. if ((!$this->succeeded()) && $this->error_message) {
  877. $ax_args['error'] = $this->error_message;
  878. }
  879. return $ax_args;
  880. }
  881. }