From 07a20a10fdc78c9adf28deacdb461852e3d3f46b Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sat, 2 Jan 2016 15:22:16 +1030 Subject: [PATCH] Move flood control from core to API layer This means that flood control can be disabled depending on the nature of the request (i.e. when authenticated using a master API key). The particular use case for this is to allow using the API to migrate data from an old forum. --- .../Controller/CreateDiscussionController.php | 16 +++- src/Api/Controller/CreatePostController.php | 16 +++- src/Core/CoreServiceProvider.php | 1 - src/Core/Listener/FloodController.php | 75 ------------------- src/Core/Post/Floodgate.php | 39 ++++++++++ .../Middleware/AuthenticateWithHeader.php | 2 + 6 files changed, 69 insertions(+), 80 deletions(-) delete mode 100755 src/Core/Listener/FloodController.php create mode 100644 src/Core/Post/Floodgate.php diff --git a/src/Api/Controller/CreateDiscussionController.php b/src/Api/Controller/CreateDiscussionController.php index a0205c21e..7af48d8c9 100644 --- a/src/Api/Controller/CreateDiscussionController.php +++ b/src/Api/Controller/CreateDiscussionController.php @@ -12,6 +12,7 @@ namespace Flarum\Api\Controller; use Flarum\Core\Command\StartDiscussion; use Flarum\Core\Command\ReadDiscussion; +use Flarum\Core\Post\Floodgate; use Illuminate\Contracts\Bus\Dispatcher; use Psr\Http\Message\ServerRequestInterface; use Tobscure\JsonApi\Document; @@ -40,11 +41,18 @@ class CreateDiscussionController extends AbstractCreateController protected $bus; /** - * @param Dispatcher $bus + * @var Floodgate */ - public function __construct(Dispatcher $bus) + protected $floodgate; + + /** + * @param Dispatcher $bus + * @param Floodgate $floodgate + */ + public function __construct(Dispatcher $bus, Floodgate $floodgate) { $this->bus = $bus; + $this->floodgate = $floodgate; } /** @@ -54,6 +62,10 @@ class CreateDiscussionController extends AbstractCreateController { $actor = $request->getAttribute('actor'); + if (! $request->getAttribute('bypassFloodgate')) { + $this->floodgate->assertNotFlooding($actor); + } + $discussion = $this->bus->dispatch( new StartDiscussion($actor, array_get($request->getParsedBody(), 'data', [])) ); diff --git a/src/Api/Controller/CreatePostController.php b/src/Api/Controller/CreatePostController.php index ab054cc3c..995c02c7c 100644 --- a/src/Api/Controller/CreatePostController.php +++ b/src/Api/Controller/CreatePostController.php @@ -12,6 +12,7 @@ namespace Flarum\Api\Controller; use Flarum\Core\Command\PostReply; use Flarum\Core\Command\ReadDiscussion; +use Flarum\Core\Post\Floodgate; use Illuminate\Contracts\Bus\Dispatcher; use Psr\Http\Message\ServerRequestInterface; use Tobscure\JsonApi\Document; @@ -39,11 +40,18 @@ class CreatePostController extends AbstractCreateController protected $bus; /** - * @param Dispatcher $bus + * @var Floodgate */ - public function __construct(Dispatcher $bus) + protected $floodgate; + + /** + * @param Dispatcher $bus + * @param Floodgate $floodgate + */ + public function __construct(Dispatcher $bus, Floodgate $floodgate) { $this->bus = $bus; + $this->floodgate = $floodgate; } /** @@ -56,6 +64,10 @@ class CreatePostController extends AbstractCreateController $discussionId = array_get($data, 'relationships.discussion.data.id'); $ipAddress = array_get($request->getServerParams(), 'REMOTE_ADDR', '127.0.0.1'); + if (! $request->getAttribute('bypassFloodgate')) { + $this->floodgate->assertNotFlooding($actor); + } + $post = $this->bus->dispatch( new PostReply($discussionId, $actor, $data, $ipAddress) ); diff --git a/src/Core/CoreServiceProvider.php b/src/Core/CoreServiceProvider.php index 4f7f1511a..229b43ecf 100644 --- a/src/Core/CoreServiceProvider.php +++ b/src/Core/CoreServiceProvider.php @@ -95,7 +95,6 @@ class CoreServiceProvider extends AbstractServiceProvider $events->subscribe('Flarum\Core\Listener\UserMetadataUpdater'); $events->subscribe('Flarum\Core\Listener\EmailConfirmationMailer'); $events->subscribe('Flarum\Core\Listener\DiscussionRenamedNotifier'); - $events->subscribe('Flarum\Core\Listener\FloodController'); $events->subscribe('Flarum\Core\Access\DiscussionPolicy'); $events->subscribe('Flarum\Core\Access\GroupPolicy'); diff --git a/src/Core/Listener/FloodController.php b/src/Core/Listener/FloodController.php deleted file mode 100755 index 68d2ed939..000000000 --- a/src/Core/Listener/FloodController.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Core\Listener; - -use DateTime; -use Flarum\Core\Exception\FloodingException; -use Flarum\Core\Post; -use Flarum\Core\User; -use Flarum\Event\DiscussionWillBeSaved; -use Flarum\Event\PostWillBeSaved; -use Illuminate\Contracts\Events\Dispatcher; - -class FloodController -{ - /** - * @param Dispatcher $events - */ - public function subscribe(Dispatcher $events) - { - $events->listen(DiscussionWillBeSaved::class, [$this, 'whenDiscussionWillBeSaved']); - $events->listen(PostWillBeSaved::class, [$this, 'whenPostWillBeSaved']); - } - - /** - * @param DiscussionWillBeSaved $event - */ - public function whenDiscussionWillBeSaved(DiscussionWillBeSaved $event) - { - if ($event->discussion->exists) { - return; - } - - $this->assertNotFlooding($event->actor); - } - - /** - * @param PostWillBeSaved $event - */ - public function whenPostWillBeSaved(PostWillBeSaved $event) - { - if ($event->post->exists) { - return; - } - - $this->assertNotFlooding($event->actor); - } - - /** - * @param User $actor - * @throws FloodingException - */ - protected function assertNotFlooding(User $actor) - { - if ($this->isFlooding($actor)) { - throw new FloodingException; - } - } - - /** - * @param User $actor - * @return bool - */ - protected function isFlooding(User $actor) - { - return Post::where('user_id', $actor->id)->where('time', '>=', new DateTime('-10 seconds'))->exists(); - } -} diff --git a/src/Core/Post/Floodgate.php b/src/Core/Post/Floodgate.php new file mode 100644 index 000000000..8332ec746 --- /dev/null +++ b/src/Core/Post/Floodgate.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Core\Post; + +use DateTime; +use Flarum\Core\Exception\FloodingException; +use Flarum\Core\Post; +use Flarum\Core\User; + +class Floodgate +{ + /** + * @param User $actor + * @throws FloodingException + */ + public function assertNotFlooding(User $actor) + { + if ($this->isFlooding($actor)) { + throw new FloodingException; + } + } + + /** + * @param User $actor + * @return bool + */ + public function isFlooding(User $actor) + { + return Post::where('user_id', $actor->id)->where('time', '>=', new DateTime('-10 seconds'))->exists(); + } +} diff --git a/src/Http/Middleware/AuthenticateWithHeader.php b/src/Http/Middleware/AuthenticateWithHeader.php index 044f1f489..0518133c9 100644 --- a/src/Http/Middleware/AuthenticateWithHeader.php +++ b/src/Http/Middleware/AuthenticateWithHeader.php @@ -40,6 +40,8 @@ class AuthenticateWithHeader implements MiddlewareInterface if (isset($parts[1])) { if (ApiKey::find($id)) { $actor = $this->getUser($parts[1]); + + $request = $request->withAttribute('bypassFloodgate', true); } } elseif ($token = AccessToken::find($id)) { $token->touch();