Skip to content

Commit

Permalink
Support password protection (#51)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Alice Williams <[email protected]>
  • Loading branch information
3 people authored Mar 21, 2024
1 parent ba1daec commit c445c31
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 5 deletions.
44 changes: 44 additions & 0 deletions src/Http/Middleware/PasswordProtected.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Rareloop\Lumberjack\Http\Middleware;

use Timber\Timber;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Rareloop\Lumberjack\Exceptions\TwigTemplateNotFoundException;
use Rareloop\Lumberjack\Http\Responses\TimberResponse;
use Rareloop\Lumberjack\Post;

class PasswordProtected implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$passwordRequestResponse = $this->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;
}
}
}
15 changes: 10 additions & 5 deletions src/Providers/WordPressControllersServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
118 changes: 118 additions & 0 deletions tests/Unit/Http/Middleware/PasswordProtectTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace Rareloop\Lumberjack\Test\Http\Middleware;

use Mockery;
use Timber\Timber;
use Brain\Monkey\Functions;
use Brain\Monkey\Filters;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Rareloop\Lumberjack\Http\Middleware\PasswordProtected;
use Rareloop\Lumberjack\Http\Responses\TimberResponse;
use Rareloop\Lumberjack\Test\Unit\BrainMonkeyPHPUnitIntegration;

/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class PasswordProtectTest extends TestCase
{
use BrainMonkeyPHPUnitIntegration;

/** @test */
public function it_does_nothing_when_password_is_not_required()
{
$middleware = new PasswordProtected;

Functions\expect('post_password_required')
->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);
}
}
58 changes: 58 additions & 0 deletions tests/Unit/Providers/WordPressControllersServiceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
{
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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'));
Expand All @@ -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');
Expand All @@ -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');
Expand All @@ -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';
Expand Down

0 comments on commit c445c31

Please sign in to comment.