userAuthentication.php 9.21 KB
Newer Older
Prosper De Laure's avatar
Prosper De Laure committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php
/*
 * Copyright (C) 2015 Maarch
 *
 * This file is part of bundle user.
 *
 * Bundle user is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Bundle user is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with bundle user.  If not, see <http://www.gnu.org/licenses/>.
 */
namespace bundle\auth\Controller;

/**
 * user authentication controller
 *
 * @package Auth
 * @author  Cyril VAZQUEZ <cyril.vazquez@maarch.org>
 */
class userAuthentication
{
    /**
     * The password encryption
     *
     * @var string
     **/
    protected $passwordEncryption;

    /**
     * The security policy of the password
     *
     * @var string
     **/
    protected $securityPolicy;

    /**
     * Constructor
     * @param object $sdoFactory         The user model
     * @param string $passwordEncryption The password encryption
     * @param array  $securityPolicy     The security policy
     */
    public function __construct(\dependency\sdo\Factory $sdoFactory, $passwordEncryption, $securityPolicy)
    {
        $this->sdoFactory = $sdoFactory;
        $this->passwordEncryption = $passwordEncryption;
        $this->securityPolicy = $securityPolicy;
    }

    /**
58
     * Authenticate a user
Prosper De Laure's avatar
Prosper De Laure committed
59
60
61
     * @param string $userName The user name
     * @param string $password The user password
     *
62
63
     * @throws \bundle\auth\Exception\authenticationException
     *
64
     * @return auth/account The user account object
Prosper De Laure's avatar
Prosper De Laure committed
65
66
67
68
69
70
71
72
73
     */
    public function login($userName, $password)
    {
        // Check userAccount exists
        $currentDate = \laabs::newTimestamp();

        $exists = $this->sdoFactory->exists('auth/account', array('accountName' => $userName));

        if (!$exists) {
74
            throw \laabs::newException('auth/authenticationException', 'Username and / or password invalid', 401);
Prosper De Laure's avatar
Prosper De Laure committed
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
        }

        $userAccount = $this->sdoFactory->read('auth/account', array('accountName' => $userName));

        // Create user login object
        $userLogin = \laabs::newInstance('auth/userLogin');
        $userLogin->accountId = $userAccount->accountId;
        $userLogin->lastIp = $_SERVER["REMOTE_ADDR"];

        // Hash password
        $encryptedPassword = $password;
        if ($this->passwordEncryption != null) {
            $encryptedPassword = hash($this->passwordEncryption, $password);
        }

        // Check enabled
        if ($userAccount->enabled != true) {
92
93
94
95
96
97
98
            $e = \laabs::newException(
                'auth/authenticationException',
                'User %1$s is disabled',
                403,
                null,
                array($userName)
            );
99
            throw $e;
Prosper De Laure's avatar
Prosper De Laure committed
100
101
102
103
104
105
        }

        // Check password
        if ($userAccount->password !== $encryptedPassword) {
            // Update bad password count
            $userLogin->badPasswordCount = $userAccount->badPasswordCount + 1;
106
            $this->sdoFactory->update($userLogin, 'auth/account');
107
108

            // If count exceeds max attempts, lock user
109
110
111
112
            if ($this->securityPolicy['loginAttempts']
                && $userLogin->badPasswordCount > $this->securityPolicy['loginAttempts'] - 1
            ) {
                $userAccountController = \laabs::newController('auth/userAccount');
Cyril Vazquez's avatar
Cyril Vazquez committed
113
                $userAccountController->lock($userLogin->accountId, true);
114
115
116
117
118
119
120
121
122

                $eventController = \laabs::newController('audit/event');
                $eventController->add(
                    "auth/userAccount/updateLock_userAccountId_",
                    array("accountId" => $userLogin->accountId),
                    null,
                    true,
                    true
                );
Prosper De Laure's avatar
Prosper De Laure committed
123
124
            }

125
126
127
128
129
130
131
132
133
134
135
136
            throw \laabs::newException('auth/authenticationException', 'Username and / or password invalid', 401);
        }

        // Check locked
        if ($userAccount->locked == true) {
            if (!isset($this->securityPolicy['lockDelay']) // No delay while locked
                || $this->securityPolicy['lockDelay'] == 0 // Unlimited delay
                || !isset($userAccount->lockDate)          // Delay but no date for lock so unlimited
                || ($currentDate->getTimestamp() - $userAccount->lockDate->getTimestamp()) < ($this->securityPolicy['lockDelay']) // Date + delay upper than current date
            ) {
                throw \laabs::newException('auth/authenticationException', 'User %1$s is locked', 403, null, array($userName));
            }
137
138
        }

Prosper De Laure's avatar
Prosper De Laure committed
139
140
141
142
        // Login success, update user account values
        $userLogin->badPasswordCount = 0;
        $userLogin->locked = false;
        $userLogin->lockDate = null;
143
        $userLogin->tokenDate = null;
Prosper De Laure's avatar
Prosper De Laure committed
144
145
146
147
148
149
150
151
152
153
154
155
156
157
        $userLogin->lastLogin = $currentDate;

        $this->sdoFactory->update($userLogin, 'auth/account');

        if (isset($this->securityPolicy['sessionTimeout'])) {
            $tokenDuration = $this->securityPolicy['sessionTimeout'];
        } else {
            $tokenDuration = 86400;
        }

        $accountToken = new \StdClass();
        $accountToken->accountId = $userAccount->accountId;
        \laabs::setToken('AUTH', $accountToken, $tokenDuration);

158
        if ($this->securityPolicy['passwordValidity'] && $this->securityPolicy["passwordValidity"] != 0) {
159
            $diff = ($currentDate->getTimestamp() - $userAccount->passwordLastChange->getTimestamp()) / $tokenDuration;
160
161
162
            if ($diff > $this->securityPolicy['passwordValidity']) {
                throw \laabs::newException('auth/userPasswordChangeRequestException');
            }
Prosper De Laure's avatar
Prosper De Laure committed
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
        }

        if ($userAccount->passwordChangeRequired == true) {
            \laabs::setToken('TEMP-AUTH', $accountToken, $tokenDuration);
            \laabs::unsetToken('AUTH');
            throw \laabs::newException('auth/userPasswordChangeRequestException');
        }

        return $userAccount;
    }

    /**
     * Get form to edit user information
     * @param string $userName    The user's name
     * @param string $oldPassword The user's old password
     * @param string $newPassword The user's new password
     * @param string $requestPath The requested path
     *
181
     * @return mixed The requested path if the account exists, false if it doesn't
Prosper De Laure's avatar
Prosper De Laure committed
182
183
184
     */
    public function definePassword($userName, $oldPassword, $newPassword, $requestPath)
    {
185
186
        $tempToken = \laabs::getToken('TEMP-AUTH');

187
188
189
190
191
192
        if ($this->sdoFactory->exists('auth/account', array('accountName' => $userName))) {
            $userAccount = $this->sdoFactory->read('auth/account', array('accountName' => $userName));
        } else {
            return false;
        }

193
194
195
196
        if ($this->sdoFactory->exists('auth/account', array('accountName' => $userName))
            && !is_null($tempToken)
            && $tempToken->accountId == $userAccount->accountId) {

197
            $this->checkPasswordPolicies($newPassword);
Prosper De Laure's avatar
Prosper De Laure committed
198
199
200
201
202
203

            $encryptedPassword = $newPassword;
            if ($this->passwordEncryption != null) {
                $encryptedPassword = hash($this->passwordEncryption, $newPassword);
            }
            if ($userAccount->password == $encryptedPassword) {
204
                throw new \core\Exception\ForbiddenException("The password is the same as the precedent.", 403);
Prosper De Laure's avatar
Prosper De Laure committed
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
            }

            $userAccount->password = $encryptedPassword;
            $userAccount->passwordLastChange = \laabs::newTimestamp();
            $userAccount->passwordChangeRequired = false;
            $this->sdoFactory->update($userAccount, 'auth/account');

            $this->login($userAccount->accountName, $newPassword, $requestPath);
            \laabs::unsetToken('TEMP-AUTH');

            return $requestPath;
        }

        return false;
    }

221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    /**
     * Validate the new password
     * @param string $newPassword The user's new password
     */
    public function checkPasswordPolicies($newPassword) {
        if ($this->securityPolicy['passwordMinLength'] && strlen($newPassword) < $this->securityPolicy['passwordMinLength']) {
            throw new \core\Exception\ForbiddenException("The password is too short.", 403);
        }

        if ($this->securityPolicy['passwordRequiresSpecialChars'] && ctype_alnum($newPassword)) {
            throw new \core\Exception\ForbiddenException("The password must contain special characters.", 403);
        }

        if ($this->securityPolicy['passwordRequiresDigits'] && !preg_match('~[0-9]~', $newPassword)) {
            throw new \core\Exception\ForbiddenException("The password must contain digits.", 403);
        }

        if ($this->securityPolicy['passwordRequiresMixedCase'] && (!preg_match('~[A-Z]~', $newPassword) || !preg_match('~[a-z]~', $newPassword))) {
            throw new \core\Exception\ForbiddenException("The password must contain upper and lower case.", 403);
        }
    }

Prosper De Laure's avatar
Prosper De Laure committed
243
244
245
246
247
    /**
     * Log out a user
     */
    public function logout()
    {
248
249
        \laabs::unsetToken("AUTH");
        \laabs::unsetToken("ORGANIZATION");
Prosper De Laure's avatar
Prosper De Laure committed
250
251
    }
}