Manager.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. <?php
  2. /**
  3. * Yadis service manager to be used during yadis-driven authentication
  4. * attempts.
  5. *
  6. * @package OpenID
  7. */
  8. /**
  9. * The base session class used by the Auth_Yadis_Manager. This
  10. * class wraps the default PHP session machinery and should be
  11. * subclassed if your application doesn't use PHP sessioning.
  12. *
  13. * @package OpenID
  14. */
  15. class Auth_Yadis_PHPSession {
  16. /**
  17. * Set a session key/value pair.
  18. *
  19. * @param string $name The name of the session key to add.
  20. * @param string $value The value to add to the session.
  21. */
  22. function set($name, $value)
  23. {
  24. $_SESSION[$name] = $value;
  25. }
  26. /**
  27. * Get a key's value from the session.
  28. *
  29. * @param string $name The name of the key to retrieve.
  30. * @param string $default The optional value to return if the key
  31. * is not found in the session.
  32. * @return string $result The key's value in the session or
  33. * $default if it isn't found.
  34. */
  35. function get($name, $default=null)
  36. {
  37. if (isset($_SESSION) && array_key_exists($name, $_SESSION)) {
  38. return $_SESSION[$name];
  39. } else {
  40. return $default;
  41. }
  42. }
  43. /**
  44. * Remove a key/value pair from the session.
  45. *
  46. * @param string $name The name of the key to remove.
  47. */
  48. function del($name)
  49. {
  50. unset($_SESSION[$name]);
  51. }
  52. /**
  53. * Return the contents of the session in array form.
  54. */
  55. function contents()
  56. {
  57. return $_SESSION;
  58. }
  59. }
  60. /**
  61. * A session helper class designed to translate between arrays and
  62. * objects. Note that the class used must have a constructor that
  63. * takes no parameters. This is not a general solution, but it works
  64. * for dumb objects that just need to have attributes set. The idea
  65. * is that you'll subclass this and override $this->check($data) ->
  66. * bool to implement your own session data validation.
  67. *
  68. * @package OpenID
  69. */
  70. class Auth_Yadis_SessionLoader {
  71. /**
  72. * Override this.
  73. *
  74. * @access private
  75. */
  76. function check($data)
  77. {
  78. return true;
  79. }
  80. /**
  81. * Given a session data value (an array), this creates an object
  82. * (returned by $this->newObject()) whose attributes and values
  83. * are those in $data. Returns null if $data lacks keys found in
  84. * $this->requiredKeys(). Returns null if $this->check($data)
  85. * evaluates to false. Returns null if $this->newObject()
  86. * evaluates to false.
  87. *
  88. * @access private
  89. */
  90. function fromSession($data)
  91. {
  92. if (!$data) {
  93. return null;
  94. }
  95. $required = $this->requiredKeys();
  96. foreach ($required as $k) {
  97. if (!array_key_exists($k, $data)) {
  98. return null;
  99. }
  100. }
  101. if (!$this->check($data)) {
  102. return null;
  103. }
  104. $data = array_merge($data, $this->prepareForLoad($data));
  105. $obj = $this->newObject($data);
  106. if (!$obj) {
  107. return null;
  108. }
  109. foreach ($required as $k) {
  110. $obj->$k = $data[$k];
  111. }
  112. return $obj;
  113. }
  114. /**
  115. * Prepares the data array by making any necessary changes.
  116. * Returns an array whose keys and values will be used to update
  117. * the original data array before calling $this->newObject($data).
  118. *
  119. * @access private
  120. */
  121. function prepareForLoad($data)
  122. {
  123. return array();
  124. }
  125. /**
  126. * Returns a new instance of this loader's class, using the
  127. * session data to construct it if necessary. The object need
  128. * only be created; $this->fromSession() will take care of setting
  129. * the object's attributes.
  130. *
  131. * @access private
  132. */
  133. function newObject($data)
  134. {
  135. return null;
  136. }
  137. /**
  138. * Returns an array of keys and values built from the attributes
  139. * of $obj. If $this->prepareForSave($obj) returns an array, its keys
  140. * and values are used to update the $data array of attributes
  141. * from $obj.
  142. *
  143. * @access private
  144. */
  145. function toSession($obj)
  146. {
  147. $data = array();
  148. foreach ($obj as $k => $v) {
  149. $data[$k] = $v;
  150. }
  151. $extra = $this->prepareForSave($obj);
  152. if ($extra && is_array($extra)) {
  153. foreach ($extra as $k => $v) {
  154. $data[$k] = $v;
  155. }
  156. }
  157. return $data;
  158. }
  159. /**
  160. * Override this.
  161. *
  162. * @access private
  163. */
  164. function prepareForSave($obj)
  165. {
  166. return array();
  167. }
  168. }
  169. /**
  170. * A concrete loader implementation for Auth_OpenID_ServiceEndpoints.
  171. *
  172. * @package OpenID
  173. */
  174. class Auth_OpenID_ServiceEndpointLoader extends Auth_Yadis_SessionLoader {
  175. function newObject($data)
  176. {
  177. return new Auth_OpenID_ServiceEndpoint();
  178. }
  179. function requiredKeys()
  180. {
  181. $obj = new Auth_OpenID_ServiceEndpoint();
  182. $data = array();
  183. foreach ($obj as $k => $v) {
  184. $data[] = $k;
  185. }
  186. return $data;
  187. }
  188. function check($data)
  189. {
  190. return is_array($data['type_uris']);
  191. }
  192. }
  193. /**
  194. * A concrete loader implementation for Auth_Yadis_Managers.
  195. *
  196. * @package OpenID
  197. */
  198. class Auth_Yadis_ManagerLoader extends Auth_Yadis_SessionLoader {
  199. function requiredKeys()
  200. {
  201. return array('starting_url',
  202. 'yadis_url',
  203. 'services',
  204. 'session_key',
  205. '_current',
  206. 'stale');
  207. }
  208. function newObject($data)
  209. {
  210. return new Auth_Yadis_Manager($data['starting_url'],
  211. $data['yadis_url'],
  212. $data['services'],
  213. $data['session_key']);
  214. }
  215. function check($data)
  216. {
  217. return is_array($data['services']);
  218. }
  219. function prepareForLoad($data)
  220. {
  221. $loader = new Auth_OpenID_ServiceEndpointLoader();
  222. $services = array();
  223. foreach ($data['services'] as $s) {
  224. $services[] = $loader->fromSession($s);
  225. }
  226. return array('services' => $services);
  227. }
  228. function prepareForSave($obj)
  229. {
  230. $loader = new Auth_OpenID_ServiceEndpointLoader();
  231. $services = array();
  232. foreach ($obj->services as $s) {
  233. $services[] = $loader->toSession($s);
  234. }
  235. return array('services' => $services);
  236. }
  237. }
  238. /**
  239. * The Yadis service manager which stores state in a session and
  240. * iterates over <Service> elements in a Yadis XRDS document and lets
  241. * a caller attempt to use each one. This is used by the Yadis
  242. * library internally.
  243. *
  244. * @package OpenID
  245. */
  246. class Auth_Yadis_Manager {
  247. /**
  248. * Intialize a new yadis service manager.
  249. *
  250. * @access private
  251. */
  252. function Auth_Yadis_Manager($starting_url, $yadis_url,
  253. $services, $session_key)
  254. {
  255. // The URL that was used to initiate the Yadis protocol
  256. $this->starting_url = $starting_url;
  257. // The URL after following redirects (the identifier)
  258. $this->yadis_url = $yadis_url;
  259. // List of service elements
  260. $this->services = $services;
  261. $this->session_key = $session_key;
  262. // Reference to the current service object
  263. $this->_current = null;
  264. // Stale flag for cleanup if PHP lib has trouble.
  265. $this->stale = false;
  266. }
  267. /**
  268. * @access private
  269. */
  270. function length()
  271. {
  272. // How many untried services remain?
  273. return count($this->services);
  274. }
  275. /**
  276. * Return the next service
  277. *
  278. * $this->current() will continue to return that service until the
  279. * next call to this method.
  280. */
  281. function nextService()
  282. {
  283. if ($this->services) {
  284. $this->_current = array_shift($this->services);
  285. } else {
  286. $this->_current = null;
  287. }
  288. return $this->_current;
  289. }
  290. /**
  291. * @access private
  292. */
  293. function current()
  294. {
  295. // Return the current service.
  296. // Returns None if there are no services left.
  297. return $this->_current;
  298. }
  299. /**
  300. * @access private
  301. */
  302. function forURL($url)
  303. {
  304. return in_array($url, array($this->starting_url, $this->yadis_url));
  305. }
  306. /**
  307. * @access private
  308. */
  309. function started()
  310. {
  311. // Has the first service been returned?
  312. return $this->_current !== null;
  313. }
  314. }
  315. /**
  316. * State management for discovery.
  317. *
  318. * High-level usage pattern is to call .getNextService(discover) in
  319. * order to find the next available service for this user for this
  320. * session. Once a request completes, call .cleanup() to clean up the
  321. * session state.
  322. *
  323. * @package OpenID
  324. */
  325. class Auth_Yadis_Discovery {
  326. /**
  327. * @access private
  328. */
  329. var $DEFAULT_SUFFIX = 'auth';
  330. /**
  331. * @access private
  332. */
  333. var $PREFIX = '_yadis_services_';
  334. /**
  335. * Initialize a discovery object.
  336. *
  337. * @param Auth_Yadis_PHPSession $session An object which
  338. * implements the Auth_Yadis_PHPSession API.
  339. * @param string $url The URL on which to attempt discovery.
  340. * @param string $session_key_suffix The optional session key
  341. * suffix override.
  342. */
  343. function Auth_Yadis_Discovery($session, $url,
  344. $session_key_suffix = null)
  345. {
  346. /// Initialize a discovery object
  347. $this->session = $session;
  348. $this->url = $url;
  349. if ($session_key_suffix === null) {
  350. $session_key_suffix = $this->DEFAULT_SUFFIX;
  351. }
  352. $this->session_key_suffix = $session_key_suffix;
  353. $this->session_key = $this->PREFIX . $this->session_key_suffix;
  354. }
  355. /**
  356. * Return the next authentication service for the pair of
  357. * user_input and session. This function handles fallback.
  358. */
  359. function getNextService($discover_cb, $fetcher)
  360. {
  361. $manager = $this->getManager();
  362. if (!$manager || (!$manager->services)) {
  363. $this->destroyManager();
  364. list($yadis_url, $services) = call_user_func_array($discover_cb,
  365. array(
  366. $this->url,
  367. $fetcher,
  368. ));
  369. $manager = $this->createManager($services, $yadis_url);
  370. }
  371. if ($manager) {
  372. $loader = new Auth_Yadis_ManagerLoader();
  373. $service = $manager->nextService();
  374. $this->session->set($this->session_key,
  375. serialize($loader->toSession($manager)));
  376. } else {
  377. $service = null;
  378. }
  379. return $service;
  380. }
  381. /**
  382. * Clean up Yadis-related services in the session and return the
  383. * most-recently-attempted service from the manager, if one
  384. * exists.
  385. *
  386. * @param $force True if the manager should be deleted regardless
  387. * of whether it's a manager for $this->url.
  388. */
  389. function cleanup($force=false)
  390. {
  391. $manager = $this->getManager($force);
  392. if ($manager) {
  393. $service = $manager->current();
  394. $this->destroyManager($force);
  395. } else {
  396. $service = null;
  397. }
  398. return $service;
  399. }
  400. /**
  401. * @access private
  402. */
  403. function getSessionKey()
  404. {
  405. // Get the session key for this starting URL and suffix
  406. return $this->PREFIX . $this->session_key_suffix;
  407. }
  408. /**
  409. * @access private
  410. *
  411. * @param $force True if the manager should be returned regardless
  412. * of whether it's a manager for $this->url.
  413. */
  414. function getManager($force=false)
  415. {
  416. // Extract the YadisServiceManager for this object's URL and
  417. // suffix from the session.
  418. $manager_str = $this->session->get($this->getSessionKey());
  419. $manager = null;
  420. if ($manager_str !== null) {
  421. $loader = new Auth_Yadis_ManagerLoader();
  422. $manager = $loader->fromSession(unserialize($manager_str));
  423. }
  424. if ($manager && ($manager->forURL($this->url) || $force)) {
  425. return $manager;
  426. }
  427. }
  428. /**
  429. * @access private
  430. */
  431. function createManager($services, $yadis_url = null)
  432. {
  433. $key = $this->getSessionKey();
  434. if ($this->getManager()) {
  435. return $this->getManager();
  436. }
  437. if ($services) {
  438. $loader = new Auth_Yadis_ManagerLoader();
  439. $manager = new Auth_Yadis_Manager($this->url, $yadis_url,
  440. $services, $key);
  441. $this->session->set($this->session_key,
  442. serialize($loader->toSession($manager)));
  443. return $manager;
  444. }
  445. }
  446. /**
  447. * @access private
  448. *
  449. * @param $force True if the manager should be deleted regardless
  450. * of whether it's a manager for $this->url.
  451. */
  452. function destroyManager($force=false)
  453. {
  454. if ($this->getManager($force) !== null) {
  455. $key = $this->getSessionKey();
  456. $this->session->del($key);
  457. }
  458. }
  459. }