Passed
Push — master ( 794f3e...22b32d )
by
unknown
12:36
created

AuthenticationService::getGroups()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 24
nc 5
nop 2
dl 0
loc 37
rs 9.2248
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A AuthenticationService::writeLogMessage() 0 10 3
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Authentication;
17
18
use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
19
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
20
use TYPO3\CMS\Core\Database\ConnectionPool;
21
use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
22
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
23
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
24
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
27
/**
28
 * Authentication services class
29
 */
30
class AuthenticationService extends AbstractAuthenticationService
31
{
32
    /**
33
     * Process the submitted credentials.
34
     * In this case hash the clear text password if it has been submitted.
35
     *
36
     * @param array $loginData Credentials that are submitted and potentially modified by other services
37
     * @param string $passwordTransmissionStrategy Keyword of how the password has been hashed or encrypted before submission
38
     * @return bool
39
     */
40
    public function processLoginData(array &$loginData, $passwordTransmissionStrategy)
41
    {
42
        $isProcessed = false;
43
        if ($passwordTransmissionStrategy === 'normal') {
44
            $loginData = array_map('trim', $loginData);
45
            $loginData['uident_text'] = $loginData['uident'];
46
            $isProcessed = true;
47
        }
48
        return $isProcessed;
49
    }
50
51
    /**
52
     * Find a user (eg. look up the user record in database when a login is sent)
53
     *
54
     * @return mixed User array or FALSE
55
     */
56
    public function getUser()
57
    {
58
        if ($this->login['status'] !== LoginType::LOGIN) {
59
            return false;
60
        }
61
        if ((string)$this->login['uident_text'] === '') {
62
            // Failed Login attempt (no password given)
63
            $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 2, 'Login-attempt from ###IP### for username \'%s\' with an empty password!', [
64
                $this->login['uname']
65
            ]);
66
            $this->logger->warning(sprintf('Login-attempt from %s, for username \'%s\' with an empty password!', $this->authInfo['REMOTE_ADDR'], $this->login['uname']));
67
            return false;
68
        }
69
70
        $user = $this->fetchUserRecord($this->login['uname']);
71
        if (!is_array($user)) {
72
            // Failed login attempt (no username found)
73
            $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 2, 'Login-attempt from ###IP###, username \'%s\' not found!!', [$this->login['uname']]);
74
            $this->logger->info('Login-attempt from username \'' . $this->login['uname'] . '\' not found!', [
75
                'REMOTE_ADDR' => $this->authInfo['REMOTE_ADDR']
76
            ]);
77
        } else {
78
            $this->logger->debug('User found', [
79
                $this->db_user['userid_column'] => $user[$this->db_user['userid_column']],
80
                $this->db_user['username_column'] => $user[$this->db_user['username_column']]
81
            ]);
82
        }
83
        return $user;
84
    }
85
86
    /**
87
     * Authenticate a user: Check submitted user credentials against stored hashed password.
88
     *
89
     * Returns one of the following status codes:
90
     *  >= 200: User authenticated successfully. No more checking is needed by other auth services.
91
     *  >= 100: User not authenticated; this service is not responsible. Other auth services will be asked.
92
     *  > 0:    User authenticated successfully. Other auth services will still be asked.
93
     *  <= 0:   Authentication failed, no more checking needed by other auth services.
94
     *
95
     * @param array $user User data
96
     * @return int Authentication status code, one of 0, 100, 200
97
     */
98
    public function authUser(array $user): int
99
    {
100
        // Early 100 "not responsible, check other services" if username or password is empty
101
        if (!isset($this->login['uident_text']) || (string)$this->login['uident_text'] === ''
102
            || !isset($this->login['uname']) || (string)$this->login['uname'] === '') {
103
            return 100;
104
        }
105
106
        if (empty($this->db_user['table'])) {
107
            throw new \RuntimeException('User database table not set', 1533159150);
108
        }
109
110
        $submittedUsername = (string)$this->login['uname'];
111
        $submittedPassword = (string)$this->login['uident_text'];
112
        $passwordHashInDatabase = $user['password'];
113
        $userDatabaseTable = $this->db_user['table'];
114
115
        $isReHashNeeded = false;
116
117
        $saltFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
118
119
        // Get a hashed password instance for the hash stored in db of this user
120
        try {
121
            $hashInstance = $saltFactory->get($passwordHashInDatabase, $this->pObj->loginType);
122
        } catch (InvalidPasswordHashException $exception) {
123
            // Could not find a responsible hash algorithm for given password. This is unusual since other
124
            // authentication services would usually be called before this one with higher priority. We thus log
125
            // the failed login but still return '100' to proceed with other services that may follow.
126
            $message = 'Login-attempt from ###IP###, username \'%s\', no suitable hash method found!';
127
            $this->writeLogMessage($message, $submittedUsername);
128
            $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 1, $message, [$submittedUsername]);
129
            $this->logger->info(sprintf($message, $submittedUsername));
130
            // Not responsible, check other services
131
            return 100;
132
        }
133
134
        // An instance of the currently configured salted password mechanism
135
        // Don't catch InvalidPasswordHashException here: Only install tool should handle those configuration failures
136
        $defaultHashInstance = $saltFactory->getDefaultHashInstance($this->pObj->loginType);
137
138
        // We found a hash class that can handle this type of hash
139
        $isValidPassword = $hashInstance->checkPassword($submittedPassword, $passwordHashInDatabase);
140
        if ($isValidPassword) {
141
            if ($hashInstance->isHashUpdateNeeded($passwordHashInDatabase)
142
                || $defaultHashInstance != $hashInstance
143
            ) {
144
                // Lax object comparison intended: Rehash if old and new salt objects are not
145
                // instances of the same class.
146
                $isReHashNeeded = true;
147
            }
148
        }
149
150
        if (!$isValidPassword) {
151
            // Failed login attempt - wrong password
152
            $this->writeLogMessage($this->pObj->loginType . ' Authentication failed - wrong password for username \'%s\'', $submittedUsername);
153
            $message = 'Login-attempt from ###IP###, username \'%s\', password not accepted!';
154
            $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 1, $message, [$submittedUsername]);
155
            $this->logger->info(sprintf($message, $submittedUsername));
156
            // Responsible, authentication failed, do NOT check other services
157
            return 0;
158
        }
159
160
        if ($isReHashNeeded) {
161
            // Given password validated but a re-hash is needed. Do so.
162
            $this->updatePasswordHashInDatabase(
163
                $userDatabaseTable,
164
                (int)$user['uid'],
165
                $defaultHashInstance->getHashedPassword($submittedPassword)
166
            );
167
        }
168
169
        // Responsible, authentication ok. Log successful login and return 'auth ok, do NOT check other services'
170
        $this->writeLogMessage($this->pObj->loginType . ' Authentication successful for username \'%s\'', $submittedUsername);
171
        return 200;
172
    }
173
174
    /**
175
     * Method updates a FE/BE user record - in this case a new password string will be set.
176
     *
177
     * @param string $table Database table of this user, usually 'be_users' or 'fe_users'
178
     * @param int $uid uid of user record that will be updated
179
     * @param string $newPassword Field values as key=>value pairs to be updated in database
180
     */
181
    protected function updatePasswordHashInDatabase(string $table, int $uid, string $newPassword): void
182
    {
183
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
184
        $connection->update(
185
            $table,
186
            ['password' => $newPassword],
187
            ['uid' => $uid]
188
        );
189
        $this->logger->notice('Automatic password update for user record in ' . $table . ' with uid ' . $uid);
190
    }
191
192
    /**
193
     * Writes log message. Destination log depends on the current system mode.
194
     *
195
     * This function accepts variable number of arguments and can format
196
     * parameters. The syntax is the same as for sprintf()
197
     *
198
     * @param string $message Message to output
199
     * @param array<int,mixed> $params
200
     */
201
    protected function writeLogMessage(string $message, ...$params): void
202
    {
203
        if (!empty($params)) {
204
            $message = vsprintf($message, $params);
205
        }
206
        if ($this->pObj->loginType === 'FE') {
207
            $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
208
            $timeTracker->setTSlogMessage($message);
209
        }
210
        $this->logger->notice($message);
211
    }
212
}
213