PageRenderTime 53ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/php-pear-Auth-1.6.2/Auth-1.6.2/Container/DB.php

#
PHP | 639 lines | 338 code | 86 blank | 215 comment | 82 complexity | 7e0cfb35c21152ee81972bca23315ac6 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
  3. /**
  4. * Storage driver for use against PEAR DB
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * LICENSE: This source file is subject to version 3.01 of the PHP license
  9. * that is available through the world-wide-web at the following URI:
  10. * http://www.php.net/license/3_01.txt. If you did not receive a copy of
  11. * the PHP License and are unable to obtain it through the web, please
  12. * send a note to license@php.net so we can mail you a copy immediately.
  13. *
  14. * @category Authentication
  15. * @package Auth
  16. * @author Martin Jansen <mj@php.net>
  17. * @author Adam Ashley <aashley@php.net>
  18. * @copyright 2001-2006 The PHP Group
  19. * @license http://www.php.net/license/3_01.txt PHP License 3.01
  20. * @version CVS: $Id: DB.php 256753 2008-04-04 07:57:02Z aashley $
  21. * @link http://pear.php.net/package/Auth
  22. */
  23. /**
  24. * Include Auth_Container base class
  25. */
  26. require_once 'Auth/Container.php';
  27. /**
  28. * Include PEAR DB
  29. */
  30. require_once 'DB.php';
  31. /**
  32. * Storage driver for fetching login data from a database
  33. *
  34. * This storage driver can use all databases which are supported
  35. * by the PEAR DB abstraction layer to fetch login data.
  36. *
  37. * @category Authentication
  38. * @package Auth
  39. * @author Martin Jansen <mj@php.net>
  40. * @author Adam Ashley <aashley@php.net>
  41. * @copyright 2001-2006 The PHP Group
  42. * @license http://www.php.net/license/3_01.txt PHP License 3.01
  43. * @version Release: 1.6.2 File: $Revision: 256753 $
  44. * @link http://pear.php.net/package/Auth
  45. */
  46. class Auth_Container_DB extends Auth_Container
  47. {
  48. // {{{ properties
  49. /**
  50. * Additional options for the storage container
  51. * @var array
  52. */
  53. var $options = array();
  54. /**
  55. * DB object
  56. * @var object
  57. */
  58. var $db = null;
  59. var $dsn = '';
  60. /**
  61. * User that is currently selected from the DB.
  62. * @var string
  63. */
  64. var $activeUser = '';
  65. // }}}
  66. // {{{ Auth_Container_DB [constructor]
  67. /**
  68. * Constructor of the container class
  69. *
  70. * Save the initial options passed to the container. Initiation of the DB
  71. * connection is no longer performed here and is only done when needed.
  72. *
  73. * @param string Connection data or DB object
  74. * @return object Returns an error object if something went wrong
  75. */
  76. function Auth_Container_DB($dsn)
  77. {
  78. $this->_setDefaults();
  79. if (is_array($dsn)) {
  80. $this->_parseOptions($dsn);
  81. if (empty($this->options['dsn'])) {
  82. PEAR::raiseError('No connection parameters specified!');
  83. }
  84. } else {
  85. $this->options['dsn'] = $dsn;
  86. }
  87. }
  88. // }}}
  89. // {{{ _connect()
  90. /**
  91. * Connect to database by using the given DSN string
  92. *
  93. * @access private
  94. * @param string DSN string
  95. * @return mixed Object on error, otherwise bool
  96. */
  97. function _connect($dsn)
  98. {
  99. $this->log('Auth_Container_DB::_connect() called.', AUTH_LOG_DEBUG);
  100. if (is_string($dsn) || is_array($dsn)) {
  101. $this->db = DB::Connect($dsn, $this->options['db_options']);
  102. } elseif (is_subclass_of($dsn, 'db_common')) {
  103. $this->db = $dsn;
  104. } elseif (DB::isError($dsn)) {
  105. return PEAR::raiseError($dsn->getMessage(), $dsn->getCode());
  106. } else {
  107. return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
  108. 41,
  109. PEAR_ERROR_RETURN,
  110. null,
  111. null
  112. );
  113. }
  114. if (DB::isError($this->db) || PEAR::isError($this->db)) {
  115. return PEAR::raiseError($this->db->getMessage(), $this->db->getCode());
  116. } else {
  117. return true;
  118. }
  119. }
  120. // }}}
  121. // {{{ _prepare()
  122. /**
  123. * Prepare database connection
  124. *
  125. * This function checks if we have already opened a connection to
  126. * the database. If that's not the case, a new connection is opened.
  127. *
  128. * @access private
  129. * @return mixed True or a DB error object.
  130. */
  131. function _prepare()
  132. {
  133. if (!DB::isConnection($this->db)) {
  134. $res = $this->_connect($this->options['dsn']);
  135. if (DB::isError($res) || PEAR::isError($res)) {
  136. return $res;
  137. }
  138. }
  139. if ($this->options['auto_quote'] && $this->db->dsn['phptype'] != 'sqlite') {
  140. if (strpos('.', $this->options['table']) === false) {
  141. $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
  142. } else {
  143. $t = explode('.', $this->options['table']);
  144. for ($i = 0, $count = count($t); $i < $count; $i++)
  145. $t[$i] = $this->db->quoteIdentifier($t[$i]);
  146. $this->options['final_table'] = implode('.', $t);
  147. }
  148. $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
  149. $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
  150. } else {
  151. $this->options['final_table'] = $this->options['table'];
  152. $this->options['final_usernamecol'] = $this->options['usernamecol'];
  153. $this->options['final_passwordcol'] = $this->options['passwordcol'];
  154. }
  155. return true;
  156. }
  157. // }}}
  158. // {{{ query()
  159. /**
  160. * Prepare query to the database
  161. *
  162. * This function checks if we have already opened a connection to
  163. * the database. If that's not the case, a new connection is opened.
  164. * After that the query is passed to the database.
  165. *
  166. * @access public
  167. * @param string Query string
  168. * @return mixed a DB_result object or DB_OK on success, a DB
  169. * or PEAR error on failure
  170. */
  171. function query($query)
  172. {
  173. $err = $this->_prepare();
  174. if ($err !== true) {
  175. return $err;
  176. }
  177. return $this->db->query($query);
  178. }
  179. // }}}
  180. // {{{ _setDefaults()
  181. /**
  182. * Set some default options
  183. *
  184. * @access private
  185. * @return void
  186. */
  187. function _setDefaults()
  188. {
  189. $this->options['table'] = 'auth';
  190. $this->options['usernamecol'] = 'username';
  191. $this->options['passwordcol'] = 'password';
  192. $this->options['dsn'] = '';
  193. $this->options['db_fields'] = '';
  194. $this->options['cryptType'] = 'md5';
  195. $this->options['db_options'] = array();
  196. $this->options['db_where'] = '';
  197. $this->options['auto_quote'] = true;
  198. }
  199. // }}}
  200. // {{{ _parseOptions()
  201. /**
  202. * Parse options passed to the container class
  203. *
  204. * @access private
  205. * @param array
  206. */
  207. function _parseOptions($array)
  208. {
  209. foreach ($array as $key => $value) {
  210. if (isset($this->options[$key])) {
  211. $this->options[$key] = $value;
  212. }
  213. }
  214. }
  215. // }}}
  216. // {{{ _quoteDBFields()
  217. /**
  218. * Quote the db_fields option to avoid the possibility of SQL injection.
  219. *
  220. * @access private
  221. * @return string A properly quoted string that can be concatenated into a
  222. * SELECT clause.
  223. */
  224. function _quoteDBFields()
  225. {
  226. if (isset($this->options['db_fields'])) {
  227. if (is_array($this->options['db_fields'])) {
  228. if ($this->options['auto_quote']) {
  229. $fields = array();
  230. foreach ($this->options['db_fields'] as $field) {
  231. $fields[] = $this->db->quoteIdentifier($field);
  232. }
  233. return implode(', ', $fields);
  234. } else {
  235. return implode(', ', $this->options['db_fields']);
  236. }
  237. } else {
  238. if (strlen($this->options['db_fields']) > 0) {
  239. if ($this->options['auto_quote']) {
  240. return $this->db->quoteIdentifier($this->options['db_fields']);
  241. } else {
  242. return $this->options['db_fields'];
  243. }
  244. }
  245. }
  246. }
  247. return '';
  248. }
  249. // }}}
  250. // {{{ fetchData()
  251. /**
  252. * Get user information from database
  253. *
  254. * This function uses the given username to fetch
  255. * the corresponding login data from the database
  256. * table. If an account that matches the passed username
  257. * and password is found, the function returns true.
  258. * Otherwise it returns false.
  259. *
  260. * @param string Username
  261. * @param string Password
  262. * @param boolean If true password is secured using a md5 hash
  263. * the frontend and auth are responsible for making sure the container supports
  264. * challenge response password authentication
  265. * @return mixed Error object or boolean
  266. */
  267. function fetchData($username, $password, $isChallengeResponse=false)
  268. {
  269. $this->log('Auth_Container_DB::fetchData() called.', AUTH_LOG_DEBUG);
  270. // Prepare for a database query
  271. $err = $this->_prepare();
  272. if ($err !== true) {
  273. return PEAR::raiseError($err->getMessage(), $err->getCode());
  274. }
  275. // Find if db_fields contains a *, if so assume all columns are selected
  276. if (is_string($this->options['db_fields'])
  277. && strstr($this->options['db_fields'], '*')) {
  278. $sql_from = "*";
  279. } else {
  280. $sql_from = $this->options['final_usernamecol'].
  281. ", ".$this->options['final_passwordcol'];
  282. if (strlen($fields = $this->_quoteDBFields()) > 0) {
  283. $sql_from .= ', '.$fields;
  284. }
  285. }
  286. $query = "SELECT ".$sql_from.
  287. " FROM ".$this->options['final_table'].
  288. " WHERE ".$this->options['final_usernamecol']." = ".$this->db->quoteSmart($username);
  289. // check if there is an optional parameter db_where
  290. if ($this->options['db_where'] != '') {
  291. // there is one, so add it to the query
  292. $query .= " AND ".$this->options['db_where'];
  293. }
  294. $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
  295. $res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC);
  296. if (DB::isError($res)) {
  297. return PEAR::raiseError($res->getMessage(), $res->getCode());
  298. }
  299. if (!is_array($res)) {
  300. $this->activeUser = '';
  301. return false;
  302. }
  303. // Perform trimming here before the hashihg
  304. $password = trim($password, "\r\n");
  305. $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
  306. // If using Challenge Response md5 the pass with the secret
  307. if ($isChallengeResponse) {
  308. $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]
  309. .$this->_auth_obj->session['loginchallenege']);
  310. // UGLY cannot avoid without modifying verifyPassword
  311. if ($this->options['cryptType'] == 'md5') {
  312. $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
  313. }
  314. //print " Hashed Password [{$res[$this->options['passwordcol']]}]<br/>\n";
  315. }
  316. if ($this->verifyPassword($password,
  317. $res[$this->options['passwordcol']],
  318. $this->options['cryptType'])) {
  319. // Store additional field values in the session
  320. foreach ($res as $key => $value) {
  321. if ($key == $this->options['passwordcol'] ||
  322. $key == $this->options['usernamecol']) {
  323. continue;
  324. }
  325. $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG);
  326. // Use reference to the auth object if exists
  327. // This is because the auth session variable can change so a
  328. // static call to setAuthData does not make sence
  329. $this->_auth_obj->setAuthData($key, $value);
  330. }
  331. return true;
  332. }
  333. $this->activeUser = $res[$this->options['usernamecol']];
  334. return false;
  335. }
  336. // }}}
  337. // {{{ listUsers()
  338. /**
  339. * Returns a list of users from the container
  340. *
  341. * @return mixed
  342. * @access public
  343. */
  344. function listUsers()
  345. {
  346. $this->log('Auth_Container_DB::listUsers() called.', AUTH_LOG_DEBUG);
  347. $err = $this->_prepare();
  348. if ($err !== true) {
  349. return PEAR::raiseError($err->getMessage(), $err->getCode());
  350. }
  351. $retVal = array();
  352. // Find if db_fields contains a *, if so assume all col are selected
  353. if ( is_string($this->options['db_fields'])
  354. && strstr($this->options['db_fields'], '*')) {
  355. $sql_from = "*";
  356. } else {
  357. $sql_from = $this->options['final_usernamecol'].
  358. ", ".$this->options['final_passwordcol'];
  359. if (strlen($fields = $this->_quoteDBFields()) > 0) {
  360. $sql_from .= ', '.$fields;
  361. }
  362. }
  363. $query = sprintf("SELECT %s FROM %s",
  364. $sql_from,
  365. $this->options['final_table']
  366. );
  367. // check if there is an optional parameter db_where
  368. if ($this->options['db_where'] != '') {
  369. // there is one, so add it to the query
  370. $query .= " WHERE ".$this->options['db_where'];
  371. }
  372. $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
  373. $res = $this->db->getAll($query, null, DB_FETCHMODE_ASSOC);
  374. if (DB::isError($res)) {
  375. return PEAR::raiseError($res->getMessage(), $res->getCode());
  376. } else {
  377. foreach ($res as $user) {
  378. $user['username'] = $user[$this->options['usernamecol']];
  379. $retVal[] = $user;
  380. }
  381. }
  382. $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG);
  383. return $retVal;
  384. }
  385. // }}}
  386. // {{{ addUser()
  387. /**
  388. * Add user to the storage container
  389. *
  390. * @access public
  391. * @param string Username
  392. * @param string Password
  393. * @param mixed Additional information that are stored in the DB
  394. *
  395. * @return mixed True on success, otherwise error object
  396. */
  397. function addUser($username, $password, $additional = "")
  398. {
  399. $this->log('Auth_Container_DB::addUser() called.', AUTH_LOG_DEBUG);
  400. $err = $this->_prepare();
  401. if ($err !== true) {
  402. return PEAR::raiseError($err->getMessage(), $err->getCode());
  403. }
  404. if ( isset($this->options['cryptType'])
  405. && $this->options['cryptType'] == 'none') {
  406. $cryptFunction = 'strval';
  407. } elseif ( isset($this->options['cryptType'])
  408. && function_exists($this->options['cryptType'])) {
  409. $cryptFunction = $this->options['cryptType'];
  410. } else {
  411. $cryptFunction = 'md5';
  412. }
  413. $password = $cryptFunction($password);
  414. $additional_key = '';
  415. $additional_value = '';
  416. if (is_array($additional)) {
  417. foreach ($additional as $key => $value) {
  418. if ($this->options['auto_quote']) {
  419. $additional_key .= ', ' . $this->db->quoteIdentifier($key);
  420. } else {
  421. $additional_key .= ', ' . $key;
  422. }
  423. $additional_value .= ", " . $this->db->quoteSmart($value);
  424. }
  425. }
  426. $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
  427. $this->options['final_table'],
  428. $this->options['final_usernamecol'],
  429. $this->options['final_passwordcol'],
  430. $additional_key,
  431. $this->db->quoteSmart($username),
  432. $this->db->quoteSmart($password),
  433. $additional_value
  434. );
  435. $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
  436. $res = $this->query($query);
  437. if (DB::isError($res)) {
  438. return PEAR::raiseError($res->getMessage(), $res->getCode());
  439. } else {
  440. return true;
  441. }
  442. }
  443. // }}}
  444. // {{{ removeUser()
  445. /**
  446. * Remove user from the storage container
  447. *
  448. * @access public
  449. * @param string Username
  450. *
  451. * @return mixed True on success, otherwise error object
  452. */
  453. function removeUser($username)
  454. {
  455. $this->log('Auth_Container_DB::removeUser() called.', AUTH_LOG_DEBUG);
  456. $err = $this->_prepare();
  457. if ($err !== true) {
  458. return PEAR::raiseError($err->getMessage(), $err->getCode());
  459. }
  460. // check if there is an optional parameter db_where
  461. if ($this->options['db_where'] != '') {
  462. // there is one, so add it to the query
  463. $where = " AND ".$this->options['db_where'];
  464. } else {
  465. $where = '';
  466. }
  467. $query = sprintf("DELETE FROM %s WHERE %s = %s %s",
  468. $this->options['final_table'],
  469. $this->options['final_usernamecol'],
  470. $this->db->quoteSmart($username),
  471. $where
  472. );
  473. $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
  474. $res = $this->query($query);
  475. if (DB::isError($res)) {
  476. return PEAR::raiseError($res->getMessage(), $res->getCode());
  477. } else {
  478. return true;
  479. }
  480. }
  481. // }}}
  482. // {{{ changePassword()
  483. /**
  484. * Change password for user in the storage container
  485. *
  486. * @param string Username
  487. * @param string The new password (plain text)
  488. */
  489. function changePassword($username, $password)
  490. {
  491. $this->log('Auth_Container_DB::changePassword() called.', AUTH_LOG_DEBUG);
  492. $err = $this->_prepare();
  493. if ($err !== true) {
  494. return PEAR::raiseError($err->getMessage(), $err->getCode());
  495. }
  496. if ( isset($this->options['cryptType'])
  497. && $this->options['cryptType'] == 'none') {
  498. $cryptFunction = 'strval';
  499. } elseif ( isset($this->options['cryptType'])
  500. && function_exists($this->options['cryptType'])) {
  501. $cryptFunction = $this->options['cryptType'];
  502. } else {
  503. $cryptFunction = 'md5';
  504. }
  505. $password = $cryptFunction($password);
  506. // check if there is an optional parameter db_where
  507. if ($this->options['db_where'] != '') {
  508. // there is one, so add it to the query
  509. $where = " AND ".$this->options['db_where'];
  510. } else {
  511. $where = '';
  512. }
  513. $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s %s",
  514. $this->options['final_table'],
  515. $this->options['final_passwordcol'],
  516. $this->db->quoteSmart($password),
  517. $this->options['final_usernamecol'],
  518. $this->db->quoteSmart($username),
  519. $where
  520. );
  521. $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
  522. $res = $this->query($query);
  523. if (DB::isError($res)) {
  524. return PEAR::raiseError($res->getMessage(), $res->getCode());
  525. } else {
  526. return true;
  527. }
  528. }
  529. // }}}
  530. // {{{ supportsChallengeResponse()
  531. /**
  532. * Determine if this container supports
  533. * password authentication with challenge response
  534. *
  535. * @return bool
  536. * @access public
  537. */
  538. function supportsChallengeResponse()
  539. {
  540. return in_array($this->options['cryptType'], array('md5', 'none', ''));
  541. }
  542. // }}}
  543. // {{{ getCryptType()
  544. /**
  545. * Returns the selected crypt type for this container
  546. */
  547. function getCryptType()
  548. {
  549. return($this->options['cryptType']);
  550. }
  551. // }}}
  552. }
  553. ?>