Subversion Repositories Applications.papyrus

Rev

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

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