FrontendUserService   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 167
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 19
eloc 61
c 2
b 0
f 0
dl 0
loc 167
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A injectSettingsService() 0 3 1
A getMustChangePasswordReason() 0 3 1
A mustChangePassword() 0 20 5
A validateChangeHmac() 0 3 2
A isUserLoggedIn() 0 3 1
A getPasswordHash() 0 13 2
A getChangeHmac() 0 13 4
A getFrontendUser() 0 3 1
A updatePassword() 0 35 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Extension "fe_change_pwd" for TYPO3 CMS.
7
 *
8
 * For the full copyright and license information, please read the
9
 * LICENSE.txt file that was distributed with this source code.
10
 */
11
12
namespace Derhansen\FeChangePwd\Service;
13
14
use Derhansen\FeChangePwd\Exception\InvalidUserException;
15
use Derhansen\FeChangePwd\Exception\MissingPasswordHashServiceException;
16
use TYPO3\CMS\Core\Context\Context;
17
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
18
use TYPO3\CMS\Core\Database\ConnectionPool;
19
use TYPO3\CMS\Core\Session\SessionManager;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
22
23
/**
24
 * Class FrontendUserService
25
 */
26
class FrontendUserService
27
{
28
    /**
29
     * The session key
30
     */
31
    const SESSION_KEY = 'mustChangePasswordReason';
32
33
    protected SettingsService $settingsService;
34
35
    public function injectSettingsService(SettingsService $settingsService): void
36
    {
37
        $this->settingsService = $settingsService;
38
    }
39
40
    /**
41
     * Returns if the frontend user must change the password
42
     *
43
     * @param array $feUserRecord
44
     * @return bool
45
     */
46
    public function mustChangePassword(array $feUserRecord): bool
47
    {
48
        $reason = '';
49
        $result = false;
50
        $mustChangePassword = $feUserRecord['must_change_password'] ?? 0;
51
        $passwordExpiryTimestamp = $feUserRecord['password_expiry_date'] ?? 0;
52
        if ((bool)$mustChangePassword) {
53
            $reason = 'forcedChange';
54
            $result = true;
55
        } elseif (((int)$passwordExpiryTimestamp > 0 && (int)$passwordExpiryTimestamp < time())) {
56
            $reason = 'passwordExpired';
57
            $result = true;
58
        }
59
60
        if ($result) {
61
            // Store reason for password change in user session
62
            $this->getFrontendUser()->setKey('ses', self::SESSION_KEY, $reason);
63
            $this->getFrontendUser()->storeSessionData();
64
        }
65
        return $result;
66
    }
67
68
    /**
69
     * Returns the reason for the password change stored in the session
70
     *
71
     * @return mixed
72
     */
73
    public function getMustChangePasswordReason()
74
    {
75
        return $this->getFrontendUser()->getKey('ses', self::SESSION_KEY);
76
    }
77
78
    /**
79
     * Updates the password of the current user if a current user session exist
80
     *
81
     * @param string $newPassword
82
     */
83
    public function updatePassword(string $newPassword): void
84
    {
85
        if (!$this->isUserLoggedIn()) {
86
            return;
87
        }
88
89
        $password = $this->getPasswordHash($newPassword);
90
91
        $userTable = $this->getFrontendUser()->user_table;
92
        $userUid = $this->getFrontendUser()->user['uid'];
93
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
94
        $queryBuilder->getRestrictions()->removeAll();
95
        $queryBuilder->update($userTable)
96
            ->set('password', $password)
97
            ->set('must_change_password', 0)
98
            ->set('password_expiry_date', $this->settingsService->getPasswordExpiryTimestamp())
99
            ->set('tstamp', (int)$GLOBALS['EXEC_TIME'])
100
            ->where(
101
                $queryBuilder->expr()->eq(
102
                    'uid',
103
                    $queryBuilder->createNamedParameter($userUid, \PDO::PARAM_INT)
104
                )
105
            )
106
            ->execute();
107
108
        // Unset reason for password change in user session
109
        $this->getFrontendUser()->setKey('ses', self::SESSION_KEY, null);
110
111
        // Destroy all sessions of the user except the current one
112
        $sessionManager = GeneralUtility::makeInstance(SessionManager::class);
113
        $sessionBackend = $sessionManager->getSessionBackend('FE');
114
        $sessionManager->invalidateAllSessionsByUserId(
115
            $sessionBackend,
116
            (int)$this->getFrontendUser()->user['uid'],
117
            $this->getFrontendUser()
118
        );
119
    }
120
121
    /**
122
     * Returns the changeHmac for the current logged in user
123
     *
124
     * @return string
125
     */
126
    public function getChangeHmac(): string
127
    {
128
        if (!$this->isUserLoggedIn()) {
129
            return '';
130
        }
131
132
        $userUid = $this->getFrontendUser()->user['uid'];
133
        if (!is_int($userUid) || (int)$userUid <= 0) {
134
            throw new InvalidUserException('The fe_user uid is not a positive number.', 1574102778917);
135
        }
136
137
        $tstamp = $this->getFrontendUser()->user['tstamp'];
138
        return GeneralUtility::hmac('fe_user_' . $userUid . '_' . $tstamp, 'fe_change_pwd');
139
    }
140
141
    /**
142
     * Validates the given changeHmac
143
     *
144
     * @param string $changeHmac
145
     * @return bool
146
     */
147
    public function validateChangeHmac(string $changeHmac): bool
148
    {
149
        return $changeHmac !== '' && hash_equals($this->getChangeHmac(), $changeHmac);
150
    }
151
152
    /**
153
     * Returns a password hash
154
     *
155
     * @param string $password
156
     * @return string
157
     * @throws MissingPasswordHashServiceException
158
     * @throws \TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException
159
     */
160
    protected function getPasswordHash(string $password): string
161
    {
162
        if (class_exists(PasswordHashFactory::class)) {
163
            $hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('FE');
164
            $password = $hashInstance->getHashedPassword($password);
165
        } else {
166
            throw new MissingPasswordHashServiceException(
167
                'No secure password hashing service could be initialized. Please check your TYPO3 system configuration',
168
                1557550040
169
            );
170
        }
171
172
        return $password;
173
    }
174
175
    /**
176
     * Returns is there is a current user login
177
     *
178
     * @return bool
179
     */
180
    public function isUserLoggedIn(): bool
181
    {
182
        return GeneralUtility::makeInstance(Context::class)->getAspect('frontend.user')->isLoggedIn();
183
    }
184
185
    /**
186
     * Returns the frontendUserAuthentication
187
     *
188
     * @return \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication
189
     */
190
    protected function getFrontendUser(): FrontendUserAuthentication
191
    {
192
        return $GLOBALS['TSFE']->fe_user;
193
    }
194
}
195