Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Created HttpBasicAuthenticator and some Guard traits
- Loading branch information
Showing
5 changed files
with
281 additions
and
1 deletion.
There are no files selected for viewing
91 changes: 91 additions & 0 deletions
91
src/Symfony/Component/Security/Core/Authentication/Authenticator/HttpBasicAuthenticator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
src/Symfony/Component/Security/Core/Authentication/Authenticator/UserProviderTrait.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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']); | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
src/Symfony/Component/Security/Core/Authentication/Authenticator/UsernamePasswordTrait.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
...Component/Security/Core/Tests/Authentication/Authenticator/HttpBasicAuthenticatorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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']], | ||
]; | ||
} | ||
} |