-
-
Notifications
You must be signed in to change notification settings - Fork 27
/
Security.php
185 lines (153 loc) · 8.35 KB
/
Security.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\LogicException;
use Symfony\Component\Security\Core\Exception\LogoutException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* Helper class for commonly-needed security tasks.
*
* @author Ryan Weaver <[email protected]>
* @author Robin Chalas <[email protected]>
* @author Arnaud Frézet <[email protected]>
*
* @final
*/
class Security implements AuthorizationCheckerInterface
{
public function __construct(
private readonly ContainerInterface $container,
private readonly array $authenticators = [],
) {
}
public function getUser(): ?UserInterface
{
if (!$token = $this->getToken()) {
return null;
}
return $token->getUser();
}
/**
* Checks if the attributes are granted against the current authentication token and optionally supplied subject.
*/
public function isGranted(mixed $attributes, mixed $subject = null): bool
{
return $this->container->get('security.authorization_checker')
->isGranted($attributes, $subject);
}
public function getToken(): ?TokenInterface
{
return $this->container->get('security.token_storage')->getToken();
}
public function getFirewallConfig(Request $request): ?FirewallConfig
{
return $this->container->get('security.firewall.map')->getFirewallConfig($request);
}
/**
* @param UserInterface $user The user to authenticate
* @param string|null $authenticatorName The authenticator name (e.g. "form_login") or service id (e.g. SomeApiKeyAuthenticator::class) - required only if multiple authenticators are configured
* @param string|null $firewallName The firewall name - required only if multiple firewalls are configured
* @param BadgeInterface[] $badges Badges to add to the user's passport
* @param array<string, mixed> $attributes Attributes to add to the user's passport
*
* @return Response|null The authenticator success response if any
*/
public function login(UserInterface $user, ?string $authenticatorName = null, ?string $firewallName = null, array $badges = [], array $attributes = []): ?Response
{
$request = $this->container->get('request_stack')->getCurrentRequest();
if (null === $request) {
throw new LogicException('Unable to login without a request context.');
}
$firewallName ??= $this->getFirewallConfig($request)?->getName();
if (!$firewallName) {
throw new LogicException('Unable to login as the current route is not covered by any firewall.');
}
$authenticator = $this->getAuthenticator($authenticatorName, $firewallName);
$userCheckerLocator = $this->container->get('security.user_checker_locator');
$userCheckerLocator->get($firewallName)->checkPreAuth($user);
return $this->container->get('security.authenticator.managers_locator')->get($firewallName)->authenticateUser($user, $authenticator, $request, $badges, $attributes);
}
/**
* Logout the current user by dispatching the LogoutEvent.
*
* @param bool $validateCsrfToken Whether to look for a valid CSRF token based on the `logout` listener configuration
*
* @return Response|null The LogoutEvent's Response if any
*
* @throws LogoutException When $validateCsrfToken is true and the CSRF token is not found or invalid
*/
public function logout(bool $validateCsrfToken = true): ?Response
{
$request = $this->container->get('request_stack')->getMainRequest();
if (null === $request) {
throw new LogicException('Unable to logout without a request context.');
}
/** @var TokenStorageInterface $tokenStorage */
$tokenStorage = $this->container->get('security.token_storage');
if (!($token = $tokenStorage->getToken()) || !$token->getUser()) {
throw new LogicException('Unable to logout as there is no logged-in user.');
}
if (!$firewallConfig = $this->container->get('security.firewall.map')->getFirewallConfig($request)) {
throw new LogicException('Unable to logout as the request is not behind a firewall.');
}
if ($validateCsrfToken) {
if (!$this->container->has('security.csrf.token_manager') || !$logoutConfig = $firewallConfig->getLogout()) {
throw new LogicException(\sprintf('Unable to logout with CSRF token validation. Either make sure that CSRF protection is enabled and "logout" is configured on the "%s" firewall, or bypass CSRF token validation explicitly by passing false to the $validateCsrfToken argument of this method.', $firewallConfig->getName()));
}
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $logoutConfig['csrf_parameter']);
if (!\is_string($csrfToken) || !$this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($logoutConfig['csrf_token_id'], $csrfToken))) {
throw new LogoutException('Invalid CSRF token.');
}
}
$logoutEvent = new LogoutEvent($request, $token);
$this->container->get('security.firewall.event_dispatcher_locator')->get($firewallConfig->getName())->dispatch($logoutEvent);
$tokenStorage->setToken(null);
return $logoutEvent->getResponse();
}
private function getAuthenticator(?string $authenticatorName, string $firewallName): AuthenticatorInterface
{
if (!isset($this->authenticators[$firewallName])) {
throw new LogicException(\sprintf('No authenticators found for firewall "%s".', $firewallName));
}
/** @var ServiceProviderInterface $firewallAuthenticatorLocator */
$firewallAuthenticatorLocator = $this->authenticators[$firewallName];
if (!$authenticatorName) {
$authenticatorIds = array_keys($firewallAuthenticatorLocator->getProvidedServices());
if (!$authenticatorIds) {
throw new LogicException(\sprintf('No authenticator was found for the firewall "%s".', $firewallName));
}
if (1 < \count($authenticatorIds)) {
throw new LogicException(\sprintf('Too many authenticators were found for the current firewall "%s". You must provide an instance of "%s" to login programmatically. The available authenticators for the firewall "%s" are "%s".', $firewallName, AuthenticatorInterface::class, $firewallName, implode('" ,"', $authenticatorIds)));
}
return $firewallAuthenticatorLocator->get($authenticatorIds[0]);
}
if ($firewallAuthenticatorLocator->has($authenticatorName)) {
return $firewallAuthenticatorLocator->get($authenticatorName);
}
$authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName;
if (!$firewallAuthenticatorLocator->has($authenticatorId)) {
throw new LogicException(\sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Available authenticators: "%s".', $authenticatorName, $firewallName, implode('", "', array_keys($firewallAuthenticatorLocator->getProvidedServices()))));
}
return $firewallAuthenticatorLocator->get($authenticatorId);
}
}