CookieHelper::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 8
rs 10
c 1
b 0
f 0
1
<?php declare(strict_types=1);
2
3
/**
4
 * Copyright 2022 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupGateway\GatewayBundle\Sso2fa\Http;
20
21
use Psr\Log\LoggerInterface;
22
use Surfnet\StepupGateway\GatewayBundle\Sso2fa\Crypto\CryptoHelperInterface;
23
use Surfnet\StepupGateway\GatewayBundle\Sso2fa\Exception\CookieNotFoundException;
24
use Surfnet\StepupGateway\GatewayBundle\Sso2fa\ValueObject\Configuration;
25
use Surfnet\StepupGateway\GatewayBundle\Sso2fa\ValueObject\CookieValueInterface;
26
use Symfony\Component\HttpFoundation\Cookie;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\HttpFoundation\Response;
29
30
class CookieHelper implements CookieHelperInterface
31
{
32
    /**
33
     * By default, we set the cookie with the SameSite: NONE attribute.
34
     *
35
     * SameSite: NONE ensures the browser sends the cookie on cross domain requests. Which are typically performed
36
     * when doing SAML authentications. Using STRICT or LAX will cause the cookie not being sent in several scenarios.
37
     */
38
    private const SAME_SITE = Cookie::SAMESITE_NONE;
39
40
    /**
41
     * @var Configuration
42
     */
43
    private $configuration;
44
45
    /**
46
     * @var CryptoHelperInterface
47
     */
48
    private $encryptionHelper;
49
50
    /**
51
     * @var LoggerInterface
52
     */
53
    private $logger;
54
55
    public function __construct(
56
        Configuration $configuration,
57
        CryptoHelperInterface $encryptionHelper,
58
        LoggerInterface $logger
59
    ) {
60
        $this->configuration = $configuration;
61
        $this->encryptionHelper = $encryptionHelper;
62
        $this->logger = $logger;
63
    }
64
65
    public function write(Response $response, CookieValueInterface $value): void
66
    {
67
        // The CookieValue is encrypted
68
        $encryptedCookieValue = $this->encryptionHelper->encrypt($value);
69
        $fingerprint = $this->hashFingerprint($encryptedCookieValue);
70
        $this->logger->notice(sprintf('Writing a SSO on 2FA cookie with fingerprint %s', $fingerprint));
71
        // Create a Symfony HttpFoundation cookie object
72
        $cookie = $this->createCookieWithValue($encryptedCookieValue);
73
        // Which is added to the response headers
74
        $response->headers->setCookie($cookie);
75
    }
76
77
    /**
78
     * Retrieve the current cookie from the Request if it exists.
79
     */
80
    public function read(Request $request): CookieValueInterface
81
    {
82
        if (!$request->cookies || !$request->cookies->has($this->configuration->getName())) {
83
            throw new CookieNotFoundException();
84
        }
85
        $cookie = $request->cookies->get($this->configuration->getName());
86
        $fingerprint = $this->hashFingerprint($cookie);
87
        $this->logger->notice(sprintf('Reading a SSO on 2FA cookie with fingerprint %s', $fingerprint));
88
        return $this->encryptionHelper->decrypt($cookie);
89
    }
90
91
    public function fingerprint(Request $request): string
92
    {
93
        if (!$request->cookies || !$request->cookies->has($this->configuration->getName())) {
94
            throw new CookieNotFoundException();
95
        }
96
        $cookie = $request->cookies->get($this->configuration->getName());
97
        return $this->hashFingerprint($cookie);
98
    }
99
100
    private function createCookieWithValue($value): Cookie
101
    {
102
        return new Cookie(
103
            $this->configuration->getName(),
104
            $value,
105
            $this->configuration->isPersistent() ? $this->getTimestamp($this->configuration->getLifetime()): 0,
106
            '/',
107
            null,
108
            true,
109
            true,
110
            false,
111
            self::SAME_SITE
112
        );
113
    }
114
115
    private function hashFingerprint(string $encryptedCookieValue): string
116
    {
117
        return hash('sha256', $encryptedCookieValue);
118
    }
119
120
    private function getTimestamp(int $expiresInSeconds): int
121
    {
122
        $currentTimestamp = time();
123
        return $currentTimestamp + $expiresInSeconds;
124
    }
125
}
126