PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/concrete/libraries/3rdparty/Auth/OpenID/SQLStore.php

https://bitbucket.org/selfeky/xclusivescardwebsite
PHP | 569 lines | 357 code | 69 blank | 143 comment | 57 complexity | 29d29b55d83598a79dad4bed1b3ebb4e MD5 | raw file
  1. <?php
  2. /**
  3. * SQL-backed OpenID stores.
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * LICENSE: See the COPYING file included in this distribution.
  8. *
  9. * @package OpenID
  10. * @author JanRain, Inc. <openid@janrain.com>
  11. * @copyright 2005-2008 Janrain, Inc.
  12. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  13. */
  14. /**
  15. * Require the PEAR DB module because we'll need it for the SQL-based
  16. * stores implemented here. We silence any errors from the inclusion
  17. * because it might not be present, and a user of the SQL stores may
  18. * supply an Auth_OpenID_DatabaseConnection instance that implements
  19. * its own storage.
  20. */
  21. global $__Auth_OpenID_PEAR_AVAILABLE;
  22. $__Auth_OpenID_PEAR_AVAILABLE = @include_once 'DB.php';
  23. /**
  24. * @access private
  25. */
  26. require_once 'Auth/OpenID/Interface.php';
  27. require_once 'Auth/OpenID/Nonce.php';
  28. /**
  29. * @access private
  30. */
  31. require_once 'Auth/OpenID.php';
  32. /**
  33. * @access private
  34. */
  35. require_once 'Auth/OpenID/Nonce.php';
  36. /**
  37. * This is the parent class for the SQL stores, which contains the
  38. * logic common to all of the SQL stores.
  39. *
  40. * The table names used are determined by the class variables
  41. * associations_table_name and nonces_table_name. To change the name
  42. * of the tables used, pass new table names into the constructor.
  43. *
  44. * To create the tables with the proper schema, see the createTables
  45. * method.
  46. *
  47. * This class shouldn't be used directly. Use one of its subclasses
  48. * instead, as those contain the code necessary to use a specific
  49. * database. If you're an OpenID integrator and you'd like to create
  50. * an SQL-driven store that wraps an application's database
  51. * abstraction, be sure to create a subclass of
  52. * {@link Auth_OpenID_DatabaseConnection} that calls the application's
  53. * database abstraction calls. Then, pass an instance of your new
  54. * database connection class to your SQLStore subclass constructor.
  55. *
  56. * All methods other than the constructor and createTables should be
  57. * considered implementation details.
  58. *
  59. * @package OpenID
  60. */
  61. class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
  62. /**
  63. * This creates a new SQLStore instance. It requires an
  64. * established database connection be given to it, and it allows
  65. * overriding the default table names.
  66. *
  67. * @param connection $connection This must be an established
  68. * connection to a database of the correct type for the SQLStore
  69. * subclass you're using. This must either be an PEAR DB
  70. * connection handle or an instance of a subclass of
  71. * Auth_OpenID_DatabaseConnection.
  72. *
  73. * @param associations_table: This is an optional parameter to
  74. * specify the name of the table used for storing associations.
  75. * The default value is 'oid_associations'.
  76. *
  77. * @param nonces_table: This is an optional parameter to specify
  78. * the name of the table used for storing nonces. The default
  79. * value is 'oid_nonces'.
  80. */
  81. function Auth_OpenID_SQLStore($connection,
  82. $associations_table = null,
  83. $nonces_table = null)
  84. {
  85. global $__Auth_OpenID_PEAR_AVAILABLE;
  86. $this->associations_table_name = "oid_associations";
  87. $this->nonces_table_name = "oid_nonces";
  88. // Check the connection object type to be sure it's a PEAR
  89. // database connection.
  90. if (!(is_object($connection) &&
  91. (is_subclass_of($connection, 'db_common') ||
  92. is_subclass_of($connection,
  93. 'auth_openid_databaseconnection')))) {
  94. trigger_error("Auth_OpenID_SQLStore expected PEAR connection " .
  95. "object (got ".get_class($connection).")",
  96. E_USER_ERROR);
  97. return;
  98. }
  99. $this->connection = $connection;
  100. // Be sure to set the fetch mode so the results are keyed on
  101. // column name instead of column index. This is a PEAR
  102. // constant, so only try to use it if PEAR is present. Note
  103. // that Auth_Openid_Databaseconnection instances need not
  104. // implement ::setFetchMode for this reason.
  105. if ($__Auth_OpenID_PEAR_AVAILABLE) {
  106. $this->connection->setFetchMode(DB_FETCHMODE_ASSOC);
  107. }
  108. if ($associations_table) {
  109. $this->associations_table_name = $associations_table;
  110. }
  111. if ($nonces_table) {
  112. $this->nonces_table_name = $nonces_table;
  113. }
  114. $this->max_nonce_age = 6 * 60 * 60;
  115. // Be sure to run the database queries with auto-commit mode
  116. // turned OFF, because we want every function to run in a
  117. // transaction, implicitly. As a rule, methods named with a
  118. // leading underscore will NOT control transaction behavior.
  119. // Callers of these methods will worry about transactions.
  120. $this->connection->autoCommit(false);
  121. // Create an empty SQL strings array.
  122. $this->sql = array();
  123. // Call this method (which should be overridden by subclasses)
  124. // to populate the $this->sql array with SQL strings.
  125. $this->setSQL();
  126. // Verify that all required SQL statements have been set, and
  127. // raise an error if any expected SQL strings were either
  128. // absent or empty.
  129. list($missing, $empty) = $this->_verifySQL();
  130. if ($missing) {
  131. trigger_error("Expected keys in SQL query list: " .
  132. implode(", ", $missing),
  133. E_USER_ERROR);
  134. return;
  135. }
  136. if ($empty) {
  137. trigger_error("SQL list keys have no SQL strings: " .
  138. implode(", ", $empty),
  139. E_USER_ERROR);
  140. return;
  141. }
  142. // Add table names to queries.
  143. $this->_fixSQL();
  144. }
  145. function tableExists($table_name)
  146. {
  147. return !$this->isError(
  148. $this->connection->query(
  149. sprintf("SELECT * FROM %s LIMIT 0",
  150. $table_name)));
  151. }
  152. /**
  153. * Returns true if $value constitutes a database error; returns
  154. * false otherwise.
  155. */
  156. function isError($value)
  157. {
  158. return PEAR::isError($value);
  159. }
  160. /**
  161. * Converts a query result to a boolean. If the result is a
  162. * database error according to $this->isError(), this returns
  163. * false; otherwise, this returns true.
  164. */
  165. function resultToBool($obj)
  166. {
  167. if ($this->isError($obj)) {
  168. return false;
  169. } else {
  170. return true;
  171. }
  172. }
  173. /**
  174. * This method should be overridden by subclasses. This method is
  175. * called by the constructor to set values in $this->sql, which is
  176. * an array keyed on sql name.
  177. */
  178. function setSQL()
  179. {
  180. }
  181. /**
  182. * Resets the store by removing all records from the store's
  183. * tables.
  184. */
  185. function reset()
  186. {
  187. $this->connection->query(sprintf("DELETE FROM %s",
  188. $this->associations_table_name));
  189. $this->connection->query(sprintf("DELETE FROM %s",
  190. $this->nonces_table_name));
  191. }
  192. /**
  193. * @access private
  194. */
  195. function _verifySQL()
  196. {
  197. $missing = array();
  198. $empty = array();
  199. $required_sql_keys = array(
  200. 'nonce_table',
  201. 'assoc_table',
  202. 'set_assoc',
  203. 'get_assoc',
  204. 'get_assocs',
  205. 'remove_assoc'
  206. );
  207. foreach ($required_sql_keys as $key) {
  208. if (!array_key_exists($key, $this->sql)) {
  209. $missing[] = $key;
  210. } else if (!$this->sql[$key]) {
  211. $empty[] = $key;
  212. }
  213. }
  214. return array($missing, $empty);
  215. }
  216. /**
  217. * @access private
  218. */
  219. function _fixSQL()
  220. {
  221. $replacements = array(
  222. array(
  223. 'value' => $this->nonces_table_name,
  224. 'keys' => array('nonce_table',
  225. 'add_nonce',
  226. 'clean_nonce')
  227. ),
  228. array(
  229. 'value' => $this->associations_table_name,
  230. 'keys' => array('assoc_table',
  231. 'set_assoc',
  232. 'get_assoc',
  233. 'get_assocs',
  234. 'remove_assoc',
  235. 'clean_assoc')
  236. )
  237. );
  238. foreach ($replacements as $item) {
  239. $value = $item['value'];
  240. $keys = $item['keys'];
  241. foreach ($keys as $k) {
  242. if (is_array($this->sql[$k])) {
  243. foreach ($this->sql[$k] as $part_key => $part_value) {
  244. $this->sql[$k][$part_key] = sprintf($part_value,
  245. $value);
  246. }
  247. } else {
  248. $this->sql[$k] = sprintf($this->sql[$k], $value);
  249. }
  250. }
  251. }
  252. }
  253. function blobDecode($blob)
  254. {
  255. return $blob;
  256. }
  257. function blobEncode($str)
  258. {
  259. return $str;
  260. }
  261. function createTables()
  262. {
  263. $this->connection->autoCommit(true);
  264. $n = $this->create_nonce_table();
  265. $a = $this->create_assoc_table();
  266. $this->connection->autoCommit(false);
  267. if ($n && $a) {
  268. return true;
  269. } else {
  270. return false;
  271. }
  272. }
  273. function create_nonce_table()
  274. {
  275. if (!$this->tableExists($this->nonces_table_name)) {
  276. $r = $this->connection->query($this->sql['nonce_table']);
  277. return $this->resultToBool($r);
  278. }
  279. return true;
  280. }
  281. function create_assoc_table()
  282. {
  283. if (!$this->tableExists($this->associations_table_name)) {
  284. $r = $this->connection->query($this->sql['assoc_table']);
  285. return $this->resultToBool($r);
  286. }
  287. return true;
  288. }
  289. /**
  290. * @access private
  291. */
  292. function _set_assoc($server_url, $handle, $secret, $issued,
  293. $lifetime, $assoc_type)
  294. {
  295. return $this->connection->query($this->sql['set_assoc'],
  296. array(
  297. $server_url,
  298. $handle,
  299. $secret,
  300. $issued,
  301. $lifetime,
  302. $assoc_type));
  303. }
  304. function storeAssociation($server_url, $association)
  305. {
  306. if ($this->resultToBool($this->_set_assoc(
  307. $server_url,
  308. $association->handle,
  309. $this->blobEncode(
  310. $association->secret),
  311. $association->issued,
  312. $association->lifetime,
  313. $association->assoc_type
  314. ))) {
  315. $this->connection->commit();
  316. } else {
  317. $this->connection->rollback();
  318. }
  319. }
  320. /**
  321. * @access private
  322. */
  323. function _get_assoc($server_url, $handle)
  324. {
  325. $result = $this->connection->getRow($this->sql['get_assoc'],
  326. array($server_url, $handle));
  327. if ($this->isError($result)) {
  328. return null;
  329. } else {
  330. return $result;
  331. }
  332. }
  333. /**
  334. * @access private
  335. */
  336. function _get_assocs($server_url)
  337. {
  338. $result = $this->connection->getAll($this->sql['get_assocs'],
  339. array($server_url));
  340. if ($this->isError($result)) {
  341. return array();
  342. } else {
  343. return $result;
  344. }
  345. }
  346. function removeAssociation($server_url, $handle)
  347. {
  348. if ($this->_get_assoc($server_url, $handle) == null) {
  349. return false;
  350. }
  351. if ($this->resultToBool($this->connection->query(
  352. $this->sql['remove_assoc'],
  353. array($server_url, $handle)))) {
  354. $this->connection->commit();
  355. } else {
  356. $this->connection->rollback();
  357. }
  358. return true;
  359. }
  360. function getAssociation($server_url, $handle = null)
  361. {
  362. if ($handle !== null) {
  363. $assoc = $this->_get_assoc($server_url, $handle);
  364. $assocs = array();
  365. if ($assoc) {
  366. $assocs[] = $assoc;
  367. }
  368. } else {
  369. $assocs = $this->_get_assocs($server_url);
  370. }
  371. if (!$assocs || (count($assocs) == 0)) {
  372. return null;
  373. } else {
  374. $associations = array();
  375. foreach ($assocs as $assoc_row) {
  376. $assoc = new Auth_OpenID_Association($assoc_row['handle'],
  377. $assoc_row['secret'],
  378. $assoc_row['issued'],
  379. $assoc_row['lifetime'],
  380. $assoc_row['assoc_type']);
  381. $assoc->secret = $this->blobDecode($assoc->secret);
  382. if ($assoc->getExpiresIn() == 0) {
  383. $this->removeAssociation($server_url, $assoc->handle);
  384. } else {
  385. $associations[] = array($assoc->issued, $assoc);
  386. }
  387. }
  388. if ($associations) {
  389. $issued = array();
  390. $assocs = array();
  391. foreach ($associations as $key => $assoc) {
  392. $issued[$key] = $assoc[0];
  393. $assocs[$key] = $assoc[1];
  394. }
  395. array_multisort($issued, SORT_DESC, $assocs, SORT_DESC,
  396. $associations);
  397. // return the most recently issued one.
  398. list($issued, $assoc) = $associations[0];
  399. return $assoc;
  400. } else {
  401. return null;
  402. }
  403. }
  404. }
  405. /**
  406. * @access private
  407. */
  408. function _add_nonce($server_url, $timestamp, $salt)
  409. {
  410. $sql = $this->sql['add_nonce'];
  411. $result = $this->connection->query($sql, array($server_url,
  412. $timestamp,
  413. $salt));
  414. if ($this->isError($result)) {
  415. $this->connection->rollback();
  416. } else {
  417. $this->connection->commit();
  418. }
  419. return $this->resultToBool($result);
  420. }
  421. function useNonce($server_url, $timestamp, $salt)
  422. {
  423. global $Auth_OpenID_SKEW;
  424. if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
  425. return False;
  426. }
  427. return $this->_add_nonce($server_url, $timestamp, $salt);
  428. }
  429. /**
  430. * "Octifies" a binary string by returning a string with escaped
  431. * octal bytes. This is used for preparing binary data for
  432. * PostgreSQL BYTEA fields.
  433. *
  434. * @access private
  435. */
  436. function _octify($str)
  437. {
  438. $result = "";
  439. for ($i = 0; $i < Auth_OpenID::bytes($str); $i++) {
  440. $ch = substr($str, $i, 1);
  441. if ($ch == "\\") {
  442. $result .= "\\\\\\\\";
  443. } else if (ord($ch) == 0) {
  444. $result .= "\\\\000";
  445. } else {
  446. $result .= "\\" . strval(decoct(ord($ch)));
  447. }
  448. }
  449. return $result;
  450. }
  451. /**
  452. * "Unoctifies" octal-escaped data from PostgreSQL and returns the
  453. * resulting ASCII (possibly binary) string.
  454. *
  455. * @access private
  456. */
  457. function _unoctify($str)
  458. {
  459. $result = "";
  460. $i = 0;
  461. while ($i < strlen($str)) {
  462. $char = $str[$i];
  463. if ($char == "\\") {
  464. // Look to see if the next char is a backslash and
  465. // append it.
  466. if ($str[$i + 1] != "\\") {
  467. $octal_digits = substr($str, $i + 1, 3);
  468. $dec = octdec($octal_digits);
  469. $char = chr($dec);
  470. $i += 4;
  471. } else {
  472. $char = "\\";
  473. $i += 2;
  474. }
  475. } else {
  476. $i += 1;
  477. }
  478. $result .= $char;
  479. }
  480. return $result;
  481. }
  482. function cleanupNonces()
  483. {
  484. global $Auth_OpenID_SKEW;
  485. $v = time() - $Auth_OpenID_SKEW;
  486. $this->connection->query($this->sql['clean_nonce'], array($v));
  487. $num = $this->connection->affectedRows();
  488. $this->connection->commit();
  489. return $num;
  490. }
  491. function cleanupAssociations()
  492. {
  493. $this->connection->query($this->sql['clean_assoc'],
  494. array(time()));
  495. $num = $this->connection->affectedRows();
  496. $this->connection->commit();
  497. return $num;
  498. }
  499. }
  500. ?>