xref: /dokuwiki/lib/plugins/authpdo/auth.php (revision 0d586afdf931ff216350371cc55bb719b8e5e442)
1f64dbc90SAndreas Gohr<?php
2f64dbc90SAndreas Gohr/**
3f64dbc90SAndreas Gohr * DokuWiki Plugin authpdo (Auth Component)
4f64dbc90SAndreas Gohr *
5f64dbc90SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6f64dbc90SAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
7f64dbc90SAndreas Gohr */
8f64dbc90SAndreas Gohr
9f64dbc90SAndreas Gohr// must be run within Dokuwiki
10f64dbc90SAndreas Gohrif(!defined('DOKU_INC')) die();
11f64dbc90SAndreas Gohr
12*0d586afdSAndreas Gohr/**
13*0d586afdSAndreas Gohr * Class auth_plugin_authpdo
14*0d586afdSAndreas Gohr */
15f64dbc90SAndreas Gohrclass auth_plugin_authpdo extends DokuWiki_Auth_Plugin {
16f64dbc90SAndreas Gohr
17f64dbc90SAndreas Gohr    /** @var PDO */
18f64dbc90SAndreas Gohr    protected $pdo;
19f64dbc90SAndreas Gohr
20f64dbc90SAndreas Gohr    /**
21f64dbc90SAndreas Gohr     * Constructor.
22f64dbc90SAndreas Gohr     */
23f64dbc90SAndreas Gohr    public function __construct() {
24f64dbc90SAndreas Gohr        parent::__construct(); // for compatibility
25f64dbc90SAndreas Gohr
26f64dbc90SAndreas Gohr        if(!class_exists('PDO')) {
27f64dbc90SAndreas Gohr            $this->_debug('PDO extension for PHP not found.', -1, __LINE__);
28f64dbc90SAndreas Gohr            $this->success = false;
29f64dbc90SAndreas Gohr            return;
30f64dbc90SAndreas Gohr        }
31f64dbc90SAndreas Gohr
32f64dbc90SAndreas Gohr        if(!$this->getConf('dsn')) {
33f64dbc90SAndreas Gohr            $this->_debug('No DSN specified', -1, __LINE__);
34f64dbc90SAndreas Gohr            $this->success = false;
35f64dbc90SAndreas Gohr            return;
36f64dbc90SAndreas Gohr        }
37f64dbc90SAndreas Gohr
38f64dbc90SAndreas Gohr        try {
39f64dbc90SAndreas Gohr            $this->pdo = new PDO(
40f64dbc90SAndreas Gohr                $this->getConf('dsn'),
41f64dbc90SAndreas Gohr                $this->getConf('user'),
42f64dbc90SAndreas Gohr                $this->getConf('pass'),
43f64dbc90SAndreas Gohr                array(
4470a89417SAndreas Gohr                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // always fetch as array
4570a89417SAndreas Gohr                    PDO::ATTR_EMULATE_PREPARES => true, // emulating prepares allows us to reuse param names
465de3a6a5SAndreas Gohr                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // we want exceptions, not error codes
47f64dbc90SAndreas Gohr                )
48f64dbc90SAndreas Gohr            );
49f64dbc90SAndreas Gohr        } catch(PDOException $e) {
50f64dbc90SAndreas Gohr            $this->_debug($e);
51f64dbc90SAndreas Gohr            $this->success = false;
52f64dbc90SAndreas Gohr            return;
53f64dbc90SAndreas Gohr        }
54f64dbc90SAndreas Gohr
55*0d586afdSAndreas Gohr        // can Users be created?
56*0d586afdSAndreas Gohr        $this->cando['addUser'] = $this->_chkcnf(
57*0d586afdSAndreas Gohr            array(
58*0d586afdSAndreas Gohr                'select-user',
59*0d586afdSAndreas Gohr                'select-user-groups',
60*0d586afdSAndreas Gohr                'select-groups',
61*0d586afdSAndreas Gohr                'insert-user',
62*0d586afdSAndreas Gohr                'insert-group',
63*0d586afdSAndreas Gohr                'join-group'
64*0d586afdSAndreas Gohr            )
65*0d586afdSAndreas Gohr        );
66f64dbc90SAndreas Gohr
67*0d586afdSAndreas Gohr        // can Users be deleted?
68*0d586afdSAndreas Gohr        $this->cando['delUser'] = $this->_chkcnf(
69*0d586afdSAndreas Gohr            array(
70*0d586afdSAndreas Gohr                'select-user',
71*0d586afdSAndreas Gohr                'select-user-groups',
72*0d586afdSAndreas Gohr                'select-groups',
73*0d586afdSAndreas Gohr                'leave-group'
74*0d586afdSAndreas Gohr            )
75*0d586afdSAndreas Gohr        );
76*0d586afdSAndreas Gohr
77*0d586afdSAndreas Gohr        // can login names be changed?
78*0d586afdSAndreas Gohr        $this->cando['modLogin'] = $this->_chkcnf(
79*0d586afdSAndreas Gohr            array(
80*0d586afdSAndreas Gohr                'select-user',
81*0d586afdSAndreas Gohr                'select-user-groups',
82*0d586afdSAndreas Gohr                'update-user-login'
83*0d586afdSAndreas Gohr            )
84*0d586afdSAndreas Gohr        );
85*0d586afdSAndreas Gohr
86*0d586afdSAndreas Gohr        // can passwords be changed?
87*0d586afdSAndreas Gohr        $this->cando['modPass'] = $this->_chkcnf(
88*0d586afdSAndreas Gohr            array(
89*0d586afdSAndreas Gohr                'select-user',
90*0d586afdSAndreas Gohr                'select-user-groups',
91*0d586afdSAndreas Gohr                'update-user-pass'
92*0d586afdSAndreas Gohr            )
93*0d586afdSAndreas Gohr        );
94*0d586afdSAndreas Gohr
95*0d586afdSAndreas Gohr        // can real names and emails be changed?
96*0d586afdSAndreas Gohr        $this->cando['modName'] = $this->cando['modMail'] = $this->_chkcnf(
97*0d586afdSAndreas Gohr            array(
98*0d586afdSAndreas Gohr                'select-user',
99*0d586afdSAndreas Gohr                'select-user-groups',
100*0d586afdSAndreas Gohr                'update-user-info'
101*0d586afdSAndreas Gohr            )
102*0d586afdSAndreas Gohr        );
103*0d586afdSAndreas Gohr
104*0d586afdSAndreas Gohr        // can groups be changed?
105*0d586afdSAndreas Gohr        $this->cando['modGroups'] = $this->_chkcnf(
106*0d586afdSAndreas Gohr            array(
107*0d586afdSAndreas Gohr                'select-user',
108*0d586afdSAndreas Gohr                'select-user-groups',
109*0d586afdSAndreas Gohr                'select-groups',
110*0d586afdSAndreas Gohr                'leave-group',
111*0d586afdSAndreas Gohr                'join-group',
112*0d586afdSAndreas Gohr                'insert-group'
113*0d586afdSAndreas Gohr            )
114*0d586afdSAndreas Gohr        );
115*0d586afdSAndreas Gohr
116*0d586afdSAndreas Gohr        // can a filtered list of users be retrieved?
117*0d586afdSAndreas Gohr        $this->cando['getUsers'] = $this->_chkcnf(
118*0d586afdSAndreas Gohr            array(
119*0d586afdSAndreas Gohr                'list-users'
120*0d586afdSAndreas Gohr            )
121*0d586afdSAndreas Gohr        );
122*0d586afdSAndreas Gohr
123*0d586afdSAndreas Gohr        // can the number of users be retrieved?
124*0d586afdSAndreas Gohr        $this->cando['getUserCount'] = $this->_chkcnf(
125*0d586afdSAndreas Gohr            array(
126*0d586afdSAndreas Gohr                'count-users'
127*0d586afdSAndreas Gohr            )
128*0d586afdSAndreas Gohr        );
129*0d586afdSAndreas Gohr
130*0d586afdSAndreas Gohr        // can a list of available groups be retrieved?
131*0d586afdSAndreas Gohr        $this->cando['getGroups'] = $this->_chkcnf(
132*0d586afdSAndreas Gohr            array(
133*0d586afdSAndreas Gohr                'select-groups'
134*0d586afdSAndreas Gohr            )
135*0d586afdSAndreas Gohr        );
136*0d586afdSAndreas Gohr
137f64dbc90SAndreas Gohr        $this->success = true;
138f64dbc90SAndreas Gohr    }
139f64dbc90SAndreas Gohr
140f64dbc90SAndreas Gohr    /**
141f64dbc90SAndreas Gohr     * Check user+password
142f64dbc90SAndreas Gohr     *
143f64dbc90SAndreas Gohr     * @param   string $user the user name
144f64dbc90SAndreas Gohr     * @param   string $pass the clear text password
145f64dbc90SAndreas Gohr     * @return  bool
146f64dbc90SAndreas Gohr     */
147f64dbc90SAndreas Gohr    public function checkPass($user, $pass) {
148f64dbc90SAndreas Gohr
149f64dbc90SAndreas Gohr        $data = $this->_selectUser($user);
150f64dbc90SAndreas Gohr        if($data == false) return false;
151f64dbc90SAndreas Gohr
152f64dbc90SAndreas Gohr        if(isset($data['hash'])) {
153f64dbc90SAndreas Gohr            // hashed password
154f64dbc90SAndreas Gohr            $passhash = new PassHash();
155f64dbc90SAndreas Gohr            return $passhash->verify_hash($pass, $data['hash']);
156f64dbc90SAndreas Gohr        } else {
157f64dbc90SAndreas Gohr            // clear text password in the database O_o
158f64dbc90SAndreas Gohr            return ($pass == $data['clear']);
159f64dbc90SAndreas Gohr        }
160f64dbc90SAndreas Gohr    }
161f64dbc90SAndreas Gohr
162f64dbc90SAndreas Gohr    /**
163f64dbc90SAndreas Gohr     * Return user info
164f64dbc90SAndreas Gohr     *
165f64dbc90SAndreas Gohr     * Returns info about the given user needs to contain
166f64dbc90SAndreas Gohr     * at least these fields:
167f64dbc90SAndreas Gohr     *
168f64dbc90SAndreas Gohr     * name string  full name of the user
169f64dbc90SAndreas Gohr     * mail string  email addres of the user
170f64dbc90SAndreas Gohr     * grps array   list of groups the user is in
171f64dbc90SAndreas Gohr     *
172f64dbc90SAndreas Gohr     * @param   string $user the user name
173f64dbc90SAndreas Gohr     * @param   bool $requireGroups whether or not the returned data must include groups
174f64dbc90SAndreas Gohr     * @return array containing user data or false
175f64dbc90SAndreas Gohr     */
176f64dbc90SAndreas Gohr    public function getUserData($user, $requireGroups = true) {
177f64dbc90SAndreas Gohr        $data = $this->_selectUser($user);
178f64dbc90SAndreas Gohr        if($data == false) return false;
179f64dbc90SAndreas Gohr
18070a89417SAndreas Gohr        if(isset($data['hash'])) unset($data['hash']);
18170a89417SAndreas Gohr        if(isset($data['clean'])) unset($data['clean']);
182f64dbc90SAndreas Gohr
18370a89417SAndreas Gohr        if($requireGroups) {
18470a89417SAndreas Gohr            $data['grps'] = $this->_selectUserGroups($data);
1855de3a6a5SAndreas Gohr            if($data['grps'] === false) return false;
186f64dbc90SAndreas Gohr        }
187f64dbc90SAndreas Gohr
188f64dbc90SAndreas Gohr        return $data;
189f64dbc90SAndreas Gohr    }
190f64dbc90SAndreas Gohr
191f64dbc90SAndreas Gohr    /**
192f64dbc90SAndreas Gohr     * Create a new User [implement only where required/possible]
193f64dbc90SAndreas Gohr     *
194f64dbc90SAndreas Gohr     * Returns false if the user already exists, null when an error
195f64dbc90SAndreas Gohr     * occurred and true if everything went well.
196f64dbc90SAndreas Gohr     *
197f64dbc90SAndreas Gohr     * The new user HAS TO be added to the default group by this
198f64dbc90SAndreas Gohr     * function!
199f64dbc90SAndreas Gohr     *
200f64dbc90SAndreas Gohr     * Set addUser capability when implemented
201f64dbc90SAndreas Gohr     *
202f64dbc90SAndreas Gohr     * @param  string $user
2035de3a6a5SAndreas Gohr     * @param  string $clear
204f64dbc90SAndreas Gohr     * @param  string $name
205f64dbc90SAndreas Gohr     * @param  string $mail
206f64dbc90SAndreas Gohr     * @param  null|array $grps
207f64dbc90SAndreas Gohr     * @return bool|null
208f64dbc90SAndreas Gohr     */
2095de3a6a5SAndreas Gohr    public function createUser($user, $clear, $name, $mail, $grps = null) {
2105de3a6a5SAndreas Gohr        global $conf;
2115de3a6a5SAndreas Gohr
2125de3a6a5SAndreas Gohr        if(($info = $this->getUserData($user, false)) !== false) {
2135de3a6a5SAndreas Gohr            msg($this->getLang('userexists'), -1);
2145de3a6a5SAndreas Gohr            return false; // user already exists
2155de3a6a5SAndreas Gohr        }
2165de3a6a5SAndreas Gohr
2175de3a6a5SAndreas Gohr        // prepare data
2185de3a6a5SAndreas Gohr        if($grps == null) $grps = array();
2195de3a6a5SAndreas Gohr        $grps[] = $conf['defaultgroup'];
2205de3a6a5SAndreas Gohr        $grps = array_unique($grps);
2215de3a6a5SAndreas Gohr        $hash = auth_cryptPassword($clear);
2225de3a6a5SAndreas Gohr        $userdata = compact('user', 'clear', 'hash', 'name', 'mail');
2235de3a6a5SAndreas Gohr
2245de3a6a5SAndreas Gohr        // action protected by transaction
2255de3a6a5SAndreas Gohr        $this->pdo->beginTransaction();
2265de3a6a5SAndreas Gohr        {
2275de3a6a5SAndreas Gohr            // insert the user
2285de3a6a5SAndreas Gohr            $ok = $this->_query($this->getConf('insert-user'), $userdata);
2295de3a6a5SAndreas Gohr            if($ok === false) goto FAIL;
2305de3a6a5SAndreas Gohr            $userdata = $this->getUserData($user, false);
2315de3a6a5SAndreas Gohr            if($userdata === false) goto FAIL;
2325de3a6a5SAndreas Gohr
2335de3a6a5SAndreas Gohr            // create all groups that do not exist, the refetch the groups
2345de3a6a5SAndreas Gohr            $allgroups = $this->_selectGroups();
2355de3a6a5SAndreas Gohr            foreach($grps as $group) {
2365de3a6a5SAndreas Gohr                if(!isset($allgroups[$group])) {
2376459f496SAndreas Gohr                    $ok = $this->addGroup($group);
2385de3a6a5SAndreas Gohr                    if($ok === false) goto FAIL;
2395de3a6a5SAndreas Gohr                }
2405de3a6a5SAndreas Gohr            }
2415de3a6a5SAndreas Gohr            $allgroups = $this->_selectGroups();
2425de3a6a5SAndreas Gohr
2435de3a6a5SAndreas Gohr            // add user to the groups
2445de3a6a5SAndreas Gohr            foreach($grps as $group) {
2455de3a6a5SAndreas Gohr                $ok = $this->_joinGroup($userdata, $allgroups[$group]);
2465de3a6a5SAndreas Gohr                if($ok === false) goto FAIL;
2475de3a6a5SAndreas Gohr            }
2485de3a6a5SAndreas Gohr        }
2495de3a6a5SAndreas Gohr        $this->pdo->commit();
2505de3a6a5SAndreas Gohr        return true;
2515de3a6a5SAndreas Gohr
2525de3a6a5SAndreas Gohr        // something went wrong, rollback
2535de3a6a5SAndreas Gohr        FAIL:
2545de3a6a5SAndreas Gohr        $this->pdo->rollBack();
2555de3a6a5SAndreas Gohr        $this->_debug('Transaction rolled back', 0, __LINE__);
2565de3a6a5SAndreas Gohr        return null; // return error
2575de3a6a5SAndreas Gohr    }
258f64dbc90SAndreas Gohr
259f64dbc90SAndreas Gohr    /**
2604fb8dfabSAndreas Gohr     * Modify user data
261f64dbc90SAndreas Gohr     *
262f64dbc90SAndreas Gohr     * @param   string $user nick of the user to be changed
263f64dbc90SAndreas Gohr     * @param   array $changes array of field/value pairs to be changed (password will be clear text)
264f64dbc90SAndreas Gohr     * @return  bool
265f64dbc90SAndreas Gohr     */
2664fb8dfabSAndreas Gohr    public function modifyUser($user, $changes) {
2674fb8dfabSAndreas Gohr        // secure everything in transaction
2684fb8dfabSAndreas Gohr        $this->pdo->beginTransaction();
2694fb8dfabSAndreas Gohr        {
2704fb8dfabSAndreas Gohr            $olddata = $this->getUserData($user);
2714fb8dfabSAndreas Gohr            $oldgroups = $olddata['grps'];
2724fb8dfabSAndreas Gohr            unset($olddata['grps']);
2734fb8dfabSAndreas Gohr
2744fb8dfabSAndreas Gohr            // changing the user name?
2754fb8dfabSAndreas Gohr            if(isset($changes['user'])) {
2764fb8dfabSAndreas Gohr                if($this->getUserData($changes['user'], false)) goto FAIL;
2774fb8dfabSAndreas Gohr                $params = $olddata;
2784fb8dfabSAndreas Gohr                $params['newlogin'] = $changes['user'];
2794fb8dfabSAndreas Gohr
2804fb8dfabSAndreas Gohr                $ok = $this->_query($this->getConf('update-user-login'), $params);
2814fb8dfabSAndreas Gohr                if($ok === false) goto FAIL;
2824fb8dfabSAndreas Gohr            }
2834fb8dfabSAndreas Gohr
2844fb8dfabSAndreas Gohr            // changing the password?
2854fb8dfabSAndreas Gohr            if(isset($changes['pass'])) {
2864fb8dfabSAndreas Gohr                $params = $olddata;
2874fb8dfabSAndreas Gohr                $params['clear'] = $changes['pass'];
2884fb8dfabSAndreas Gohr                $params['hash'] = auth_cryptPassword($changes['pass']);
2894fb8dfabSAndreas Gohr
2904fb8dfabSAndreas Gohr                $ok = $this->_query($this->getConf('update-user-pass'), $params);
2914fb8dfabSAndreas Gohr                if($ok === false) goto FAIL;
2924fb8dfabSAndreas Gohr            }
2934fb8dfabSAndreas Gohr
2944fb8dfabSAndreas Gohr            // changing info?
2954fb8dfabSAndreas Gohr            if(isset($changes['mail']) || isset($changes['name'])) {
2964fb8dfabSAndreas Gohr                $params = $olddata;
2974fb8dfabSAndreas Gohr                if(isset($changes['mail'])) $params['mail'] = $changes['mail'];
2984fb8dfabSAndreas Gohr                if(isset($changes['name'])) $params['name'] = $changes['name'];
2994fb8dfabSAndreas Gohr
3004fb8dfabSAndreas Gohr                $ok = $this->_query($this->getConf('update-user-info'), $params);
3014fb8dfabSAndreas Gohr                if($ok === false) goto FAIL;
3024fb8dfabSAndreas Gohr            }
3034fb8dfabSAndreas Gohr
3044fb8dfabSAndreas Gohr            // changing groups?
3054fb8dfabSAndreas Gohr            if(isset($changes['grps'])) {
3064fb8dfabSAndreas Gohr                $allgroups = $this->_selectGroups();
3074fb8dfabSAndreas Gohr
3084fb8dfabSAndreas Gohr                // remove membership for previous groups
3094fb8dfabSAndreas Gohr                foreach($oldgroups as $group) {
3104fb8dfabSAndreas Gohr                    if(!in_array($group, $changes['grps'])) {
3114fb8dfabSAndreas Gohr                        $ok = $this->_leaveGroup($olddata, $allgroups[$group]);
3124fb8dfabSAndreas Gohr                        if($ok === false) goto FAIL;
3134fb8dfabSAndreas Gohr                    }
3144fb8dfabSAndreas Gohr                }
3154fb8dfabSAndreas Gohr
3164fb8dfabSAndreas Gohr                // create all new groups that are missing
3174fb8dfabSAndreas Gohr                $added = 0;
3184fb8dfabSAndreas Gohr                foreach($changes['grps'] as $group) {
3194fb8dfabSAndreas Gohr                    if(!isset($allgroups[$group])) {
3206459f496SAndreas Gohr                        $ok = $this->addGroup($group);
3214fb8dfabSAndreas Gohr                        if($ok === false) goto FAIL;
3224fb8dfabSAndreas Gohr                        $added++;
3234fb8dfabSAndreas Gohr                    }
3244fb8dfabSAndreas Gohr                }
3254fb8dfabSAndreas Gohr                // reload group info
3264fb8dfabSAndreas Gohr                if($added > 0) $allgroups = $this->_selectGroups();
3274fb8dfabSAndreas Gohr
3284fb8dfabSAndreas Gohr                // add membership for new groups
3294fb8dfabSAndreas Gohr                foreach($changes['grps'] as $group) {
3304fb8dfabSAndreas Gohr                    if(!in_array($group, $oldgroups)) {
3314fb8dfabSAndreas Gohr                        $ok = $this->_joinGroup($olddata, $allgroups[$group]);
3324fb8dfabSAndreas Gohr                        if($ok === false) goto FAIL;
3334fb8dfabSAndreas Gohr                    }
3344fb8dfabSAndreas Gohr                }
3354fb8dfabSAndreas Gohr            }
3364fb8dfabSAndreas Gohr
3374fb8dfabSAndreas Gohr        }
3384fb8dfabSAndreas Gohr        $this->pdo->commit();
3394fb8dfabSAndreas Gohr        return true;
3404fb8dfabSAndreas Gohr
3414fb8dfabSAndreas Gohr        // something went wrong, rollback
3424fb8dfabSAndreas Gohr        FAIL:
3434fb8dfabSAndreas Gohr        $this->pdo->rollBack();
3444fb8dfabSAndreas Gohr        $this->_debug('Transaction rolled back', 0, __LINE__);
3454fb8dfabSAndreas Gohr        return false; // return error
3464fb8dfabSAndreas Gohr    }
347f64dbc90SAndreas Gohr
348f64dbc90SAndreas Gohr    /**
349e19be516SAndreas Gohr     * Delete one or more users
350f64dbc90SAndreas Gohr     *
351f64dbc90SAndreas Gohr     * Set delUser capability when implemented
352f64dbc90SAndreas Gohr     *
353f64dbc90SAndreas Gohr     * @param   array $users
354f64dbc90SAndreas Gohr     * @return  int    number of users deleted
355f64dbc90SAndreas Gohr     */
356e19be516SAndreas Gohr    public function deleteUsers($users) {
357e19be516SAndreas Gohr        $count = 0;
358e19be516SAndreas Gohr        foreach($users as $user) {
359e19be516SAndreas Gohr            if($this->_deleteUser($user)) $count++;
360e19be516SAndreas Gohr        }
361e19be516SAndreas Gohr        return $count;
362e19be516SAndreas Gohr    }
363f64dbc90SAndreas Gohr
364f64dbc90SAndreas Gohr    /**
365f64dbc90SAndreas Gohr     * Bulk retrieval of user data [implement only where required/possible]
366f64dbc90SAndreas Gohr     *
367f64dbc90SAndreas Gohr     * Set getUsers capability when implemented
368f64dbc90SAndreas Gohr     *
369f64dbc90SAndreas Gohr     * @param   int $start index of first user to be returned
370f64dbc90SAndreas Gohr     * @param   int $limit max number of users to be returned
371f64dbc90SAndreas Gohr     * @param   array $filter array of field/pattern pairs, null for no filter
372f64dbc90SAndreas Gohr     * @return  array list of userinfo (refer getUserData for internal userinfo details)
373f64dbc90SAndreas Gohr     */
3746459f496SAndreas Gohr    public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
3756459f496SAndreas Gohr        if($limit < 0) $limit = 10000; // we don't support no limit
3766459f496SAndreas Gohr        if(is_null($filter)) $filter = array();
3776459f496SAndreas Gohr
3786459f496SAndreas Gohr        foreach(array('user', 'name', 'mail', 'group') as $key) {
3796459f496SAndreas Gohr            if(!isset($filter[$key])) {
3806459f496SAndreas Gohr                $filter[$key] = '%';
3816459f496SAndreas Gohr            } else {
3826459f496SAndreas Gohr                $filter[$key] = '%' . $filter[$key] . '%';
3836459f496SAndreas Gohr            }
3846459f496SAndreas Gohr        }
3856459f496SAndreas Gohr        $filter['start'] = $start;
3866459f496SAndreas Gohr        $filter['end'] = $start + $limit;
3876459f496SAndreas Gohr        $filter['limit'] = $limit;
3886459f496SAndreas Gohr
3896459f496SAndreas Gohr        $result = $this->_query($this->getConf('list-users'), $filter);
3906459f496SAndreas Gohr        if(!$result) return array();
3916459f496SAndreas Gohr        $users = array();
3926459f496SAndreas Gohr        foreach($result as $row) {
3936459f496SAndreas Gohr            if(!isset($row['user'])) {
3946459f496SAndreas Gohr                $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
3956459f496SAndreas Gohr                return array();
3966459f496SAndreas Gohr            }
3976459f496SAndreas Gohr            $users[] = $row['user'];
3986459f496SAndreas Gohr        }
3996459f496SAndreas Gohr        return $users;
4006459f496SAndreas Gohr    }
401f64dbc90SAndreas Gohr
402f64dbc90SAndreas Gohr    /**
403f64dbc90SAndreas Gohr     * Return a count of the number of user which meet $filter criteria
404f64dbc90SAndreas Gohr     *
405f64dbc90SAndreas Gohr     * @param  array $filter array of field/pattern pairs, empty array for no filter
406f64dbc90SAndreas Gohr     * @return int
407f64dbc90SAndreas Gohr     */
4086459f496SAndreas Gohr    public function getUserCount($filter = array()) {
4096459f496SAndreas Gohr        if(is_null($filter)) $filter = array();
4106459f496SAndreas Gohr
4116459f496SAndreas Gohr        foreach(array('user', 'name', 'mail', 'group') as $key) {
4126459f496SAndreas Gohr            if(!isset($filter[$key])) {
4136459f496SAndreas Gohr                $filter[$key] = '%';
4146459f496SAndreas Gohr            } else {
4156459f496SAndreas Gohr                $filter[$key] = '%' . $filter[$key] . '%';
4166459f496SAndreas Gohr            }
4176459f496SAndreas Gohr        }
4186459f496SAndreas Gohr
4196459f496SAndreas Gohr        $result = $this->_query($this->getConf('count-users'), $filter);
4206459f496SAndreas Gohr        if(!$result || !isset($result[0]['count'])) {
4216459f496SAndreas Gohr            $this->_debug("Statement did not return 'count' attribute", -1, __LINE__);
4226459f496SAndreas Gohr        }
4236459f496SAndreas Gohr        return isset($result[0]['count']);
4246459f496SAndreas Gohr    }
425f64dbc90SAndreas Gohr
426f64dbc90SAndreas Gohr    /**
4276459f496SAndreas Gohr     * Create a new group with the given name
428f64dbc90SAndreas Gohr     *
429f64dbc90SAndreas Gohr     * @param string $group
430f64dbc90SAndreas Gohr     * @return bool
431f64dbc90SAndreas Gohr     */
4326459f496SAndreas Gohr    public function addGroup($group) {
4336459f496SAndreas Gohr        $sql = $this->getConf('insert-group');
4346459f496SAndreas Gohr
4356459f496SAndreas Gohr        $result = $this->_query($sql, array(':group' => $group));
4366459f496SAndreas Gohr        if($result === false) return false;
4376459f496SAndreas Gohr        return true;
4386459f496SAndreas Gohr    }
439f64dbc90SAndreas Gohr
440f64dbc90SAndreas Gohr    /**
4415de3a6a5SAndreas Gohr     * Retrieve groups
442f64dbc90SAndreas Gohr     *
443f64dbc90SAndreas Gohr     * Set getGroups capability when implemented
444f64dbc90SAndreas Gohr     *
445f64dbc90SAndreas Gohr     * @param   int $start
446f64dbc90SAndreas Gohr     * @param   int $limit
447f64dbc90SAndreas Gohr     * @return  array
448f64dbc90SAndreas Gohr     */
4495de3a6a5SAndreas Gohr    public function retrieveGroups($start = 0, $limit = 0) {
4505de3a6a5SAndreas Gohr        $groups = array_keys($this->_selectGroups());
4515de3a6a5SAndreas Gohr        if($groups === false) return array();
452f64dbc90SAndreas Gohr
4535de3a6a5SAndreas Gohr        if(!$limit) {
4545de3a6a5SAndreas Gohr            return array_splice($groups, $start);
4555de3a6a5SAndreas Gohr        } else {
4565de3a6a5SAndreas Gohr            return array_splice($groups, $start, $limit);
457f64dbc90SAndreas Gohr        }
458f64dbc90SAndreas Gohr    }
459f64dbc90SAndreas Gohr
460f64dbc90SAndreas Gohr    /**
461f64dbc90SAndreas Gohr     * Select data of a specified user
462f64dbc90SAndreas Gohr     *
4635de3a6a5SAndreas Gohr     * @param string $user the user name
4645de3a6a5SAndreas Gohr     * @return bool|array user data, false on error
465f64dbc90SAndreas Gohr     */
466f64dbc90SAndreas Gohr    protected function _selectUser($user) {
467f64dbc90SAndreas Gohr        $sql = $this->getConf('select-user');
468f64dbc90SAndreas Gohr
4695de3a6a5SAndreas Gohr        $result = $this->_query($sql, array(':user' => $user));
47070a89417SAndreas Gohr        if(!$result) return false;
471f64dbc90SAndreas Gohr
47270a89417SAndreas Gohr        if(count($result) > 1) {
473f64dbc90SAndreas Gohr            $this->_debug('Found more than one matching user', -1, __LINE__);
474f64dbc90SAndreas Gohr            return false;
475f64dbc90SAndreas Gohr        }
476f64dbc90SAndreas Gohr
477f64dbc90SAndreas Gohr        $data = array_shift($result);
478f64dbc90SAndreas Gohr        $dataok = true;
479f64dbc90SAndreas Gohr
480f64dbc90SAndreas Gohr        if(!isset($data['user'])) {
481f64dbc90SAndreas Gohr            $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
482f64dbc90SAndreas Gohr            $dataok = false;
483f64dbc90SAndreas Gohr        }
484f64dbc90SAndreas Gohr        if(!isset($data['hash']) && !isset($data['clear'])) {
485f64dbc90SAndreas Gohr            $this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
486f64dbc90SAndreas Gohr            $dataok = false;
487f64dbc90SAndreas Gohr        }
488f64dbc90SAndreas Gohr        if(!isset($data['name'])) {
489f64dbc90SAndreas Gohr            $this->_debug("Statement did not return 'name' attribute", -1, __LINE__);
490f64dbc90SAndreas Gohr            $dataok = false;
491f64dbc90SAndreas Gohr        }
492f64dbc90SAndreas Gohr        if(!isset($data['mail'])) {
493f64dbc90SAndreas Gohr            $this->_debug("Statement did not return 'mail' attribute", -1, __LINE__);
494f64dbc90SAndreas Gohr            $dataok = false;
495f64dbc90SAndreas Gohr        }
496f64dbc90SAndreas Gohr
497f64dbc90SAndreas Gohr        if(!$dataok) return false;
498f64dbc90SAndreas Gohr        return $data;
499f64dbc90SAndreas Gohr    }
500f64dbc90SAndreas Gohr
501f64dbc90SAndreas Gohr    /**
502e19be516SAndreas Gohr     * Delete a user after removing all their group memberships
503e19be516SAndreas Gohr     *
504e19be516SAndreas Gohr     * @param string $user
505e19be516SAndreas Gohr     * @return bool true when the user was deleted
506e19be516SAndreas Gohr     */
507e19be516SAndreas Gohr    protected function _deleteUser($user) {
508e19be516SAndreas Gohr        $this->pdo->beginTransaction();
509e19be516SAndreas Gohr        {
510e19be516SAndreas Gohr            $userdata = $this->getUserData($user);
511e19be516SAndreas Gohr            if($userdata === false) goto FAIL;
512e19be516SAndreas Gohr            $allgroups = $this->_selectGroups();
513e19be516SAndreas Gohr
514e19be516SAndreas Gohr            // remove group memberships (ignore errors)
515e19be516SAndreas Gohr            foreach($userdata['grps'] as $group) {
516e19be516SAndreas Gohr                $this->_leaveGroup($userdata, $allgroups[$group]);
517e19be516SAndreas Gohr            }
518e19be516SAndreas Gohr
519e19be516SAndreas Gohr            $ok = $this->_query($this->getConf('delete-user'), $userdata);
520e19be516SAndreas Gohr            if($ok === false) goto FAIL;
521e19be516SAndreas Gohr        }
522e19be516SAndreas Gohr        $this->pdo->commit();
523e19be516SAndreas Gohr        return true;
524e19be516SAndreas Gohr
525e19be516SAndreas Gohr        FAIL:
526e19be516SAndreas Gohr        $this->pdo->rollBack();
527e19be516SAndreas Gohr        return false;
528e19be516SAndreas Gohr    }
529e19be516SAndreas Gohr
530e19be516SAndreas Gohr    /**
53170a89417SAndreas Gohr     * Select all groups of a user
53270a89417SAndreas Gohr     *
53370a89417SAndreas Gohr     * @param array $userdata The userdata as returned by _selectUser()
5345de3a6a5SAndreas Gohr     * @return array|bool list of group names, false on error
53570a89417SAndreas Gohr     */
53670a89417SAndreas Gohr    protected function _selectUserGroups($userdata) {
53770a89417SAndreas Gohr        global $conf;
53870a89417SAndreas Gohr        $sql = $this->getConf('select-user-groups');
5395de3a6a5SAndreas Gohr        $result = $this->_query($sql, $userdata);
5405de3a6a5SAndreas Gohr        if($result === false) return false;
54170a89417SAndreas Gohr
54270a89417SAndreas Gohr        $groups = array($conf['defaultgroup']); // always add default config
5435de3a6a5SAndreas Gohr        foreach($result as $row) {
5445de3a6a5SAndreas Gohr            if(!isset($row['group'])) {
5455de3a6a5SAndreas Gohr                $this->_debug("No 'group' field returned in select-user-groups statement");
5465de3a6a5SAndreas Gohr                return false;
5475de3a6a5SAndreas Gohr            }
54870a89417SAndreas Gohr            $groups[] = $row['group'];
54970a89417SAndreas Gohr        }
55070a89417SAndreas Gohr
55170a89417SAndreas Gohr        $groups = array_unique($groups);
55270a89417SAndreas Gohr        sort($groups);
55370a89417SAndreas Gohr        return $groups;
55470a89417SAndreas Gohr    }
55570a89417SAndreas Gohr
55670a89417SAndreas Gohr    /**
5575de3a6a5SAndreas Gohr     * Select all available groups
5585de3a6a5SAndreas Gohr     *
5595de3a6a5SAndreas Gohr     * @todo this should be cached
5605de3a6a5SAndreas Gohr     * @return array|bool list of all available groups and their properties
5615de3a6a5SAndreas Gohr     */
5625de3a6a5SAndreas Gohr    protected function _selectGroups() {
5635de3a6a5SAndreas Gohr        $sql = $this->getConf('select-groups');
5645de3a6a5SAndreas Gohr        $result = $this->_query($sql);
5655de3a6a5SAndreas Gohr        if($result === false) return false;
5665de3a6a5SAndreas Gohr
5675de3a6a5SAndreas Gohr        $groups = array();
5685de3a6a5SAndreas Gohr        foreach($result as $row) {
5695de3a6a5SAndreas Gohr            if(!isset($row['group'])) {
5705de3a6a5SAndreas Gohr                $this->_debug("No 'group' field returned from select-groups statement", -1, __LINE__);
5715de3a6a5SAndreas Gohr                return false;
5725de3a6a5SAndreas Gohr            }
5735de3a6a5SAndreas Gohr
5745de3a6a5SAndreas Gohr            // relayout result with group name as key
5755de3a6a5SAndreas Gohr            $group = $row['group'];
5765de3a6a5SAndreas Gohr            $groups[$group] = $row;
5775de3a6a5SAndreas Gohr        }
5785de3a6a5SAndreas Gohr
5795de3a6a5SAndreas Gohr        ksort($groups);
5805de3a6a5SAndreas Gohr        return $groups;
5815de3a6a5SAndreas Gohr    }
5825de3a6a5SAndreas Gohr
5835de3a6a5SAndreas Gohr    /**
5844fb8dfabSAndreas Gohr     * Adds the user to the group
5855de3a6a5SAndreas Gohr     *
5865de3a6a5SAndreas Gohr     * @param array $userdata all the user data
5875de3a6a5SAndreas Gohr     * @param array $groupdata all the group data
5885de3a6a5SAndreas Gohr     * @return bool
5895de3a6a5SAndreas Gohr     */
5905de3a6a5SAndreas Gohr    protected function _joinGroup($userdata, $groupdata) {
5915de3a6a5SAndreas Gohr        $data = array_merge($userdata, $groupdata);
5925de3a6a5SAndreas Gohr        $sql = $this->getConf('join-group');
5935de3a6a5SAndreas Gohr        $result = $this->_query($sql, $data);
5945de3a6a5SAndreas Gohr        if($result === false) return false;
5955de3a6a5SAndreas Gohr        return true;
5965de3a6a5SAndreas Gohr    }
5975de3a6a5SAndreas Gohr
5985de3a6a5SAndreas Gohr    /**
5994fb8dfabSAndreas Gohr     * Removes the user from the group
6004fb8dfabSAndreas Gohr     *
6014fb8dfabSAndreas Gohr     * @param array $userdata all the user data
6024fb8dfabSAndreas Gohr     * @param array $groupdata all the group data
6034fb8dfabSAndreas Gohr     * @return bool
6044fb8dfabSAndreas Gohr     */
6054fb8dfabSAndreas Gohr    protected function _leaveGroup($userdata, $groupdata) {
6064fb8dfabSAndreas Gohr        $data = array_merge($userdata, $groupdata);
6074fb8dfabSAndreas Gohr        $sql = $this->getConf('leave-group');
6084fb8dfabSAndreas Gohr        $result = $this->_query($sql, $data);
6094fb8dfabSAndreas Gohr        if($result === false) return false;
6104fb8dfabSAndreas Gohr        return true;
6114fb8dfabSAndreas Gohr    }
6124fb8dfabSAndreas Gohr
6134fb8dfabSAndreas Gohr    /**
61470a89417SAndreas Gohr     * Executes a query
61570a89417SAndreas Gohr     *
61670a89417SAndreas Gohr     * @param string $sql The SQL statement to execute
61770a89417SAndreas Gohr     * @param array $arguments Named parameters to be used in the statement
6185de3a6a5SAndreas Gohr     * @return array|bool The result as associative array, false on error
61970a89417SAndreas Gohr     */
6205de3a6a5SAndreas Gohr    protected function _query($sql, $arguments = array()) {
6215de3a6a5SAndreas Gohr        if(empty($sql)) {
6225de3a6a5SAndreas Gohr            $this->_debug('No SQL query given', -1, __LINE__);
6235de3a6a5SAndreas Gohr            return false;
6245de3a6a5SAndreas Gohr        }
6255de3a6a5SAndreas Gohr
62670a89417SAndreas Gohr        // prepare parameters - we only use those that exist in the SQL
62770a89417SAndreas Gohr        $params = array();
62870a89417SAndreas Gohr        foreach($arguments as $key => $value) {
62970a89417SAndreas Gohr            if(is_array($value)) continue;
63070a89417SAndreas Gohr            if(is_object($value)) continue;
63170a89417SAndreas Gohr            if($key[0] != ':') $key = ":$key"; // prefix with colon if needed
63270a89417SAndreas Gohr            if(strpos($sql, $key) !== false) $params[$key] = $value;
63370a89417SAndreas Gohr        }
63470a89417SAndreas Gohr
63570a89417SAndreas Gohr        // execute
63670a89417SAndreas Gohr        $sth = $this->pdo->prepare($sql);
6375de3a6a5SAndreas Gohr        try {
63870a89417SAndreas Gohr            $sth->execute($params);
63970a89417SAndreas Gohr            $result = $sth->fetchAll();
6405de3a6a5SAndreas Gohr        } catch(Exception $e) {
6414fb8dfabSAndreas Gohr            // report the caller's line
6424fb8dfabSAndreas Gohr            $trace = debug_backtrace();
6434fb8dfabSAndreas Gohr            $line = $trace[0]['line'];
6445de3a6a5SAndreas Gohr            $dsql = $this->_debugSQL($sql, $params, !defined('DOKU_UNITTEST'));
6454fb8dfabSAndreas Gohr            $this->_debug($e, -1, $line);
6464fb8dfabSAndreas Gohr            $this->_debug("SQL: <pre>$dsql</pre>", -1, $line);
64770a89417SAndreas Gohr            $result = false;
6485de3a6a5SAndreas Gohr        } finally {
64970a89417SAndreas Gohr            $sth->closeCursor();
65070a89417SAndreas Gohr            $sth = null;
65170a89417SAndreas Gohr        }
65270a89417SAndreas Gohr
6535de3a6a5SAndreas Gohr        return $result;
6545de3a6a5SAndreas Gohr    }
65570a89417SAndreas Gohr
65670a89417SAndreas Gohr    /**
657f64dbc90SAndreas Gohr     * Wrapper around msg() but outputs only when debug is enabled
658f64dbc90SAndreas Gohr     *
659f64dbc90SAndreas Gohr     * @param string|Exception $message
660f64dbc90SAndreas Gohr     * @param int $err
661f64dbc90SAndreas Gohr     * @param int $line
662f64dbc90SAndreas Gohr     */
663f64dbc90SAndreas Gohr    protected function _debug($message, $err = 0, $line = 0) {
664f64dbc90SAndreas Gohr        if(!$this->getConf('debug')) return;
665f64dbc90SAndreas Gohr        if(is_a($message, 'Exception')) {
666f64dbc90SAndreas Gohr            $err = -1;
667f64dbc90SAndreas Gohr            $msg = $message->getMessage();
6684fb8dfabSAndreas Gohr            if(!$line) $line = $message->getLine();
669f64dbc90SAndreas Gohr        } else {
670f64dbc90SAndreas Gohr            $msg = $message;
671f64dbc90SAndreas Gohr        }
672f64dbc90SAndreas Gohr
673f64dbc90SAndreas Gohr        if(defined('DOKU_UNITTEST')) {
674f64dbc90SAndreas Gohr            printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
675f64dbc90SAndreas Gohr        } else {
676f64dbc90SAndreas Gohr            msg('authpdo: ' . $msg, $err, $line, __FILE__);
677f64dbc90SAndreas Gohr        }
678f64dbc90SAndreas Gohr    }
6795de3a6a5SAndreas Gohr
6805de3a6a5SAndreas Gohr    /**
6815de3a6a5SAndreas Gohr     * Check if the given config strings are set
6825de3a6a5SAndreas Gohr     *
6835de3a6a5SAndreas Gohr     * @author  Matthias Grimm <matthiasgrimm@users.sourceforge.net>
6845de3a6a5SAndreas Gohr     *
6855de3a6a5SAndreas Gohr     * @param   string[] $keys
6865de3a6a5SAndreas Gohr     * @return  bool
6875de3a6a5SAndreas Gohr     */
6885de3a6a5SAndreas Gohr    protected function _chkcnf($keys) {
6895de3a6a5SAndreas Gohr        foreach($keys as $key) {
6905de3a6a5SAndreas Gohr            if(!$this->getConf($key)) return false;
6915de3a6a5SAndreas Gohr        }
6925de3a6a5SAndreas Gohr
6935de3a6a5SAndreas Gohr        return true;
6945de3a6a5SAndreas Gohr    }
6955de3a6a5SAndreas Gohr
6965de3a6a5SAndreas Gohr    /**
6975de3a6a5SAndreas Gohr     * create an approximation of the SQL string with parameters replaced
6985de3a6a5SAndreas Gohr     *
6995de3a6a5SAndreas Gohr     * @param string $sql
7005de3a6a5SAndreas Gohr     * @param array $params
7015de3a6a5SAndreas Gohr     * @param bool $htmlescape Should the result be escaped for output in HTML?
7025de3a6a5SAndreas Gohr     * @return string
7035de3a6a5SAndreas Gohr     */
7045de3a6a5SAndreas Gohr    protected function _debugSQL($sql, $params, $htmlescape = true) {
7055de3a6a5SAndreas Gohr        foreach($params as $key => $val) {
7065de3a6a5SAndreas Gohr            if(is_int($val)) {
7075de3a6a5SAndreas Gohr                $val = $this->pdo->quote($val, PDO::PARAM_INT);
7085de3a6a5SAndreas Gohr            } elseif(is_bool($val)) {
7095de3a6a5SAndreas Gohr                $val = $this->pdo->quote($val, PDO::PARAM_BOOL);
7105de3a6a5SAndreas Gohr            } elseif(is_null($val)) {
7115de3a6a5SAndreas Gohr                $val = 'NULL';
7125de3a6a5SAndreas Gohr            } else {
7135de3a6a5SAndreas Gohr                $val = $this->pdo->quote($val);
7145de3a6a5SAndreas Gohr            }
7155de3a6a5SAndreas Gohr            $sql = str_replace($key, $val, $sql);
7165de3a6a5SAndreas Gohr        }
7175de3a6a5SAndreas Gohr        if($htmlescape) $sql = hsc($sql);
7185de3a6a5SAndreas Gohr        return $sql;
7195de3a6a5SAndreas Gohr    }
720f64dbc90SAndreas Gohr}
721f64dbc90SAndreas Gohr
722f64dbc90SAndreas Gohr// vim:ts=4:sw=4:et:
723