Skip to content

Commit

Permalink
Run API Client requests through middleware (#2783)
Browse files Browse the repository at this point in the history
- Add integration tests for login and registration
- Use URL instead of controller
- Add fluent API
- Allow setting parent request, user, session
  • Loading branch information
askvortsov1 authored May 10, 2021
1 parent b5e8b6d commit f927a24
Show file tree
Hide file tree
Showing 13 changed files with 427 additions and 93 deletions.
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
{
$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
* @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

0 comments on commit f927a24

Please sign in to comment.