From c445c31a036235653bdd772c3f7af38704329eae Mon Sep 17 00:00:00 2001 From: Joe Lambert Date: Thu, 21 Mar 2024 11:06:34 +0000 Subject: [PATCH] Support password protection (#51) * Support password protection * Fix PHPCS * Add filter to allow changing the password protect template * Use TimberResponse when returning view * Assert that response is instance of `TimberResponse` --------- Co-authored-by: Joe Lambert Co-authored-by: Alice Williams --- src/Http/Middleware/PasswordProtected.php | 44 +++++++ .../WordPressControllersServiceProvider.php | 15 ++- .../Http/Middleware/PasswordProtectTest.php | 118 ++++++++++++++++++ ...ordPressControllersServiceProviderTest.php | 58 +++++++++ 4 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 src/Http/Middleware/PasswordProtected.php create mode 100644 tests/Unit/Http/Middleware/PasswordProtectTest.php diff --git a/src/Http/Middleware/PasswordProtected.php b/src/Http/Middleware/PasswordProtected.php new file mode 100644 index 0000000..a4e1bb0 --- /dev/null +++ b/src/Http/Middleware/PasswordProtected.php @@ -0,0 +1,44 @@ +handlePasswordProtected(); + + if ($passwordRequestResponse) { + return $passwordRequestResponse; + } + + return $handler->handle($request); + } + + protected function handlePasswordProtected(): ?ResponseInterface + { + if (!post_password_required()) { + return null; + } + + $context = Timber::context(); + $context['post'] = new Post(); + + $template = apply_filters('lumberjack/password_protect_template', 'single-password.twig'); + + try { + return new TimberResponse($template, $context); + } catch (TwigTemplateNotFoundException $e) { + return null; + } + } +} diff --git a/src/Providers/WordPressControllersServiceProvider.php b/src/Providers/WordPressControllersServiceProvider.php index ad39f81..29170f5 100644 --- a/src/Providers/WordPressControllersServiceProvider.php +++ b/src/Providers/WordPressControllersServiceProvider.php @@ -8,6 +8,7 @@ use mindplay\middleman\Dispatcher; use Rareloop\Router\ResponseFactory; use Psr\Http\Message\RequestInterface; +use Rareloop\Lumberjack\Http\Middleware\PasswordProtected; use Zend\Diactoros\ServerRequestFactory; use Rareloop\Router\ProvidesControllerMiddleware; @@ -82,11 +83,15 @@ public function handleRequest(RequestInterface $request, $controllerName, $metho })->all(); } - $middlewares[] = function ($request) use ($controller, $methodName) { - $invoker = new Invoker($this->app); - $output = $invoker->setRequest($request)->call([$controller, $methodName]); - return ResponseFactory::create($request, $output); - }; + $middlewares = [ + $this->app->get(PasswordProtected::class), + ...$middlewares, + function ($request) use ($controller, $methodName) { + $invoker = new Invoker($this->app); + $output = $invoker->setRequest($request)->call([$controller, $methodName]); + return ResponseFactory::create($request, $output); + } + ]; $dispatcher = $this->createDispatcher($middlewares); return $dispatcher->dispatch($request); diff --git a/tests/Unit/Http/Middleware/PasswordProtectTest.php b/tests/Unit/Http/Middleware/PasswordProtectTest.php new file mode 100644 index 0000000..b720d0e --- /dev/null +++ b/tests/Unit/Http/Middleware/PasswordProtectTest.php @@ -0,0 +1,118 @@ +once() + ->andReturn(false); + + $request = Mockery::mock(ServerRequestInterface::class); + $response = Mockery::mock(ResponseInterface::class); + $handler = Mockery::mock(RequestHandlerInterface::class); + + $handler->shouldReceive('handle')->once()->with($request)->andReturn($response); + + $this->assertSame($response, $middleware->process($request, $handler)); + } + + /** @test */ + public function it_does_nothing_when_the_password_twig_file_is_not_found() + { + $middleware = new PasswordProtected; + + Functions\expect('post_password_required') + ->once() + ->andReturn(true); + + Functions\expect('get_the_ID') + ->once() + ->andReturn(123); + + Functions\expect('get_post') + ->once(); + + $timber = \Mockery::mock('alias:' . Timber::class); + $timber->shouldReceive('compile') + ->withArgs(function ($template) { + return $template === 'single-password.twig'; + }) + ->once() + ->andReturn(false); + + $timber->shouldReceive('context') + ->once() + ->andReturn([]); + + $request = Mockery::mock(ServerRequestInterface::class); + $response = Mockery::mock(ResponseInterface::class); + $handler = Mockery::mock(RequestHandlerInterface::class); + + $handler->shouldReceive('handle')->once()->with($request)->andReturn($response); + + $this->assertSame($response, $middleware->process($request, $handler)); + } + + /** @test */ + public function it_renders_the_password_template_when_needed() + { + Functions\expect('post_password_required') + ->once() + ->andReturn(true); + + Functions\expect('get_the_ID') + ->once() + ->andReturn(123); + + Functions\expect('get_post') + ->once(); + + $request = Mockery::mock(ServerRequestInterface::class); + $handler = Mockery::mock(RequestHandlerInterface::class); + + $timber = \Mockery::mock('alias:' . Timber::class); + $timber->shouldReceive('compile') + ->withArgs(function ($template) { + return $template === 'single-password.twig'; + }) + ->once() + ->andReturn('testing123'); + + $timber->shouldReceive('context') + ->once() + ->andReturn([]); + + $handler->shouldReceive('handle')->never(); + + $middleware = new PasswordProtected; + $response = $middleware->process($request, $handler); + + $this->assertInstanceOf(TimberResponse::class, $response); + $this->assertSame('testing123', $response->getBody()->getContents()); + $this->assertTrue(Filters\applied('lumberjack/password_protect_template') > 0); + } +} diff --git a/tests/Unit/Providers/WordPressControllersServiceProviderTest.php b/tests/Unit/Providers/WordPressControllersServiceProviderTest.php index e51f0c5..f588181 100644 --- a/tests/Unit/Providers/WordPressControllersServiceProviderTest.php +++ b/tests/Unit/Providers/WordPressControllersServiceProviderTest.php @@ -4,6 +4,7 @@ use Brain\Monkey\Filters; use Brain\Monkey\Functions; +use Laminas\Diactoros\Response\HtmlResponse; use Monolog\Logger; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; @@ -24,6 +25,7 @@ use Zend\Diactoros\Response\TextResponse; use Zend\Diactoros\ServerRequest; use \Mockery; +use Rareloop\Lumberjack\Http\Middleware\PasswordProtected; class WordPressControllersServiceProviderTest extends TestCase { @@ -156,6 +158,10 @@ public function handle_request_writes_warning_to_logs_if_controller_does_not_exi /** @test */ public function handle_request_will_mark_request_handled_in_app_if_controller_does_exist() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + $app = new Application(__DIR__ . '/../'); $provider = new WordPressControllersServiceProvider($app); @@ -182,6 +188,10 @@ public function handle_request_will_not_mark_request_handled_in_app_if_controlle /** @test */ public function handle_request_returns_response_when_controller_does_exist() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + $app = new Application(__DIR__ . '/../'); $provider = new WordPressControllersServiceProvider($app); @@ -195,6 +205,10 @@ public function handle_request_returns_response_when_controller_does_exist() /** @test */ public function handle_request_returns_response_when_controller_returns_a_responsable() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + $app = new Application(__DIR__ . '/../'); $provider = new WordPressControllersServiceProvider($app); @@ -209,6 +223,10 @@ public function handle_request_returns_response_when_controller_returns_a_respon /** @test */ public function handle_request_resolves_constructor_params_from_container() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + $app = new Application(__DIR__ . '/../'); $provider = new WordPressControllersServiceProvider($app); @@ -222,6 +240,10 @@ public function handle_request_resolves_constructor_params_from_container() /** @test */ public function handle_request_resolves_controller_method_params_from_container() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + $app = new Application(__DIR__ . '/../'); $provider = new WordPressControllersServiceProvider($app); @@ -235,6 +257,10 @@ public function handle_request_resolves_controller_method_params_from_container( /** @test */ public function handle_request_supports_middleware() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + $app = new Application(__DIR__ . '/../'); $controller = new TestControllerWithMiddleware; $controller->middleware(new AddHeaderMiddleware('X-Header', 'testing123')); @@ -249,9 +275,33 @@ public function handle_request_supports_middleware() $this->assertSame('testing123', $response->getHeader('X-Header')[0]); } + /** @test */ + public function handle_request_adds_password_protect_middleware() + { + $mock = Mockery::mock(PasswordProtected::class); + $mock->shouldReceive('process')->once()->andReturn(new HtmlResponse('password-protected')); + + $app = new Application(__DIR__ . '/../'); + $app->bind(PasswordProtected::class, $mock); + + $controller = new TestControllerWithMiddleware; + $app->bind(TestControllerWithMiddleware::class, $controller); + + $provider = new WordPressControllersServiceProvider($app); + $provider->boot($app); + + $response = $provider->handleRequest(new ServerRequest, TestControllerWithMiddleware::class, 'handle'); + + $this->assertSame('password-protected', $response->getBody()->getContents()); + } + /** @test */ public function handle_request_supports_middleware_applied_to_a_specific_method_using_only() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + $app = new Application(__DIR__ . '/../'); $controller = new TestControllerWithMiddleware; $controller->middleware(new AddHeaderMiddleware('X-Header', 'testing123'))->only('notHandle'); @@ -268,6 +318,10 @@ public function handle_request_supports_middleware_applied_to_a_specific_method_ /** @test */ public function handle_request_supports_middleware_applied_to_a_specific_method_using_except() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + $app = new Application(__DIR__ . '/../'); $controller = new TestControllerWithMiddleware; $controller->middleware(new AddHeaderMiddleware('X-Header', 'testing123'))->except('handle'); @@ -284,6 +338,10 @@ public function handle_request_supports_middleware_applied_to_a_specific_method_ /** @test */ public function handle_request_supports_middleware_aliases() { + Functions\expect('post_password_required') + ->once() + ->andReturn(false); + Functions\when('get_bloginfo')->alias(function ($key) { if ($key === 'url') { return 'http://example.com';