Subversion Repositories Applications.papyrus

Rev

Rev 1713 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1173 jp_milcent 1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
3
 
4
/**
5
 * Storage driver for use against PEAR MDB
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
1713 jp_milcent 17
 * @author     Lorenzo Alberton <l.alberton@quipo.it>
1173 jp_milcent 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
2150 mathias 21
 * @version    CVS: $Id: MDB.php,v 1.35 2007/06/12 03:11:26 aashley Exp $
1173 jp_milcent 22
 * @link       http://pear.php.net/package/Auth
23
 * @since      File available since Release 1.2.3
24
 */
25
 
26
/**
27
 * Include Auth_Container base class
28
 */
29
require_once 'Auth/Container.php';
30
/**
31
 * Include PEAR MDB package
32
 */
33
require_once 'MDB.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 MDB 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
2150 mathias 47
 * @version    Release: 1.5.4  File: $Revision: 1.35 $
1173 jp_milcent 48
 * @link       http://pear.php.net/package/Auth
49
 * @since      Class available since Release 1.2.3
50
 */
51
class Auth_Container_MDB 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_MDB() [constructor]
77
 
78
    /**
79
     * Constructor of the container class
80
     *
81
     * Initate connection to the database via PEAR::MDB
82
     *
83
     * @param  string Connection data or MDB object
84
     * @return object Returns an error object if something went wrong
85
     */
86
    function Auth_Container_MDB($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
    {
1713 jp_milcent 112
        $this->log('Auth_Container_MDB::_connect() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 113
        if (is_string($dsn) || is_array($dsn)) {
114
            $this->db =& MDB::connect($dsn, $this->options['db_options']);
115
        } elseif (is_subclass_of($dsn, 'mdb_common')) {
116
            $this->db = $dsn;
117
        } elseif (is_object($dsn) && MDB::isError($dsn)) {
118
            return PEAR::raiseError($dsn->getMessage(), $dsn->code);
119
        } else {
120
            return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
121
                                    41,
122
                                    PEAR_ERROR_RETURN,
123
                                    null,
124
                                    null
125
                                    );
126
 
127
        }
128
 
129
        if (MDB::isError($this->db) || PEAR::isError($this->db)) {
130
            return PEAR::raiseError($this->db->getMessage(), $this->db->code);
131
        }
132
 
133
        if ($this->options['auto_quote']) {
134
            $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
135
            $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
136
            $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
137
        } else {
138
            $this->options['final_table'] = $this->options['table'];
139
            $this->options['final_usernamecol'] = $this->options['usernamecol'];
140
            $this->options['final_passwordcol'] = $this->options['passwordcol'];
141
        }
142
 
143
        return true;
144
    }
145
 
146
    // }}}
147
    // {{{ _prepare()
148
 
149
    /**
150
     * Prepare database connection
151
     *
152
     * This function checks if we have already opened a connection to
153
     * the database. If that's not the case, a new connection is opened.
154
     *
155
     * @access private
156
     * @return mixed True or a MDB error object.
157
     */
158
    function _prepare()
159
    {
160
        if (is_subclass_of($this->db, 'mdb_common')) {
161
            return true;
162
        }
163
        return $this->_connect($this->options['dsn']);
164
    }
165
 
166
    // }}}
167
    // {{{ query()
168
 
169
    /**
170
     * Prepare query to the database
171
     *
172
     * This function checks if we have already opened a connection to
173
     * the database. If that's not the case, a new connection is opened.
174
     * After that the query is passed to the database.
175
     *
176
     * @access public
177
     * @param  string Query string
178
     * @return mixed  a MDB_result object or MDB_OK on success, a MDB
179
     *                or PEAR error on failure
180
     */
181
    function query($query)
182
    {
1713 jp_milcent 183
        $this->log('Auth_Container_MDB::query() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 184
        $err = $this->_prepare();
185
        if ($err !== true) {
186
            return $err;
187
        }
188
        return $this->db->query($query);
189
    }
190
 
191
    // }}}
192
    // {{{ _setDefaults()
193
 
194
    /**
195
     * Set some default options
196
     *
197
     * @access private
198
     * @return void
199
     */
200
    function _setDefaults()
201
    {
202
        $this->options['table']       = 'auth';
203
        $this->options['usernamecol'] = 'username';
204
        $this->options['passwordcol'] = 'password';
205
        $this->options['dsn']         = '';
206
        $this->options['db_fields']   = '';
207
        $this->options['cryptType']   = 'md5';
208
        $this->options['db_options']  = array();
1713 jp_milcent 209
        $this->options['db_where']    = '';
1173 jp_milcent 210
        $this->options['auto_quote']  = true;
211
    }
212
 
213
    // }}}
214
    // {{{ _parseOptions()
215
 
216
    /**
217
     * Parse options passed to the container class
218
     *
219
     * @access private
220
     * @param  array
221
     */
222
    function _parseOptions($array)
223
    {
224
        foreach ($array as $key => $value) {
225
            if (isset($this->options[$key])) {
226
                $this->options[$key] = $value;
227
            }
228
        }
229
    }
230
 
231
    // }}}
232
    // {{{ _quoteDBFields()
233
 
234
    /**
235
     * Quote the db_fields option to avoid the possibility of SQL injection.
236
     *
237
     * @access private
238
     * @return string A properly quoted string that can be concatenated into a
239
     * SELECT clause.
240
     */
241
    function _quoteDBFields()
242
    {
243
        if (isset($this->options['db_fields'])) {
244
            if (is_array($this->options['db_fields'])) {
245
                if ($this->options['auto_quote']) {
246
                    $fields = array();
247
                    foreach ($this->options['db_fields'] as $field) {
248
                        $fields[] = $this->db->quoteIdentifier($field);
249
                    }
250
                    return implode(', ', $fields);
251
                } else {
252
                    return implode(', ', $this->options['db_fields']);
253
                }
254
            } else {
255
                if (strlen($this->options['db_fields']) > 0) {
256
                    if ($this->options['auto_quote']) {
257
                        return $this->db->quoteIdentifier($this->options['db_fields']);
258
                    } else {
259
                        return $this->options['db_fields'];
260
                    }
261
                }
262
            }
263
        }
264
 
265
        return '';
266
    }
1713 jp_milcent 267
 
1173 jp_milcent 268
    // }}}
269
    // {{{ fetchData()
270
 
271
    /**
272
     * Get user information from database
273
     *
274
     * This function uses the given username to fetch
275
     * the corresponding login data from the database
276
     * table. If an account that matches the passed username
277
     * and password is found, the function returns true.
278
     * Otherwise it returns false.
279
     *
280
     * @param   string Username
281
     * @param   string Password
282
     * @param   boolean If true password is secured using a md5 hash
283
     *                  the frontend and auth are responsible for making sure the container supports
284
     *                  challenge response password authentication
285
     * @return  mixed  Error object or boolean
286
     */
287
    function fetchData($username, $password, $isChallengeResponse=false)
288
    {
1713 jp_milcent 289
        $this->log('Auth_Container_MDB::fetchData() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 290
        // Prepare for a database query
291
        $err = $this->_prepare();
292
        if ($err !== true) {
293
            return PEAR::raiseError($err->getMessage(), $err->getCode());
294
        }
295
 
296
        //Check if db_fields contains a *, if so assume all columns are selected
297
        if (is_string($this->options['db_fields'])
298
            && strstr($this->options['db_fields'], '*')) {
299
            $sql_from = '*';
300
        } else {
301
            $sql_from = $this->options['final_usernamecol'].
302
                ", ".$this->options['final_passwordcol'];
303
 
304
            if (strlen($fields = $this->_quoteDBFields()) > 0) {
305
                $sql_from .= ', '.$fields;
306
            }
307
        }
308
 
309
        $query = sprintf("SELECT %s FROM %s WHERE %s = %s",
310
                         $sql_from,
311
                         $this->options['final_table'],
312
                         $this->options['final_usernamecol'],
313
                         $this->db->getTextValue($username)
314
                         );
315
 
1713 jp_milcent 316
        // check if there is an optional parameter db_where
317
        if ($this->options['db_where'] != '') {
318
            // there is one, so add it to the query
319
            $query .= " AND ".$this->options['db_where'];
320
        }
321
 
322
        $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
323
 
1173 jp_milcent 324
        $res = $this->db->getRow($query, null, null, null, MDB_FETCHMODE_ASSOC);
325
 
326
        if (MDB::isError($res) || PEAR::isError($res)) {
327
            return PEAR::raiseError($res->getMessage(), $res->getCode());
328
        }
329
        if (!is_array($res)) {
330
            $this->activeUser = '';
331
            return false;
332
        }
333
 
334
        // Perform trimming here before the hashing
335
        $password = trim($password, "\r\n");
336
        $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
1713 jp_milcent 337
 
1173 jp_milcent 338
        // If using Challenge Response md5 the pass with the secret
339
        if ($isChallengeResponse) {
340
            $res[$this->options['passwordcol']] =
341
                md5($res[$this->options['passwordcol']].$this->_auth_obj->session['loginchallenege']);
342
            // UGLY cannot avoid without modifying verifyPassword
343
            if ($this->options['cryptType'] == 'md5') {
344
                $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
345
            }
346
        }
1713 jp_milcent 347
 
1173 jp_milcent 348
        if ($this->verifyPassword($password,
349
                                  $res[$this->options['passwordcol']],
350
                                  $this->options['cryptType'])) {
351
            // Store additional field values in the session
352
            foreach ($res as $key => $value) {
353
                if ($key == $this->options['passwordcol'] ||
354
                    $key == $this->options['usernamecol']) {
355
                    continue;
356
                }
1713 jp_milcent 357
 
358
                $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG);
1173 jp_milcent 359
                // Use reference to the auth object if exists
360
                // This is because the auth session variable can change so a static
361
                // call to setAuthData does not make sense
362
                $this->_auth_obj->setAuthData($key, $value);
363
            }
364
            return true;
365
        }
366
 
367
        $this->activeUser = $res[$this->options['usernamecol']];
368
        return false;
369
    }
370
 
371
    // }}}
372
    // {{{ listUsers()
373
 
374
    /**
375
     * Returns a list of users from the container
376
     *
377
     * @return mixed array|PEAR_Error
378
     * @access public
379
     */
380
    function listUsers()
381
    {
1713 jp_milcent 382
        $this->log('Auth_Container_MDB::listUsers() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 383
        $err = $this->_prepare();
384
        if ($err !== true) {
385
            return PEAR::raiseError($err->getMessage(), $err->getCode());
386
        }
387
 
388
        $retVal = array();
389
 
390
        //Check if db_fields contains a *, if so assume all columns are selected
391
        if (   is_string($this->options['db_fields'])
392
            && strstr($this->options['db_fields'], '*')) {
393
            $sql_from = '*';
394
        } else {
395
            $sql_from = $this->options['final_usernamecol']
396
                .', '.$this->options['final_passwordcol'];
1713 jp_milcent 397
 
1173 jp_milcent 398
            if (strlen($fields = $this->_quoteDBFields()) > 0) {
399
                $sql_from .= ', '.$fields;
400
            }
401
        }
402
 
403
        $query = sprintf('SELECT %s FROM %s',
404
                         $sql_from,
405
                         $this->options['final_table']
406
                         );
407
 
1713 jp_milcent 408
        // check if there is an optional parameter db_where
409
        if ($this->options['db_where'] != '') {
410
            // there is one, so add it to the query
411
            $query .= " WHERE ".$this->options['db_where'];
412
        }
413
 
414
        $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
415
 
1173 jp_milcent 416
        $res = $this->db->getAll($query, null, null, null, MDB_FETCHMODE_ASSOC);
417
 
418
        if (MDB::isError($res)) {
419
            return PEAR::raiseError($res->getMessage(), $res->getCode());
420
        } else {
421
            foreach ($res as $user) {
422
                $user['username'] = $user[$this->options['usernamecol']];
423
                $retVal[] = $user;
424
            }
425
        }
1713 jp_milcent 426
        $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG);
1173 jp_milcent 427
        return $retVal;
428
    }
429
 
430
    // }}}
431
    // {{{ addUser()
432
 
433
    /**
434
     * Add user to the storage container
435
     *
436
     * @access public
437
     * @param  string Username
438
     * @param  string Password
439
     * @param  mixed  Additional information that are stored in the DB
440
     *
441
     * @return mixed True on success, otherwise error object
442
     */
443
    function addUser($username, $password, $additional = "")
444
    {
1713 jp_milcent 445
        $this->log('Auth_Container_MDB::addUser() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 446
        $err = $this->_prepare();
447
        if ($err !== true) {
448
            return PEAR::raiseError($err->getMessage(), $err->getCode());
449
        }
450
 
451
        if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
452
            $cryptFunction = 'strval';
453
        } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
454
            $cryptFunction = $this->options['cryptType'];
455
        } else {
456
            $cryptFunction = 'md5';
457
        }
458
 
459
        $password = $cryptFunction($password);
460
 
461
        $additional_key   = '';
462
        $additional_value = '';
463
 
464
        if (is_array($additional)) {
465
            foreach ($additional as $key => $value) {
466
                if ($this->options['auto_quote']) {
467
                    $additional_key   .= ', ' . $this->db->quoteIdentifier($key);
468
                } else {
469
                    $additional_key   .= ', ' . $key;
470
                }
471
                $additional_value .= ', ' . $this->db->getTextValue($value);
472
            }
473
        }
474
 
475
        $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
476
                         $this->options['final_table'],
477
                         $this->options['final_usernamecol'],
478
                         $this->options['final_passwordcol'],
479
                         $additional_key,
480
                         $this->db->getTextValue($username),
481
                         $this->db->getTextValue($password),
482
                         $additional_value
483
                         );
484
 
1713 jp_milcent 485
        $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
486
 
1173 jp_milcent 487
        $res = $this->query($query);
488
 
489
        if (MDB::isError($res)) {
490
            return PEAR::raiseError($res->getMessage(), $res->code);
491
        }
492
        return true;
493
    }
494
 
495
    // }}}
496
    // {{{ removeUser()
497
 
498
    /**
499
     * Remove user from the storage container
500
     *
501
     * @access public
502
     * @param  string Username
503
     *
504
     * @return mixed True on success, otherwise error object
505
     */
506
    function removeUser($username)
507
    {
1713 jp_milcent 508
        $this->log('Auth_Container_MDB::removeUser() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 509
        $err = $this->_prepare();
510
        if ($err !== true) {
511
            return PEAR::raiseError($err->getMessage(), $err->getCode());
512
        }
513
 
514
        $query = sprintf("DELETE FROM %s WHERE %s = %s",
515
                         $this->options['final_table'],
516
                         $this->options['final_usernamecol'],
517
                         $this->db->getTextValue($username)
518
                         );
519
 
1713 jp_milcent 520
        // check if there is an optional parameter db_where
521
        if ($this->options['db_where'] != '') {
522
            // there is one, so add it to the query
523
            $query .= " AND ".$this->options['db_where'];
524
        }
525
 
526
        $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
527
 
1173 jp_milcent 528
        $res = $this->query($query);
529
 
530
        if (MDB::isError($res)) {
531
            return PEAR::raiseError($res->getMessage(), $res->code);
532
        }
533
        return true;
534
    }
535
 
536
    // }}}
537
    // {{{ changePassword()
538
 
539
    /**
540
     * Change password for user in the storage container
541
     *
542
     * @param string Username
543
     * @param string The new password (plain text)
544
     */
545
    function changePassword($username, $password)
546
    {
1713 jp_milcent 547
        $this->log('Auth_Container_MDB::changePassword() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 548
        $err = $this->_prepare();
549
        if ($err !== true) {
550
            return PEAR::raiseError($err->getMessage(), $err->getCode());
551
        }
552
 
553
        if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
554
            $cryptFunction = 'strval';
555
        } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
556
            $cryptFunction = $this->options['cryptType'];
557
        } else {
558
            $cryptFunction = 'md5';
559
        }
560
 
561
        $password = $cryptFunction($password);
562
 
563
        $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s",
564
                         $this->options['final_table'],
565
                         $this->options['final_passwordcol'],
566
                         $this->db->getTextValue($password),
567
                         $this->options['final_usernamecol'],
568
                         $this->db->getTextValue($username)
569
                         );
570
 
1713 jp_milcent 571
        // check if there is an optional parameter db_where
572
        if ($this->options['db_where'] != '') {
573
            // there is one, so add it to the query
574
            $query .= " AND ".$this->options['db_where'];
575
        }
576
 
577
        $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
578
 
1173 jp_milcent 579
        $res = $this->query($query);
580
 
581
        if (MDB::isError($res)) {
582
            return PEAR::raiseError($res->getMessage(), $res->code);
583
        }
584
        return true;
585
    }
586
 
587
    // }}}
588
    // {{{ supportsChallengeResponse()
589
 
590
    /**
591
     * Determine if this container supports
592
     * password authentication with challenge response
593
     *
594
     * @return bool
595
     * @access public
596
     */
597
    function supportsChallengeResponse()
598
    {
599
        return in_array($this->options['cryptType'], array('md5', 'none', ''));
600
    }
601
 
602
    // }}}
603
    // {{{ getCryptType()
604
 
605
    /**
606
     * Returns the selected crypt type for this container
607
     *
608
     * @return string Function used to crypt the password
609
     */
610
    function getCryptType()
611
    {
612
        return $this->options['cryptType'];
613
    }
614
 
615
    // }}}
616
 
617
}
618
?>