Skip to content

Commit

Permalink
IBX-8140: Enabled authenticator manager-based security (#368)
Browse files Browse the repository at this point in the history
* IBX-8140: Enabled authenticator manager-based security

* adjusted tests after removing obsolete services

* added proper access denied listener grouping all access issues and redirecting to the login form

* [TMP][Behat] Added @javascript to failed scenarios to see screenshots

* fixed phpstan

* renamed AccessDeniedListener to *Subscriber and made it more loose when detecting permission issues

* [TMP] Added dependencies.json

* [TMP] Dropped core self-reference from dependencies.json

* regenerated phpstan baseline

* cr remarks

* adjusted unit tests

* removed unused methods due to SecurityPass changes

* reverted decorating `DefaultAuthenticationSuccessHandler`

* cr remarks

* dropped @javascript due to performance reasons

* removed dependencies.json

---------

Co-authored-by: Andrew Longosz <[email protected]>
  • Loading branch information
konradoboza and alongosz authored May 23, 2024
1 parent 1ed6afa commit eeb08b4
Show file tree
Hide file tree
Showing 19 changed files with 149 additions and 1,233 deletions.
222 changes: 1 addition & 221 deletions phpstan-baseline.neon

Large diffs are not rendered by default.

68 changes: 15 additions & 53 deletions src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
namespace Ibexa\Bundle\Core\DependencyInjection\Compiler;

use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Core\MVC\Symfony\Security\Authentication\AnonymousAuthenticationProvider;
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
use Ibexa\Core\MVC\Symfony\Security\Authentication\DefaultAuthenticationSuccessHandler;
use Ibexa\Core\MVC\Symfony\Security\Authentication\GuardRepositoryAuthenticationProvider;
use Ibexa\Core\MVC\Symfony\Security\Authentication\RememberMeRepositoryAuthenticationProvider;
use Ibexa\Core\MVC\Symfony\Security\Authentication\RepositoryAuthenticationProvider;
use Ibexa\Core\MVC\Symfony\Security\HttpUtils;
use Ibexa\Core\MVC\Symfony\SiteAccess;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
Expand All @@ -22,52 +20,24 @@

/**
* Security related compiler pass.
* Manipulates Symfony core security services to adapt them to eZ security needs.
* Manipulates Symfony core security services to adapt them to Ibexa security needs.
*/
class SecurityPass implements CompilerPassInterface
final class SecurityPass implements CompilerPassInterface
{
public const CONSTANT_AUTH_TIME_SETTING = 'ibexa.security.authentication.constant_auth_time';
public const string CONSTANT_AUTH_TIME_SETTING = 'ibexa.security.authentication.constant_auth_time';

public const CONSTANT_AUTH_TIME_DEFAULT = 1.0;
public const float CONSTANT_AUTH_TIME_DEFAULT = 1.0;

public function process(ContainerBuilder $container)
public function process(ContainerBuilder $container): void
{
if (!($container->hasDefinition('security.authentication.provider.dao') &&
$container->hasDefinition('security.authentication.provider.rememberme') &&
$container->hasDefinition('security.authentication.provider.guard') &&
$container->hasDefinition('security.authentication.provider.anonymous'))) {
if (
!$container->hasDefinition('security.authentication.provider.rememberme') ||
!$container->hasDefinition('security.authentication.provider.guard')
) {
return;
}

$configResolverRef = new Reference('ibexa.config.resolver');
$permissionResolverRef = new Reference(PermissionResolver::class);
$userServiceRef = new Reference(UserService::class);
$loggerRef = new Reference('logger');

// Override and inject the Repository in the authentication provider.
// We need it for checking user credentials
$daoAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.dao');
$daoAuthenticationProviderDef->setClass(RepositoryAuthenticationProvider::class);
$daoAuthenticationProviderDef->addMethodCall(
'setPermissionResolver',
[$permissionResolverRef]
);
$daoAuthenticationProviderDef->addMethodCall(
'setUserService',
[$userServiceRef]
);
$daoAuthenticationProviderDef->addMethodCall(
'setConstantAuthTime',
[
$container->hasParameter(self::CONSTANT_AUTH_TIME_SETTING) ?
(float)$container->getParameter(self::CONSTANT_AUTH_TIME_SETTING) :
self::CONSTANT_AUTH_TIME_DEFAULT,
]
);
$daoAuthenticationProviderDef->addMethodCall(
'setLogger',
[$loggerRef]
);

$rememberMeAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.rememberme');
$rememberMeAuthenticationProviderDef->setClass(RememberMeRepositoryAuthenticationProvider::class);
Expand All @@ -83,18 +53,6 @@ public function process(ContainerBuilder $container)
[$permissionResolverRef]
);

$anonymousAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.anonymous');
$anonymousAuthenticationProviderDef->setClass(AnonymousAuthenticationProvider::class);
$anonymousAuthenticationProviderDef->addMethodCall(
'setPermissionResolver',
[$permissionResolverRef]
);

$anonymousAuthenticationProviderDef->addMethodCall(
'setConfigResolver',
[$configResolverRef]
);

if (!$container->hasDefinition('security.http_utils')) {
return;
}
Expand All @@ -114,11 +72,15 @@ public function process(ContainerBuilder $container)
$successHandlerDef->setClass(DefaultAuthenticationSuccessHandler::class);
$successHandlerDef->addMethodCall(
'setConfigResolver',
[$configResolverRef]
[new Reference(ConfigResolverInterface::class)]
);
$successHandlerDef->addMethodCall(
'setEventDispatcher',
[new Reference('event_dispatcher')]
);
$successHandlerDef->addMethodCall(
'setPermissionResolver',
[$permissionResolverRef]
);
}
}
23 changes: 0 additions & 23 deletions src/bundle/Core/DependencyInjection/Security/HttpBasicFactory.php

This file was deleted.

2 changes: 2 additions & 0 deletions src/bundle/Core/Features/Content/content_preview.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Feature: Preview of content drafts
While I'm editing content
I need to preview the result before publishing

@broken
Scenario: Previewing the first version of a content item works
Given that I am logged in
And I create an folder draft
Expand All @@ -17,6 +18,7 @@ Feature: Preview of content drafts
When I preview this draft
Then the output is valid

@broken
Scenario: Previewing a draft of a content item with published version(s) works
Given that I am logged in
And I create a draft of an existing content item
Expand Down
11 changes: 5 additions & 6 deletions src/bundle/Core/IbexaCoreBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\Core;

Expand Down Expand Up @@ -37,7 +38,6 @@
use Ibexa\Bundle\Core\DependencyInjection\Configuration\Parser as ConfigParser;
use Ibexa\Bundle\Core\DependencyInjection\Configuration\Parser\Repository as RepositoryConfigParser;
use Ibexa\Bundle\Core\DependencyInjection\IbexaCoreExtension;
use Ibexa\Bundle\Core\DependencyInjection\Security\HttpBasicFactory;
use Ibexa\Contracts\Core\MVC\View\VariableProvider;
use Ibexa\Core\Base\Container\Compiler\FieldTypeRegistryPass;
use Ibexa\Core\Base\Container\Compiler\GenericFieldTypeConverterPass;
Expand All @@ -49,11 +49,12 @@
use Ibexa\Core\Base\Container\Compiler\Storage\Legacy\RoleLimitationConverterPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class IbexaCoreBundle extends Bundle
final class IbexaCoreBundle extends Bundle
{
public function build(ContainerBuilder $container)
public function build(ContainerBuilder $container): void
{
parent::build($container);
$container->addCompilerPass(new GenericFieldTypeConverterPass(), PassConfig::TYPE_OPTIMIZE);
Expand Down Expand Up @@ -92,15 +93,13 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new RoleLimitationConverterPass());
$container->addCompilerPass(new QueryTypePass());

$securityExtension = $container->getExtension('security');
$securityExtension->addSecurityListenerFactory(new HttpBasicFactory());
$container->addCompilerPass(new TranslationCollectorPass());
$container->addCompilerPass(new SlugConverterConfigurationPass());

$container->registerForAutoconfiguration(VariableProvider::class)->addTag('ezplatform.view.variable_provider');
}

public function getContainerExtension()
public function getContainerExtension(): ?ExtensionInterface
{
if (!isset($this->extension)) {
$this->extension = new IbexaCoreExtension(
Expand Down
17 changes: 4 additions & 13 deletions src/bundle/Core/Resources/config/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,10 @@ services:
class: Ibexa\Core\MVC\Symfony\Controller\SecurityController
arguments: ["@twig", '@ibexa.config.resolver', "@security.authentication_utils"]

Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener:
class: Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener
arguments:
- '@Ibexa\Contracts\Core\Repository\PermissionResolver'
- '@Ibexa\Contracts\Core\Repository\UserService'
- '@ibexa.config.resolver'
- "@event_dispatcher"
- "@security.token_storage"
- "@security.authorization_checker"
- "%fragment.path%"
tags:
- { name: kernel.event_subscriber }

ibexa.security.user_provider: '@Ibexa\Core\MVC\Symfony\Security\User\UsernameProvider'
ibexa.security.user_provider.username: '@Ibexa\Core\MVC\Symfony\Security\User\UsernameProvider'
ibexa.security.user_provider.email: '@Ibexa\Core\MVC\Symfony\Security\User\EmailProvider'

Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber\AccessDeniedSubscriber:
autowire: true
autoconfigure: true
Empty file removed src/lib/.gitkeep
Empty file.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,27 @@
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Core\MVC\Symfony\Security\Authentication;

use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
use Ibexa\Core\MVC\Symfony\Security\UserInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler as BaseSuccessHandler;

class DefaultAuthenticationSuccessHandler extends BaseSuccessHandler
final class DefaultAuthenticationSuccessHandler extends BaseSuccessHandler
{
private EventDispatcherInterface $eventDispatcher;

private ConfigResolverInterface $configResolver;

/**
* Injects the ConfigResolver to potentially override default_target_path for redirections after authentication success.
*/
private PermissionResolver $permissionResolver;

public function setConfigResolver(ConfigResolverInterface $configResolver): void
{
$this->configResolver = $configResolver;
Expand All @@ -31,20 +35,42 @@ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): v
$this->eventDispatcher = $eventDispatcher;
}

protected function determineTargetUrl(Request $request)
public function setPermissionResolver(PermissionResolver $permissionResolver): void
{
$this->permissionResolver = $permissionResolver;
}

public function onAuthenticationSuccess(Request $request, TokenInterface $token): ?Response
{
$user = $token->getUser();
if ($user instanceof UserInterface && isset($this->permissionResolver)) {
$this->permissionResolver->setCurrentUserReference($user->getAPIUser());
}

return parent::onAuthenticationSuccess($request, $token);
}

protected function determineTargetUrl(Request $request): string
{
if (isset($this->configResolver)) {
$defaultPage = $this->configResolver->getParameter('default_page');
if ($defaultPage !== null) {
$this->options['default_target_path'] = $defaultPage;
$this->setOptions([
'default_target_path' => $defaultPage,
]);
}
}

if (isset($this->eventDispatcher)) {
$event = new DetermineTargetUrlEvent($request, $this->options, $this->getFirewallName());
$event = new DetermineTargetUrlEvent(
$request,
$this->getOptions(),
$this->getFirewallName() ?? ''
);

$this->eventDispatcher->dispatch($event);

$this->options = $event->getOptions();
$this->setOptions($event->getOptions());
}

return parent::determineTargetUrl($request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber;

use Ibexa\Contracts\Core\Repository\Exceptions\Exception as IbexaRepositoryException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

final readonly class AccessDeniedSubscriber implements EventSubscriberInterface
{
public function __construct(private UrlGeneratorInterface $urlGenerator)
{
}

public static function getSubscribedEvents(): array
{
return [
// the priority must be greater than the Security HTTP
// ExceptionListener, to make sure it's called before
// the default exception listener
KernelEvents::EXCEPTION => ['onKernelException', 2],
];
}

public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable()->getPrevious();

if ($exception instanceof IbexaRepositoryException) {
return;
}

$event->setResponse(new RedirectResponse(
$this->urlGenerator->generate('login')
));
}
}
Loading

0 comments on commit eeb08b4

Please sign in to comment.