From bcce2625a8cb0b630d945c6849014049869e10ce Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Tue, 27 Nov 2018 12:01:36 +0100 Subject: Implemented AuthController for login * Moved /login functionality to AuthController * Refactored password handling logic to use the Authenticator --- tests/Unit/Controllers/AuthControllerTest.php | 132 +++++++++++++++++++-- .../Controllers/Stub/ControllerImplementation.php | 8 -- .../Helpers/AuthenticatorServiceProviderTest.php | 9 ++ tests/Unit/Helpers/AuthenticatorTest.php | 125 +++++++++++++++++-- .../Unit/Http/UrlGeneratorServiceProviderTest.php | 5 +- 5 files changed, 254 insertions(+), 25 deletions(-) (limited to 'tests/Unit') diff --git a/tests/Unit/Controllers/AuthControllerTest.php b/tests/Unit/Controllers/AuthControllerTest.php index c5349cda..0fad3b6d 100644 --- a/tests/Unit/Controllers/AuthControllerTest.php +++ b/tests/Unit/Controllers/AuthControllerTest.php @@ -3,40 +3,154 @@ namespace Engelsystem\Test\Unit\Controllers; use Engelsystem\Controllers\AuthController; +use Engelsystem\Helpers\Authenticator; +use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Http\UrlGeneratorInterface; +use Engelsystem\Models\User\Settings; +use Engelsystem\Models\User\User; +use Engelsystem\Test\Unit\HasDatabase; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\SessionInterface; class AuthControllerTest extends TestCase { + use HasDatabase; + /** * @covers \Engelsystem\Controllers\AuthController::__construct - * @covers \Engelsystem\Controllers\AuthController::logout + * @covers \Engelsystem\Controllers\AuthController::login */ - public function testLogout() + public function testLogin() { /** @var Response|MockObject $response */ $response = $this->createMock(Response::class); /** @var SessionInterface|MockObject $session */ - $session = $this->getMockForAbstractClass(SessionInterface::class); /** @var UrlGeneratorInterface|MockObject $url */ - $url = $this->getMockForAbstractClass(UrlGeneratorInterface::class); + /** @var Authenticator|MockObject $auth */ + list(, $session, $url, $auth) = $this->getMocks(); - $session->expects($this->once()) - ->method('invalidate'); + $response->expects($this->once()) + ->method('withView') + ->with('pages/login') + ->willReturn($response); + + $controller = new AuthController($response, $session, $url, $auth); + $controller->login(); + } + + /** + * @covers \Engelsystem\Controllers\AuthController::postLogin + * @covers \Engelsystem\Controllers\AuthController::authenticateUser + */ + public function testPostLogin() + { + $this->initDatabase(); + $request = new Request(); + /** @var Response|MockObject $response */ + $response = $this->createMock(Response::class); + /** @var SessionInterface|MockObject $session */ + /** @var UrlGeneratorInterface|MockObject $url */ + /** @var Authenticator|MockObject $auth */ + list(, $session, $url, $auth) = $this->getMocks(); + + $user = new User([ + 'name' => 'foo', + 'password' => '', + 'email' => '', + 'api_key' => '', + 'last_login_at' => null, + ]); + $user->forceFill(['id' => 42,]); + $user->save(); + + $settings = new Settings(['language' => 'de_DE', 'theme' => '']); + $settings->user() + ->associate($user) + ->save(); + + $auth->expects($this->exactly(2)) + ->method('authenticate') + ->with('foo', 'bar') + ->willReturnOnConsecutiveCalls(null, $user); + + $response->expects($this->exactly(3)) + ->method('withView') + ->withConsecutive( + ['pages/login', ['errors' => ['auth.no-nickname'], 'show_password_recovery' => true]], + ['pages/login', ['errors' => ['auth.no-password'], 'show_password_recovery' => true]], + ['pages/login', ['errors' => ['auth.not-found'], 'show_password_recovery' => true]]) + ->willReturn($response); $response->expects($this->once()) ->method('redirectTo') - ->with('https://foo.bar/'); + ->with('news') + ->willReturn($response); + + $session->expects($this->once()) + ->method('invalidate'); + + $session->expects($this->exactly(2)) + ->method('set') + ->withConsecutive( + ['user_id', 42], + ['locale', 'de_DE'] + ); + + $controller = new AuthController($response, $session, $url, $auth); + $controller->postLogin($request); + + $request = new Request(['login' => 'foo']); + $controller->postLogin($request); + + $request = new Request(['login' => 'foo', 'password' => 'bar']); + // No user found + $controller->postLogin($request); + // Authenticated user + $controller->postLogin($request); + + $this->assertNotNull($user->last_login_at); + } + + /** + * @covers \Engelsystem\Controllers\AuthController::logout + */ + public function testLogout() + { + /** @var Response $response */ + /** @var SessionInterface|MockObject $session */ + /** @var UrlGeneratorInterface|MockObject $url */ + /** @var Authenticator|MockObject $auth */ + list($response, $session, $url, $auth) = $this->getMocks(); + + $session->expects($this->once()) + ->method('invalidate'); $url->expects($this->once()) ->method('to') ->with('/') ->willReturn('https://foo.bar/'); - $controller = new AuthController($response, $session, $url); - $controller->logout(); + $controller = new AuthController($response, $session, $url, $auth); + $return = $controller->logout(); + + $this->assertEquals(['https://foo.bar/'], $return->getHeader('location')); + } + + /** + * @return array + */ + protected function getMocks() + { + $response = new Response(); + /** @var SessionInterface|MockObject $session */ + $session = $this->getMockForAbstractClass(SessionInterface::class); + /** @var UrlGeneratorInterface|MockObject $url */ + $url = $this->getMockForAbstractClass(UrlGeneratorInterface::class); + /** @var Authenticator|MockObject $auth */ + $auth = $this->createMock(Authenticator::class); + + return [$response, $session, $url, $auth]; } } diff --git a/tests/Unit/Controllers/Stub/ControllerImplementation.php b/tests/Unit/Controllers/Stub/ControllerImplementation.php index 01d9f250..a8bf538c 100644 --- a/tests/Unit/Controllers/Stub/ControllerImplementation.php +++ b/tests/Unit/Controllers/Stub/ControllerImplementation.php @@ -14,12 +14,4 @@ class ControllerImplementation extends BaseController 'dolor', ], ]; - - /** - * @param array $permissions - */ - public function setPermissions(array $permissions) - { - $this->permissions = $permissions; - } } diff --git a/tests/Unit/Helpers/AuthenticatorServiceProviderTest.php b/tests/Unit/Helpers/AuthenticatorServiceProviderTest.php index b1767ebc..ab9b23ec 100644 --- a/tests/Unit/Helpers/AuthenticatorServiceProviderTest.php +++ b/tests/Unit/Helpers/AuthenticatorServiceProviderTest.php @@ -3,6 +3,7 @@ namespace Engelsystem\Test\Unit\Helpers; use Engelsystem\Application; +use Engelsystem\Config\Config; use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\AuthenticatorServiceProvider; use Engelsystem\Http\Request; @@ -19,11 +20,19 @@ class AuthenticatorServiceProviderTest extends ServiceProviderTest $app = new Application(); $app->bind(ServerRequestInterface::class, Request::class); + $config = new Config(); + $config->set('password_algorithm', PASSWORD_DEFAULT); + $app->instance('config', $config); + $serviceProvider = new AuthenticatorServiceProvider($app); $serviceProvider->register(); $this->assertInstanceOf(Authenticator::class, $app->get(Authenticator::class)); $this->assertInstanceOf(Authenticator::class, $app->get('authenticator')); $this->assertInstanceOf(Authenticator::class, $app->get('auth')); + + /** @var Authenticator $auth */ + $auth = $app->get(Authenticator::class); + $this->assertEquals(PASSWORD_DEFAULT, $auth->getPasswordAlgorithm()); } } diff --git a/tests/Unit/Helpers/AuthenticatorTest.php b/tests/Unit/Helpers/AuthenticatorTest.php index 400278f2..83dc72ad 100644 --- a/tests/Unit/Helpers/AuthenticatorTest.php +++ b/tests/Unit/Helpers/AuthenticatorTest.php @@ -4,6 +4,7 @@ namespace Engelsystem\Test\Unit\Helpers; use Engelsystem\Helpers\Authenticator; use Engelsystem\Models\User\User; +use Engelsystem\Test\Unit\HasDatabase; use Engelsystem\Test\Unit\Helpers\Stub\UserModelImplementation; use Engelsystem\Test\Unit\ServiceProviderTest; use PHPUnit\Framework\MockObject\MockObject; @@ -12,6 +13,8 @@ use Symfony\Component\HttpFoundation\Session\Session; class AuthenticatorTest extends ServiceProviderTest { + use HasDatabase; + /** * @covers \Engelsystem\Helpers\Authenticator::__construct( * @covers \Engelsystem\Helpers\Authenticator::user @@ -29,7 +32,7 @@ class AuthenticatorTest extends ServiceProviderTest $session->expects($this->exactly(3)) ->method('get') - ->with('uid') + ->with('user_id') ->willReturnOnConsecutiveCalls( null, 42, @@ -114,16 +117,13 @@ class AuthenticatorTest extends ServiceProviderTest /** @var User|MockObject $user */ $user = $this->createMock(User::class); - $user->expects($this->once()) - ->method('save'); - - $session->expects($this->exactly(2)) + $session->expects($this->once()) ->method('get') - ->with('uid') + ->with('user_id') ->willReturn(42); $session->expects($this->once()) ->method('remove') - ->with('uid'); + ->with('user_id'); /** @var Authenticator|MockObject $auth */ $auth = $this->getMockBuilder(Authenticator::class) @@ -151,4 +151,115 @@ class AuthenticatorTest extends ServiceProviderTest // Permissions cached $this->assertTrue($auth->can('bar')); } + + /** + * @covers \Engelsystem\Helpers\Authenticator::authenticate + */ + public function testAuthenticate() + { + $this->initDatabase(); + + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->getMockForAbstractClass(ServerRequestInterface::class); + /** @var Session|MockObject $session */ + $session = $this->createMock(Session::class); + $userRepository = new User(); + + (new User([ + 'name' => 'lorem', + 'password' => password_hash('testing', PASSWORD_DEFAULT), + 'email' => 'lorem@foo.bar', + 'api_key' => '', + ]))->save(); + (new User([ + 'name' => 'ipsum', + 'password' => '', + 'email' => 'ipsum@foo.bar', + 'api_key' => '', + ]))->save(); + + $auth = new Authenticator($request, $session, $userRepository); + $this->assertNull($auth->authenticate('not-existing', 'foo')); + $this->assertNull($auth->authenticate('ipsum', 'wrong-password')); + $this->assertInstanceOf(User::class, $auth->authenticate('lorem', 'testing')); + $this->assertInstanceOf(User::class, $auth->authenticate('lorem@foo.bar', 'testing')); + } + + /** + * @covers \Engelsystem\Helpers\Authenticator::verifyPassword + */ + public function testVerifyPassword() + { + $this->initDatabase(); + $password = password_hash('testing', PASSWORD_ARGON2I); + $user = new User([ + 'name' => 'lorem', + 'password' => $password, + 'email' => 'lorem@foo.bar', + 'api_key' => '', + ]); + $user->save(); + + /** @var Authenticator|MockObject $auth */ + $auth = $this->getMockBuilder(Authenticator::class) + ->disableOriginalConstructor() + ->setMethods(['setPassword']) + ->getMock(); + + $auth->expects($this->once()) + ->method('setPassword') + ->with($user, 'testing'); + $auth->setPasswordAlgorithm(PASSWORD_BCRYPT); + + $this->assertFalse($auth->verifyPassword($user, 'randomStuff')); + $this->assertTrue($auth->verifyPassword($user, 'testing')); + } + + /** + * @covers \Engelsystem\Helpers\Authenticator::setPassword + */ + public function testSetPassword() + { + $this->initDatabase(); + $user = new User([ + 'name' => 'ipsum', + 'password' => '', + 'email' => 'ipsum@foo.bar', + 'api_key' => '', + ]); + $user->save(); + + $auth = $this->getAuthenticator(); + $auth->setPasswordAlgorithm(PASSWORD_ARGON2I); + + $auth->setPassword($user, 'FooBar'); + $this->assertTrue($user->isClean()); + + $this->assertTrue(password_verify('FooBar', $user->password)); + $this->assertFalse(password_needs_rehash($user->password, PASSWORD_ARGON2I)); + } + + /** + * @covers \Engelsystem\Helpers\Authenticator::setPasswordAlgorithm + * @covers \Engelsystem\Helpers\Authenticator::getPasswordAlgorithm + */ + public function testPasswordAlgorithm() + { + $auth = $this->getAuthenticator(); + + $auth->setPasswordAlgorithm(PASSWORD_ARGON2I); + $this->assertEquals(PASSWORD_ARGON2I, $auth->getPasswordAlgorithm()); + } + + /** + * @return Authenticator + */ + protected function getAuthenticator() + { + return new class extends Authenticator + { + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() { } + }; + } } diff --git a/tests/Unit/Http/UrlGeneratorServiceProviderTest.php b/tests/Unit/Http/UrlGeneratorServiceProviderTest.php index 61bf3e7c..6d18f160 100644 --- a/tests/Unit/Http/UrlGeneratorServiceProviderTest.php +++ b/tests/Unit/Http/UrlGeneratorServiceProviderTest.php @@ -19,7 +19,7 @@ class UrlGeneratorServiceProviderTest extends ServiceProviderTest $urlGenerator = $this->getMockBuilder(UrlGenerator::class) ->getMock(); - $app = $this->getApp(); + $app = $this->getApp(['make', 'instance', 'bind']); $this->setExpects($app, 'make', [UrlGenerator::class], $urlGenerator); $app->expects($this->exactly(2)) @@ -29,6 +29,9 @@ class UrlGeneratorServiceProviderTest extends ServiceProviderTest ['http.urlGenerator', $urlGenerator], [UrlGeneratorInterface::class, $urlGenerator] ); + $app->expects($this->once()) + ->method('bind') + ->with(UrlGeneratorInterface::class, UrlGenerator::class); $serviceProvider = new UrlGeneratorServiceProvider($app); $serviceProvider->register(); -- cgit v1.2.3-54-g00ecf