Subversion Repositories Applications.papyrus

Rev

Rev 1173 | Go to most recent revision | 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 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
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
1713 jp_milcent 21
 * @version    CVS: $Id: MDB2.php,v 1.2 2007-11-19 15:11:00 jp_milcent Exp $
1173 jp_milcent 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
1713 jp_milcent 47
 * @version    Release: 1.5.4  File: $Revision: 1.2 $
1173 jp_milcent 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
    {
1713 jp_milcent 112
        $this->log('Auth_Container_MDB2::_connect() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 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
        }
1713 jp_milcent 132
 
1173 jp_milcent 133
        if ($this->options['auto_quote']) {
134
            $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table'], true);
135
            $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol'], true);
136
            $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol'], true);
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
        }
1713 jp_milcent 142
 
1173 jp_milcent 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, 'MDB2_Driver_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_MDB2::query() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 184
        $err = $this->_prepare();
185
        if ($err !== true) {
186
            return $err;
187
        }
188
        return $this->db->exec($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, true);
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'], true);
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_MDB2::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
        $query = sprintf("SELECT %s FROM %s WHERE %s = %s",
309
                         $sql_from,
310
                         $this->options['final_table'],
311
                         $this->options['final_usernamecol'],
312
                         $this->db->quote($username, 'text')
313
                         );
314
 
1713 jp_milcent 315
        // check if there is an optional parameter db_where
316
        if ($this->options['db_where'] != '') {
317
            // there is one, so add it to the query
318
            $query .= " AND ".$this->options['db_where'];
319
        }
320
 
321
        $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
322
 
1173 jp_milcent 323
        $res = $this->db->queryRow($query, null, MDB2_FETCHMODE_ASSOC);
324
        if (MDB2::isError($res) || PEAR::isError($res)) {
325
            return PEAR::raiseError($res->getMessage(), $res->getCode());
326
        }
327
        if (!is_array($res)) {
328
            $this->activeUser = '';
329
            return false;
330
        }
331
 
332
        // Perform trimming here before the hashing
333
        $password = trim($password, "\r\n");
334
        $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
335
        // If using Challenge Response md5 the pass with the secret
336
        if ($isChallengeResponse) {
337
            $res[$this->options['passwordcol']] =
338
                md5($res[$this->options['passwordcol']].$this->_auth_obj->session['loginchallenege']);
339
            // UGLY cannot avoid without modifying verifyPassword
340
            if ($this->options['cryptType'] == 'md5') {
341
                $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
342
            }
343
        }
344
        if ($this->verifyPassword($password,
345
                                  $res[$this->options['passwordcol']],
346
                                  $this->options['cryptType'])) {
347
            // Store additional field values in the session
348
            foreach ($res as $key => $value) {
349
                if ($key == $this->options['passwordcol'] ||
350
                    $key == $this->options['usernamecol']) {
351
                    continue;
352
                }
1713 jp_milcent 353
 
354
                $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG);
355
 
1173 jp_milcent 356
                // Use reference to the auth object if exists
357
                // This is because the auth session variable can change so a static call to setAuthData does not make sense
358
                $this->_auth_obj->setAuthData($key, $value);
359
            }
360
            return true;
361
        }
362
 
363
        $this->activeUser = $res[$this->options['usernamecol']];
364
        return false;
365
    }
366
 
367
    // }}}
368
    // {{{ listUsers()
369
 
370
    /**
371
     * Returns a list of users from the container
372
     *
373
     * @return mixed array|PEAR_Error
374
     * @access public
375
     */
376
    function listUsers()
377
    {
1713 jp_milcent 378
        $this->log('Auth_Container_MDB2::listUsers() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 379
        $err = $this->_prepare();
380
        if ($err !== true) {
381
            return PEAR::raiseError($err->getMessage(), $err->getCode());
382
        }
383
 
384
        $retVal = array();
385
 
386
        //Check if db_fields contains a *, if so assume all columns are selected
387
        if (   is_string($this->options['db_fields'])
388
            && strstr($this->options['db_fields'], '*')) {
389
            $sql_from = '*';
390
        } else {
391
            $sql_from = $this->options['final_usernamecol'].
392
                ", ".$this->options['final_passwordcol'];
393
 
394
            if (strlen($fields = $this->_quoteDBFields()) > 0) {
395
                $sql_from .= ', '.$fields;
396
            }
397
        }
398
 
399
        $query = sprintf('SELECT %s FROM %s',
400
                         $sql_from,
401
                         $this->options['final_table']
402
                         );
403
 
1713 jp_milcent 404
        // check if there is an optional parameter db_where
405
        if ($this->options['db_where'] != '') {
406
            // there is one, so add it to the query
407
            $query .= " WHERE ".$this->options['db_where'];
408
        }
409
 
410
        $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
411
 
1173 jp_milcent 412
        $res = $this->db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
413
        if (MDB2::isError($res)) {
414
            return PEAR::raiseError($res->getMessage(), $res->getCode());
415
        } else {
416
            foreach ($res as $user) {
417
                $user['username'] = $user[$this->options['usernamecol']];
418
                $retVal[] = $user;
419
            }
420
        }
1713 jp_milcent 421
        $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG);
1173 jp_milcent 422
        return $retVal;
423
    }
424
 
425
    // }}}
426
    // {{{ addUser()
427
 
428
    /**
429
     * Add user to the storage container
430
     *
431
     * @access public
432
     * @param  string Username
433
     * @param  string Password
434
     * @param  mixed  Additional information that are stored in the DB
435
     *
436
     * @return mixed True on success, otherwise error object
437
     */
438
    function addUser($username, $password, $additional = "")
439
    {
1713 jp_milcent 440
        $this->log('Auth_Container_MDB2::addUser() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 441
 
442
        // Prepare for a database query
443
        $err = $this->_prepare();
444
        if ($err !== true) {
445
            return PEAR::raiseError($err->getMessage(), $err->getCode());
446
        }
447
 
448
        if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
449
            $cryptFunction = 'strval';
450
        } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
451
            $cryptFunction = $this->options['cryptType'];
452
        } else {
453
            $cryptFunction = 'md5';
454
        }
455
 
456
        $password = $cryptFunction($password);
457
 
458
        $additional_key   = '';
459
        $additional_value = '';
460
 
461
        if (is_array($additional)) {
462
            foreach ($additional as $key => $value) {
463
                if ($this->options['auto_quote']) {
464
                    $additional_key   .= ', ' . $this->db->quoteIdentifier($key, true);
465
                } else {
466
                    $additional_key   .= ', ' . $key;
467
                }
468
                $additional_value .= ', ' . $this->db->quote($value, 'text');
469
            }
470
        }
471
 
472
        $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
473
                         $this->options['final_table'],
474
                         $this->options['final_usernamecol'],
475
                         $this->options['final_passwordcol'],
476
                         $additional_key,
477
                         $this->db->quote($username, 'text'),
478
                         $this->db->quote($password, 'text'),
479
                         $additional_value
480
                         );
481
 
1713 jp_milcent 482
        $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
483
 
1173 jp_milcent 484
        $res = $this->query($query);
485
 
486
        if (MDB2::isError($res)) {
487
            return PEAR::raiseError($res->getMessage(), $res->code);
488
        }
489
        return true;
490
    }
491
 
492
    // }}}
493
    // {{{ removeUser()
494
 
495
    /**
496
     * Remove user from the storage container
497
     *
498
     * @access public
499
     * @param  string Username
500
     *
501
     * @return mixed True on success, otherwise error object
502
     */
503
    function removeUser($username)
504
    {
1713 jp_milcent 505
        $this->log('Auth_Container_MDB2::removeUser() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 506
        // Prepare for a database query
507
        $err = $this->_prepare();
508
        if ($err !== true) {
509
            return PEAR::raiseError($err->getMessage(), $err->getCode());
510
        }
511
 
512
        $query = sprintf("DELETE FROM %s WHERE %s = %s",
513
                         $this->options['final_table'],
514
                         $this->options['final_usernamecol'],
515
                         $this->db->quote($username, 'text')
516
                         );
517
 
1713 jp_milcent 518
        // check if there is an optional parameter db_where
519
        if ($this->options['db_where'] != '') {
520
            // there is one, so add it to the query
521
            $query .= " AND ".$this->options['db_where'];
522
        }
523
 
524
        $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
525
 
1173 jp_milcent 526
        $res = $this->query($query);
527
 
528
        if (MDB2::isError($res)) {
529
            return PEAR::raiseError($res->getMessage(), $res->code);
530
        }
531
        return true;
532
    }
533
 
534
    // }}}
535
    // {{{ changePassword()
536
 
537
    /**
538
     * Change password for user in the storage container
539
     *
540
     * @param string Username
541
     * @param string The new password (plain text)
542
     */
543
    function changePassword($username, $password)
544
    {
1713 jp_milcent 545
        $this->log('Auth_Container_MDB2::changePassword() called.', AUTH_LOG_DEBUG);
1173 jp_milcent 546
        // Prepare for a database query
547
        $err = $this->_prepare();
548
        if ($err !== true) {
549
            return PEAR::raiseError($err->getMessage(), $err->getCode());
550
        }
551
 
552
        if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
553
            $cryptFunction = 'strval';
554
        } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
555
            $cryptFunction = $this->options['cryptType'];
556
        } else {
557
            $cryptFunction = 'md5';
558
        }
559
 
560
        $password = $cryptFunction($password);
561
 
562
        $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s",
563
                         $this->options['final_table'],
564
                         $this->options['final_passwordcol'],
565
                         $this->db->quote($password, 'text'),
566
                         $this->options['final_usernamecol'],
567
                         $this->db->quote($username, 'text')
568
                         );
569
 
1713 jp_milcent 570
        // check if there is an optional parameter db_where
571
        if ($this->options['db_where'] != '') {
572
            // there is one, so add it to the query
573
            $query .= " AND ".$this->options['db_where'];
574
        }
575
 
576
        $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
577
 
1173 jp_milcent 578
        $res = $this->query($query);
579
 
580
        if (MDB2::isError($res)) {
581
            return PEAR::raiseError($res->getMessage(), $res->code);
582
        }
583
        return true;
584
    }
585
 
586
    // }}}
587
    // {{{ supportsChallengeResponse()
588
 
589
    /**
590
     * Determine if this container supports
591
     * password authentication with challenge response
592
     *
593
     * @return bool
594
     * @access public
595
     */
596
    function supportsChallengeResponse()
597
    {
598
        return in_array($this->options['cryptType'], array('md5', 'none', ''));
599
    }
600
 
601
    // }}}
602
    // {{{ getCryptType()
603
 
604
    /**
605
     * Returns the selected crypt type for this container
606
     *
607
     * @return string Function used to crypt the password
608
     */
609
    function getCryptType()
610
    {
611
        return $this->options['cryptType'];
612
    }
613
 
614
    // }}}
615
 
616
}
617
?>