Skip to content

Commit

Permalink
Merge pull request lolautruche#46 from lolautruche/feat/loginEmail
Browse files Browse the repository at this point in the history
Implemented user authentication by email
  • Loading branch information
lolautruche committed Jul 16, 2018
2 parents 4c5b70b + 969b0b9 commit df367f2
Show file tree
Hide file tree
Showing 14 changed files with 613 additions and 2 deletions.
29 changes: 29 additions & 0 deletions DependencyInjection/Compiler/SecurityPass.php
@@ -0,0 +1,29 @@
<?php

/*
* This file is part of the EzCoreExtraBundle package.
*
* @copyright Jérôme Vieilledent <jerome@vieilledent.fr>
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/

namespace Lolautruche\EzCoreExtraBundle\DependencyInjection\Compiler;

use Lolautruche\EzCoreExtraBundle\Security\RepositoryAuthenticationProvider;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class SecurityPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('security.authentication.provider.dao')) {
return;
}

$container->findDefinition('security.authentication.provider.dao')
->setClass(RepositoryAuthenticationProvider::class)
->addMethodCall('setConfigResolver', [new Reference('ezpublish.config.resolver')]);
}
}
4 changes: 4 additions & 0 deletions DependencyInjection/Configuration.php
Expand Up @@ -29,6 +29,10 @@ public function getConfigTreeBuilder()
->useAttributeAsKey('variable_name')
->example(array('foo' => '"bar"', 'pi' => 3.14))
->prototype('variable')->end()
->end()
->booleanNode('enable_email_authentication')
->info('Whether eZ users can authenticate against their e-mail or not.')
->defaultFalse()
->end();

return $treeBuilder;
Expand Down
7 changes: 7 additions & 0 deletions DependencyInjection/EzCoreExtraExtension.php
Expand Up @@ -12,6 +12,7 @@
namespace Lolautruche\EzCoreExtraBundle\DependencyInjection;

use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\ConfigurationProcessor;
use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\ContextualizerInterface;
use Lolautruche\EzCoreExtraBundle\View\ViewParameterProviderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
Expand All @@ -31,6 +32,12 @@ public function load(array $configs, ContainerBuilder $container)
$processor = new ConfigurationProcessor($container, 'ez_core_extra');

$this->configureDesigns($config, $processor, $container);
$processor->mapConfig(
$config,
function (array $scopeSettings, $currentScope, ContextualizerInterface $contextualizer) {
$contextualizer->setContextualParameter('security.authentication_email.enabled', $currentScope, $scopeSettings['enable_email_authentication']);
}
);

if (method_exists($container, 'registerForAutoconfiguration')) {
$container->registerForAutoconfiguration(ViewParameterProviderInterface::class)
Expand Down
2 changes: 2 additions & 0 deletions EzCoreExtraBundle.php
Expand Up @@ -12,6 +12,7 @@
namespace Lolautruche\EzCoreExtraBundle;

use Lolautruche\EzCoreExtraBundle\DependencyInjection\Compiler\ParameterProviderPass;
use Lolautruche\EzCoreExtraBundle\DependencyInjection\Compiler\SecurityPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

Expand All @@ -21,5 +22,6 @@ public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ParameterProviderPass());
$container->addCompilerPass(new SecurityPass());
}
}
19 changes: 17 additions & 2 deletions README.md
Expand Up @@ -55,10 +55,23 @@ Adds extra features to eZ Platform.

Simplifies calls to `$this->isGranted()` from inside controllers and `is_granted()` from within templates when checking
against eZ inner permission system (module/function/valueObject).

* **[Authentication by e-mail](Resources/doc/email_authentication.md)**

By activating `enable_email_authentication` flag, it will be possible for users to authenticate using their e-mail,
in addition to their username.

```yaml
ez_core_extra:
system:
my_siteaccess:
enable_email_authentication: true
```

## Requirements
EzCoreExtraBundle currently works with **eZ Publish 5.4/2014.11** (and *should work* with Netgen variant)
and eZ Platform (kernel version >=6.0).
EzCoreExtraBundle currently works eZ Platform v1 and v2 (kernel v6 and v7).

> If you're using eZ publish 5.4/2014.11 or Netgen variant, look at `1.1` branch and/or `v1.x` releases.
## Installation
This bundle is available on [Packagist](https://packagist.org/packages/lolautruche/ez-core-extra-bundle).
Expand All @@ -70,6 +83,8 @@ composer require lolautruche/ez-core-extra-bundle

Then add it to your application:

> `EzCoreExtraBundle` **MUST** be instanciated **AFTER** eZ bundles.
```php
// ezpublish/EzPublishKernel.php

Expand Down
1 change: 1 addition & 0 deletions Resources/config/default_settings.yml
@@ -1,2 +1,3 @@
parameters:
ez_core_extra.default.twig_globals: {}
ez_core_extra.default.security.authentication_email.enabled: false
9 changes: 9 additions & 0 deletions Resources/config/services.yml
Expand Up @@ -31,3 +31,12 @@ services:

ez_core_extra.view.expression_language:
class: Lolautruche\EzCoreExtraBundle\View\ExpressionLanguage

ez_core_extra.security.email_user_provider:
class: Lolautruche\EzCoreExtraBundle\Security\UserProvider
decorates: ezpublish.security.user_provider
arguments:
- '@ez_core_extra.security.email_user_provider.inner'
- '@ezpublish.api.service.user'
calls:
- ['setConfigResolver', ['@ezpublish.config.resolver']]
1 change: 1 addition & 0 deletions Resources/doc/README.md
Expand Up @@ -4,3 +4,4 @@
- [Template variables injection using **ExpressionLanguage**](view_parameters_expressions.md)
- [View parameters providers (dynamic variables injection)](view_parameters_providers.md)
- [Simplified authorization checks](simplified_auth_checks.md)
- [Authentication by e-mail](email_authentication.md)
22 changes: 22 additions & 0 deletions Resources/doc/email_authentication.md
@@ -0,0 +1,22 @@
# Authentication by e-mail

By default, eZ users can only authenticate using their username. However, using e-mail for authentication is quite a
common use case.

EzCoreExtraBundle enables the possibility for any eZ user to authenticate against their e-mail, in addition to their username.

You can easily activate it for your SiteAccess using the following config, where `my_siteaccess` is the name of
your SiteAccess or SiteAccess group:

```yaml
ez_core_extra:
system:
my_siteaccess:
enable_email_authentication: true
```

Original behavior - authentication by username - is kept and will always have precedence (e.g. username will always
be tested first).

> **Important note**: `EzCoreExtraBundle` **MUST** be instanciated
> **after eZ bundles** in `AppKernel`.
33 changes: 33 additions & 0 deletions Security/EmailAuthenticationActivationChecker.php
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the EzCoreExtraBundle package.
*
* @copyright Jérôme Vieilledent <jerome@vieilledent.fr>
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/

namespace Lolautruche\EzCoreExtraBundle\Security;

use eZ\Publish\Core\MVC\ConfigResolverInterface;

trait EmailAuthenticationActivationChecker
{
/**
* @var ConfigResolverInterface
*/
private $configResolver;

public function setConfigResolver(ConfigResolverInterface $configResolver)
{
$this->configResolver = $configResolver;
}

/**
* @return bool
*/
protected function isEmailAuthenticationEnabled()
{
return (bool)$this->configResolver->getParameter('security.authentication_email.enabled', 'ez_core_extra');
}
}
69 changes: 69 additions & 0 deletions Security/RepositoryAuthenticationProvider.php
@@ -0,0 +1,69 @@
<?php

/*
* This file is part of the EzCoreExtraBundle package.
*
* @copyright Jérôme Vieilledent <jerome@vieilledent.fr>
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/

namespace Lolautruche\EzCoreExtraBundle\Security;

use eZ\Publish\API\Repository\Repository;
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
use eZ\Publish\Core\MVC\Symfony\Security\Authentication\RepositoryAuthenticationProvider as BaseProvider;
use eZ\Publish\Core\MVC\Symfony\Security\UserInterface as EzUserInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserInterface;

/**
* eZ Repository authentication provider override.
* Allows to authenticate against e-mail, in addition to traditional username.
*
* Original behavior is kept and always has precedence.
*/
class RepositoryAuthenticationProvider extends BaseProvider
{
use EmailAuthenticationActivationChecker;

/**
* @var Repository
*/
private $contentRepository;

/**
* @var \eZ\Publish\API\Repository\UserService
*/
private $userService;

public function setRepository(Repository $repository)
{
parent::setRepository($repository);
$this->contentRepository = $repository;
$this->userService = $repository->getUserService();
}

protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
{
try {
parent::checkAuthentication($user, $token);
} catch (BadCredentialsException $e) {
if (!($this->isEmailAuthenticationEnabled() && $user instanceof EzUserInterface)) {
throw $e;
}

// This check was already made in parent implementation and really represents an exception, so rethrow it.
if ($token->getUser() instanceof UserInterface) {
throw $e;
}

try {
$authenticatedRepoUser = $this->userService->loadUserByCredentials($user->getUsername(), $token->getCredentials());
$this->contentRepository->setCurrentUser($authenticatedRepoUser);
} catch (NotFoundException $exception) {
throw new BadCredentialsException('Invalid credentials', 0, $e);
}
}
}
}
78 changes: 78 additions & 0 deletions Security/UserProvider.php
@@ -0,0 +1,78 @@
<?php

/*
* This file is part of the EzCoreExtraBundle package.
*
* (c) Jérôme Vieilledent <jerome@vieilledent.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Lolautruche\EzCoreExtraBundle\Security;

use eZ\Publish\API\Repository\UserService;
use eZ\Publish\API\Repository\Values\User\User as APIUser;
use eZ\Publish\Core\MVC\ConfigResolverInterface;
use eZ\Publish\Core\MVC\Symfony\Security\User\APIUserProviderInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;

/**
* eZ User provider decorator.
* Allows to fetch users using e-mail, in addition to traditional username.
*/
class UserProvider implements APIUserProviderInterface
{
use EmailAuthenticationActivationChecker;

/**
* @var APIUserProviderInterface
*/
private $innerUserProvider;

/**
* @var UserService
*/
private $userService;

public function __construct(APIUserProviderInterface $innerUserProvider, UserService $userService)
{
$this->innerUserProvider = $innerUserProvider;
$this->userService = $userService;
}

public function loadUserByUsername($username)
{
try {
return $this->innerUserProvider->loadUserByUsername($username);
} catch (UsernameNotFoundException $e) {
if (!$this->isEmailAuthenticationEnabled()) {
throw $e;
}

$users = $this->userService->loadUsersByEmail($username);
if (empty($users)) {
throw new UsernameNotFoundException("Could not find a user with idenfifier $username");
}

return $this->loadUserByAPIUser(reset($users));
}
}

public function refreshUser(UserInterface $user)
{
return $this->innerUserProvider->refreshUser($user);
}

public function supportsClass($class)
{
return $this->innerUserProvider->supportsClass($class);
}

public function loadUserByAPIUser(APIUser $apiUser)
{
return $this->innerUserProvider->loadUserByAPIUser($apiUser);
}
}

0 comments on commit df367f2

Please sign in to comment.