source: branches/rel/html/test/kakinaka/pear/Auth/Container/MDB2.php @ 12157

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