Test Failed
Pull Request — master (#24)
by Sergei
02:04
created

SessionCurrentIdentity   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 85
dl 0
loc 267
rs 9.1199
c 0
b 0
f 0
wmc 41

14 Methods

Rating   Name   Duplication   Size   Complexity  
A beforeLogout() 0 5 1
A beforeSave() 0 5 1
A set() 0 3 1
D renewAuthStatus() 0 41 16
A setAuthTimeout() 0 4 1
A switchIdentity() 0 21 5
A clear() 0 7 3
A afterSave() 0 3 1
A isGuest() 0 3 1
A get() 0 17 5
A save() 0 9 3
A afterLogout() 0 3 1
A setAbsoluteAuthTimeout() 0 4 1
A __construct() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like SessionCurrentIdentity often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SessionCurrentIdentity, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\User\CurrentIdentity;
6
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use RuntimeException;
9
use Throwable;
10
use Yiisoft\Auth\IdentityInterface;
11
use Yiisoft\Auth\IdentityRepositoryInterface;
12
use Yiisoft\Session\SessionInterface;
13
use Yiisoft\User\Event\AfterLogin;
14
use Yiisoft\User\Event\AfterLogout;
15
use Yiisoft\User\Event\BeforeLogin;
16
use Yiisoft\User\Event\BeforeLogout;
17
use Yiisoft\User\GuestIdentity;
18
19
final class SessionCurrentIdentity implements CurrentIdentityInterface
20
{
21
    private const SESSION_AUTH_ID = '__auth_id';
22
    private const SESSION_AUTH_EXPIRE = '__auth_expire';
23
    private const SESSION_AUTH_ABSOLUTE_EXPIRE = '__auth_absolute_expire';
24
25
    private ?IdentityInterface $identity = null;
26
27
    /**
28
     * @var int|null the number of seconds in which the user will be logged out automatically in case of
29
     * remaining inactive. If this property is not set, the user will be logged out after
30
     * the current session expires.
31
     */
32
    private ?int $authTimeout = null;
33
34
    /**
35
     * @var int|null the number of seconds in which the user will be logged out automatically
36
     * regardless of activity.
37
     */
38
    private ?int $absoluteAuthTimeout = null;
39
40
    private IdentityRepositoryInterface $identityRepository;
41
    private EventDispatcherInterface $eventDispatcher;
42
    private ?SessionInterface $session;
43
44
    /**
45
     * @param SessionInterface|null $session session to persist authentication status across multiple requests.
46
     * If not set, authentication has to be performed on each request, which is often the case for stateless
47
     * application such as RESTful API.
48
     */
49
    public function __construct(
50
        IdentityRepositoryInterface $identityRepository,
51
        EventDispatcherInterface $eventDispatcher,
52
        SessionInterface $session = null
53
    ) {
54
        $this->identityRepository = $identityRepository;
55
        $this->eventDispatcher = $eventDispatcher;
56
        $this->session = $session;
57
    }
58
59
    public function setAuthTimeout(int $timeout = null): self
60
    {
61
        $this->authTimeout = $timeout;
62
        return $this;
63
    }
64
65
    public function setAbsoluteAuthTimeout(int $timeout = null): self
66
    {
67
        $this->absoluteAuthTimeout = $timeout;
68
        return $this;
69
    }
70
71
    /**
72
     * Returns the identity object associated with the currently logged-in user.
73
     * This method read the user's authentication data
74
     * stored in session and reconstruct the corresponding identity object, if it has not done so before.
75
     *
76
     * @param bool $autoRenew whether to automatically renew authentication status if it has not been done so before.
77
     *
78
     * @throws Throwable
79
     *
80
     * @return IdentityInterface the identity object associated with the currently logged-in user.
81
     *
82
     * @see logout()
83
     * @see login()
84
     */
85
    public function get(bool $autoRenew = true): IdentityInterface
86
    {
87
        if ($this->identity !== null) {
88
            return $this->identity;
89
        }
90
        if ($this->session === null || !$autoRenew) {
91
            return new GuestIdentity();
92
        }
93
        try {
94
            $this->renewAuthStatus();
95
        } catch (Throwable $e) {
96
            $this->identity = null;
97
            throw $e;
98
        }
99
100
        /** @psalm-suppress TypeDoesNotContainType */
101
        return $this->identity ?? new GuestIdentity();
102
    }
103
104
    public function set(IdentityInterface $identity): void
105
    {
106
        $this->identity = $identity;
107
    }
108
109
    /**
110
     * Switches to a new identity for the current user.
111
     *
112
     * This method use session to store the user identity information.
113
     * Please refer to {@see login()} for more details.
114
     *
115
     * This method is mainly called by {@see login()} and {@see logout()}
116
     * when the current user needs to be associated with the corresponding identity information.
117
     *
118
     * @param IdentityInterface $identity the identity information to be associated with the current user.
119
     * In order to indicate that the user is guest, use {{@see GuestIdentity}}.
120
     */
121
    private function switchIdentity(IdentityInterface $identity): void
122
    {
123
        $this->set($identity);
124
        if ($this->session === null) {
125
            return;
126
        }
127
128
        $this->session->regenerateID();
129
130
        $this->session->remove(self::SESSION_AUTH_ID);
131
        $this->session->remove(self::SESSION_AUTH_EXPIRE);
132
133
        if ($identity->getId() === null) {
134
            return;
135
        }
136
        $this->session->set(self::SESSION_AUTH_ID, $identity->getId());
137
        if ($this->authTimeout !== null) {
138
            $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
139
        }
140
        if ($this->absoluteAuthTimeout !== null) {
141
            $this->session->set(self::SESSION_AUTH_ABSOLUTE_EXPIRE, time() + $this->absoluteAuthTimeout);
142
        }
143
    }
144
145
    /**
146
     * Returns a value indicating whether the user is a guest (not authenticated).
147
     *
148
     * @return bool whether the current user is a guest.
149
     *
150
     * @see get()
151
     */
152
    private function isGuest(): bool
153
    {
154
        return $this->get() instanceof GuestIdentity;
155
    }
156
157
    public function save(): void
158
    {
159
        if ($this->identity === null) {
160
            throw new RuntimeException('Need set identity.');
161
        }
162
163
        if ($this->beforeSave($this->identity)) {
164
            $this->switchIdentity($this->identity);
165
            $this->afterSave($this->identity);
166
        }
167
    }
168
169
    /**
170
     * This method is called before logging in a user.
171
     * The default implementation will trigger the {@see beforeSave} event.
172
     * If you override this method, make sure you call the parent implementation
173
     * so that the event is triggered.
174
     *
175
     * @param IdentityInterface $identity the user identity information
176
     *
177
     * @return bool whether the user should continue to be logged in
178
     */
179
    private function beforeSave(IdentityInterface $identity): bool
180
    {
181
        $event = new BeforeLogin($identity);
182
        $this->eventDispatcher->dispatch($event);
183
        return $event->isValid();
184
    }
185
186
    /**
187
     * This method is called after the user is successfully logged in.
188
     *
189
     * @param IdentityInterface $identity the user identity information
190
     */
191
    private function afterSave(IdentityInterface $identity): void
192
    {
193
        $this->eventDispatcher->dispatch(new AfterLogin($identity));
194
    }
195
196
    /**
197
     * Logs out the current user.
198
     * This will remove authentication-related session data.
199
     * If `$destroySession` is true, all session data will be removed.
200
     */
201
    public function clear(): void
202
    {
203
        if (!$this->isGuest()) {
204
            $identity = $this->get();
205
            if ($this->beforeLogout($identity)) {
206
                $this->switchIdentity(new GuestIdentity());
207
                $this->afterLogout($identity);
208
            }
209
        }
210
    }
211
212
    /**
213
     * This method is invoked when calling {@see logout()} to log out a user.
214
     *
215
     * @param IdentityInterface $identity the user identity information
216
     *
217
     * @return bool whether the user should continue to be logged out
218
     */
219
    private function beforeLogout(IdentityInterface $identity): bool
220
    {
221
        $event = new BeforeLogout($identity);
222
        $this->eventDispatcher->dispatch($event);
223
        return $event->isValid();
224
    }
225
226
    /**
227
     * This method is invoked right after a user is logged out via {@see logout()}.
228
     *
229
     * @param IdentityInterface $identity the user identity information
230
     */
231
    private function afterLogout(IdentityInterface $identity): void
232
    {
233
        $this->eventDispatcher->dispatch(new AfterLogout($identity));
234
    }
235
236
    /**
237
     * Updates the authentication status using the information from session.
238
     *
239
     * This method will try to determine the user identity using a session variable.
240
     *
241
     * If {@see authTimeout} is set, this method will refresh the timer.
242
     *
243
     * @throws Throwable
244
     */
245
    private function renewAuthStatus(): void
246
    {
247
        if ($this->session === null) {
248
            $this->set(new GuestIdentity());
249
            return;
250
        }
251
252
        /** @var mixed $id */
253
        $id = $this->session->get(self::SESSION_AUTH_ID);
254
255
        $identity = null;
256
        if ($id !== null) {
257
            $identity = $this->identityRepository->findIdentity((string)$id);
258
        }
259
        if ($identity === null) {
260
            $identity = new GuestIdentity();
261
        }
262
        $this->set($identity);
263
264
        if (
265
            !($identity instanceof GuestIdentity) &&
266
            ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)
267
        ) {
268
            /** @var mixed $expire */
269
            $expire = $this->authTimeout !== null ? $this->session->get(self::SESSION_AUTH_EXPIRE) : null;
270
            if ($expire !== null) {
271
                $expire = (int)$expire;
272
            }
273
274
            /** @var mixed $expireAbsolute */
275
            $expireAbsolute = $this->absoluteAuthTimeout !== null
276
                ? $this->session->get(self::SESSION_AUTH_ABSOLUTE_EXPIRE)
277
                : null;
278
            if ($expireAbsolute !== null) {
279
                $expireAbsolute = (int)$expire;
280
            }
281
282
            if (($expire !== null && $expire < time()) || ($expireAbsolute !== null && $expireAbsolute < time())) {
283
                $this->clear();
284
            } elseif ($this->authTimeout !== null) {
285
                $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
286
            }
287
        }
288
    }
289
}
290