Passed
Push — develop ( 0f73df...04cd86 )
by Nikolay
04:35
created

SessionController::checkCredentials()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 26
rs 9.7998
cc 4
nc 4
nop 2
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\AdminCabinet\Controllers;
21
22
use ErrorException;
23
use MikoPBX\AdminCabinet\Forms\LoginForm;
24
use MikoPBX\Common\Models\AuthTokens;
25
use MikoPBX\Common\Models\PbxSettings;
26
use MikoPBX\Common\Models\PbxSettingsConstants;
27
use MikoPBX\Common\Providers\AclProvider;
28
use MikoPBX\Common\Providers\PBXConfModulesProvider;
29
use MikoPBX\Modules\Config\WebUIConfigInterface;
30
31
/**
32
 * SessionController
33
 *
34
 * Allows to authenticate users
35
 */
36
class SessionController extends BaseController
37
{
38
    public const SESSION_ID = 'authAdminCabinet';
39
40
    public const ROLE = 'role';
41
42
    public const HOME_PAGE = 'homePage';
43
44
    public const USER_NAME = 'userName';
45
46
47
    /**
48
     * Renders the login page with form and settings values.
49
     */
50
    public function indexAction(): void
51
    {
52
        $this->view->NameFromSettings
53
            = PbxSettings::getValueByKey(PbxSettingsConstants::PBX_NAME);
54
        $this->view->DescriptionFromSettings
55
            = PbxSettings::getValueByKey(PbxSettingsConstants::PBX_DESCRIPTION);
56
        if ($this->view->DescriptionFromSettings==='auth_DefaultCloudPasswordInstructions'){
57
            $this->view->DescriptionFromSettings=$this->translation->_($this->view->DescriptionFromSettings);
58
        }
59
        $this->view->form = new LoginForm();
60
    }
61
62
    /**
63
     * Handles the login form submission and authentication.
64
     */
65
    public function startAction(): void
66
    {
67
        if (!$this->request->isPost()) {
68
            $this->forward('session/index');
69
        }
70
        $loginFromUser = (string)$this->request->getPost('login', null, 'guest');
71
        $passFromUser = (string)$this->request->getPost('password', null, 'guest');
72
        $this->flash->clear();
73
        $userLoggedIn = false;
74
        $sessionParams = [];
75
        // Check if the provided login and password match the stored values
76
        if ($this->checkCredentials($loginFromUser, $passFromUser))
77
            {
78
            $sessionParams = [
79
                SessionController::ROLE => AclProvider::ROLE_ADMINS,
80
                SessionController::HOME_PAGE => $this->url->get('extensions/index'),
81
                SessionController::USER_NAME => $loginFromUser
82
            ];
83
            $userLoggedIn = true;
84
        } else {
85
            // Try to authenticate user over additional module
86
            $additionalModules = PBXConfModulesProvider::hookModulesMethod(WebUIConfigInterface::AUTHENTICATE_USER, [$loginFromUser, $passFromUser]);
87
88
            // Check if any additional module successfully authenticated the user
89
            foreach ($additionalModules as $moduleUniqueId => $sessionData) {
90
                if (!empty($sessionData)) {
91
                    $this->loggerAuth->info("User $loginFromUser was authenticated over module $moduleUniqueId");
92
                    $sessionParams = $sessionData;
93
                    $userLoggedIn = true;
94
                    break;
95
                }
96
            }
97
        }
98
99
        if ($userLoggedIn) {
100
            // Register the session with the specified parameters
101
            $this->_registerSession($sessionParams);
102
            if ($this->session->has(PbxSettingsConstants::WEB_ADMIN_LANGUAGE)){
103
                LanguageController::updateSystemLanguage($this->session->get(PbxSettingsConstants::WEB_ADMIN_LANGUAGE));
104
            }
105
            $this->view->success = true;
106
            $backUri = $this->request->getPost('backUri');
107
            if (!empty($backUri)) {
108
                $this->view->reload = $backUri;
109
            } else {
110
                $this->view->reload = $this->session->get(SessionController::HOME_PAGE);
111
            }
112
        } else {
113
            // Authentication failed
114
            $this->view->success = false;
115
            $this->flash->error($this->translation->_('auth_WrongLoginPassword'));
116
            $remoteAddress = $this->request->getClientAddress(true);
117
            $userAgent = $this->request->getUserAgent();
118
            $this->loggerAuth->warning("From: {$remoteAddress} UserAgent:{$userAgent} Cause: Wrong password");
119
            $this->clearAuthCookies();
120
        }
121
122
    }
123
124
    /**
125
     * Register an authenticated user into session data
126
     *
127
     */
128
    private function _registerSession(array $sessionParams): void
129
    {
130
        $this->session->set(self::SESSION_ID, $sessionParams);
131
132
        if ($this->request->getPost('rememberMeCheckBox') === 'on') {
133
            $this->updateRememberMeCookies($sessionParams);
134
        } else {
135
            $this->clearAuthCookies();
136
        }
137
    }
138
139
    /**
140
     * Setups random password and selector to browser cookie storage to remember me facility
141
     *
142
     * @param array $sessionParams
143
     */
144
    private function updateRememberMeCookies(array $sessionParams): void
145
    {
146
        $cookieExpirationTime = time() + (30 * 24 * 60 * 60);  // for 1 month
147
148
        $randomPassword = $this->security->getSaltBytes(32);
149
        $this->cookies->set("random_token", $randomPassword, $cookieExpirationTime);
150
151
        $randomPasswordHash = $this->security->hash($randomPassword);
152
153
        $expiryDate = date("Y-m-d H:i:s", $cookieExpirationTime);
154
155
        // Get token for username
156
        $parameters = [
157
            'conditions' => 'tokenHash = :tokenHash:',
158
            'binds' => [
159
                'tokenHash' => $randomPasswordHash,
160
            ],
161
        ];
162
        $userToken = AuthTokens::findFirst($parameters);
163
        if ($userToken === null) {
164
            $userToken = new AuthTokens();
165
        }
166
        // Insert new token
167
        $userToken->tokenHash = $randomPasswordHash;
168
        $userToken->expiryDate = $expiryDate;
169
        $userToken->sessionParams = json_encode($sessionParams);
170
        $userToken->save();
171
    }
172
173
    /**
174
     * Clears remember me cookies
175
     */
176
    private function clearAuthCookies(): void
177
    {
178
        if ($this->cookies->has('random_token')) {
179
            $cookie = $this->cookies->get('random_token');
180
            $value = $cookie->getValue();
181
            $userTokens = AuthTokens::find();
182
            foreach ($userTokens as $userToken) {
183
                if ($this->security->checkHash($value, $userToken->tokenHash)) {
184
                    $userToken->delete();
185
                }
186
            }
187
            $cookie->delete();
188
        }
189
    }
190
191
    /**
192
     * Finishes the active session redirecting to the index
193
     *
194
     */
195
    public function endAction(): void
196
    {
197
        $this->session->remove(self::SESSION_ID);
198
        $this->session->destroy();
199
        $this->clearAuthCookies();
200
    }
201
202
    /**
203
     * Checks if the provided login and password match the stored values
204
     * @param string $login Login name from user input.
205
     * @param string $password Password from user input.
206
     * @return bool
207
     * @throws ErrorException
208
     */
209
    private function checkCredentials(string $login, string $password):bool
210
    {
211
        // Check admin login name
212
        $storedLogin = PbxSettings::getValueByKey(PbxSettingsConstants::WEB_ADMIN_LOGIN);
213
        if ($storedLogin !== $login) {
214
            return false;
215
        }
216
217
        // Old password check method
218
        $passwordHash = PbxSettings::getValueByKey(PbxSettingsConstants::WEB_ADMIN_PASSWORD);
219
        if ($passwordHash === $password) {
220
            return true;
221
        }
222
223
        // New password check method
224
        set_error_handler(function($severity, $message, $file, $line) {
225
            throw new ErrorException($message, 0, $severity, $file, $line);
226
        });
227
228
        try {
229
            $result = $this->security->checkHash($password, $passwordHash);
230
        } catch (ErrorException $e) {
231
            $result = false;
232
        }
233
        restore_error_handler();
234
        return $result;
235
    }
236
}
237