Created HttpBasicAuthenticator and some Guard traits
This commit is contained in:
parent
c321f4d73a
commit
a6890dbcf0
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Authentication\Authenticator;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Guard\AuthenticatorInterface;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
class HttpBasicAuthenticator implements AuthenticatorInterface
|
||||
{
|
||||
use UserProviderTrait, UsernamePasswordTrait {
|
||||
UserProviderTrait::getUser as getUserTrait;
|
||||
}
|
||||
|
||||
private $realmName;
|
||||
private $userProvider;
|
||||
private $encoderFactory;
|
||||
private $logger;
|
||||
|
||||
public function __construct(string $realmName, UserProviderInterface $userProvider, EncoderFactoryInterface $encoderFactory, ?LoggerInterface $logger = null)
|
||||
{
|
||||
$this->realmName = $realmName;
|
||||
$this->userProvider = $userProvider;
|
||||
$this->encoderFactory = $encoderFactory;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function start(Request $request, AuthenticationException $authException = null)
|
||||
{
|
||||
$response = new Response();
|
||||
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName));
|
||||
$response->setStatusCode(401);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function supports(Request $request): bool
|
||||
{
|
||||
return $request->headers->has('PHP_AUTH_USER');
|
||||
}
|
||||
|
||||
public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface
|
||||
{
|
||||
return $this->getUserTrait($credentials, $this->userProvider);
|
||||
}
|
||||
|
||||
public function getCredentials(Request $request)
|
||||
{
|
||||
return [
|
||||
'username' => $request->headers->get('PHP_AUTH_USER'),
|
||||
'password' => $request->headers->get('PHP_AUTH_PW', ''),
|
||||
];
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Basic authentication failed for user.', ['username' => $request->headers->get('PHP_AUTH_USER'), 'exception' => $exception]);
|
||||
}
|
||||
|
||||
return $this->start($request, $exception);
|
||||
}
|
||||
|
||||
public function supportsRememberMe(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Authentication\Authenticator;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
trait UserProviderTrait
|
||||
{
|
||||
public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface
|
||||
{
|
||||
return $userProvider->loadUserByUsername($credentials['username']);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Authentication\Authenticator;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @property EncoderFactoryInterface $encoderFactory
|
||||
*/
|
||||
trait UsernamePasswordTrait
|
||||
{
|
||||
public function checkCredentials($credentials, UserInterface $user): bool
|
||||
{
|
||||
if (!$this->encoderFactory instanceof EncoderFactoryInterface) {
|
||||
throw new \LogicException(\get_class($this).' uses the '.__CLASS__.' trait, which requires an $encoderFactory property to be initialized with an '.EncoderFactoryInterface::class.' implementation.');
|
||||
}
|
||||
|
||||
if ('' === $credentials['password']) {
|
||||
throw new BadCredentialsException('The presented password cannot be empty.');
|
||||
}
|
||||
|
||||
if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $credentials['password'], null)) {
|
||||
throw new BadCredentialsException('The presented password is invalid.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(UserInterface $user, $providerKey): GuardTokenInterface
|
||||
{
|
||||
return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
|
||||
}
|
||||
}
|
@ -12,13 +12,14 @@
|
||||
namespace Symfony\Component\Security\Core\Authentication\Token;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
|
||||
|
||||
/**
|
||||
* UsernamePasswordToken implements a username and password token.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class UsernamePasswordToken extends AbstractToken
|
||||
class UsernamePasswordToken extends AbstractToken implements GuardTokenInterface
|
||||
{
|
||||
private $credentials;
|
||||
private $providerKey;
|
||||
|
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Security\Core\Tests\Authentication\Authenticator;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Authenticator\HttpBasicAuthenticator;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
class HttpBasicAuthenticatorTest extends TestCase
|
||||
{
|
||||
/** @var UserProviderInterface|MockObject */
|
||||
private $userProvider;
|
||||
/** @var EncoderFactoryInterface|MockObject */
|
||||
private $encoderFactory;
|
||||
/** @var PasswordEncoderInterface|MockObject */
|
||||
private $encoder;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->userProvider = $this->getMockBuilder(UserProviderInterface::class)->getMock();
|
||||
$this->encoderFactory = $this->getMockBuilder(EncoderFactoryInterface::class)->getMock();
|
||||
$this->encoder = $this->getMockBuilder(PasswordEncoderInterface::class)->getMock();
|
||||
$this->encoderFactory
|
||||
->expects($this->any())
|
||||
->method('getEncoder')
|
||||
->willReturn($this->encoder);
|
||||
}
|
||||
|
||||
public function testValidUsernameAndPasswordServerParameters()
|
||||
{
|
||||
$request = new Request([], [], [], [], [], [
|
||||
'PHP_AUTH_USER' => 'TheUsername',
|
||||
'PHP_AUTH_PW' => 'ThePassword',
|
||||
]);
|
||||
|
||||
$guard = new HttpBasicAuthenticator('test', $this->userProvider, $this->encoderFactory);
|
||||
$credentials = $guard->getCredentials($request);
|
||||
$this->assertEquals([
|
||||
'username' => 'TheUsername',
|
||||
'password' => 'ThePassword',
|
||||
], $credentials);
|
||||
|
||||
$mockedUser = $this->getMockBuilder(UserInterface::class)->getMock();
|
||||
$mockedUser->expects($this->any())->method('getPassword')->willReturn('ThePassword');
|
||||
|
||||
$this->userProvider
|
||||
->expects($this->any())
|
||||
->method('loadUserByUsername')
|
||||
->with('TheUsername')
|
||||
->willReturn($mockedUser);
|
||||
|
||||
$user = $guard->getUser($credentials, $this->userProvider);
|
||||
$this->assertSame($mockedUser, $user);
|
||||
|
||||
$this->encoder
|
||||
->expects($this->any())
|
||||
->method('isPasswordValid')
|
||||
->with('ThePassword', 'ThePassword', null)
|
||||
->willReturn(true);
|
||||
|
||||
$checkCredentials = $guard->checkCredentials($credentials, $user);
|
||||
$this->assertTrue($checkCredentials);
|
||||
}
|
||||
|
||||
/** @dataProvider provideInvalidPasswords */
|
||||
public function testInvalidPassword($presentedPassword, $exceptionMessage)
|
||||
{
|
||||
$guard = new HttpBasicAuthenticator('test', $this->userProvider, $this->encoderFactory);
|
||||
|
||||
$this->encoder
|
||||
->expects($this->any())
|
||||
->method('isPasswordValid')
|
||||
->willReturn(false);
|
||||
|
||||
$this->expectException(BadCredentialsException::class);
|
||||
$this->expectExceptionMessage($exceptionMessage);
|
||||
|
||||
$guard->checkCredentials([
|
||||
'username' => 'TheUsername',
|
||||
'password' => $presentedPassword,
|
||||
], $this->getMockBuilder(UserInterface::class)->getMock());
|
||||
}
|
||||
|
||||
public function provideInvalidPasswords()
|
||||
{
|
||||
return [
|
||||
['InvalidPassword', 'The presented password is invalid.'],
|
||||
['', 'The presented password cannot be empty.'],
|
||||
];
|
||||
}
|
||||
|
||||
/** @dataProvider provideMissingHttpBasicServerParameters */
|
||||
public function testHttpBasicServerParametersMissing(array $serverParameters)
|
||||
{
|
||||
$request = new Request([], [], [], [], [], $serverParameters);
|
||||
|
||||
$guard = new HttpBasicAuthenticator('test', $this->userProvider, $this->encoderFactory);
|
||||
$this->assertFalse($guard->supports($request));
|
||||
}
|
||||
|
||||
public function provideMissingHttpBasicServerParameters()
|
||||
{
|
||||
return [
|
||||
[[]],
|
||||
[['PHP_AUTH_PW' => 'ThePassword']],
|
||||
];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user