Subversion Repositories Sites.obs-saisons.fr

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 aurelien 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.74 2008/04/04 07:57:02 aashley 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.6.1  File: $Revision: 1.74 $
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
            if (strpos('.', $this->options['table']) === false) {
158
                $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
159
            } else {
160
                $t = explode('.', $this->options['table']);
161
                for ($i = 0, $count = count($t); $i < $count; $i++)
162
                    $t[$i] = $this->db->quoteIdentifier($t[$i]);
163
                $this->options['final_table'] = implode('.', $t);
164
            }
165
            $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
166
            $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
167
        } else {
168
            $this->options['final_table'] = $this->options['table'];
169
            $this->options['final_usernamecol'] = $this->options['usernamecol'];
170
            $this->options['final_passwordcol'] = $this->options['passwordcol'];
171
        }
172
        return true;
173
    }
174
 
175
    // }}}
176
    // {{{ query()
177
 
178
    /**
179
     * Prepare query to the database
180
     *
181
     * This function checks if we have already opened a connection to
182
     * the database. If that's not the case, a new connection is opened.
183
     * After that the query is passed to the database.
184
     *
185
     * @access public
186
     * @param  string Query string
187
     * @return mixed  a DB_result object or DB_OK on success, a DB
188
     *                or PEAR error on failure
189
     */
190
    function query($query)
191
    {
192
        $err = $this->_prepare();
193
        if ($err !== true) {
194
            return $err;
195
        }
196
        return $this->db->query($query);
197
    }
198
 
199
    // }}}
200
    // {{{ _setDefaults()
201
 
202
    /**
203
     * Set some default options
204
     *
205
     * @access private
206
     * @return void
207
     */
208
    function _setDefaults()
209
    {
210
        $this->options['table']       = 'auth';
211
        $this->options['usernamecol'] = 'username';
212
        $this->options['passwordcol'] = 'password';
213
        $this->options['dsn']         = '';
214
        $this->options['db_fields']   = '';
215
        $this->options['cryptType']   = 'md5';
216
        $this->options['db_options']  = array();
217
        $this->options['db_where']    = '';
218
        $this->options['auto_quote']  = true;
219
    }
220
 
221
    // }}}
222
    // {{{ _parseOptions()
223
 
224
    /**
225
     * Parse options passed to the container class
226
     *
227
     * @access private
228
     * @param  array
229
     */
230
    function _parseOptions($array)
231
    {
232
        foreach ($array as $key => $value) {
233
            if (isset($this->options[$key])) {
234
                $this->options[$key] = $value;
235
            }
236
        }
237
    }
238
 
239
    // }}}
240
    // {{{ _quoteDBFields()
241
 
242
    /**
243
     * Quote the db_fields option to avoid the possibility of SQL injection.
244
     *
245
     * @access private
246
     * @return string A properly quoted string that can be concatenated into a
247
     * SELECT clause.
248
     */
249
    function _quoteDBFields()
250
    {
251
        if (isset($this->options['db_fields'])) {
252
            if (is_array($this->options['db_fields'])) {
253
                if ($this->options['auto_quote']) {
254
                    $fields = array();
255
                    foreach ($this->options['db_fields'] as $field) {
256
                        $fields[] = $this->db->quoteIdentifier($field);
257
                    }
258
                    return implode(', ', $fields);
259
                } else {
260
                    return implode(', ', $this->options['db_fields']);
261
                }
262
            } else {
263
                if (strlen($this->options['db_fields']) > 0) {
264
                    if ($this->options['auto_quote']) {
265
                        return $this->db->quoteIdentifier($this->options['db_fields']);
266
                    } else {
267
                        return $this->options['db_fields'];
268
                    }
269
                }
270
            }
271
        }
272
 
273
        return '';
274
    }
275
 
276
    // }}}
277
    // {{{ fetchData()
278
 
279
    /**
280
     * Get user information from database
281
     *
282
     * This function uses the given username to fetch
283
     * the corresponding login data from the database
284
     * table. If an account that matches the passed username
285
     * and password is found, the function returns true.
286
     * Otherwise it returns false.
287
     *
288
     * @param   string Username
289
     * @param   string Password
290
     * @param   boolean If true password is secured using a md5 hash
291
     *                  the frontend and auth are responsible for making sure the container supports
292
     *                  challenge response password authentication
293
     * @return  mixed  Error object or boolean
294
     */
295
    function fetchData($username, $password, $isChallengeResponse=false)
296
    {
297
        $this->log('Auth_Container_DB::fetchData() called.', AUTH_LOG_DEBUG);
298
        // Prepare for a database query
299
        $err = $this->_prepare();
300
        if ($err !== true) {
301
            return PEAR::raiseError($err->getMessage(), $err->getCode());
302
        }
303
 
304
        // Find if db_fields contains a *, if so assume all columns are selected
305
        if (is_string($this->options['db_fields'])
306
            && strstr($this->options['db_fields'], '*')) {
307
            $sql_from = "*";
308
        } else {
309
            $sql_from = $this->options['final_usernamecol'].
310
                ", ".$this->options['final_passwordcol'];
311
 
312
            if (strlen($fields = $this->_quoteDBFields()) > 0) {
313
                $sql_from .= ', '.$fields;
314
            }
315
        }
316
 
317
        $query = "SELECT ".$sql_from.
318
                " FROM ".$this->options['final_table'].
319
                " WHERE ".$this->options['final_usernamecol']." = ".$this->db->quoteSmart($username);
320
 
321
        // check if there is an optional parameter db_where
322
        if ($this->options['db_where'] != '') {
323
            // there is one, so add it to the query
324
            $query .= " AND ".$this->options['db_where'];
325
        }
326
 
327
        $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
328
 
329
        $res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC);
330
 
331
        if (DB::isError($res)) {
332
            return PEAR::raiseError($res->getMessage(), $res->getCode());
333
        }
334
 
335
        if (!is_array($res)) {
336
            $this->activeUser = '';
337
            return false;
338
        }
339
 
340
        // Perform trimming here before the hashihg
341
        $password = trim($password, "\r\n");
342
        $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
343
 
344
        // If using Challenge Response md5 the pass with the secret
345
        if ($isChallengeResponse) {
346
            $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]
347
                    .$this->_auth_obj->session['loginchallenege']);
348
 
349
            // UGLY cannot avoid without modifying verifyPassword
350
            if ($this->options['cryptType'] == 'md5') {
351
                $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
352
            }
353
 
354
            //print " Hashed Password [{$res[$this->options['passwordcol']]}]<br/>\n";
355
        }
356
 
357
        if ($this->verifyPassword($password,
358
                                  $res[$this->options['passwordcol']],
359
                                  $this->options['cryptType'])) {
360
            // Store additional field values in the session
361
            foreach ($res as $key => $value) {
362
                if ($key == $this->options['passwordcol'] ||
363
                    $key == $this->options['usernamecol']) {
364
                    continue;
365
                }
366
 
367
                $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG);
368
 
369
                // Use reference to the auth object if exists
370
                // This is because the auth session variable can change so a
371
                // static call to setAuthData does not make sence
372
                $this->_auth_obj->setAuthData($key, $value);
373
            }
374
            return true;
375
        }
376
        $this->activeUser = $res[$this->options['usernamecol']];
377
        return false;
378
    }
379
 
380
    // }}}
381
    // {{{ listUsers()
382
 
383
    /**
384
     * Returns a list of users from the container
385
     *
386
     * @return mixed
387
     * @access public
388
     */
389
    function listUsers()
390
    {
391
        $this->log('Auth_Container_DB::listUsers() called.', AUTH_LOG_DEBUG);
392
        $err = $this->_prepare();
393
        if ($err !== true) {
394
            return PEAR::raiseError($err->getMessage(), $err->getCode());
395
        }
396
 
397
        $retVal = array();
398
 
399
        // Find if db_fields contains a *, if so assume all col are selected
400
        if (   is_string($this->options['db_fields'])
401
            && strstr($this->options['db_fields'], '*')) {
402
            $sql_from = "*";
403
        } else {
404
            $sql_from = $this->options['final_usernamecol'].
405
                ", ".$this->options['final_passwordcol'];
406
 
407
            if (strlen($fields = $this->_quoteDBFields()) > 0) {
408
                $sql_from .= ', '.$fields;
409
            }
410
        }
411
 
412
        $query = sprintf("SELECT %s FROM %s",
413
                         $sql_from,
414
                         $this->options['final_table']
415
                         );
416
 
417
        // check if there is an optional parameter db_where
418
        if ($this->options['db_where'] != '') {
419
            // there is one, so add it to the query
420
            $query .= " WHERE ".$this->options['db_where'];
421
        }
422
 
423
        $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
424
 
425
        $res = $this->db->getAll($query, null, DB_FETCHMODE_ASSOC);
426
 
427
        if (DB::isError($res)) {
428
            return PEAR::raiseError($res->getMessage(), $res->getCode());
429
        } else {
430
            foreach ($res as $user) {
431
                $user['username'] = $user[$this->options['usernamecol']];
432
                $retVal[] = $user;
433
            }
434
        }
435
        $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG);
436
        return $retVal;
437
    }
438
 
439
    // }}}
440
    // {{{ addUser()
441
 
442
    /**
443
     * Add user to the storage container
444
     *
445
     * @access public
446
     * @param  string Username
447
     * @param  string Password
448
     * @param  mixed  Additional information that are stored in the DB
449
     *
450
     * @return mixed True on success, otherwise error object
451
     */
452
    function addUser($username, $password, $additional = "")
453
    {
454
        $this->log('Auth_Container_DB::addUser() called.', AUTH_LOG_DEBUG);
455
        $err = $this->_prepare();
456
        if ($err !== true) {
457
            return PEAR::raiseError($err->getMessage(), $err->getCode());
458
        }
459
 
460
        if (   isset($this->options['cryptType'])
461
            && $this->options['cryptType'] == 'none') {
462
            $cryptFunction = 'strval';
463
        } elseif (   isset($this->options['cryptType'])
464
                  && function_exists($this->options['cryptType'])) {
465
            $cryptFunction = $this->options['cryptType'];
466
        } else {
467
            $cryptFunction = 'md5';
468
        }
469
 
470
        $password = $cryptFunction($password);
471
 
472
        $additional_key   = '';
473
        $additional_value = '';
474
 
475
        if (is_array($additional)) {
476
            foreach ($additional as $key => $value) {
477
                if ($this->options['auto_quote']) {
478
                    $additional_key .= ', ' . $this->db->quoteIdentifier($key);
479
                } else {
480
                    $additional_key .= ', ' . $key;
481
                }
482
                $additional_value .= ", " . $this->db->quoteSmart($value);
483
            }
484
        }
485
 
486
        $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
487
                         $this->options['final_table'],
488
                         $this->options['final_usernamecol'],
489
                         $this->options['final_passwordcol'],
490
                         $additional_key,
491
                         $this->db->quoteSmart($username),
492
                         $this->db->quoteSmart($password),
493
                         $additional_value
494
                         );
495
 
496
        $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
497
 
498
        $res = $this->query($query);
499
 
500
        if (DB::isError($res)) {
501
            return PEAR::raiseError($res->getMessage(), $res->getCode());
502
        } else {
503
            return true;
504
        }
505
    }
506
 
507
    // }}}
508
    // {{{ removeUser()
509
 
510
    /**
511
     * Remove user from the storage container
512
     *
513
     * @access public
514
     * @param  string Username
515
     *
516
     * @return mixed True on success, otherwise error object
517
     */
518
    function removeUser($username)
519
    {
520
        $this->log('Auth_Container_DB::removeUser() called.', AUTH_LOG_DEBUG);
521
 
522
        $err = $this->_prepare();
523
        if ($err !== true) {
524
            return PEAR::raiseError($err->getMessage(), $err->getCode());
525
        }
526
 
527
        // check if there is an optional parameter db_where
528
        if ($this->options['db_where'] != '') {
529
            // there is one, so add it to the query
530
            $where = " AND ".$this->options['db_where'];
531
        } else {
532
            $where = '';
533
        }
534
 
535
        $query = sprintf("DELETE FROM %s WHERE %s = %s %s",
536
                         $this->options['final_table'],
537
                         $this->options['final_usernamecol'],
538
                         $this->db->quoteSmart($username),
539
                         $where
540
                         );
541
 
542
        $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
543
 
544
        $res = $this->query($query);
545
 
546
        if (DB::isError($res)) {
547
           return PEAR::raiseError($res->getMessage(), $res->getCode());
548
        } else {
549
          return true;
550
        }
551
    }
552
 
553
    // }}}
554
    // {{{ changePassword()
555
 
556
    /**
557
     * Change password for user in the storage container
558
     *
559
     * @param string Username
560
     * @param string The new password (plain text)
561
     */
562
    function changePassword($username, $password)
563
    {
564
        $this->log('Auth_Container_DB::changePassword() called.', AUTH_LOG_DEBUG);
565
        $err = $this->_prepare();
566
        if ($err !== true) {
567
            return PEAR::raiseError($err->getMessage(), $err->getCode());
568
        }
569
 
570
        if (   isset($this->options['cryptType'])
571
            && $this->options['cryptType'] == 'none') {
572
            $cryptFunction = 'strval';
573
        } elseif (   isset($this->options['cryptType'])
574
                  && function_exists($this->options['cryptType'])) {
575
            $cryptFunction = $this->options['cryptType'];
576
        } else {
577
            $cryptFunction = 'md5';
578
        }
579
 
580
        $password = $cryptFunction($password);
581
 
582
        // check if there is an optional parameter db_where
583
        if ($this->options['db_where'] != '') {
584
            // there is one, so add it to the query
585
            $where = " AND ".$this->options['db_where'];
586
        } else {
587
            $where = '';
588
        }
589
 
590
        $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s %s",
591
                         $this->options['final_table'],
592
                         $this->options['final_passwordcol'],
593
                         $this->db->quoteSmart($password),
594
                         $this->options['final_usernamecol'],
595
                         $this->db->quoteSmart($username),
596
                         $where
597
                         );
598
 
599
        $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
600
 
601
        $res = $this->query($query);
602
 
603
        if (DB::isError($res)) {
604
            return PEAR::raiseError($res->getMessage(), $res->getCode());
605
        } else {
606
            return true;
607
        }
608
    }
609
 
610
    // }}}
611
    // {{{ supportsChallengeResponse()
612
 
613
    /**
614
     * Determine if this container supports
615
     * password authentication with challenge response
616
     *
617
     * @return bool
618
     * @access public
619
     */
620
    function supportsChallengeResponse()
621
    {
622
        return in_array($this->options['cryptType'], array('md5', 'none', ''));
623
    }
624
 
625
    // }}}
626
    // {{{ getCryptType()
627
 
628
    /**
629
      * Returns the selected crypt type for this container
630
      */
631
    function getCryptType()
632
    {
633
        return($this->options['cryptType']);
634
    }
635
 
636
    // }}}
637
 
638
}
639
?>