Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
38.05% covered (danger)
38.05%
43 / 113
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
db
38.05% covered (danger)
38.05%
43 / 113
50.00% covered (danger)
50.00%
1 / 2
214.37
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 login
35.19% covered (danger)
35.19%
38 / 108
0.00% covered (danger)
0.00%
0 / 1
225.50
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb\auth\provider;
15
16use phpbb\captcha\factory;
17use phpbb\captcha\plugins\captcha_abstract;
18use phpbb\config\config;
19use phpbb\db\driver\driver_interface;
20use phpbb\passwords\manager;
21use phpbb\user;
22
23/**
24 * Database authentication provider for phpBB3
25 * This is for authentication via the integrated user table
26 */
27class db extends base
28{
29    /** @var factory CAPTCHA factory */
30    protected $captcha_factory;
31
32    /** @var config phpBB config */
33    protected $config;
34
35    /** @var driver_interface DBAL driver instance */
36    protected $db;
37
38    /** @var user User object */
39    protected $user;
40
41    /**
42    * phpBB passwords manager
43    *
44    * @var manager
45    */
46    protected $passwords_manager;
47
48    /**
49     * Database Authentication Constructor
50     *
51     * @param factory $captcha_factory
52     * @param    config         $config
53     * @param    driver_interface        $db
54     * @param    manager    $passwords_manager
55     * @param    user            $user
56     */
57    public function __construct(factory $captcha_factory, config $config, driver_interface $db, manager $passwords_manager, user $user)
58    {
59        $this->captcha_factory = $captcha_factory;
60        $this->config = $config;
61        $this->db = $db;
62        $this->passwords_manager = $passwords_manager;
63        $this->user = $user;
64    }
65
66    /**
67     * {@inheritdoc}
68     */
69    public function login($username, $password)
70    {
71        // Auth plugins get the password untrimmed.
72        // For compatibility we trim() here.
73        $password = trim($password);
74
75        // do not allow empty password
76        if (!$password)
77        {
78            return array(
79                'status'    => LOGIN_ERROR_PASSWORD,
80                'error_msg'    => 'NO_PASSWORD_SUPPLIED',
81                'user_row'    => array('user_id' => ANONYMOUS),
82            );
83        }
84
85        if (!$username)
86        {
87            return array(
88                'status'    => LOGIN_ERROR_USERNAME,
89                'error_msg'    => 'LOGIN_ERROR_USERNAME',
90                'user_row'    => array('user_id' => ANONYMOUS),
91            );
92        }
93
94        $username_clean = utf8_clean_string($username);
95
96        $sql = 'SELECT *
97            FROM ' . USERS_TABLE . "
98            WHERE username_clean = '" . $this->db->sql_escape($username_clean) . "'";
99        $result = $this->db->sql_query($sql);
100        $row = $this->db->sql_fetchrow($result);
101        $this->db->sql_freeresult($result);
102
103        if (($this->user->ip && !$this->config['ip_login_limit_use_forwarded']) ||
104            ($this->user->forwarded_for && $this->config['ip_login_limit_use_forwarded']))
105        {
106            $sql = 'SELECT COUNT(*) AS attempts
107                FROM ' . LOGIN_ATTEMPT_TABLE . '
108                WHERE attempt_time > ' . (time() - (int) $this->config['ip_login_limit_time']);
109            if ($this->config['ip_login_limit_use_forwarded'])
110            {
111                $sql .= " AND attempt_forwarded_for = '" . $this->db->sql_escape($this->user->forwarded_for) . "'";
112            }
113            else
114            {
115                $sql .= " AND attempt_ip = '" . $this->db->sql_escape($this->user->ip) . "' ";
116            }
117
118            $result = $this->db->sql_query($sql);
119            $attempts = (int) $this->db->sql_fetchfield('attempts');
120            $this->db->sql_freeresult($result);
121
122            $attempt_data = array(
123                'attempt_ip'            => $this->user->ip,
124                'attempt_browser'        => trim(substr($this->user->browser, 0, 149)),
125                'attempt_forwarded_for'    => $this->user->forwarded_for,
126                'attempt_time'            => time(),
127                'user_id'                => ($row) ? (int) $row['user_id'] : 0,
128                'username'                => $username,
129                'username_clean'        => $username_clean,
130            );
131            $sql = 'INSERT INTO ' . LOGIN_ATTEMPT_TABLE . $this->db->sql_build_array('INSERT', $attempt_data);
132            $this->db->sql_query($sql);
133        }
134        else
135        {
136            $attempts = 0;
137        }
138
139        $login_error_attempts = 'LOGIN_ERROR_ATTEMPTS';
140
141        $user_login_attempts    = (is_array($row) && $this->config['max_login_attempts'] && $row['user_login_attempts'] >= $this->config['max_login_attempts']);
142        $ip_login_attempts        = ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max']);
143
144        $show_captcha = $user_login_attempts || $ip_login_attempts;
145
146        if ($show_captcha)
147        {
148            $captcha = $this->captcha_factory->get_instance($this->config['captcha_plugin']);
149
150            // Get custom message for login error when exceeding maximum number of attempts
151            if ($captcha instanceof captcha_abstract)
152            {
153                $login_error_attempts = $captcha->get_login_error_attempts();
154            }
155        }
156
157        if (!$row)
158        {
159            if ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max'])
160            {
161                return array(
162                    'status'        => LOGIN_ERROR_ATTEMPTS,
163                    'error_msg'        => $login_error_attempts,
164                    'user_row'        => array('user_id' => ANONYMOUS),
165                );
166            }
167
168            return array(
169                'status'    => LOGIN_ERROR_USERNAME,
170                'error_msg'    => 'LOGIN_ERROR_USERNAME',
171                'user_row'    => array('user_id' => ANONYMOUS),
172            );
173        }
174
175        // If there are too many login attempts, we need to check for a confirm image
176        // Every auth module is able to define what to do by itself...
177        if ($show_captcha)
178        {
179            $captcha->init(CONFIRM_LOGIN);
180            $vc_response = $captcha->validate($row);
181            if ($vc_response)
182            {
183                return array(
184                    'status'        => LOGIN_ERROR_ATTEMPTS,
185                    'error_msg'        => $login_error_attempts,
186                    'user_row'        => $row,
187                );
188            }
189            else
190            {
191                $captcha->reset();
192            }
193
194        }
195
196        // Check password ...
197        if ($this->passwords_manager->check($password, $row['user_password'], $row))
198        {
199            // Check for old password hash...
200            if ($this->passwords_manager->convert_flag || strlen($row['user_password']) == 32)
201            {
202                $hash = $this->passwords_manager->hash($password);
203
204                // Update the password in the users table to the new format
205                $sql = 'UPDATE ' . USERS_TABLE . "
206                    SET user_password = '" . $this->db->sql_escape($hash) . "'
207                    WHERE user_id = {$row['user_id']}";
208                $this->db->sql_query($sql);
209
210                $row['user_password'] = $hash;
211            }
212
213            $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . '
214                WHERE user_id = ' . $row['user_id'];
215            $this->db->sql_query($sql);
216
217            if ($row['user_login_attempts'] != 0)
218            {
219                // Successful, reset login attempts (the user passed all stages)
220                $sql = 'UPDATE ' . USERS_TABLE . '
221                    SET user_login_attempts = 0
222                    WHERE user_id = ' . $row['user_id'];
223                $this->db->sql_query($sql);
224            }
225
226            // User inactive...
227            if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE)
228            {
229                return array(
230                    'status'        => LOGIN_ERROR_ACTIVE,
231                    'error_msg'        => 'ACTIVE_ERROR',
232                    'user_row'        => $row,
233                );
234            }
235
236            // Successful login... set user_login_attempts to zero...
237            return array(
238                'status'        => LOGIN_SUCCESS,
239                'error_msg'        => false,
240                'user_row'        => $row,
241            );
242        }
243
244        // Password incorrect - increase login attempts
245        $sql = 'UPDATE ' . USERS_TABLE . '
246            SET user_login_attempts = user_login_attempts + 1
247            WHERE user_id = ' . (int) $row['user_id'] . '
248                AND user_login_attempts < ' . LOGIN_ATTEMPTS_MAX;
249        $this->db->sql_query($sql);
250
251        // Give status about wrong password...
252        return array(
253            'status'        => ($show_captcha) ? LOGIN_ERROR_ATTEMPTS : LOGIN_ERROR_PASSWORD,
254            'error_msg'        => 'LOGIN_ERROR_PASSWORD',
255            'user_row'        => $row,
256        );
257    }
258}