Passed
Push — master ( 25a926...39145a )
by
unknown
20:30
created

MfaProviderPropertyManager::updateProperties()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 9
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Core\Authentication\Mfa;
19
20
use Psr\Log\LoggerAwareInterface;
21
use Psr\Log\LoggerAwareTrait;
22
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
23
use TYPO3\CMS\Core\Context\Context;
24
use TYPO3\CMS\Core\Database\Connection;
25
use TYPO3\CMS\Core\Database\ConnectionPool;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
28
/**
29
 * Basic manager for MFA providers to access and update their
30
 * properties (information) from the mfa column in the user array.
31
 *
32
 * @internal This is an experimental TYPO3 Core API and subject to change until v11 LTS
33
 */
34
class MfaProviderPropertyManager implements LoggerAwareInterface
35
{
36
    use LoggerAwareTrait;
37
38
    protected AbstractUserAuthentication $user;
39
    protected array $mfa;
40
    protected string $providerIdentifier;
41
    protected array $providerProperties;
42
    protected const DATABASE_FIELD_NAME = 'mfa';
43
44
    public function __construct(AbstractUserAuthentication $user, string $provider)
45
    {
46
        $this->user = $user;
47
        $this->mfa = json_decode($user->user[self::DATABASE_FIELD_NAME] ?? '', true) ?? [];
48
        $this->providerIdentifier = $provider;
49
        $this->providerProperties = $this->mfa[$provider] ?? [];
50
    }
51
52
    /**
53
     * Check if a provider entry exists for the current user
54
     *
55
     * @return bool
56
     */
57
    public function hasProviderEntry(): bool
58
    {
59
        return isset($this->mfa[$this->providerIdentifier]);
60
    }
61
62
    /**
63
     * Check if a provider property exists
64
     *
65
     * @param string $key
66
     * @return bool
67
     */
68
    public function hasProperty(string $key): bool
69
    {
70
        return isset($this->providerProperties[$key]);
71
    }
72
73
    /**
74
     * Get a provider specific property value or the defined
75
     * default value if the requested property was not found.
76
     *
77
     * @param string $key
78
     * @param null $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
79
     * @return mixed|null
80
     */
81
    public function getProperty(string $key, $default = null)
82
    {
83
        return $this->providerProperties[$key] ?? $default;
84
    }
85
86
    /**
87
     * Get provider specific properties
88
     *
89
     * @return array
90
     */
91
    public function getProperties(): array
92
    {
93
        return $this->providerProperties;
94
    }
95
96
    /**
97
     * Update the provider properties or create the provider entry if not yet present
98
     *
99
     * @param array $properties
100
     * @return bool
101
     */
102
    public function updateProperties(array $properties): bool
103
    {
104
        if (!isset($properties['updated'])) {
105
            $properties['updated'] = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp');
106
        }
107
108
        $this->providerProperties = array_replace($this->providerProperties, $properties) ?? [];
109
        $this->mfa[$this->providerIdentifier] = $this->providerProperties;
110
        return $this->storeProperties();
111
    }
112
113
    /**
114
     * Create a new provider entry for the current user
115
     * Note: If a entry already exists, use updateProperties() instead.
116
     *       This can be checked with hasProviderEntry().
117
     *
118
     * @param array $properties
119
     * @return bool
120
     */
121
    public function createProviderEntry(array $properties): bool
122
    {
123
        // This is to prevent unintentional overwriting of provider entries
124
        if ($this->hasProviderEntry()) {
125
            throw new \InvalidArgumentException(
126
                'A entry for provider ' . $this->providerIdentifier . ' already exists. Use updateProperties() instead.',
127
                1612781782
128
            );
129
        }
130
131
        if (!isset($properties['created'])) {
132
            $properties['created'] = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp');
133
        }
134
135
        if (!isset($properties['updated'])) {
136
            $properties['updated'] = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp');
137
        }
138
139
        $this->providerProperties = $properties;
140
        $this->mfa[$this->providerIdentifier] = $this->providerProperties;
141
        return $this->storeProperties();
142
    }
143
144
    /**
145
     * Delete a provider entry for the current user
146
     *
147
     * @return bool
148
     * @throws \JsonException
149
     */
150
    public function deleteProviderEntry(): bool
151
    {
152
        $this->providerProperties = [];
153
        unset($this->mfa[$this->providerIdentifier]);
154
        return $this->storeProperties();
155
    }
156
157
    /**
158
     * Stores the updated properties in the user array and the database
159
     *
160
     * @return bool
161
     * @throws \JsonException
162
     */
163
    protected function storeProperties(): bool
164
    {
165
        // encode the mfa properties to store them in the database and the user array
166
        $mfa = json_encode($this->mfa, JSON_THROW_ON_ERROR) ?: '';
167
168
        // Write back the updated mfa properties to the user array
169
        $this->user->user[self::DATABASE_FIELD_NAME] = $mfa;
170
171
        // Log MFA update
172
        $this->logger->debug('MFA properties updated', [
173
            'provider' => $this->providerIdentifier,
174
            'user' => [
175
                'uid' => $this->user->user[$this->user->userid_column],
176
                'username' => $this->user->user[$this->user->username_column]
177
            ]
178
        ]);
179
180
        // Store updated mfa properties in the database
181
        return (bool)GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user->user_table)->update(
182
            $this->user->user_table,
183
            [self::DATABASE_FIELD_NAME => $mfa],
184
            [$this->user->userid_column => (int)$this->user->user[$this->user->userid_column]],
185
            [self::DATABASE_FIELD_NAME => Connection::PARAM_LOB]
186
        );
187
    }
188
189
    /**
190
     * Return the current user
191
     *
192
     * @return AbstractUserAuthentication
193
     */
194
    public function getUser(): AbstractUserAuthentication
195
    {
196
        return $this->user;
197
    }
198
199
    /**
200
     * Return the current providers identifier
201
     *
202
     * @return string
203
     */
204
    public function getIdentifier(): string
205
    {
206
        return $this->providerIdentifier;
207
    }
208
209
    /**
210
     * Create property manager for the user with the given provider
211
     *
212
     * @param MfaProviderManifestInterface $provider
213
     * @param AbstractUserAuthentication $user
214
     * @return MfaProviderPropertyManager
215
     */
216
    public static function create(MfaProviderManifestInterface $provider, AbstractUserAuthentication $user): self
217
    {
218
        return GeneralUtility::makeInstance(self::class, $user, $provider->getIdentifier());
219
    }
220
}
221