Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Flow 7+ compatibility #21

Merged
merged 4 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* This script belongs to the Neos Flow package "Wwwision.PrivateResources". *
* */

use Neos\Flow\Http\Response as HttpResponse;
use Neos\Flow\ResourceManagement\PersistentResource;
use Psr\Http\Message\ResponseInterface as HttpResponseInterface;

Expand Down
9 changes: 4 additions & 5 deletions Classes/Http/FileServeStrategy/ReadfileStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
* This script belongs to the Neos Flow package "Wwwision.PrivateResources". *
* */

use GuzzleHttp\Psr7\Utils;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\ResourceManagement\PersistentResource;
use Psr\Http\Message\ResponseInterface as HttpResponseInterface;
use Wwwision\PrivateResources\Http\Middleware\Exception\FileNotFoundException;
use Wwwision\PrivateResources\Utility\ProtectedResourceUtility;

use function GuzzleHttp\Psr7\stream_for;

/**
* A file serve strategy that outputs the given file using PHPs readfile function
*
Expand All @@ -25,13 +25,12 @@ class ReadfileStrategy implements FileServeStrategyInterface
* @param HttpResponseInterface $httpResponse The current HTTP response (allows setting headers, ...)
* @param array $options
* @return HttpResponseInterface
* @throws FileNotFoundException
*/
public function serve(PersistentResource $resource, HttpResponseInterface $httpResponse, array $options): HttpResponseInterface
{
$filePathAndName = ProtectedResourceUtility::getStoragePathAndFilenameByHash($resource->getSha1(), $options['basePath']);

/** @var HttpResponseInterface $response */
$response = $httpResponse->withBody(stream_for(fopen($filePathAndName, 'rb')));
return $response;
return $httpResponse->withBody(Utils::streamFor(fopen($filePathAndName, 'rb')));
}
}
8 changes: 2 additions & 6 deletions Classes/Http/FileServeStrategy/StreamStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
* This script belongs to the Neos Flow package "Wwwision.PrivateResources". *
* */

use GuzzleHttp\Psr7\Utils;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\ResourceManagement\PersistentResource;
use Psr\Http\Message\ResponseInterface as HttpResponseInterface;
use Wwwision\PrivateResources\Utility\ProtectedResourceUtility;

use function GuzzleHttp\Psr7\stream_for;

/**
* A file serve strategy that streams the given resource
Expand All @@ -30,9 +28,7 @@ public function serve(PersistentResource $resource, HttpResponseInterface $httpR
{
$stream = $resource->getStream();

/** @var HttpResponseInterface $response */
$response = $httpResponse->withBody(stream_for($stream));
return $response;
return $httpResponse->withBody(Utils::streamFor($stream));
}

}
7 changes: 3 additions & 4 deletions Classes/Http/FileServeStrategy/XAccelRedirectStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Neos\Flow\Annotations as Flow;
use Neos\Flow\ResourceManagement\PersistentResource;
use Psr\Http\Message\ResponseInterface as HttpResponseInterface;
use Wwwision\PrivateResources\Http\Middleware\Exception\FileNotFoundException;
use Wwwision\PrivateResources\Utility\ProtectedResourceUtility;

/**
Expand All @@ -26,13 +27,11 @@ class XAccelRedirectStrategy implements FileServeStrategyInterface
* @param HttpResponseInterface $httpResponse The current HTTP response (allows setting headers, ...)
* @param array $options
* @return HttpResponseInterface
* @throws FileNotFoundException
*/
public function serve(PersistentResource $resource, HttpResponseInterface $httpResponse, array $options): HttpResponseInterface
{
$filePathAndName = ProtectedResourceUtility::getStoragePathAndFilenameByHash($resource->getSha1(), $options['basePath']);

/** @var HttpResponseInterface $response */
$response = $httpResponse->withHeader('X-Accel-Redirect', $filePathAndName);
return $response;
return $httpResponse->withHeader('X-Accel-Redirect', $filePathAndName);
}
}
8 changes: 3 additions & 5 deletions Classes/Http/FileServeStrategy/XSendfileStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Neos\Flow\Annotations as Flow;
use Neos\Flow\ResourceManagement\PersistentResource;
use Psr\Http\Message\ResponseInterface as HttpResponseInterface;
use Wwwision\PrivateResources\Http\Middleware\Exception\FileNotFoundException;
use Wwwision\PrivateResources\Utility\ProtectedResourceUtility;

/**
Expand All @@ -25,14 +26,11 @@ class XSendfileStrategy implements FileServeStrategyInterface
* @param HttpResponseInterface $httpResponse The current HTTP response (allows setting headers, ...)
* @param array $options
* @return HttpResponseInterface
* @throws FileNotFoundException
*/
public function serve(PersistentResource $resource, HttpResponseInterface $httpResponse, array $options): HttpResponseInterface
{
$filePathAndName = ProtectedResourceUtility::getStoragePathAndFilenameByHash($resource->getSha1(), $options['basePath']);

/** @var HttpResponseInterface $response */
$response = $httpResponse->withHeader('X-Sendfile', $filePathAndName);
return $response;

return $httpResponse->withHeader('X-Sendfile', $filePathAndName);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php
namespace Wwwision\PrivateResources\Http\Component\Exception;
namespace Wwwision\PrivateResources\Http\Middleware\Exception;

/* *
* This script belongs to the Neos Flow package "Wwwision.PrivateResources". *
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<?php
namespace Wwwision\PrivateResources\Http\Component;
namespace Wwwision\PrivateResources\Http\Middleware;

/* *
* This script belongs to the Neos Flow package "Wwwision.PrivateResources". *
* */

use GuzzleHttp\Psr7\Response;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Configuration\Exception\InvalidConfigurationTypeException;
use Neos\Flow\Exception as FlowException;
use Neos\Flow\Http\Component\ComponentContext;
use Neos\Flow\Http\Component\ComponentInterface;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\ObjectManagement\DependencyInjection\DependencyProxy;
use Neos\Flow\ObjectManagement\ObjectManagerInterface;
Expand All @@ -22,17 +22,17 @@
use Neos\Flow\Security\Exception\NoSuchRoleException;
use Neos\Flow\Security\Policy\Role;
use Neos\Flow\Utility\Now;
use Neos\Utility\Files;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as HttpRequestInterface;
use Wwwision\PrivateResources\Http\Component\Exception\FileNotFoundException;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Wwwision\PrivateResources\Http\Middleware\Exception\FileNotFoundException;
use Wwwision\PrivateResources\Http\FileServeStrategy\FileServeStrategyInterface;
use Neos\Flow\Http\Component\ComponentChain;

/**
* A HTTP Component that checks for the request argument "__protectedResource" and outputs the requested resource if the client tokens match
* A HTTP Middleware that checks for the request argument "__protectedResource" and outputs the requested resource if the client tokens match
*/
class ProtectedResourceComponent implements ComponentInterface
class ProtectedResourceMiddleware implements MiddlewareInterface
{

/**
Expand Down Expand Up @@ -66,31 +66,30 @@ class ProtectedResourceComponent implements ComponentInterface
protected $now;

/**
* @Flow\InjectConfiguration(path="middleware")
* @var array
*/
protected $options;

/**
* @param array $options
*/
public function __construct(array $options = [])
{
$this->options = $options;
}

/**
* @param ComponentContext $componentContext
* @return void
* @throws FileNotFoundException|AccessDeniedException|FlowException
* @param HttpRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
* @throws AccessDeniedException
* @throws FileNotFoundException
* @throws FlowException
* @throws InvalidConfigurationTypeException
* @throws NoSuchRoleException
* @throws SecurityException
* @throws SecurityException\InvalidArgumentForHashGenerationException
*/
public function handle(ComponentContext $componentContext)
public function process(HttpRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
/** @var HttpRequestInterface $httpRequest */
$httpRequest = $componentContext->getHttpRequest();
$queryParams = $httpRequest->getQueryParams();
$queryParams = $request->getQueryParams();

if (!array_key_exists('__protectedResource', $queryParams) || $queryParams['__protectedResource'] === '' || $queryParams['__protectedResource'] === false) {
return;
return $handler->handle($request);
}
try {
$encodedResourceData = $this->hashService->validateAndStripHmac($queryParams['__protectedResource']);
Expand All @@ -99,8 +98,8 @@ public function handle(ComponentContext $componentContext)
}
$tokenData = json_decode(base64_decode($encodedResourceData), true);

$this->verifyExpiration($tokenData, $httpRequest);
$this->verifySecurityContext($tokenData, $httpRequest);
$this->verifyExpiration($tokenData, $request);
$this->verifySecurityContext($tokenData, $request);

$resource = $this->resourceManager->getResourceBySha1($tokenData['resourceIdentifier']);
if ($resource === null) {
Expand All @@ -117,27 +116,23 @@ public function handle(ComponentContext $componentContext)
get_class($fileServeStrategy)), 1429704284);
}
/** @var ResponseInterface $httpResponse */
$httpResponse = $componentContext->getHttpResponse()
->withHeader('Content-Type', $resource->getMediaType())
->withHeader('Content-Disposition', 'attachment;filename="' . $resource->getFilename() . '"')
->withHeader('Content-Length', $resource->getFileSize());

$this->emitResourceServed($resource, $httpRequest);
$httpResponse = new Response();
$httpResponse = $httpResponse->withHeader('Content-Type', $resource->getMediaType());
$httpResponse = $httpResponse->withHeader('Content-Disposition', 'attachment;filename="' . $resource->getFilename() . '"');
$httpResponse = $httpResponse->withHeader('Content-Length', $resource->getFileSize());
bwaidelich marked this conversation as resolved.
Show resolved Hide resolved

$httpResponse = $fileServeStrategy->serve($resource, $httpResponse, $this->options);
$componentContext->replaceHttpResponse($httpResponse);
$componentContext->setParameter(ComponentChain::class, 'cancel', true);
$this->emitResourceServed($resource, $request);
return $fileServeStrategy->serve($resource, $httpResponse, $this->options);
}

/**
* Checks whether the token is expired
*
* @param array $tokenData
* @param HttpRequestInterface $httpRequest
* @return void
* @throws AccessDeniedException
*/
protected function verifyExpiration(array $tokenData, HttpRequestInterface $httpRequest)
protected function verifyExpiration(array $tokenData, HttpRequestInterface $httpRequest): void
{
if (!isset($tokenData['expirationDateTime'])) {
return;
Expand All @@ -157,10 +152,12 @@ protected function verifyExpiration(array $tokenData, HttpRequestInterface $http
*
* @param array $tokenData
* @param HttpRequestInterface $httpRequest
* @return void
* @throws AccessDeniedException | SecurityException | NoSuchRoleException
* @throws AccessDeniedException
* @throws InvalidConfigurationTypeException
* @throws NoSuchRoleException
* @throws SecurityException
*/
protected function verifySecurityContext(array $tokenData, HttpRequestInterface $httpRequest)
protected function verifySecurityContext(array $tokenData, HttpRequestInterface $httpRequest): void
{
if (!isset($tokenData['securityContextHash']) && !isset($tokenData['privilegedRole'])) {
return;
Expand Down Expand Up @@ -191,7 +188,7 @@ protected function verifySecurityContext(array $tokenData, HttpRequestInterface
* @param HttpRequestInterface $httpRequest the current HTTP request
* @return void
*/
protected function emitResourceServed(PersistentResource $resource, HttpRequestInterface $httpRequest)
protected function emitResourceServed(PersistentResource $resource, HttpRequestInterface $httpRequest): void
{
}

Expand All @@ -203,7 +200,7 @@ protected function emitResourceServed(PersistentResource $resource, HttpRequestI
* @param HttpRequestInterface $httpRequest the current HTTP request
* @return void
*/
protected function emitAccessDenied(array $tokenData, HttpRequestInterface $httpRequest)
protected function emitAccessDenied(array $tokenData, HttpRequestInterface $httpRequest): void
{
}

Expand All @@ -216,7 +213,7 @@ protected function emitAccessDenied(array $tokenData, HttpRequestInterface $http
* @param HttpRequestInterface $httpRequest the current HTTP request
* @return void
*/
protected function emitInvalidSecurityContextHash(array $tokenData, HttpRequestInterface $httpRequest)
protected function emitInvalidSecurityContextHash(array $tokenData, HttpRequestInterface $httpRequest): void
{
}
}
2 changes: 1 addition & 1 deletion Classes/Resource/Target/ProtectedResourceTarget.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ protected function getHttpRequest(): ?HttpRequestInterface
if (!$requestHandler instanceof HttpRequestHandlerInterface) {
return null;
}
$this->httpRequest = $requestHandler-> getComponentContext()->getHttpRequest();
$this->httpRequest = $requestHandler->getHttpRequest();
}
return $this->httpRequest;
}
Expand Down
8 changes: 2 additions & 6 deletions Classes/Utility/ProtectedResourceUtility.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
* This script belongs to the Neos Flow package "Wwwision.PrivateResources". *
* */

use Neos\Flow\Annotations as Flow;
use Neos\Utility\Files;
use Wwwision\PrivateResources\Http\Component\Exception\FileNotFoundException;
use Wwwision\PrivateResources\Http\Middleware\Exception\FileNotFoundException;

/**
* A HTTP Component that checks for the request argument "__protectedResource" and outputs the requested resource if the client tokens match
*/
class ProtectedResourceUtility
{

Expand All @@ -22,7 +18,7 @@ class ProtectedResourceUtility
* @return string
* @throws FileNotFoundException
*/
public static function getStoragePathAndFilenameByHash($sha1Hash, $basePath)
public static function getStoragePathAndFilenameByHash(string $sha1Hash, string $basePath): string
{
// TODO there should be a better way to determine the absolute path of the resource? Resource::createTemporaryLocalCopy() is too expensive
$resourcePathAndFilename = Files::concatenatePaths(
Expand Down
16 changes: 16 additions & 0 deletions Configuration/Settings.Http.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Neos:
Flow:
http:
middlewares:
'protectedResources':
position: 'before routing'
middleware: 'Wwwision\PrivateResources\Http\Middleware\ProtectedResourceMiddleware'

Wwwision:
PrivateResources:
middleware:
# absolute root path of resources
basePath: '%FLOW_PATH_DATA%Persistent/Resources/'
# how the file should be served (see README)
serveStrategy: 'Wwwision\PrivateResources\Http\FileServeStrategy\ReadfileStrategy'

14 changes: 0 additions & 14 deletions Configuration/Settings.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
Neos:
Flow:
http:
chain:
'process':
chain:
'protectedResources':
position: 'before routing'
component: 'Wwwision\PrivateResources\Http\Component\ProtectedResourceComponent'
componentOptions:
# absolute root path of resources
basePath: '%FLOW_PATH_DATA%Persistent/Resources/'

# how the file should be served (see README)
serveStrategy: 'Wwwision\PrivateResources\Http\FileServeStrategy\ReadfileStrategy'

resource:
targets:
'protectedResourcesTarget':
Expand Down
Loading