1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * A framework for authentication and authorization in PHP applications
6 *
7 * LiveUser is an authentication/permission framework designed
8 * to be flexible and easily extendable.
9 *
10 * Since it is impossible to have a
11 * "one size fits all" it takes a container
12 * approach which should enable it to
13 * be versatile enough to meet most needs.
14 *
15 * PHP version 4 and 5
16 *
17 * LICENSE: This library is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public
19 * License as published by the Free Software Foundation; either
20 * version 2.1 of the License, or (at your option) any later version.
21 *
22 * This library is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25 * Lesser General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this library; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
30 * MA  02111-1307  USA
31 *
32 *
33 * @category authentication
34 * @package LiveUser
35 * @author  Markus Wolff <wolff@21st.de>
36 * @author  Helgi �ormar �orbj�rnsson <dufuz@php.net>
37 * @author  Lukas Smith <smith@pooteeweet.org>
38 * @author  Arnaud Limbourg <arnaud@php.net>
39 * @author  Pierre-Alain Joye <pajoye@php.net>
40 * @author  Bjoern Kraus <krausbn@php.net>
41 * @copyright 2002-2006 Markus Wolff
42 * @license http://www.gnu.org/licenses/lgpl.txt
43 * @version CVS: $Id: PDO.php 304421 2010-10-15 13:30:56Z clockwerx $
44 * @link http://pear.php.net/LiveUser
45 */
46
47/**
48 * Require parent class definition.
49 */
50require_once 'LiveUser/Perm/Storage/SQL.php';
51
52/**
53 * PDO container for permission handling.
54 *
55 * This is a PDO backend driver for the LiveUser class.
56 * A PDO connection object can be passed to the constructor to reuse an
57 * existing connection. Alternatively, a DSN can be passed to open a new one.
58 *
59 * Requirements:
60 * - PHP5
61 * - File "Liveuser.php" (contains the parent class "LiveUser")
62 * - Array of connection options
63 *   passed to the constructor.
64 *   Example: array('dsn'     => 'mysql:host:localhost;dbname=db_name',
65 *                  'options' => array('username' => 'root', 'password' => 'secret', 'attr' => array()));
66 *
67 * @category  authentication
68 * @package   LiveUser
69 * @author    Arnaud Limbourg <arnaud@php.net>
70 * @copyright 2002-2006 Markus Wolff
71 * @license   http://www.gnu.org/licenses/lgpl.txt
72 * @version   Release: @package_version@
73 * @link      http://pear.php.net/LiveUser
74 */
75class LiveUser_Perm_Storage_PDO extends LiveUser_Perm_Storage_SQL
76{
77    /**
78     * determines of the use of sequences should be forced
79     *
80     * @var bool
81     * @access private
82     */
83    var $force_seq = true;
84
85    /**
86     * Initialize the storage container
87     *
88     * @param array Array with the storage configuration
89     * @return bool true on success, false on failure.
90     *
91     * @access public
92     */
93    function init(&$storageConf)
94    {
95        parent::init($storageConf);
96
97        if (!is_a($this->dbc, 'pdo') && !is_null($this->dsn)) {
98            $login = $password = $extra = null;
99            if (!empty($this->options)) {
100                if (array_key_exists('username', $this->options)) {
101                    $login = $this->options['username'];
102                }
103                if (array_key_exists('password', $this->options)) {
104                    $password = $this->options['password'];
105                }
106                if (array_key_exists('attr', $this->options)) {
107                    $extra = $this->options['attr'];
108                }
109            }
110            try {
111                $dbc = new PDO($this->dsn, $login, $password, $extra);
112            } catch (PDOException $e) {
113                $this->stack->push(LIVEUSER_ERROR_INIT_ERROR, 'error',
114                    array(
115                        'container' => 'could not connect: ' . $e->getMessage(),
116                        'debug'     => $e->getTrace()
117                    )
118                );
119                return false;
120            }
121            $this->dbc = $dbc;
122        }
123
124        if (!is_a($this->dbc, 'pdo')) {
125            $this->stack->push(LIVEUSER_ERROR_INIT_ERROR, 'error',
126                array('container' => 'storage layer configuration missing'));
127            return false;
128        }
129
130        return true;
131    }
132
133    /**
134     * map an auth user to a perm user
135     *
136     * @param int           auth user id
137     * @param string        name of the container
138     * @return array|false  requested data or false on failure
139     *
140     * @access public
141     */
142    function mapUser($auth_user_id, $containerName)
143    {
144        $query = '
145            SELECT
146                ' . $this->alias['perm_user_id'] . ' AS perm_user_id,
147                ' . $this->alias['perm_type'] . '    AS perm_type
148            FROM
149                '.$this->prefix.$this->alias['perm_users'].'
150            WHERE
151                ' . $this->alias['auth_user_id'] . ' = '.
152                    $this->dbc->quote($auth_user_id).'
153            AND
154                ' . $this->alias['auth_container_name'] . ' = '.
155                    $this->dbc->quote((string)$containerName);
156
157        $result = $this->dbc->query($query);
158
159        if ($result === false) {
160            $error_info = $this->dbc->errorInfo();
161            $this->stack->push(LIVEUSER_ERROR, 'exception', array(),
162                'error in query ' . $error_info[2] . ' - ' . $query);
163            return false;
164        }
165
166        $row = $result->fetch(PDO::FETCH_ASSOC);
167
168        if ($row === false && $this->dbc->errorCode() === '00000') {
169            $row = null;
170        }
171
172        return $row;
173    }
174
175    /**
176     * Reads all rights of current user into a
177     * two-dimensional associative array, having the
178     * area names as the key of the 1st dimension.
179     * Group rights and invididual rights are being merged
180     * in the process.
181     *
182     * @param int perm user id
183     * @return array requested data or false on failure
184     *
185     * @access public
186     */
187    function readUserRights($perm_user_id)
188    {
189        $query = '
190            SELECT
191                ' . $this->alias['right_id'] . ',
192                ' . $this->alias['right_level'] . '
193            FROM
194                '.$this->prefix.$this->alias['userrights'].'
195            WHERE
196                ' . $this->alias['perm_user_id'] . ' = '.
197                    $this->dbc->quote($perm_user_id);
198
199        $result = $this->dbc->query($query);
200
201        if ($result === false) {
202            $error_info = $this->dbc->errorInfo();
203            $this->stack->push(LIVEUSER_ERROR, 'exception', array(),
204                'error in query ' . $error_info[2] . ' - ' . $query);
205            return false;
206        }
207
208        $rows = array();
209        while ($row = $result->fetch(PDO::FETCH_NUM)) {
210            $rows[$row[0]] = $row[1];
211        }
212
213        return $rows;
214    }
215
216    /**
217     * read the areas in which a user is an area admin
218     *
219     * @param int perm user id
220     * @return array requested data or false on failure
221     *
222     * @access public
223     */
224    function readAreaAdminAreas($perm_user_id)
225    {
226        // get all areas in which the user is area admin
227        $query = '
228            SELECT
229                R.' . $this->alias['right_id'] . ' AS right_id,
230                '.LIVEUSER_MAX_LEVEL.'             AS right_level
231            FROM
232                '.$this->prefix.$this->alias['area_admin_areas'].' AAA,
233                '.$this->prefix.$this->alias['rights'].' R
234            WHERE
235                AAA.area_id = R.area_id
236            AND
237                AAA.' . $this->alias['perm_user_id'] . ' = '.
238                    $this->dbc->quote($perm_user_id);
239
240        $result = $this->dbc->query($query);
241
242        if ($result === false) {
243            $error_info = $this->dbc->errorInfo();
244            $this->stack->push(LIVEUSER_ERROR, 'exception', array(),
245                'error in query ' . $error_info[2] . ' - ' . $query);
246            return false;
247        }
248
249        $rows = array();
250        while ($row = $result->fetch(PDO::FETCH_NUM)) {
251            $rows[$row[0]] = $row[1];
252        }
253
254        return $rows;
255    }
256
257    /**
258     * Reads all the group ids in that the user is also a member of
259     * (all groups that are subgroups of these are also added recursively)
260     *
261     * @param int perm user id
262     * @return array requested data or false on failure
263     *
264     * @see    readRights()
265     * @access public
266     */
267    function readGroups($perm_user_id)
268    {
269        $query = '
270            SELECT
271                GU.' . $this->alias['group_id'] . '
272            FROM
273                '.$this->prefix.$this->alias['groupusers'].' GU,
274                '.$this->prefix.$this->alias['groups'].' G
275            WHERE
276                GU.' . $this->alias['group_id'] . ' = G. ' . $this->alias['group_id'] . '
277            AND
278                GU.' . $this->alias['perm_user_id'] . ' = '.
279                    $this->dbc->quote($perm_user_id);
280
281        if (array_key_exists('is_active', $this->tables['groups']['fields'])) {
282            $query .= ' AND
283                G.' . $this->alias['is_active'] . '=' .
284                    $this->dbc->quote(true);
285        }
286
287        $result = $this->dbc->query($query);
288
289        if ($result === false) {
290            $error_info = $this->dbc->errorInfo();
291            $this->stack->push(LIVEUSER_ERROR, 'exception', array(),
292                'error in query ' . $error_info[2] . ' - ' . $query);
293            return false;
294        }
295
296        $col = $result->fetchAll(PDO::FETCH_COLUMN);
297
298        return $col;
299    }
300
301    /**
302     * Reads the group rights
303     * and put them in the array
304     *
305     * right => 1
306     *
307     * @param int group ids
308     * @return array requested data or false on failure
309     *
310     * @access public
311     */
312    function readGroupRights($group_ids)
313    {
314        $query = '
315            SELECT
316                GR.' . $this->alias['right_id'] . ',
317                MAX(GR.' . $this->alias['right_level'] . ')
318            FROM
319                '.$this->prefix.$this->alias['grouprights'].' GR
320            WHERE
321                GR.' . $this->alias['group_id'] . ' IN('.
322                    implode(', ', $group_ids).')
323            GROUP BY
324                GR.' . $this->alias['right_id'] . '';
325
326        $result = $this->dbc->query($query);
327
328        if ($result === false) {
329            $error_info = $this->dbc->errorInfo();
330            $this->stack->push(LIVEUSER_ERROR, 'exception', array(),
331                'error in query ' . $error_info[2] . ' - ' . $query);
332            return false;
333        }
334
335        $rows = array();
336        while ($row = $result->fetch(PDO::FETCH_NUM)) {
337            $rows[$row[0]] = $row[1];
338        }
339
340        return $rows;
341    }
342
343    /**
344     * Read the sub groups of the new groups that are not part of the group ids
345     *
346     * @param array group ids
347     * @param array new group ids
348     * @return array requested data or false on failure
349     *
350     * @access public
351     */
352    function readSubGroups($group_ids, $newGroupIds)
353    {
354        $query = '
355            SELECT
356                DISTINCT SG.' . $this->alias['subgroup_id'] . '
357            FROM
358                '.$this->prefix.$this->alias['groups'].' G,
359                '.$this->prefix.$this->alias['group_subgroups'].' SG
360            WHERE
361                SG.' . $this->alias['subgroup_id'] . ' = G.' .
362                    $this->alias['group_id'] . '
363            AND
364                SG.' . $this->alias['group_id'] . ' IN ('.
365                    implode(', ', $newGroupIds).')
366            AND
367                SG.' . $this->alias['subgroup_id'] . ' NOT IN ('.
368                    implode(', ', $group_ids).')';
369
370        if (array_key_exists('is_active', $this->tables['groups']['fields'])) {
371            $query .= ' AND
372                G.' . $this->alias['is_active'] . '=' .
373                    $this->dbc->quote(true);
374        }
375
376        $result = $this->dbc->query($query);
377
378        if ($result === false) {
379            $error_info = $this->dbc->errorInfo();
380            $this->stack->push(LIVEUSER_ERROR, 'exception', array(),
381                'error in query ' . $error_info[2] . ' - ' . $query);
382            return false;
383        }
384
385        $col = $result->fetchAll(PDO::FETCH_COLUMN);
386
387        return $col;
388    }
389
390    /**
391     * Read out the rights from the userrights or grouprights table
392     * that imply other rights along with their level
393     *
394     * @param array right ids
395     * @param string name of the table
396     * @return array requested data or false on failure
397     *
398     * @access public
399     */
400    function readImplyingRights($rightIds, $table)
401    {
402        $query = '
403            SELECT
404            DISTINCT
405                TR.' . $this->alias['right_level'] . ',
406                TR.' . $this->alias['right_id'] . '
407            FROM
408                '.$this->prefix.$this->alias['rights'].' R,
409                '.$this->prefix.$this->alias[$table.'rights'].' TR
410            WHERE
411                TR.' . $this->alias['right_id'] . ' = R.' . $this->alias['right_id'] . '
412            AND
413                R.' . $this->alias['right_id'] . ' IN ('.
414                    implode(', ', array_keys($rightIds)).')
415            AND
416                R.' . $this->alias['has_implied'] . '='.
417                    $this->dbc->quote(true);
418
419        $result = $this->dbc->query($query);
420
421        if ($result === false) {
422            $error_info = $this->dbc->errorInfo();
423            $this->stack->push(LIVEUSER_ERROR, 'exception', array(),
424                'error in query ' . $error_info[2] . ' - ' . $query);
425            return false;
426        }
427
428        $rows = array();
429        while ($row = $result->fetch(PDO::FETCH_NUM)) {
430            $rows[$row[0]][] = $row[1];
431        }
432
433        return $rows;
434    }
435
436    /**
437    * Read out the implied rights with a given level from the implied_rights table
438    *
439    * @param array current right ids
440    * @param string current level
441     * @return array requested data or false on failure
442    *
443    * @access public
444    */
445    function readImpliedRights($currentRights, $currentLevel)
446    {
447        $query = '
448            SELECT
449                RI.' . $this->alias['implied_right_id'] . ' AS right_id,
450                '.$currentLevel.'                           AS right_level,
451                R.' . $this->alias['has_implied'] . '       AS has_implied
452            FROM
453                '.$this->prefix.$this->alias['rights'].' R,
454                '.$this->prefix.$this->alias['right_implied'].' RI
455            WHERE
456                RI.' . $this->alias['implied_right_id'] . ' = R.' . $this->alias['right_id'] . '
457            AND
458                RI.' . $this->alias['right_id'] . ' IN ('.
459                    implode(', ', $currentRights).')';
460
461        $result = $this->dbc->query($query);
462
463        if ($result === false) {
464            $error_info = $this->dbc->errorInfo();
465            $this->stack->push(LIVEUSER_ERROR, 'exception', array(),
466                'error in query ' . $error_info[2] . ' - ' . $query);
467            return false;
468        }
469
470        $rows = $result->fetchAll(PDO::FETCH_ASSOC);
471
472        return (array)$rows;
473    }
474
475    /**
476     * Override the disconnect method from the parent class.
477     *
478     * @return void
479     */
480    function disconnect()
481    {
482        if ($this->dsn) {
483            $this->dbc = null;
484        }
485    }
486}
487?>
488