Skip to content

Commit

Permalink
add security.firewalls.not_full_fledged_handler option
Browse files Browse the repository at this point in the history
if not authenticated at all
  • Loading branch information
eltharin committed Jul 8, 2024
1 parent 8f71c8d commit a7ace88
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CHANGELOG
---

* Allow configuring the secret used to sign login links
* Add `security.firewalls.not_full_fledged_handler` option to configure behavior where user is not full fledged

7.1
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
'access_denied_url' => $firewallConfig->getAccessDeniedUrl(),
'user_checker' => $firewallConfig->getUserChecker(),
'authenticators' => $firewallConfig->getAuthenticators(),
'not_full_fledged_handler' => $firewallConfig->getNotFullFledgedHandler(),
];

// generate exit impersonation path from current request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
use Symfony\Component\Security\Http\Authorization\SameAsNotFullFledgedHandle;

/**
* SecurityExtension configuration structure.
Expand Down Expand Up @@ -214,6 +215,14 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
->booleanNode('stateless')->defaultFalse()->end()
->booleanNode('lazy')->defaultFalse()->end()
->scalarNode('context')->cannotBeEmpty()->end()
->scalarNode('not_full_fledged_handler')
->beforeNormalization()
->ifTrue(fn ($v): bool => $v == 'original')
->then(fn ($v) => null)
->ifTrue(fn ($v): bool => $v == 'same')
->then(fn ($v) => SameAsNotFullFledgedHandle::class)
->end()
->end()
->arrayNode('logout')
->treatTrueLike([])
->canBeUnset()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $

$config->replaceArgument(10, $listenerKeys);
$config->replaceArgument(11, $firewall['switch_user'] ?? null);
$config->replaceArgument(13, $firewall['not_full_fledged_handler'] ?? null);

return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null, $firewallAuthenticationProviders];
}
Expand Down Expand Up @@ -875,6 +876,11 @@ private function createExceptionListener(ContainerBuilder $container, array $con
$listener->replaceArgument(5, $config['access_denied_url']);
}

// not full fledged handler setup
if (isset($config['not_full_fledged_handler'])) {
$listener->replaceArgument(9, new Reference($config['not_full_fledged_handler']));
}

return $exceptionListenerId;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use Symfony\Component\Security\Core\User\MissingUserProvider;
use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Security\Http\Authorization\SameAsNotFullFledgedHandle;
use Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver;
use Symfony\Component\Security\Http\Controller\UserValueResolver;
use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener;
Expand Down Expand Up @@ -218,6 +219,7 @@
[], // listeners
null, // switch_user
null, // logout
null, //not_full_fledged_handler
])

->set('security.logout_url_generator', LogoutUrlGenerator::class)
Expand Down Expand Up @@ -310,5 +312,7 @@
->set('cache.security_is_csrf_token_valid_attribute_expression_language')
->parent('cache.system')
->tag('cache.pool')

->set('security.same_as_not_full_fledged_handle', SameAsNotFullFledgedHandle::class)
;
};
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
service('security.access.denied_handler')->nullOnInvalid(),
service('logger')->nullOnInvalid(),
false, // Stateless
service('security.not.full.fledged_handler')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
<th>authenticators</th>
<td>{{ collector.firewall.authenticators is empty ? '(none)' : profiler_dump(collector.firewall.authenticators, maxDepth=1) }}</td>
</tr>
<tr>
<th>not_full_fledged_handler</th>
<td>{{ collector.firewall.not_full_fledged_handler ?: '(none)' }}</td>
</tr>
</tbody>
</table>
{% endif %}
Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function __construct(
private readonly array $authenticators = [],
private readonly ?array $switchUser = null,
private readonly ?array $logout = null,
private readonly ?string $notFullFledgedHandler = null,
) {
}

Expand Down Expand Up @@ -104,4 +105,9 @@ public function getLogout(): ?array
{
return $this->logout;
}

public function getNotFullFledgedHandler(): ?string
{
return $this->notFullFledgedHandler;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ public function testFirewalls()
[],
null,
null,
null,
],
[
'secure',
Expand Down Expand Up @@ -184,6 +185,7 @@ public function testFirewalls()
'enable_csrf' => null,
'clear_site_data' => [],
],
null,
],
[
'host',
Expand All @@ -201,6 +203,7 @@ public function testFirewalls()
],
null,
null,
null,
],
[
'with_user_checker',
Expand All @@ -218,6 +221,7 @@ public function testFirewalls()
],
null,
null,
null,
],
], $configs);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?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\Component\Security\Http\Authorization;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

/**
* This is used by the ExceptionListener to translate an AccessDeniedException
* to a Response object.
*
* @author Roman JOLY <[email protected]>
*/
interface NotFullFledgedHandlerInterface
{
/**
* Handles a not full fledged case for acces denied failure.
* @return bool|Response : bool|Response
* true : user have to be fully authenticated, continu to startAuthentication
* false : user have'nt to be fully authenticated, throw original AcessDeniedException
* Response: you can return your own response, AccesDeniedException wil be ignored
*/
public function handle(Request $request, AccessDeniedException $accessDeniedException): bool|Response;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?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\Component\Security\Http\Authorization;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

/**
* This is a basic NotFullFledgedHandle
* If IS_AUTHENTICATED_FULLY is in access denied Exception Attrribute, behavior will be as before,
* Otherwise The original AccessDeniedException is throw
*
* @author Roman JOLY <[email protected]>
*/
class SameAsNotFullFledgedHandle implements NotFullFledgedHandlerInterface
{
public function handle(Request $request, AccessDeniedException $accessDeniedException): bool|Response
{
foreach($accessDeniedException->getAttributes() as $attribute) {
if(in_array($attribute, [AuthenticatedVoter::IS_AUTHENTICATED_FULLY])) {
return true;
}
}
return false;
}
}
30 changes: 19 additions & 11 deletions src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Symfony\Component\Security\Core\Exception\LazyResponseException;
use Symfony\Component\Security\Core\Exception\LogoutException;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
use Symfony\Component\Security\Http\Authorization\NotFullFledgedHandlerInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException;
use Symfony\Component\Security\Http\HttpUtils;
Expand Down Expand Up @@ -57,6 +58,7 @@ public function __construct(
private ?AccessDeniedHandlerInterface $accessDeniedHandler = null,
private ?LoggerInterface $logger = null,
private bool $stateless = false,
private ?NotFullFledgedHandlerInterface $notFullFledgedHandler = null,
) {
}

Expand Down Expand Up @@ -127,20 +129,26 @@ private function handleAccessDeniedException(ExceptionEvent $event, AccessDenied

$token = $this->tokenStorage->getToken();
if (!$this->authenticationTrustResolver->isFullFledged($token)) {
$this->logger?->debug('Access denied, the user is not fully authenticated; redirecting to authentication entry point.', ['exception' => $exception]);

try {
$insufficientAuthenticationException = new InsufficientAuthenticationException('Full authentication is required to access this resource.', 0, $exception);
if (null !== $token) {
$insufficientAuthenticationException->setToken($token);
$response = ((!$this->authenticationTrustResolver->isAuthenticated($token)) || (null === $this->notFullFledgedHandler) ? true : $this->notFullFledgedHandler->handle($event->getRequest(), $exception));

if ($response instanceof Response) {
$event->setResponse($response);
} elseif ($response === true) {
$this->logger?->debug('Access denied, the user is not fully authenticated; redirecting to authentication entry point.', ['exception' => $exception]);

try {
$insufficientAuthenticationException = new InsufficientAuthenticationException('Full authentication is required to access this resource.', 0, $exception);
if (null !== $token) {
$insufficientAuthenticationException->setToken($token);
}

$event->setResponse($this->startAuthentication($event->getRequest(), $insufficientAuthenticationException));
} catch (\Exception $e) {
$event->setThrowable($e);
}

$event->setResponse($this->startAuthentication($event->getRequest(), $insufficientAuthenticationException));
} catch (\Exception $e) {
$event->setThrowable($e);
return;
}

return;
}

$this->logger?->debug('Access denied, the user is neither anonymous, nor remember-me.', ['exception' => $exception]);
Expand Down
Loading

0 comments on commit a7ace88

Please sign in to comment.