CookieLoginMiddleware   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 112
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 44
dl 0
loc 112
ccs 49
cts 49
cp 1
rs 10
c 1
b 0
f 0
wmc 19

3 Methods

Rating   Name   Duplication   Size   Complexity  
B process() 0 23 8
A __construct() 0 7 1
B authenticateUserByCookieFromRequest() 0 54 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\User\Login\Cookie;
6
7
use JsonException;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Server\MiddlewareInterface;
11
use Psr\Http\Server\RequestHandlerInterface;
12
use Psr\Log\LoggerInterface;
13
use RuntimeException;
14
use Throwable;
15
use Yiisoft\Auth\IdentityRepositoryInterface;
16
use Yiisoft\User\CurrentUser;
17
18
use function array_key_exists;
19
use function count;
20
use function is_array;
21
use function json_decode;
22
use function sprintf;
23
use function time;
24
25
/**
26
 * `CookieLoginMiddleware` automatically logs user in based on cookie.
27
 */
28
final class CookieLoginMiddleware implements MiddlewareInterface
29
{
30
    /**
31
     * @param CurrentUser $currentUser The current user instance.
32
     * @param IdentityRepositoryInterface $identityRepository The identity repository instance.
33
     * @param LoggerInterface $logger The logger instance.
34
     * @param CookieLogin $cookieLogin The cookie login instance.
35
     * @param bool $forceAddCookie Whether to force add a cookie.
36
     */
37 19
    public function __construct(
38
        private CurrentUser $currentUser,
39
        private IdentityRepositoryInterface $identityRepository,
40
        private LoggerInterface $logger,
41
        private CookieLogin $cookieLogin,
42
        private bool $forceAddCookie = false
43
    ) {
44 19
    }
45
46
    /**
47
     * {@inheritDoc}
48
     *
49
     * @throws JsonException If an error occurs when JSON encoding the cookie value while adding the cookie file.
50
     * @throws RuntimeException If during authentication, the identity repository {@see IdentityRepositoryInterface}
51
     * does not return an instance of {@see CookieLoginIdentityInterface}.
52
     */
53 17
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
54
    {
55 17
        if ($this->currentUser->isGuest()) {
56 17
            $this->authenticateUserByCookieFromRequest($request);
57
        }
58
59 16
        $guestBeforeHandle = $this->currentUser->isGuest();
60 16
        $response = $handler->handle($request);
61 16
        $guestAfterHandle = $this->currentUser->isGuest();
62
63 16
        if ($this->forceAddCookie && $guestBeforeHandle && !$guestAfterHandle) {
64 1
            $identity = $this->currentUser->getIdentity();
65
66 1
            if ($identity instanceof CookieLoginIdentityInterface) {
67 1
                $response = $this->cookieLogin->addCookie($identity, $response);
68
            }
69
        }
70
71 16
        if (!$guestBeforeHandle && $guestAfterHandle) {
72 1
            $response = $this->cookieLogin->expireCookie($response);
73
        }
74
75 16
        return $response;
76
    }
77
78
    /**
79
     * Authenticate user by auto-login cookie from request.
80
     *
81
     * @param ServerRequestInterface $request Request instance containing auto-login cookie.
82
     *
83
     * @throws RuntimeException If the identity repository {@see IdentityRepositoryInterface}
84
     * does not return an instance of {@see CookieLoginIdentityInterface}.
85
     */
86 17
    private function authenticateUserByCookieFromRequest(ServerRequestInterface $request): void
87
    {
88 17
        $cookieName = $this->cookieLogin->getCookieName();
89 17
        $cookies = $request->getCookieParams();
90
91 17
        if (!array_key_exists($cookieName, $cookies)) {
92 3
            return;
93
        }
94
95
        try {
96 14
            $data = json_decode((string) $cookies[$cookieName], true, 512, JSON_THROW_ON_ERROR);
97 1
        } catch (Throwable) {
98 1
            $this->logger->warning('Unable to authenticate user by cookie. Invalid cookie.');
99 1
            return;
100
        }
101
102 13
        if (!is_array($data) || count($data) !== 3) {
103 1
            $this->logger->warning('Unable to authenticate user by cookie. Invalid cookie.');
104 1
            return;
105
        }
106
107 12
        [$id, $key, $expires] = $data;
108
109 12
        $id = (string) $id;
110 12
        $key = (string) $key;
111 12
        $expires = (int) $expires;
112
113 12
        $identity = $this->identityRepository->findIdentity($id);
114
115 12
        if ($identity === null) {
116 1
            $this->logger->warning("Unable to authenticate user by cookie. Identity \"$id\" not found.");
117 1
            return;
118
        }
119
120 11
        if (!$identity instanceof CookieLoginIdentityInterface) {
121 1
            throw new RuntimeException(
122 1
                sprintf(
123 1
                    'Identity repository must return an instance of %s in order for auto-login to function.',
124 1
                    CookieLoginIdentityInterface::class,
125 1
                )
126 1
            );
127
        }
128
129 10
        if (!$identity->validateCookieLoginKey($key)) {
130 1
            $this->logger->warning('Unable to authenticate user by cookie. Invalid key.');
131 1
            return;
132
        }
133
134 9
        if ($expires !== 0 && $expires < time()) {
135 1
            $this->logger->warning('Unable to authenticate user by cookie. Lifetime has expired.');
136 1
            return;
137
        }
138
139 8
        $this->currentUser->login($identity);
140
    }
141
}
142