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