PageRenderTime 24ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/Auth/Yadis/Manager.php

http://github.com/openid/php-openid
PHP | 572 lines | 271 code | 70 blank | 231 comment | 25 complexity | 816d0a299f03a0e4f2d7644aed7ac479 MD5 | raw file
Possible License(s): Apache-2.0
  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 mixed $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 mixed $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. abstract class Auth_Yadis_SessionLoader {
  71. /**
  72. * Override this.
  73. *
  74. * @access private
  75. * @param array $data
  76. * @return bool
  77. */
  78. function check($data)
  79. {
  80. return true;
  81. }
  82. public abstract function requiredKeys();
  83. /**
  84. * Given a session data value (an array), this creates an object
  85. * (returned by $this->newObject()) whose attributes and values
  86. * are those in $data. Returns null if $data lacks keys found in
  87. * $this->requiredKeys(). Returns null if $this->check($data)
  88. * evaluates to false. Returns null if $this->newObject()
  89. * evaluates to false.
  90. *
  91. * @access private
  92. * @param array $data
  93. * @return null
  94. */
  95. function fromSession($data)
  96. {
  97. if (!$data) {
  98. return null;
  99. }
  100. $required = $this->requiredKeys();
  101. foreach ($required as $k) {
  102. if (!array_key_exists($k, $data)) {
  103. return null;
  104. }
  105. }
  106. if (!$this->check($data)) {
  107. return null;
  108. }
  109. $data = array_merge($data, $this->prepareForLoad($data));
  110. $obj = $this->newObject($data);
  111. if (!$obj) {
  112. return null;
  113. }
  114. foreach ($required as $k) {
  115. $obj->$k = $data[$k];
  116. }
  117. return $obj;
  118. }
  119. /**
  120. * Prepares the data array by making any necessary changes.
  121. * Returns an array whose keys and values will be used to update
  122. * the original data array before calling $this->newObject($data).
  123. *
  124. * @access private
  125. * @param array $data
  126. * @return array
  127. */
  128. function prepareForLoad($data)
  129. {
  130. return [];
  131. }
  132. /**
  133. * Returns a new instance of this loader's class, using the
  134. * session data to construct it if necessary. The object need
  135. * only be created; $this->fromSession() will take care of setting
  136. * the object's attributes.
  137. *
  138. * @access private
  139. * @param array $data
  140. * @return null
  141. */
  142. function newObject($data)
  143. {
  144. return null;
  145. }
  146. /**
  147. * Returns an array of keys and values built from the attributes
  148. * of $obj. If $this->prepareForSave($obj) returns an array, its keys
  149. * and values are used to update the $data array of attributes
  150. * from $obj.
  151. *
  152. * @access private
  153. * @param object $obj
  154. * @return array
  155. */
  156. function toSession($obj)
  157. {
  158. $data = [];
  159. foreach ($obj as $k => $v) {
  160. $data[$k] = $v;
  161. }
  162. $extra = $this->prepareForSave($obj);
  163. if ($extra && is_array($extra)) {
  164. foreach ($extra as $k => $v) {
  165. $data[$k] = $v;
  166. }
  167. }
  168. return $data;
  169. }
  170. /**
  171. * Override this.
  172. *
  173. * @access private
  174. * @param object $obj
  175. * @return array
  176. */
  177. function prepareForSave($obj)
  178. {
  179. return [];
  180. }
  181. }
  182. /**
  183. * A concrete loader implementation for Auth_OpenID_ServiceEndpoints.
  184. *
  185. * @package OpenID
  186. */
  187. class Auth_OpenID_ServiceEndpointLoader extends Auth_Yadis_SessionLoader {
  188. function newObject($data)
  189. {
  190. return new Auth_OpenID_ServiceEndpoint();
  191. }
  192. function requiredKeys()
  193. {
  194. $obj = new Auth_OpenID_ServiceEndpoint();
  195. $data = [];
  196. foreach ($obj as $k => $v) {
  197. $data[] = $k;
  198. }
  199. return $data;
  200. }
  201. function check($data)
  202. {
  203. return is_array($data['type_uris']);
  204. }
  205. }
  206. /**
  207. * A concrete loader implementation for Auth_Yadis_Managers.
  208. *
  209. * @package OpenID
  210. */
  211. class Auth_Yadis_ManagerLoader extends Auth_Yadis_SessionLoader {
  212. function requiredKeys()
  213. {
  214. return [
  215. 'starting_url',
  216. 'yadis_url',
  217. 'services',
  218. 'session_key',
  219. '_current',
  220. 'stale',
  221. ];
  222. }
  223. function newObject($data)
  224. {
  225. return new Auth_Yadis_Manager($data['starting_url'],
  226. $data['yadis_url'],
  227. $data['services'],
  228. $data['session_key']);
  229. }
  230. function check($data)
  231. {
  232. return is_array($data['services']);
  233. }
  234. function prepareForLoad($data)
  235. {
  236. $loader = new Auth_OpenID_ServiceEndpointLoader();
  237. $services = [];
  238. foreach ($data['services'] as $s) {
  239. $services[] = $loader->fromSession($s);
  240. }
  241. return ['services' => $services];
  242. }
  243. function prepareForSave($obj)
  244. {
  245. $loader = new Auth_OpenID_ServiceEndpointLoader();
  246. $services = [];
  247. foreach ($obj->services as $s) {
  248. $services[] = $loader->toSession($s);
  249. }
  250. return ['services' => $services];
  251. }
  252. }
  253. /**
  254. * The Yadis service manager which stores state in a session and
  255. * iterates over <Service> elements in a Yadis XRDS document and lets
  256. * a caller attempt to use each one. This is used by the Yadis
  257. * library internally.
  258. *
  259. * @package OpenID
  260. */
  261. class Auth_Yadis_Manager {
  262. /** @var string */
  263. public $starting_url;
  264. /** @var string */
  265. public $yadis_url;
  266. /** @var array */
  267. public $services;
  268. /** @var string */
  269. public $session_key;
  270. /** @var Auth_OpenID_ServiceEndpoint */
  271. public $_current;
  272. /**
  273. * Intialize a new yadis service manager.
  274. *
  275. * @access private
  276. * @param string $starting_url
  277. * @param string $yadis_url
  278. * @param array $services
  279. * @param string $session_key
  280. */
  281. function __construct($starting_url, $yadis_url,
  282. $services, $session_key)
  283. {
  284. // The URL that was used to initiate the Yadis protocol
  285. $this->starting_url = $starting_url;
  286. // The URL after following redirects (the identifier)
  287. $this->yadis_url = $yadis_url;
  288. // List of service elements
  289. $this->services = $services;
  290. $this->session_key = $session_key;
  291. // Reference to the current service object
  292. $this->_current = null;
  293. // Stale flag for cleanup if PHP lib has trouble.
  294. $this->stale = false;
  295. }
  296. /**
  297. * @access private
  298. */
  299. function length()
  300. {
  301. // How many untried services remain?
  302. return count($this->services);
  303. }
  304. /**
  305. * Return the next service
  306. *
  307. * $this->current() will continue to return that service until the
  308. * next call to this method.
  309. */
  310. function nextService()
  311. {
  312. if ($this->services) {
  313. $this->_current = array_shift($this->services);
  314. } else {
  315. $this->_current = null;
  316. }
  317. return $this->_current;
  318. }
  319. /**
  320. * @access private
  321. */
  322. function current()
  323. {
  324. // Return the current service.
  325. // Returns None if there are no services left.
  326. return $this->_current;
  327. }
  328. /**
  329. * @access private
  330. * @param string $url
  331. * @return bool
  332. */
  333. function forURL($url)
  334. {
  335. return in_array($url, [$this->starting_url, $this->yadis_url]);
  336. }
  337. /**
  338. * @access private
  339. */
  340. function started()
  341. {
  342. // Has the first service been returned?
  343. return $this->_current !== null;
  344. }
  345. }
  346. /**
  347. * State management for discovery.
  348. *
  349. * High-level usage pattern is to call .getNextService(discover) in
  350. * order to find the next available service for this user for this
  351. * session. Once a request completes, call .cleanup() to clean up the
  352. * session state.
  353. *
  354. * @package OpenID
  355. */
  356. class Auth_Yadis_Discovery {
  357. /**
  358. * @access private
  359. */
  360. public $DEFAULT_SUFFIX = 'auth';
  361. /**
  362. * @access private
  363. */
  364. public $PREFIX = '_yadis_services_';
  365. /**
  366. * Initialize a discovery object.
  367. *
  368. * @param Auth_Yadis_PHPSession $session An object which
  369. * implements the Auth_Yadis_PHPSession API.
  370. * @param string $url The URL on which to attempt discovery.
  371. * @param string $session_key_suffix The optional session key
  372. * suffix override.
  373. */
  374. function __construct($session, $url,
  375. $session_key_suffix = null)
  376. {
  377. /// Initialize a discovery object
  378. $this->session = $session;
  379. $this->url = $url;
  380. if ($session_key_suffix === null) {
  381. $session_key_suffix = $this->DEFAULT_SUFFIX;
  382. }
  383. $this->session_key_suffix = $session_key_suffix;
  384. $this->session_key = $this->PREFIX . $this->session_key_suffix;
  385. }
  386. /**
  387. * Return the next authentication service for the pair of
  388. * user_input and session. This function handles fallback.
  389. *
  390. * @param callback $discover_cb
  391. * @param object $fetcher
  392. * @return null|Auth_OpenID_ServiceEndpoint
  393. */
  394. function getNextService($discover_cb, $fetcher)
  395. {
  396. $manager = $this->getManager();
  397. if (!$manager || (!$manager->services)) {
  398. $this->destroyManager();
  399. list($yadis_url, $services) = call_user_func_array($discover_cb,
  400. [
  401. $this->url,
  402. $fetcher,
  403. ]);
  404. $manager = $this->createManager($services, $yadis_url);
  405. }
  406. if ($manager) {
  407. $loader = new Auth_Yadis_ManagerLoader();
  408. $service = $manager->nextService();
  409. $this->session->set($this->session_key,
  410. serialize($loader->toSession($manager)));
  411. } else {
  412. $service = null;
  413. }
  414. return $service;
  415. }
  416. /**
  417. * Clean up Yadis-related services in the session and return the
  418. * most-recently-attempted service from the manager, if one
  419. * exists.
  420. *
  421. * @param bool $force True if the manager should be deleted regardless
  422. * of whether it's a manager for $this->url.
  423. * @return null|Auth_OpenID_ServiceEndpoint
  424. */
  425. function cleanup($force=false)
  426. {
  427. $manager = $this->getManager($force);
  428. if ($manager) {
  429. $service = $manager->current();
  430. $this->destroyManager($force);
  431. } else {
  432. $service = null;
  433. }
  434. return $service;
  435. }
  436. /**
  437. * @access private
  438. */
  439. function getSessionKey()
  440. {
  441. // Get the session key for this starting URL and suffix
  442. return $this->PREFIX . $this->session_key_suffix;
  443. }
  444. /**
  445. * @access private
  446. *
  447. * @param bool $force True if the manager should be returned regardless
  448. * of whether it's a manager for $this->url.
  449. * @return null|Auth_Yadis_Manager
  450. */
  451. function getManager($force=false)
  452. {
  453. // Extract the YadisServiceManager for this object's URL and
  454. // suffix from the session.
  455. $manager_str = $this->session->get($this->getSessionKey());
  456. /** @var Auth_Yadis_Manager $manager */
  457. $manager = null;
  458. if ($manager_str !== null) {
  459. $loader = new Auth_Yadis_ManagerLoader();
  460. $manager = $loader->fromSession(unserialize($manager_str));
  461. }
  462. if ($manager && ($manager->forURL($this->url) || $force)) {
  463. return $manager;
  464. }
  465. return null;
  466. }
  467. /**
  468. * @access private
  469. * @param array $services
  470. * @param null|string $yadis_url
  471. * @return Auth_Yadis_Manager|null
  472. */
  473. function createManager($services, $yadis_url = null)
  474. {
  475. $key = $this->getSessionKey();
  476. if ($this->getManager()) {
  477. return $this->getManager();
  478. }
  479. if ($services) {
  480. $loader = new Auth_Yadis_ManagerLoader();
  481. $manager = new Auth_Yadis_Manager($this->url, $yadis_url,
  482. $services, $key);
  483. $this->session->set($this->session_key,
  484. serialize($loader->toSession($manager)));
  485. return $manager;
  486. }
  487. return null;
  488. }
  489. /**
  490. * @access private
  491. *
  492. * @param bool $force True if the manager should be deleted regardless
  493. * of whether it's a manager for $this->url.
  494. */
  495. function destroyManager($force=false)
  496. {
  497. if ($this->getManager($force) !== null) {
  498. $key = $this->getSessionKey();
  499. $this->session->del($key);
  500. }
  501. }
  502. }