Subversion Repositories Applications.papyrus

Rev

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