1<?php
2/**
3 * Copyright 2002 Ronnie Garcia <ronnie@mk2.net>
4 * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
5 *
6 * See the enclosed file COPYING for license information (LGPL). If you did
7 * not receive this file, see http://www.horde.org/licenses/lgpl21.
8 *
9 * @author   Ronnie Garcia <ronnie@mk2.net>
10 * @author   Chuck Hagenbuch <chuck@horde.org>
11 * @author   Joel Vandal <joel@scopserv.com>
12 * @category Horde
13 * @license  http://opensource.org/licenses/lgpl-2.1.php LGPL-2.1
14 * @package  Auth
15 */
16
17/**
18 * The Horde_Auth_Customsql class provides a sql implementation of the Horde
19 * authentication system with the possibility to set custom-made queries.
20 *
21 * @author    Ronnie Garcia <ronnie@mk2.net>
22 * @author    Chuck Hagenbuch <chuck@horde.org>
23 * @author    Joel Vandal <joel@scopserv.com>
24 * @category  Horde
25 * @copyright 2002 Ronnie Garcia <ronnie@mk2.net>
26 * @copyright 2002-2017 Horde LLC
27 * @license   http://opensource.org/licenses/lgpl-2.1.php LGPL-2.1
28 * @package   Auth
29 */
30class Horde_Auth_Customsql extends Horde_Auth_Sql
31{
32    /**
33     * An array of capabilities, so that the driver can report which
34     * operations it supports and which it doesn't.
35     *
36     * @var array
37     */
38    protected $_capabilities = array(
39        'add' => true,
40        'list' => true,
41        'remove' => true,
42        'resetpassword' => true,
43        'update' => true,
44        'authenticate' => true,
45    );
46
47    /**
48     * Constructor.
49     *
50     * Some special tokens can be used in the SQL query. They are replaced
51     * at the query stage:
52     *   '\L' will be replaced by the user's login
53     *   '\P' will be replaced by the user's password.
54     *   '\O' will be replaced by the old user's login (required for update)
55     *
56     * Eg: "SELECT * FROM users WHERE uid = \L
57     *                          AND passwd = \P
58     *                          AND billing = 'paid'"
59     *
60     * @param array $params  Configuration parameters:
61     *   - query_auth:          (string) Authenticate the user. ('\L' & '\P')
62     *   - query_add:           (string) Add user. ('\L' & '\P')
63     *   - query_getpw:         (string) Get one user's password. ('\L')
64     *   - query_update:        (string) Update user. ('\O', '\L' & '\P')
65     *   - query_resetpassword: (string) Reset password. ('\L', & '\P')
66     *   - query_remove:        (string) Remove user. ('\L')
67     *   - query_list:          (string) List user.
68     *   - query_exists:        (string) Check for existance of user. ('\L')
69     */
70    public function __construct(array $params = array())
71    {
72        foreach (array('query_auth', 'query_add',
73                       'query_update', 'query_resetpassword', 'query_remove',
74                       'query_list') as $val) {
75            if (empty($params[$val])) {
76                switch($val) {
77                case 'query_auth':
78                   $this->_capabilities['authenticate'] = false;
79                   break;
80                case 'query_add':
81                   $this->_capabilities['add'] = false;
82                   break;
83                case 'query_update':
84                   $this->_capabilities['update'] = false;
85                   break;
86                case 'query_resetpassword':
87                   $this->_capabilities['resetpassword'] = false;
88                   break;
89                case 'query_remove':
90                   $this->_capabilities['remove'] = false;
91                   break;
92                case 'query_list':
93                   $this->_capabilities['list'] = false;
94                   break;
95                }
96            }
97        }
98
99        parent::__construct($params);
100    }
101
102    /**
103     * Find out if a set of login credentials are valid.
104     *
105     * @param string $userId      The userId to check.
106     * @param array $credentials  The credentials to use.
107     *
108     * @throws Horde_Auth_Exception
109     */
110    protected function _authenticate($userId, $credentials)
111    {
112        /* Build a custom query, based on the config file. */
113        $query = str_replace(
114            array('\L', '\P'),
115            array(
116                $this->_db->quote($userId),
117                $this->_db->quote(Horde_Auth::getCryptedPassword($credentials['password'], $this->_getPassword($userId), $this->_params['encryption'], $this->_params['show_encryption']))
118            ),
119            $this->_params['query_auth']
120        );
121
122        try {
123            if ($this->_db->selectValue($query)) {
124                return;
125            }
126            throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
127        } catch (Horde_Db_Exception $e) {
128            throw new Horde_Auth_Exception('', Horde_Auth::REASON_FAILED);
129        }
130    }
131
132    /**
133     * Add a set of authentication credentials.
134     *
135     * @param string $userId      The userId to add.
136     * @param array $credentials  The credentials to add.
137     *
138     * @throws Horde_Auth_Exception
139     */
140    public function addUser($userId, $credentials)
141    {
142        /* Build a custom query, based on the config file. */
143        $query = str_replace(
144            array('\L', '\P'),
145            array(
146                $this->_db->quote($userId),
147                $this->_db->quote(Horde_Auth::getCryptedPassword($credentials['password'], '', $this->_params['encryption'], $this->_params['show_encryption']))
148            ),
149            $this->_params['query_add']
150        );
151
152        try {
153            $this->_db->insert($query);
154        } catch (Horde_Db_Exception $e) {
155            throw new Horde_Auth_Exception($e);
156        }
157    }
158
159    /**
160     * Update a set of authentication credentials.
161     *
162     * @param string $oldId       The old userId.
163     * @param string $newId       The new userId.
164     * @param array $credentials  The new credentials
165     *
166     * @throws Horde_Auth_Exception
167     */
168    public function updateUser($oldId, $newId, $credentials)
169    {
170        /* Build a custom query, based on the config file. */
171        $query = str_replace(
172            array('\O', '\L', '\P'),
173            array(
174                $this->_db->quote($oldId),
175                $this->_db->quote($newId),
176                $this->_db->quote(Horde_Auth::getCryptedPassword($credentials['password'], $this->_getPassword($oldId), $this->_params['encryption'], $this->_params['show_encryption']))
177            ),
178            $this->_params['query_update']
179        );
180
181        try {
182            $this->_db->update($query);
183        } catch (Horde_Db_Exception $e) {
184            throw new Horde_Auth_Exception($e);
185        }
186    }
187
188    /**
189     * Resets a user's password. Used for example when the user does not
190     * remember the existing password.
191     *
192     * @param string $userId  The user id for which to reset the password.
193     *
194     * @return string  The new password on success.
195     * @throws Horde_Auth_Exception
196     */
197    public function resetPassword($userId)
198    {
199        /* Get a new random password. */
200        $password = Horde_Auth::genRandomPassword();
201
202        /* Build the SQL query. */
203        $query = str_replace(
204            array('\L', '\P'),
205            array(
206                $this->_db->quote($userId),
207                $this->_db->quote(Horde_Auth::getCryptedPassword($password, '', $this->_params['encryption'], $this->_params['show_encryption']))
208            ),
209            $this->_params['query_resetpassword']
210        );
211
212        try {
213            $this->_db->update($query);
214        } catch (Horde_Db_Exception $e) {
215            throw new Horde_Auth_Exception($e);
216        }
217
218        return $password;
219    }
220
221    /**
222     * Delete a set of authentication credentials.
223     *
224     * @param string $userId  The userId to delete.
225     *
226     * @throws Horde_Auth_Exception
227     */
228    public function removeUser($userId)
229    {
230        /* Build a custom query, based on the config file. */
231        $query = str_replace(
232            '\L',
233            $this->_db->quote($userId),
234            $this->_params['query_remove']
235        );
236
237        try {
238            $this->_db->delete($query);
239        } catch (Horde_Db_Exception $e) {
240            throw new Horde_Auth_Exception($e);
241        }
242    }
243
244    /**
245     * Lists all users in the system.
246     *
247     * @param boolean $sort  Sort the users?
248     *
249     * @return array  The array of userIds.
250     * @throws Horde_Auth_Exception
251     */
252    public function listUsers($sort = false)
253    {
254        /* Build a custom query, based on the config file. */
255        $query = str_replace(
256            '\L',
257            $this->_db->quote($this->_params['default_user']),
258            $this->_params['query_list']
259        );
260
261        try {
262            $users = $this->_db->selectValues($query);
263            // Find a way to sort in database with portable SQL
264            return $this->_sort($users, $sort);
265        } catch (Horde_Db_Exception $e) {
266            throw new Horde_Auth_Exception($e);
267        }
268    }
269
270    /**
271     * Checks if a userId exists in the system.
272     *
273     * @param string $userId  User ID for which to check
274     *
275     * @return boolean  Whether or not the userId already exists.
276     */
277    public function exists($userId)
278    {
279        if (empty($this->_params['query_exists'])) {
280            return parent::exists($userId);
281        }
282
283        /* Build a custom query, based on the config file. */
284        $query = str_replace(
285            '\L',
286            $this->_db->quote($userId),
287            $this->_params['query_exists']
288        );
289
290        try {
291            return (bool)$this->_db->selectValue($query);
292        } catch (Horde_Db_Exception $e) {
293            return false;
294        }
295    }
296
297    /**
298     * Fetch $userId's current password - needed for the salt with some
299     * encryption schemes when doing authentication or updates.
300     *
301     * @param string $userId  The userId to query.
302     *
303     * @return string  $userId's current password.
304     */
305    protected function _getPassword($userId)
306    {
307        /* Retrieve the old password in case we need the salt. */
308        $query = str_replace(
309            '\L',
310            $this->_db->quote($userId),
311            $this->_params['query_getpw']
312        );
313
314        try {
315            return $this->_db->selectValue($query);
316        } catch (Horde_Db_Exception $e) {
317            return null;
318        }
319    }
320
321}
322