Passed
Push — master ( ff614d...6c840c )
by Anton
05:03 queued 02:24
created

SessionMiddleware::process()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
dl 0
loc 16
rs 9.9666
c 1
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
declare(strict_types=1);
9
10
namespace Spiral\Session\Middleware;
11
12
use Psr\Http\Message\ResponseInterface as Response;
13
use Psr\Http\Message\ServerRequestInterface as Request;
14
use Psr\Http\Message\UriInterface;
15
use Psr\Http\Server\MiddlewareInterface;
16
use Psr\Http\Server\RequestHandlerInterface as Handler;
17
use Spiral\Cookies\Config\CookiesConfig;
18
use Spiral\Cookies\Cookie;
19
use Spiral\Core\ScopeInterface;
20
use Spiral\Http\Config\HttpConfig;
21
use Spiral\Session\Config\SessionConfig;
22
use Spiral\Session\SessionFactory;
23
use Spiral\Session\SessionInterface;
24
25
final class SessionMiddleware implements MiddlewareInterface
26
{
27
    public const ATTRIBUTE = 'session';
28
29
    // Header set used to sign session
30
    protected const SIGNATURE_HEADERS = ['User-Agent', 'Accept-Language', 'Accept-Encoding'];
31
32
    /** @var SessionConfig */
33
    private $config;
34
35
    /** @var HttpConfig */
36
    private $httpConfig;
37
38
    /** @var CookiesConfig */
39
    private $cookiesConfig;
40
41
    /** @var SessionFactory */
42
    private $factory;
43
44
    /** @var ScopeInterface */
45
    private $scope;
46
47
    /**
48
     * @param SessionConfig  $config
49
     * @param HttpConfig     $httpConfig
50
     * @param CookiesConfig  $cookiesConfig
51
     * @param SessionFactory $factory
52
     * @param ScopeInterface $scope
53
     */
54
    public function __construct(
55
        SessionConfig $config,
56
        HttpConfig $httpConfig,
57
        CookiesConfig $cookiesConfig,
58
        SessionFactory $factory,
59
        ScopeInterface $scope
60
    ) {
61
        $this->config = $config;
62
        $this->httpConfig = $httpConfig;
63
        $this->cookiesConfig = $cookiesConfig;
64
        $this->factory = $factory;
65
        $this->scope = $scope;
66
    }
67
68
    /**
69
     * @inheritdoc
70
     */
71
    public function process(Request $request, Handler $handler): Response
72
    {
73
        //Initiating session, this can only be done once!
74
        $session = $this->factory->initSession(
75
            $this->clientSignature($request),
76
            $this->fetchID($request)
77
        );
78
79
        try {
80
            $response = $handler->handle($request->withAttribute(static::ATTRIBUTE, $session));
81
        } catch (\Throwable $e) {
82
            $session->abort();
83
            throw $e;
84
        }
85
86
        return $this->commitSession($session, $request, $response);
87
    }
88
89
    /**
90
     * @param SessionInterface $session
91
     * @param Request          $request
92
     * @param Response         $response
93
     * @return Response
94
     */
95
    protected function commitSession(
96
        SessionInterface $session,
97
        Request $request,
98
        Response $response
99
    ): Response {
100
        if (!$session->isStarted()) {
101
            return $response;
102
        }
103
104
        $session->commit();
105
106
        //SID changed
107
        if ($this->fetchID($request) != $session->getID()) {
108
            return $this->withCookie($request, $response, $session->getID());
109
        }
110
111
        //Nothing to do
112
        return $response;
113
    }
114
115
    /**
116
     * Attempt to locate session ID in request.
117
     *
118
     * @param Request $request
119
     * @return string|null
120
     */
121
    protected function fetchID(Request $request): ?string
122
    {
123
        $cookies = $request->getCookieParams();
124
        if (empty($cookies[$this->config->getCookie()])) {
125
            return null;
126
        }
127
128
        return $cookies[$this->config->getCookie()];
129
    }
130
131
    /**
132
     * @param Request  $request
133
     * @param Response $response
134
     * @param string   $id
135
     * @return Response
136
     */
137
    protected function withCookie(Request $request, Response $response, string $id = null): Response
138
    {
139
        $response = $response->withAddedHeader(
140
            'Set-Cookie',
141
            $this->sessionCookie($request->getUri(), $id)->createHeader()
142
        );
143
144
        return $response;
145
    }
146
147
    /**
148
     * Must return string which identifies client on other end. Not for security check but for
149
     * session fixation.
150
     *
151
     * @param Request $request
152
     * @return string
153
     */
154
    protected function clientSignature(Request $request): string
155
    {
156
        $signature = '';
157
        foreach (static::SIGNATURE_HEADERS as $header) {
158
            $signature .= $request->getHeaderLine($header) . ';';
159
        }
160
161
        return hash('sha256', $signature);
162
    }
163
164
    /**
165
     * Generate session cookie.
166
     *
167
     * @param UriInterface $uri Incoming uri.
168
     * @param string|null  $id
169
     * @return Cookie
170
     */
171
    private function sessionCookie(UriInterface $uri, string $id = null): Cookie
172
    {
173
        return Cookie::create(
174
            $this->config->getCookie(),
175
            $id,
176
            $this->config->getLifetime(),
177
            $this->httpConfig->getBasePath(),
178
            $this->cookiesConfig->resolveDomain($uri),
179
            $this->config->isSecure(),
180
            true
181
        );
182
    }
183
}
184