diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index 913c6493a..f7da26d04 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -11,13 +11,23 @@ namespace Flarum\Admin; +use Flarum\Admin\Middleware\RequireAdministrateAbility; +use Flarum\Event\ConfigureMiddleware; use Flarum\Extension\Event\Disabled; use Flarum\Extension\Event\Enabled; use Flarum\Foundation\AbstractServiceProvider; +use Flarum\Http\Middleware\AuthenticateWithSession; +use Flarum\Http\Middleware\DispatchRoute; +use Flarum\Http\Middleware\HandleErrors; +use Flarum\Http\Middleware\ParseJsonBody; +use Flarum\Http\Middleware\RememberFromCookie; +use Flarum\Http\Middleware\SetLocale; +use Flarum\Http\Middleware\StartSession; use Flarum\Http\RouteCollection; use Flarum\Http\RouteHandlerFactory; use Flarum\Http\UrlGenerator; use Flarum\Settings\Event\Saved; +use Zend\Stratigility\MiddlewarePipe; class AdminServiceProvider extends AbstractServiceProvider { @@ -33,6 +43,29 @@ class AdminServiceProvider extends AbstractServiceProvider $this->app->singleton('flarum.admin.routes', function () { return new RouteCollection; }); + + $this->app->singleton('flarum.admin.middleware', function ($app) { + $pipe = new MiddlewarePipe; + $pipe->raiseThrowables(); + + // All requests should first be piped through our global error handler + $debugMode = ! $app->isUpToDate() || $app->inDebugMode(); + $errorDir = __DIR__.'/../../error'; + $pipe->pipe(new HandleErrors($errorDir, $app->make('log'), $debugMode)); + + $pipe->pipe($app->make(ParseJsonBody::class)); + $pipe->pipe($app->make(StartSession::class)); + $pipe->pipe($app->make(RememberFromCookie::class)); + $pipe->pipe($app->make(AuthenticateWithSession::class)); + $pipe->pipe($app->make(SetLocale::class)); + $pipe->pipe($app->make(RequireAdministrateAbility::class)); + + event(new ConfigureMiddleware($pipe, 'admin')); + + $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.admin.routes')])); + + return $pipe; + }); } /** diff --git a/framework/core/src/Admin/Server.php b/framework/core/src/Admin/Server.php deleted file mode 100644 index 43aa38116..000000000 --- a/framework/core/src/Admin/Server.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Admin; - -use Flarum\Event\ConfigureMiddleware; -use Flarum\Foundation\Application; -use Flarum\Http\AbstractServer; -use Flarum\Http\Middleware\HandleErrors; -use Zend\Stratigility\MiddlewarePipe; - -class Server extends AbstractServer -{ - /** - * {@inheritdoc} - */ - protected function getMiddleware(Application $app) - { - $pipe = new MiddlewarePipe; - $pipe->raiseThrowables(); - - if ($app->isInstalled()) { - $path = parse_url($app->url('admin'), PHP_URL_PATH); - $errorDir = __DIR__.'/../../error'; - - // All requests should first be piped through our global error handler - $debugMode = ! $app->isUpToDate() || $app->inDebugMode(); - $pipe->pipe($path, new HandleErrors($errorDir, $app->make('log'), $debugMode)); - - if ($app->isUpToDate()) { - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\ParseJsonBody')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\StartSession')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\RememberFromCookie')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\AuthenticateWithSession')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\SetLocale')); - $pipe->pipe($path, $app->make('Flarum\Admin\Middleware\RequireAdministrateAbility')); - - event(new ConfigureMiddleware($pipe, $path, $this)); - - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\DispatchRoute', ['routes' => $app->make('flarum.admin.routes')])); - } else { - $app->register('Flarum\Update\UpdateServiceProvider'); - - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\DispatchRoute', ['routes' => $app->make('flarum.update.routes')])); - } - } - - return $pipe; - } -} diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index ed4f916f3..7360358a3 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -12,17 +12,28 @@ namespace Flarum\Api; use Flarum\Api\Controller\AbstractSerializeController; +use Flarum\Api\Middleware\FakeHttpMethods; +use Flarum\Api\Middleware\HandleErrors; use Flarum\Api\Serializer\AbstractSerializer; use Flarum\Api\Serializer\NotificationSerializer; use Flarum\Event\ConfigureApiRoutes; +use Flarum\Event\ConfigureMiddleware; use Flarum\Event\ConfigureNotificationTypes; use Flarum\Foundation\AbstractServiceProvider; +use Flarum\Http\Middleware\AuthenticateWithHeader; +use Flarum\Http\Middleware\AuthenticateWithSession; +use Flarum\Http\Middleware\DispatchRoute; +use Flarum\Http\Middleware\ParseJsonBody; +use Flarum\Http\Middleware\RememberFromCookie; +use Flarum\Http\Middleware\SetLocale; +use Flarum\Http\Middleware\StartSession; use Flarum\Http\RouteCollection; use Flarum\Http\RouteHandlerFactory; use Flarum\Http\UrlGenerator; use Tobscure\JsonApi\ErrorHandler; use Tobscure\JsonApi\Exception\Handler\FallbackExceptionHandler; use Tobscure\JsonApi\Exception\Handler\InvalidParameterExceptionHandler; +use Zend\Stratigility\MiddlewarePipe; class ApiServiceProvider extends AbstractServiceProvider { @@ -39,6 +50,27 @@ class ApiServiceProvider extends AbstractServiceProvider return new RouteCollection; }); + $this->app->singleton('flarum.api.middleware', function ($app) { + $pipe = new MiddlewarePipe; + $pipe->raiseThrowables(); + + $pipe->pipe($app->make(HandleErrors::class)); + + $pipe->pipe($app->make(ParseJsonBody::class)); + $pipe->pipe($app->make(FakeHttpMethods::class)); + $pipe->pipe($app->make(StartSession::class)); + $pipe->pipe($app->make(RememberFromCookie::class)); + $pipe->pipe($app->make(AuthenticateWithSession::class)); + $pipe->pipe($app->make(AuthenticateWithHeader::class)); + $pipe->pipe($app->make(SetLocale::class)); + + event(new ConfigureMiddleware($pipe, 'api')); + + $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.api.routes')])); + + return $pipe; + }); + $this->app->singleton(ErrorHandler::class, function () { $handler = new ErrorHandler; diff --git a/framework/core/src/Api/Server.php b/framework/core/src/Api/Server.php deleted file mode 100644 index 79dbb03b6..000000000 --- a/framework/core/src/Api/Server.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api; - -use Flarum\Event\ConfigureMiddleware; -use Flarum\Foundation\Application; -use Flarum\Http\AbstractServer; -use Tobscure\JsonApi\Document; -use Zend\Stratigility\MiddlewarePipe; - -class Server extends AbstractServer -{ - /** - * {@inheritdoc} - */ - protected function getMiddleware(Application $app) - { - $pipe = new MiddlewarePipe; - $pipe->raiseThrowables(); - - $path = parse_url($app->url('api'), PHP_URL_PATH); - - if ($app->isInstalled() && $app->isUpToDate()) { - $pipe->pipe($path, $app->make('Flarum\Api\Middleware\HandleErrors')); - - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\ParseJsonBody')); - $pipe->pipe($path, $app->make('Flarum\Api\Middleware\FakeHttpMethods')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\StartSession')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\RememberFromCookie')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\AuthenticateWithSession')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\AuthenticateWithHeader')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\SetLocale')); - - event(new ConfigureMiddleware($pipe, $path, $this)); - - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\DispatchRoute', ['routes' => $app->make('flarum.api.routes')])); - } else { - $pipe->pipe($path, function () { - $document = new Document; - $document->setErrors([ - [ - 'code' => 503, - 'title' => 'Service Unavailable' - ] - ]); - - return new JsonApiResponse($document, 503); - }); - } - - return $pipe; - } -} diff --git a/framework/core/src/Event/ConfigureMiddleware.php b/framework/core/src/Event/ConfigureMiddleware.php index 80ebb649f..e602d5532 100644 --- a/framework/core/src/Event/ConfigureMiddleware.php +++ b/framework/core/src/Event/ConfigureMiddleware.php @@ -11,10 +11,6 @@ namespace Flarum\Event; -use Flarum\Admin\Server as AdminServer; -use Flarum\Api\Server as ApiServer; -use Flarum\Forum\Server as ForumServer; -use Flarum\Foundation\AbstractServer; use Zend\Stratigility\MiddlewarePipe; class ConfigureMiddleware @@ -27,42 +23,35 @@ class ConfigureMiddleware /** * @var string */ - public $path; - - /** - * @var AbstractServer - */ - public $server; + public $stackName; /** * @param MiddlewarePipe $pipe - * @param string $path - * @param AbstractServer $server + * @param string $stackName */ - public function __construct(MiddlewarePipe $pipe, $path, AbstractServer $server) + public function __construct(MiddlewarePipe $pipe, $stackName) { $this->pipe = $pipe; - $this->path = $path; - $this->server = $server; + $this->stackName = $stackName; } public function pipe(callable $middleware) { - $this->pipe->pipe($this->path, $middleware); + $this->pipe->pipe($middleware); } public function isForum() { - return $this->server instanceof ForumServer; + return $this->stackName === 'forum'; } public function isAdmin() { - return $this->server instanceof AdminServer; + return $this->stackName === 'admin'; } public function isApi() { - return $this->server instanceof ApiServer; + return $this->stackName === 'api'; } } diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 682d8194b..1e2e058b4 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -12,13 +12,22 @@ namespace Flarum\Forum; use Flarum\Event\ConfigureForumRoutes; +use Flarum\Event\ConfigureMiddleware; use Flarum\Extension\Event\Disabled; use Flarum\Extension\Event\Enabled; use Flarum\Foundation\AbstractServiceProvider; +use Flarum\Http\Middleware\AuthenticateWithSession; +use Flarum\Http\Middleware\DispatchRoute; +use Flarum\Http\Middleware\HandleErrors; +use Flarum\Http\Middleware\ParseJsonBody; +use Flarum\Http\Middleware\RememberFromCookie; +use Flarum\Http\Middleware\SetLocale; +use Flarum\Http\Middleware\StartSession; use Flarum\Http\RouteCollection; use Flarum\Http\RouteHandlerFactory; use Flarum\Http\UrlGenerator; use Flarum\Settings\Event\Saved; +use Zend\Stratigility\MiddlewarePipe; class ForumServiceProvider extends AbstractServiceProvider { @@ -34,6 +43,28 @@ class ForumServiceProvider extends AbstractServiceProvider $this->app->singleton('flarum.forum.routes', function () { return new RouteCollection; }); + + $this->app->singleton('flarum.forum.middleware', function ($app) { + $pipe = new MiddlewarePipe; + $pipe->raiseThrowables(); + + // All requests should first be piped through our global error handler + $debugMode = ! $app->isUpToDate() || $app->inDebugMode(); + $errorDir = __DIR__.'/../../error'; + $pipe->pipe(new HandleErrors($errorDir, $app->make('log'), $debugMode)); + + $pipe->pipe($app->make(ParseJsonBody::class)); + $pipe->pipe($app->make(StartSession::class)); + $pipe->pipe($app->make(RememberFromCookie::class)); + $pipe->pipe($app->make(AuthenticateWithSession::class)); + $pipe->pipe($app->make(SetLocale::class)); + + event(new ConfigureMiddleware($pipe, 'forum')); + + $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.forum.routes')])); + + return $pipe; + }); } /** diff --git a/framework/core/src/Forum/Server.php b/framework/core/src/Forum/Server.php deleted file mode 100644 index 893ddc52c..000000000 --- a/framework/core/src/Forum/Server.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Forum; - -use Flarum\Event\ConfigureMiddleware; -use Flarum\Foundation\Application; -use Flarum\Http\AbstractServer; -use Flarum\Http\Middleware\HandleErrors; -use Zend\Diactoros\Response\HtmlResponse; -use Zend\Stratigility\MiddlewarePipe; - -class Server extends AbstractServer -{ - /** - * {@inheritdoc} - */ - protected function getMiddleware(Application $app) - { - $pipe = new MiddlewarePipe; - $pipe->raiseThrowables(); - - $path = parse_url($app->url(), PHP_URL_PATH); - $errorDir = __DIR__.'/../../error'; - - if (! $app->isInstalled()) { - $app->register('Flarum\Install\InstallServiceProvider'); - - $pipe->pipe($path, new HandleErrors($errorDir, $app->make('log'), true)); - - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\StartSession')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\DispatchRoute', ['routes' => $app->make('flarum.install.routes')])); - } elseif ($app->isUpToDate() && ! $app->isDownForMaintenance()) { - $pipe->pipe($path, new HandleErrors($errorDir, $app->make('log'), $app->inDebugMode())); - - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\ParseJsonBody')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\StartSession')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\RememberFromCookie')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\AuthenticateWithSession')); - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\SetLocale')); - - event(new ConfigureMiddleware($pipe, $path, $this)); - - $pipe->pipe($path, $app->make('Flarum\Http\Middleware\DispatchRoute', ['routes' => $app->make('flarum.forum.routes')])); - } else { - $pipe->pipe($path, function () use ($errorDir) { - return new HtmlResponse(file_get_contents($errorDir.'/503.html', 503)); - }); - } - - return $pipe; - } -} diff --git a/framework/core/src/Http/AbstractServer.php b/framework/core/src/Http/AbstractServer.php deleted file mode 100644 index 0cf7bd39d..000000000 --- a/framework/core/src/Http/AbstractServer.php +++ /dev/null @@ -1,81 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Http; - -use Flarum\Foundation\AbstractServer as BaseAbstractServer; -use Flarum\Foundation\Application; -use Flarum\User\AuthToken; -use Flarum\User\EmailToken; -use Flarum\User\PasswordToken; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Zend\Diactoros\Server; -use Zend\Stratigility\MiddlewareInterface; -use Zend\Stratigility\NoopFinalHandler; - -abstract class AbstractServer extends BaseAbstractServer -{ - public function listen() - { - Server::createServer( - $this, - $_SERVER, - $_GET, - $_POST, - $_COOKIE, - $_FILES - )->listen(new NoopFinalHandler()); - } - - /** - * Use as PSR-7 middleware. - * - * @param ServerRequestInterface $request - * @param ResponseInterface $response - * @param callable $out - * @return ResponseInterface - */ - public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out) - { - $app = $this->getApp(); - - $this->collectGarbage($app); - - $middleware = $this->getMiddleware($app); - - return $middleware($request, $response, $out); - } - - /** - * @param Application $app - * @return MiddlewareInterface - */ - abstract protected function getMiddleware(Application $app); - - private function collectGarbage() - { - if ($this->hitsLottery()) { - AccessToken::whereRaw('last_activity <= ? - lifetime', [time()])->delete(); - - $earliestToKeep = date('Y-m-d H:i:s', time() - 24 * 60 * 60); - - EmailToken::where('created_at', '<=', $earliestToKeep)->delete(); - PasswordToken::where('created_at', '<=', $earliestToKeep)->delete(); - AuthToken::where('created_at', '<=', $earliestToKeep)->delete(); - } - } - - private function hitsLottery() - { - return mt_rand(1, 100) <= 2; - } -} diff --git a/framework/core/src/Http/FullStackServer.php b/framework/core/src/Http/FullStackServer.php deleted file mode 100644 index 70b331aba..000000000 --- a/framework/core/src/Http/FullStackServer.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Http; - -use Flarum\Admin\Server as AdminServer; -use Flarum\Api\Server as ApiServer; -use Flarum\Forum\Server as ForumServer; -use Flarum\Foundation\Application; -use Zend\Stratigility\MiddlewarePipe; - -class FullStackServer extends AbstractServer -{ - /** - * @param Application $app - * @return \Zend\Stratigility\MiddlewareInterface - */ - protected function getMiddleware(Application $app) - { - $pipe = new MiddlewarePipe; - $pipe->raiseThrowables(); - - $pipe->pipe(new ApiServer); - $pipe->pipe(new AdminServer); - $pipe->pipe(new ForumServer); - - return $pipe; - } -} diff --git a/framework/core/src/Http/Server.php b/framework/core/src/Http/Server.php new file mode 100644 index 000000000..832b58dce --- /dev/null +++ b/framework/core/src/Http/Server.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Http; + +use Flarum\Foundation\AbstractServer; +use Flarum\Foundation\Application; +use Flarum\Http\Middleware\DispatchRoute; +use Flarum\Http\Middleware\HandleErrors; +use Flarum\Http\Middleware\StartSession; +use Flarum\Install\InstallServiceProvider; +use Flarum\Update\UpdateServiceProvider; +use Flarum\User\AuthToken; +use Flarum\User\EmailToken; +use Flarum\User\PasswordToken; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Zend\Diactoros\Response\HtmlResponse; +use Zend\Diactoros\Server as DiactorosServer; +use Zend\Stratigility\MiddlewareInterface; +use Zend\Stratigility\MiddlewarePipe; +use Zend\Stratigility\NoopFinalHandler; + +class Server extends AbstractServer +{ + public function listen() + { + DiactorosServer::createServer( + $this, + $_SERVER, + $_GET, + $_POST, + $_COOKIE, + $_FILES + )->listen(new NoopFinalHandler()); + } + + /** + * Use as PSR-7 middleware. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param callable $out + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out) + { + $app = $this->getApp(); + + $this->collectGarbage($app); + + $middleware = $this->getMiddleware($app, $request->getUri()->getPath()); + + return $middleware($request, $response, $out); + } + + /** + * @param Application $app + * @param string $requestPath + * @return MiddlewareInterface + */ + protected function getMiddleware(Application $app, $requestPath) + { + $pipe = new MiddlewarePipe; + $pipe->raiseThrowables(); + + if (! $app->isInstalled()) { + return $this->getInstallerMiddleware($pipe, $app); + } else if ($app->isDownForMaintenance()) { + return $this->getMaintenanceMiddleware($pipe); + } else if (! $app->isUpToDate()) { + return $this->getUpdaterMiddleware($pipe, $app); + } + + $forum = parse_url($app->url(''), PHP_URL_PATH) ?: '/'; + $admin = parse_url($app->url('admin'), PHP_URL_PATH); + $api = parse_url($app->url('api'), PHP_URL_PATH); + + if ($this->pathStartsWith($requestPath, $api)) { + $pipe->pipe($api, $app->make('flarum.api.middleware')); + } else if ($this->pathStartsWith($requestPath, $admin)) { + $pipe->pipe($admin, $app->make('flarum.admin.middleware')); + } else { + $pipe->pipe($forum, $app->make('flarum.forum.middleware')); + } + + return $pipe; + } + + private function pathStartsWith($path, $prefix) + { + return $path === $prefix || starts_with($path, "$prefix/"); + } + + protected function getInstallerMiddleware(MiddlewarePipe $pipe, Application $app) + { + $app->register(InstallServiceProvider::class); + + $pipe->pipe(new HandleErrors($this->getErrorDir(), $app->make('log'), true)); + + $pipe->pipe($app->make(StartSession::class)); + $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.install.routes')])); + + return $pipe; + } + + protected function getMaintenanceMiddleware(MiddlewarePipe $pipe) + { + $pipe->pipe(function () { + return new HtmlResponse(file_get_contents($this->getErrorDir().'/503.html', 503)); + }); + + // TODO: FOR API render JSON-API error document for HTTP 503 + + return $pipe; + } + + protected function getUpdaterMiddleware(MiddlewarePipe $pipe, Application $app) + { + $app->register(UpdateServiceProvider::class); + + $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.update.routes')])); + + // TODO: FOR API render JSON-API error document for HTTP 503 + + return $pipe; + } + + private function collectGarbage() + { + if ($this->hitsLottery()) { + AccessToken::whereRaw('last_activity <= ? - lifetime', [time()])->delete(); + + $earliestToKeep = date('Y-m-d H:i:s', time() - 24 * 60 * 60); + + EmailToken::where('created_at', '<=', $earliestToKeep)->delete(); + PasswordToken::where('created_at', '<=', $earliestToKeep)->delete(); + AuthToken::where('created_at', '<=', $earliestToKeep)->delete(); + } + } + + private function getErrorDir() + { + return __DIR__.'/../../error'; + } + + private function hitsLottery() + { + return mt_rand(1, 100) <= 2; + } +}