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

Run API Client requests through middleware #2783

Merged
merged 27 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e11f415
Add integration tests for login and registration
askvortsov1 Apr 12, 2021
b80c8e2
Use route name for API Client, run through simplified api middleware …
askvortsov1 Apr 12, 2021
8998d4d
Test that ThrottleApi applies to routes accessed via API client
askvortsov1 Apr 12, 2021
dc5de46
Apply fixes from StyleCI
askvortsov1 Apr 12, 2021
7c8ec13
Client pipe should be provided by service provider
askvortsov1 Apr 19, 2021
4704ded
Apply fixes from StyleCI
askvortsov1 Apr 19, 2021
2637a96
Make sure user isn't overriden in API Client middleware pipe
askvortsov1 Apr 20, 2021
35ad8e5
Apply fixes from StyleCI
askvortsov1 Apr 20, 2021
e872adf
Add request as param to API Client
askvortsov1 Apr 22, 2021
5bb9c87
reverse arg order
askvortsov1 Apr 22, 2021
4f241f0
Apply fixes from StyleCI
askvortsov1 Apr 22, 2021
fa0ef3b
Make middleware to exclude a singleton
askvortsov1 May 8, 2021
4cae57b
Apply fixes from StyleCI
askvortsov1 May 8, 2021
598aafa
Trigger CI
askvortsov1 May 8, 2021
2dfaec3
Alias cleanup
askvortsov1 May 8, 2021
d55f5b4
Restore getApiDocument functions to `protected`
askvortsov1 May 8, 2021
a9cb095
Fluent API, use route path instead of name
askvortsov1 May 9, 2021
f521cd0
Apply fixes from StyleCI
askvortsov1 May 9, 2021
5b27989
type fix
askvortsov1 May 9, 2021
30e57f3
Fix GET api client calls
askvortsov1 May 9, 2021
606c047
Don't always skip CSRF verification, make `send` public, remove unnec…
askvortsov1 May 9, 2021
6d72e91
Apply fixes from StyleCI
askvortsov1 May 9, 2021
f425d3d
Copy parent csrf header line
askvortsov1 May 9, 2021
7d62773
Restore csrf exemption for API client.
askvortsov1 May 9, 2021
2fc4f66
Remove unnecessary withParsedBody
askvortsov1 May 10, 2021
927c97f
Discussion ID might be string
askvortsov1 May 10, 2021
f229e7a
Update phpDoc
SychO9 May 10, 2021
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
28 changes: 28 additions & 0 deletions src/Api/ApiServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,34 @@ public function register()
'discussionRenamed' => BasicDiscussionSerializer::class
];
});

$this->container->singleton('flarum.api_client.exclude_middleware', function () {
return [
HttpMiddleware\InjectActorReference::class,
HttpMiddleware\ParseJsonBody::class,
Middleware\FakeHttpMethods::class,
HttpMiddleware\StartSession::class,
HttpMiddleware\AuthenticateWithSession::class,
HttpMiddleware\AuthenticateWithHeader::class,
HttpMiddleware\CheckCsrfToken::class
];
});

$this->container->singleton(Client::class, function ($container) {
$pipe = new MiddlewarePipe;

$middlewareStack = array_filter($container->make('flarum.api.middleware'), function ($middlewareClass) use ($container) {
return ! in_array($middlewareClass, $container->make('flarum.api_client.exclude_middleware'));
});

foreach ($middlewareStack as $middleware) {
$pipe->pipe($container->make($middleware));
}

$pipe->pipe(new HttpMiddleware\ExecuteRoute());

return new Client($pipe);
});
}

/**
Expand Down
142 changes: 102 additions & 40 deletions src/Api/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,138 @@

namespace Flarum\Api;

use Exception;
use Flarum\Foundation\ErrorHandling\JsonApiFormatter;
use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Http\RequestUtil;
use Flarum\User\User;
use Illuminate\Contracts\Container\Container;
use InvalidArgumentException;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\Uri;
use Laminas\Stratigility\MiddlewarePipeInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Throwable;
use Psr\Http\Message\ServerRequestInterface;

class Client
{
/**
* @var Container
* @var MiddlewarePipeInterface
*/
protected $container;
protected $pipe;

/**
* @var Registry
* @var User
*/
protected $registry;
protected $actor;

/**
* @var ServerRequestInterface
*/
protected $parent;

/**
* @var array
*/
protected $queryParams = [];

/**
* @var array
*/
protected $body = [];

/**
* @param Container $container
* @param Registry $registry
*/
public function __construct(Container $container, Registry $registry)
public function __construct(MiddlewarePipeInterface $pipe)
{
$this->container = $container;
$this->registry = $registry;
$this->pipe = $pipe;
}

/**
* Set the request actor.
* This is not needed if a parent request is provided.
* It can, however, override the parent request's actor.
*/
public function withActor(User $actor): Client
{
$new = clone $this;
$new->actor = $actor;

return $new;
}

public function withParentRequest(ServerRequestInterface $parent): Client
{
$new = clone $this;
$new->parent = $parent;

return $new;
}

public function withQueryParams(array $queryParams): Client
{
$new = clone $this;
$new->queryParams = $queryParams;

return $new;
}

public function withBody(array $body): Client
askvortsov1 marked this conversation as resolved.
Show resolved Hide resolved
{
$new = clone $this;
$new->body = $body;

return $new;
}

public function get(string $path): ResponseInterface
{
return $this->send('GET', $path);
}

public function post(string $path): ResponseInterface
{
return $this->send('POST', $path);
}

public function put(string $path): ResponseInterface
{
return $this->send('PUT', $path);
}

public function patch(string $path): ResponseInterface
{
return $this->send('PATCH', $path);
}

public function delete(string $path): ResponseInterface
{
return $this->send('DELETE', $path);
}

/**
* Execute the given API action class, pass the input and return its response.
*
* @param string|RequestHandlerInterface $controller
* @param User|null $actor
* @param array $queryParams
* @param array $body
* @param string $method
* @param string $path
* @return ResponseInterface
askvortsov1 marked this conversation as resolved.
Show resolved Hide resolved
* @throws Exception
*
* @internal
*/
public function send($controller, User $actor = null, array $queryParams = [], array $body = []): ResponseInterface
public function send(string $method, string $path): ResponseInterface
{
$request = ServerRequestFactory::fromGlobals(null, $queryParams, $body);

$request = RequestUtil::withActor($request, $actor);
$request = ServerRequestFactory::fromGlobals(null, $this->queryParams, $this->body)
->withMethod($method)
->withUri(new Uri($path));

if (is_string($controller)) {
$controller = $this->container->make($controller);
if ($this->parent) {
$request = $request
->withAttribute('session', $this->parent->getAttribute('session'));
$request = RequestUtil::withActor($request, RequestUtil::getActor($this->parent));
}

if (! ($controller instanceof RequestHandlerInterface)) {
throw new InvalidArgumentException(
'Endpoint must be an instance of '.RequestHandlerInterface::class
);
// This should override the actor from the parent request, if one exists.
if ($this->actor) {
$request = RequestUtil::withActor($request, $this->actor);
}

try {
return $controller->handle($request);
} catch (Throwable $e) {
$error = $this->registry->handle($e);

if ($error->shouldBeReported()) {
throw $e;
}

return (new JsonApiFormatter)->format($error, $request);
}
return $this->pipe->handle($request);
}
}
17 changes: 8 additions & 9 deletions src/Forum/Content/Discussion.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
use Flarum\Api\Client;
use Flarum\Frontend\Document;
use Flarum\Http\Exception\RouteNotFoundException;
use Flarum\Http\RequestUtil;
use Flarum\Http\UrlGenerator;
use Flarum\User\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Arr;
use Psr\Http\Message\ServerRequestInterface as Request;
Expand Down Expand Up @@ -51,18 +49,19 @@ public function __construct(Client $api, UrlGenerator $url, Factory $view)
public function __invoke(Document $document, Request $request)
{
$queryParams = $request->getQueryParams();
$id = Arr::get($queryParams, 'id');
$page = max(1, intval(Arr::get($queryParams, 'page')));

$params = [
'id' => (int) Arr::get($queryParams, 'id'),
'id' => $id,
'page' => [
'near' => Arr::get($queryParams, 'near'),
'offset' => ($page - 1) * 20,
'limit' => 20
]
];

$apiDocument = $this->getApiDocument(RequestUtil::getActor($request), $params);
$apiDocument = $this->getApiDocument($request, $id, $params);

$getResource = function ($link) use ($apiDocument) {
return Arr::first($apiDocument->included, function ($value) use ($link) {
Expand Down Expand Up @@ -98,15 +97,15 @@ public function __invoke(Document $document, Request $request)
/**
* Get the result of an API request to show a discussion.
*
* @param User $actor
* @param array $params
* @return object
* @throws RouteNotFoundException
*/
protected function getApiDocument(User $actor, array $params)
protected function getApiDocument(Request $request, string $id, array $params)
{
$params['bySlug'] = true;
$response = $this->api->send('Flarum\Api\Controller\ShowDiscussionController', $actor, $params);
$response = $this->api
->withParentRequest($request)
->withQueryParams($params)
->get("/discussions/$id");
$statusCode = $response->getStatusCode();

if ($statusCode === 404) {
Expand Down
11 changes: 4 additions & 7 deletions src/Forum/Content/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@
namespace Flarum\Forum\Content;

use Flarum\Api\Client;
use Flarum\Api\Controller\ListDiscussionsController;
use Flarum\Frontend\Document;
use Flarum\Http\RequestUtil;
use Flarum\Http\UrlGenerator;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Arr;
use Psr\Http\Message\ServerRequestInterface as Request;
Expand Down Expand Up @@ -84,7 +81,7 @@ public function __invoke(Document $document, Request $request)
$params['filter']['q'] = $q;
}

$apiDocument = $this->getApiDocument(RequestUtil::getActor($request), $params);
$apiDocument = $this->getApiDocument($request, $params);
$defaultRoute = $this->settings->get('default_route');

$document->title = $this->translator->trans('core.forum.index.meta_title_text');
Expand Down Expand Up @@ -113,12 +110,12 @@ private function getSortMap()
/**
* Get the result of an API request to list discussions.
*
* @param User $actor
* @param Request $request
* @param array $params
* @return object
*/
private function getApiDocument(User $actor, array $params)
protected function getApiDocument(Request $request, array $params)
{
return json_decode($this->api->send(ListDiscussionsController::class, $actor, $params)->getBody());
return json_decode($this->api->withParentRequest($request)->withQueryParams($params)->get('/discussions')->getBody());
}
}
20 changes: 4 additions & 16 deletions src/Forum/Content/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@
namespace Flarum\Forum\Content;

use Flarum\Api\Client;
use Flarum\Api\Controller\ShowUserController;
use Flarum\Frontend\Document;
use Flarum\Http\RequestUtil;
use Flarum\Http\UrlGenerator;
use Flarum\User\User as FlarumUser;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Arr;
use Psr\Http\Message\ServerRequestInterface as Request;
Expand Down Expand Up @@ -44,14 +41,9 @@ public function __construct(Client $api, UrlGenerator $url)
public function __invoke(Document $document, Request $request)
{
$queryParams = $request->getQueryParams();
$actor = RequestUtil::getActor($request);
$userId = Arr::get($queryParams, 'username');
$username = Arr::get($queryParams, 'username');

$params = [
'id' => $userId,
];

$apiDocument = $this->getApiDocument($actor, $params);
$apiDocument = $this->getApiDocument($request, $username);
$user = $apiDocument->data->attributes;

$document->title = $user->displayName;
Expand All @@ -64,15 +56,11 @@ public function __invoke(Document $document, Request $request)
/**
* Get the result of an API request to show a user.
*
* @param FlarumUser $actor
* @param array $params
* @return object
* @throws ModelNotFoundException
*/
protected function getApiDocument(FlarumUser $actor, array $params)
protected function getApiDocument(Request $request, string $username)
{
$params['bySlug'] = true;
$response = $this->api->send(ShowUserController::class, $actor, $params);
$response = $this->api->withParentRequest($request)->withQueryParams(['bySlug' => true])->get("/users/$username");
$statusCode = $response->getStatusCode();

if ($statusCode === 404) {
Expand Down
5 changes: 1 addition & 4 deletions src/Forum/Controller/LogInController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@
namespace Flarum\Forum\Controller;

use Flarum\Api\Client;
use Flarum\Api\Controller\CreateTokenController;
use Flarum\Http\AccessToken;
use Flarum\Http\RememberAccessToken;
use Flarum\Http\Rememberer;
use Flarum\Http\RequestUtil;
use Flarum\Http\SessionAuthenticator;
use Flarum\User\Event\LoggedIn;
use Flarum\User\UserRepository;
Expand Down Expand Up @@ -71,11 +69,10 @@ public function __construct(UserRepository $users, Client $apiClient, SessionAut
*/
public function handle(Request $request): ResponseInterface
{
$actor = RequestUtil::getActor($request);
$body = $request->getParsedBody();
$params = Arr::only($body, ['identification', 'password', 'remember']);

$response = $this->apiClient->send(CreateTokenController::class, $actor, [], $params);
$response = $this->apiClient->withParentRequest($request)->withBody($params)->post('/token');

if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody());
Expand Down
Loading