MDB2Store.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <?php
  2. /**
  3. * SQL-backed OpenID stores for use with PEAR::MDB2.
  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 Janrain, Inc.
  12. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  13. */
  14. require_once 'MDB2.php';
  15. /**
  16. * @access private
  17. */
  18. require_once 'Auth/OpenID/Interface.php';
  19. /**
  20. * @access private
  21. */
  22. require_once 'Auth/OpenID.php';
  23. /**
  24. * @access private
  25. */
  26. require_once 'Auth/OpenID/Nonce.php';
  27. /**
  28. * This store uses a PEAR::MDB2 connection to store persistence
  29. * information.
  30. *
  31. * The table names used are determined by the class variables
  32. * associations_table_name and nonces_table_name. To change the name
  33. * of the tables used, pass new table names into the constructor.
  34. *
  35. * To create the tables with the proper schema, see the createTables
  36. * method.
  37. *
  38. * @package OpenID
  39. */
  40. class Auth_OpenID_MDB2Store extends Auth_OpenID_OpenIDStore {
  41. /**
  42. * This creates a new MDB2Store instance. It requires an
  43. * established database connection be given to it, and it allows
  44. * overriding the default table names.
  45. *
  46. * @param connection $connection This must be an established
  47. * connection to a database of the correct type for the SQLStore
  48. * subclass you're using. This must be a PEAR::MDB2 connection
  49. * handle.
  50. *
  51. * @param associations_table: This is an optional parameter to
  52. * specify the name of the table used for storing associations.
  53. * The default value is 'oid_associations'.
  54. *
  55. * @param nonces_table: This is an optional parameter to specify
  56. * the name of the table used for storing nonces. The default
  57. * value is 'oid_nonces'.
  58. */
  59. function Auth_OpenID_MDB2Store($connection,
  60. $associations_table = null,
  61. $nonces_table = null)
  62. {
  63. $this->associations_table_name = "oid_associations";
  64. $this->nonces_table_name = "oid_nonces";
  65. // Check the connection object type to be sure it's a PEAR
  66. // database connection.
  67. if (!is_object($connection) ||
  68. !is_subclass_of($connection, 'mdb2_driver_common')) {
  69. trigger_error("Auth_OpenID_MDB2Store expected PEAR connection " .
  70. "object (got ".get_class($connection).")",
  71. E_USER_ERROR);
  72. return;
  73. }
  74. $this->connection = $connection;
  75. // Be sure to set the fetch mode so the results are keyed on
  76. // column name instead of column index.
  77. $this->connection->setFetchMode(MDB2_FETCHMODE_ASSOC);
  78. if (@PEAR::isError($this->connection->loadModule('Extended'))) {
  79. trigger_error("Unable to load MDB2_Extended module", E_USER_ERROR);
  80. return;
  81. }
  82. if ($associations_table) {
  83. $this->associations_table_name = $associations_table;
  84. }
  85. if ($nonces_table) {
  86. $this->nonces_table_name = $nonces_table;
  87. }
  88. $this->max_nonce_age = 6 * 60 * 60;
  89. }
  90. function tableExists($table_name)
  91. {
  92. return !@PEAR::isError($this->connection->query(
  93. sprintf("SELECT * FROM %s LIMIT 0",
  94. $table_name)));
  95. }
  96. function createTables()
  97. {
  98. $n = $this->create_nonce_table();
  99. $a = $this->create_assoc_table();
  100. if (!$n || !$a) {
  101. return false;
  102. }
  103. return true;
  104. }
  105. function create_nonce_table()
  106. {
  107. if (!$this->tableExists($this->nonces_table_name)) {
  108. switch ($this->connection->phptype) {
  109. case "mysql":
  110. case "mysqli":
  111. // Custom SQL for MySQL to use InnoDB and variable-
  112. // length keys
  113. $r = $this->connection->exec(
  114. sprintf("CREATE TABLE %s (\n".
  115. " server_url VARCHAR(2047) NOT NULL DEFAULT '',\n".
  116. " timestamp INTEGER NOT NULL,\n".
  117. " salt CHAR(40) NOT NULL,\n".
  118. " UNIQUE (server_url(255), timestamp, salt)\n".
  119. ") TYPE=InnoDB",
  120. $this->nonces_table_name));
  121. if (@PEAR::isError($r)) {
  122. return false;
  123. }
  124. break;
  125. default:
  126. if (@PEAR::isError(
  127. $this->connection->loadModule('Manager'))) {
  128. return false;
  129. }
  130. $fields = array(
  131. "server_url" => array(
  132. "type" => "text",
  133. "length" => 2047,
  134. "notnull" => true
  135. ),
  136. "timestamp" => array(
  137. "type" => "integer",
  138. "notnull" => true
  139. ),
  140. "salt" => array(
  141. "type" => "text",
  142. "length" => 40,
  143. "fixed" => true,
  144. "notnull" => true
  145. )
  146. );
  147. $constraint = array(
  148. "unique" => 1,
  149. "fields" => array(
  150. "server_url" => true,
  151. "timestamp" => true,
  152. "salt" => true
  153. )
  154. );
  155. $r = $this->connection->createTable($this->nonces_table_name,
  156. $fields);
  157. if (@PEAR::isError($r)) {
  158. return false;
  159. }
  160. $r = $this->connection->createConstraint(
  161. $this->nonces_table_name,
  162. $this->nonces_table_name . "_constraint",
  163. $constraint);
  164. if (@PEAR::isError($r)) {
  165. return false;
  166. }
  167. break;
  168. }
  169. }
  170. return true;
  171. }
  172. function create_assoc_table()
  173. {
  174. if (!$this->tableExists($this->associations_table_name)) {
  175. switch ($this->connection->phptype) {
  176. case "mysql":
  177. case "mysqli":
  178. // Custom SQL for MySQL to use InnoDB and variable-
  179. // length keys
  180. $r = $this->connection->exec(
  181. sprintf("CREATE TABLE %s(\n".
  182. " server_url VARCHAR(2047) NOT NULL DEFAULT '',\n".
  183. " handle VARCHAR(255) NOT NULL,\n".
  184. " secret BLOB NOT NULL,\n".
  185. " issued INTEGER NOT NULL,\n".
  186. " lifetime INTEGER NOT NULL,\n".
  187. " assoc_type VARCHAR(64) NOT NULL,\n".
  188. " PRIMARY KEY (server_url(255), handle)\n".
  189. ") TYPE=InnoDB",
  190. $this->associations_table_name));
  191. if (@PEAR::isError($r)) {
  192. return false;
  193. }
  194. break;
  195. default:
  196. if (@PEAR::isError(
  197. $this->connection->loadModule('Manager'))) {
  198. return false;
  199. }
  200. $fields = array(
  201. "server_url" => array(
  202. "type" => "text",
  203. "length" => 2047,
  204. "notnull" => true
  205. ),
  206. "handle" => array(
  207. "type" => "text",
  208. "length" => 255,
  209. "notnull" => true
  210. ),
  211. "secret" => array(
  212. "type" => "blob",
  213. "length" => "255",
  214. "notnull" => true
  215. ),
  216. "issued" => array(
  217. "type" => "integer",
  218. "notnull" => true
  219. ),
  220. "lifetime" => array(
  221. "type" => "integer",
  222. "notnull" => true
  223. ),
  224. "assoc_type" => array(
  225. "type" => "text",
  226. "length" => 64,
  227. "notnull" => true
  228. )
  229. );
  230. $options = array(
  231. "primary" => array(
  232. "server_url" => true,
  233. "handle" => true
  234. )
  235. );
  236. $r = $this->connection->createTable(
  237. $this->associations_table_name,
  238. $fields,
  239. $options);
  240. if (@PEAR::isError($r)) {
  241. return false;
  242. }
  243. break;
  244. }
  245. }
  246. return true;
  247. }
  248. function storeAssociation($server_url, $association)
  249. {
  250. $fields = array(
  251. "server_url" => array(
  252. "value" => $server_url,
  253. "key" => true
  254. ),
  255. "handle" => array(
  256. "value" => $association->handle,
  257. "key" => true
  258. ),
  259. "secret" => array(
  260. "value" => $association->secret,
  261. "type" => "blob"
  262. ),
  263. "issued" => array(
  264. "value" => $association->issued
  265. ),
  266. "lifetime" => array(
  267. "value" => $association->lifetime
  268. ),
  269. "assoc_type" => array(
  270. "value" => $association->assoc_type
  271. )
  272. );
  273. return !@PEAR::isError($this->connection->replace(
  274. $this->associations_table_name,
  275. $fields));
  276. }
  277. function cleanupNonces()
  278. {
  279. global $Auth_OpenID_SKEW;
  280. $v = time() - $Auth_OpenID_SKEW;
  281. return $this->connection->exec(
  282. sprintf("DELETE FROM %s WHERE timestamp < %d",
  283. $this->nonces_table_name, $v));
  284. }
  285. function cleanupAssociations()
  286. {
  287. return $this->connection->exec(
  288. sprintf("DELETE FROM %s WHERE issued + lifetime < %d",
  289. $this->associations_table_name, time()));
  290. }
  291. function getAssociation($server_url, $handle = null)
  292. {
  293. $sql = "";
  294. $params = null;
  295. $types = array(
  296. "text",
  297. "blob",
  298. "integer",
  299. "integer",
  300. "text"
  301. );
  302. if ($handle !== null) {
  303. $sql = sprintf("SELECT handle, secret, issued, lifetime, assoc_type " .
  304. "FROM %s WHERE server_url = ? AND handle = ?",
  305. $this->associations_table_name);
  306. $params = array($server_url, $handle);
  307. } else {
  308. $sql = sprintf("SELECT handle, secret, issued, lifetime, assoc_type " .
  309. "FROM %s WHERE server_url = ? ORDER BY issued DESC",
  310. $this->associations_table_name);
  311. $params = array($server_url);
  312. }
  313. $assoc = $this->connection->getRow($sql, $types, $params);
  314. if (!$assoc || @PEAR::isError($assoc)) {
  315. return null;
  316. } else {
  317. $association = new Auth_OpenID_Association($assoc['handle'],
  318. stream_get_contents(
  319. $assoc['secret']),
  320. $assoc['issued'],
  321. $assoc['lifetime'],
  322. $assoc['assoc_type']);
  323. fclose($assoc['secret']);
  324. return $association;
  325. }
  326. }
  327. function removeAssociation($server_url, $handle)
  328. {
  329. $r = $this->connection->execParam(
  330. sprintf("DELETE FROM %s WHERE server_url = ? AND handle = ?",
  331. $this->associations_table_name),
  332. array($server_url, $handle));
  333. if (@PEAR::isError($r) || $r == 0) {
  334. return false;
  335. }
  336. return true;
  337. }
  338. function useNonce($server_url, $timestamp, $salt)
  339. {
  340. global $Auth_OpenID_SKEW;
  341. if (abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
  342. return false;
  343. }
  344. $fields = array(
  345. "timestamp" => $timestamp,
  346. "salt" => $salt
  347. );
  348. if (!empty($server_url)) {
  349. $fields["server_url"] = $server_url;
  350. }
  351. $r = $this->connection->autoExecute(
  352. $this->nonces_table_name,
  353. $fields,
  354. MDB2_AUTOQUERY_INSERT);
  355. if (@PEAR::isError($r)) {
  356. return false;
  357. }
  358. return true;
  359. }
  360. /**
  361. * Resets the store by removing all records from the store's
  362. * tables.
  363. */
  364. function reset()
  365. {
  366. $this->connection->query(sprintf("DELETE FROM %s",
  367. $this->associations_table_name));
  368. $this->connection->query(sprintf("DELETE FROM %s",
  369. $this->nonces_table_name));
  370. }
  371. }
  372. ?>