mirror of
https://github.com/flarum/core.git
synced 2025-08-07 17:07:19 +02:00
Move floodgate to middleware, add extender + integration tests (#2170)
This commit is contained in:
committed by
GitHub
parent
387b4fd315
commit
1a5e4d454e
@@ -42,6 +42,20 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
return $routes;
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.api.throttlers', function () {
|
||||
return [
|
||||
'bypassThrottlingAttribute' => function ($request) {
|
||||
if ($request->getAttribute('bypassThrottling')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
$this->app->bind(Middleware\ThrottleApi::class, function ($app) {
|
||||
return new Middleware\ThrottleApi($app->make('flarum.api.throttlers'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.api.middleware', function () {
|
||||
return [
|
||||
'flarum.api.error_handler',
|
||||
@@ -53,7 +67,8 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\AuthenticateWithHeader::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.api.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
Middleware\ThrottleApi::class
|
||||
];
|
||||
});
|
||||
|
||||
|
@@ -64,6 +64,9 @@ class CreateDiscussionController extends AbstractCreateController
|
||||
$actor = $request->getAttribute('actor');
|
||||
$ipAddress = Arr::get($request->getServerParams(), 'REMOTE_ADDR', '127.0.0.1');
|
||||
|
||||
/**
|
||||
* @deprecated, remove in beta 15.
|
||||
*/
|
||||
if (! $request->getAttribute('bypassFloodgate')) {
|
||||
$this->floodgate->assertNotFlooding($actor);
|
||||
}
|
||||
|
@@ -65,6 +65,9 @@ class CreatePostController extends AbstractCreateController
|
||||
$discussionId = Arr::get($data, 'relationships.discussion.data.id');
|
||||
$ipAddress = Arr::get($request->getServerParams(), 'REMOTE_ADDR', '127.0.0.1');
|
||||
|
||||
/**
|
||||
* @deprecated, remove in beta 15.
|
||||
*/
|
||||
if (! $request->getAttribute('bypassFloodgate')) {
|
||||
$this->floodgate->assertNotFlooding($actor);
|
||||
}
|
||||
|
57
src/Api/Middleware/ThrottleApi.php
Normal file
57
src/Api/Middleware/ThrottleApi.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Middleware;
|
||||
|
||||
use Flarum\Post\Exception\FloodingException;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
|
||||
class ThrottleApi implements Middleware
|
||||
{
|
||||
protected $throttlers;
|
||||
|
||||
public function __construct(array $throttlers)
|
||||
{
|
||||
$this->throttlers = $throttlers;
|
||||
}
|
||||
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
if ($this->throttle($request)) {
|
||||
throw new FloodingException;
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function throttle(Request $request): bool
|
||||
{
|
||||
$throttle = false;
|
||||
foreach ($this->throttlers as $throttler) {
|
||||
$result = $throttler($request);
|
||||
|
||||
// Explicitly returning false overrides all throttling.
|
||||
// Explicitly returning true marks the request to be throttled.
|
||||
// Anything else is ignored.
|
||||
if ($result === false) {
|
||||
return false;
|
||||
} elseif ($result === true) {
|
||||
$throttle = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $throttle;
|
||||
}
|
||||
}
|
74
src/Extend/ThrottleApi.php
Normal file
74
src/Extend/ThrottleApi.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class ThrottleApi implements ExtenderInterface
|
||||
{
|
||||
private $setThrottlers = [];
|
||||
private $removeThrottlers = [];
|
||||
|
||||
/**
|
||||
* Add a new throttler (or override one with the same name).
|
||||
*
|
||||
* @param string $name: The name of the throttler.
|
||||
* @param string|callable $callback
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - $request: The current `\Psr\Http\Message\ServerRequestInterface` request object.
|
||||
* `$request->getAttribute('actor')` can be used to get the current user.
|
||||
* `$request->getAttribute('routeName')` can be used to get the current route.
|
||||
* Please note that every throttler runs by default on every route.
|
||||
* If you only want to throttle certain routes, you'll need to check for that inside your logic.
|
||||
*
|
||||
* The callable should return one of:
|
||||
* - `false`: This marks the request as NOT to be throttled. It overrides all other throttlers
|
||||
* - `true`: This marks the request as to be throttled.
|
||||
* All other outputs will be ignored.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function set(string $name, $callback)
|
||||
{
|
||||
$this->setThrottlers[$name] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a throttler registered with this name.
|
||||
*
|
||||
* @param string $name: The name of the throttler to remove.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function remove(string $name)
|
||||
{
|
||||
$this->removeThrottlers[] = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.api.throttlers', function ($throttlers) use ($container) {
|
||||
$throttlers = array_diff_key($throttlers, array_flip($this->removeThrottlers));
|
||||
|
||||
foreach ($this->setThrottlers as $name => $throttler) {
|
||||
$throttlers[$name] = ContainerUtil::wrapCallback($throttler, $container);
|
||||
}
|
||||
|
||||
return $throttlers;
|
||||
});
|
||||
}
|
||||
}
|
@@ -38,7 +38,7 @@ class AuthenticateWithHeader implements Middleware
|
||||
$actor = $key->user ?? $this->getUser($userId);
|
||||
|
||||
$request = $request->withAttribute('apiKey', $key);
|
||||
$request = $request->withAttribute('bypassFloodgate', true);
|
||||
$request = $request->withAttribute('bypassThrottling', true);
|
||||
} elseif ($token = AccessToken::find($id)) {
|
||||
$token->touch();
|
||||
|
||||
|
@@ -15,6 +15,9 @@ use Flarum\Post\Exception\FloodingException;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
/**
|
||||
* @deprecated beta 14, removed beta 15 in favor of Floodgate middleware
|
||||
*/
|
||||
class Floodgate
|
||||
{
|
||||
/**
|
||||
|
@@ -9,11 +9,38 @@
|
||||
|
||||
namespace Flarum\Post;
|
||||
|
||||
use DateTime;
|
||||
use Flarum\Event\ConfigurePostTypes;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
|
||||
class PostServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->extend('flarum.api.throttlers', function ($throttlers) {
|
||||
$throttlers['postTimeout'] = function ($request) {
|
||||
if (! in_array($request->getAttribute('routeName'), ['discussions.create', 'posts.create'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$actor = $request->getAttribute('actor');
|
||||
|
||||
if ($actor->can('postWithoutThrottle')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Post::where('user_id', $actor->id)->where('created_at', '>=', new DateTime('-10 seconds'))->exists()) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return $throttlers;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
Reference in New Issue
Block a user