diff --git a/_config/requestprocessors.yml b/_config/requestprocessors.yml index 280eb1e6f..41704d179 100644 --- a/_config/requestprocessors.yml +++ b/_config/requestprocessors.yml @@ -1,16 +1,16 @@ --- Name: requestprocessors --- -SilverStripe\Control\Director: - middlewares: - TrustedProxyMiddleware: '%$SilverStripe\Control\TrustedProxyMiddleware' - AllowedHostsMiddleware: '%$SilverStripe\Control\AllowedHostsMiddleware' - SessionMiddleware: 'SilverStripe\Control\SessionMiddleware' - RequestProcessor: 'SilverStripe\Control\RequestProcessor' - FlushMiddleware: '%$SilverStripe\Control\FlushMiddleware' - - SilverStripe\Core\Injector\Injector: + SilverStripe\Control\Director: + properties: + Middlewares: + TrustedProxyMiddleware: '%$SilverStripe\Control\TrustedProxyMiddleware' + AllowedHostsMiddleware: '%$SilverStripe\Control\AllowedHostsMiddleware' + SessionMiddleware: '%$SilverStripe\Control\SessionMiddleware' + RequestProcessor: '%$SilverStripe\Control\RequestProcessor' + FlushMiddleware: '%$SilverStripe\Control\FlushMiddleware' + SilverStripe\Control\AllowedHostsMiddleware: properties: AllowedHosts: "`SS_ALLOWED_HOSTS`" diff --git a/_config/security.yml b/_config/security.yml index 03dbf19ea..be0cf758f 100644 --- a/_config/security.yml +++ b/_config/security.yml @@ -19,10 +19,11 @@ SilverStripe\Core\Injector\Injector: --- Name: coresecurity --- -SilverStripe\Control\Director: - middlewares: - - %$SilverStripe\Security\AuthenticationMiddleware SilverStripe\Core\Injector\Injector: + SilverStripe\Control\Director: + properties: + Middlewares: + - %$SilverStripe\Security\AuthenticationMiddleware SilverStripe\Security\AuthenticationMiddleware: properties: AuthenticationHandler: %$SilverStripe\Security\AuthenticationHandler diff --git a/src/Control/Director.php b/src/Control/Director.php index 52a9dd8ff..ed1a565d9 100644 --- a/src/Control/Director.php +++ b/src/Control/Director.php @@ -29,6 +29,7 @@ use SilverStripe\View\TemplateGlobalProvider; class Director implements TemplateGlobalProvider { use Configurable; + use HTTPMiddlewareAware; /** * Specifies this url is relative to the base. @@ -132,7 +133,7 @@ class Director implements TemplateGlobalProvider ) { return static::mockRequest( function (HTTPRequest $request) { - return static::handleRequest($request); + return Injector::inst()->get(Director::class)->handleRequest($request); }, $url, $postVars, @@ -302,15 +303,12 @@ class Director implements TemplateGlobalProvider * @return HTTPResponse * @throws HTTPResponse_Exception */ - public static function handleRequest(HTTPRequest $request) + public function handleRequest(HTTPRequest $request) { Injector::inst()->registerService($request, HTTPRequest::class); $rules = Director::config()->uninherited('rules'); - // Get global middlewares - $middlewares = Director::config()->uninherited('middlewares') ?: []; - // Default handler - mo URL rules matched, so return a 404 error. $handler = function () { return new HTTPResponse('No URL rule was matched', 404); @@ -326,18 +324,6 @@ class Director implements TemplateGlobalProvider } } - // Add controller-specific middlewares - if (isset($controllerOptions['Middlewares'])) { - // Force to array - if (!is_array($controllerOptions['Middlewares'])) { - $controllerOptions['Middlewares'] = [$controllerOptions['Middlewares']]; - } - $middlewares = array_merge($middlewares, $controllerOptions['Middlewares']); - } - - // Remove null middlewares (may be included due to limitatons of config yml) - $middlewares = array_filter($middlewares); - // Match pattern $arguments = $request->match($pattern, true); if ($arguments !== false) { @@ -363,12 +349,25 @@ class Director implements TemplateGlobalProvider // Find the controller name $controller = $arguments['Controller']; - $controllerObj = Injector::inst()->create($controller); + + // String = service name + if (is_string($controller)) { + $controllerObj = Injector::inst()->get($controller); + // Array = service spec + } elseif (is_array($controller)) { + $controllerObj = Injector::inst()->createFromSpec($controller); + } else { + throw new \LogicException("Invalid Controller value '$controller'"); + } // Handler for calling a controller $handler = function ($request) use ($controllerObj) { try { - return $controllerObj->handleRequest($request); + // Apply the controller's middleware. We do this outside of handleRequest so that + // subclasses of handleRequest will be called after the middlware processing + return $controllerObj->callMiddleware($request, function ($request) use ($controllerObj) { + return $controllerObj->handleRequest($request); + }); } catch (HTTPResponse_Exception $responseException) { return $responseException->getResponse(); } @@ -377,12 +376,8 @@ class Director implements TemplateGlobalProvider } } - // Call the handler with the given middlewares - $response = self::callWithMiddlewares( - $request, - $middlewares, - $handler - ); + // Call the handler with the configured middlewares + $response = $this->callMiddleware($request, $handler); // Note that if a different request was previously registered, this will now be lost // In these cases it's better to use Kernel::nest() prior to kicking off a nested request diff --git a/src/Control/HTTPApplication.php b/src/Control/HTTPApplication.php index 51b386ca1..552ffba69 100644 --- a/src/Control/HTTPApplication.php +++ b/src/Control/HTTPApplication.php @@ -3,6 +3,7 @@ namespace SilverStripe\Control; use SilverStripe\Core\Application; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Control\HTTPMiddleware; use SilverStripe\Core\Kernel; @@ -11,10 +12,8 @@ use SilverStripe\Core\Kernel; */ class HTTPApplication implements Application { - /** - * @var HTTPMiddleware[] - */ - protected $middlewares = []; + + use HTTPMiddlewareAware; /** * @var Kernel @@ -26,54 +25,6 @@ class HTTPApplication implements Application $this->kernel = $kernel; } - /** - * @return HTTPMiddleware[] - */ - public function getMiddlewares() - { - return $this->middlewares; - } - - /** - * @param HTTPMiddleware[] $middlewares - * @return $this - */ - public function setMiddlewares($middlewares) - { - $this->middlewares = $middlewares; - return $this; - } - - /** - * @param HTTPMiddleware $middleware - * @return $this - */ - public function addMiddleware(HTTPMiddleware $middleware) - { - $this->middlewares[] = $middleware; - return $this; - } - - /** - * Call middleware - * - * @param HTTPRequest $request - * @param callable $last Last config to call - * @return HTTPResponse - */ - protected function callMiddleware(HTTPRequest $request, $last) - { - // Reverse middlewares - $next = $last; - /** @var HTTPMiddleware $middleware */ - foreach (array_reverse($this->getMiddlewares()) as $middleware) { - $next = function ($request) use ($middleware, $next) { - return $middleware->process($request, $next); - }; - } - return call_user_func($next, $request); - } - /** * Get the kernel for this application * @@ -96,7 +47,7 @@ class HTTPApplication implements Application // Ensure boot is invoked return $this->execute($request, function (HTTPRequest $request) { - return Director::handleRequest($request); + return Injector::inst()->get(Director::class)->handleRequest($request); }, $flush); } diff --git a/src/Control/HTTPMiddlewareAware.php b/src/Control/HTTPMiddlewareAware.php new file mode 100644 index 000000000..d3aff5574 --- /dev/null +++ b/src/Control/HTTPMiddlewareAware.php @@ -0,0 +1,64 @@ +middlewares; + } + + /** + * @param HTTPMiddleware[] $middlewares + * @return $this + */ + public function setMiddlewares($middlewares) + { + // Allow nulls in the middlewares array to deal with limitations of yml config + $this->middlewares = array_filter((array)$middlewares); + return $this; + } + + /** + * @param HTTPMiddleware $middleware + * @return $this + */ + public function addMiddleware(HTTPMiddleware $middleware) + { + $this->middlewares[] = $middleware; + return $this; + } + + /** + * Call middleware + * + * @param $request The request to pass to the middlewares and callback + * @param $last The callback to call after all middlewares + * @return HTTPResponse + */ + public function callMiddleware(HTTPRequest $request, callable $last) + { + // Reverse middlewares + $next = $last; + /** @var HTTPMiddleware $middleware */ + foreach (array_reverse($this->getMiddlewares()) as $middleware) { + $next = function ($request) use ($middleware, $next) { + return $middleware->process($request, $next); + }; + } + return $next($request); + } +} diff --git a/src/Control/RequestHandler.php b/src/Control/RequestHandler.php index 86f4460aa..ce76900e0 100644 --- a/src/Control/RequestHandler.php +++ b/src/Control/RequestHandler.php @@ -46,6 +46,9 @@ use BadMethodCallException; */ class RequestHandler extends ViewableData { + + use HTTPMiddlewareAware; + /** * Optional url_segment for this request handler *