Closed
Description
PHP Version
8.2
CodeIgniter4 Version
4.5.7
CodeIgniter4 Installation Method
Composer (using codeigniter4/appstarter
)
Which operating systems have you tested for this bug?
Linux
Which server did you use?
apache
Database
No response
What happened?
When using Cors and Token filter together, if token check failed and return in before filter, the Cors after filter can not running.
Steps to Reproduce
- install Shield
- setup app/Config/Filters.php
public array $filters = [
'cors' => [
'before' => ['swagger', 'auth/token', 'api/*'],
'after' => ['swagger', 'auth/token', 'api/*'],
],
'tokens' => ['before' => ['api/*']],
];
- using Swagger UI (different url from the api url) to test api with wrong Authorization header
Expected Output
Cors filter should add all headers in before filter, not in after filter.
Anything else?
Cors.php
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Filters;
use CodeIgniter\HTTP\Cors as CorsService;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
/**
* @see \CodeIgniter\Filters\CorsTest
*/
class Cors implements FilterInterface
{
private ?CorsService $cors = null;
/**
* @testTag $config is used for testing purposes only.
*
* @param array{
* allowedOrigins?: list<string>,
* allowedOriginsPatterns?: list<string>,
* supportsCredentials?: bool,
* allowedHeaders?: list<string>,
* exposedHeaders?: list<string>,
* allowedMethods?: list<string>,
* maxAge?: int,
* } $config
*/
public function __construct(array $config = [])
{
if ($config !== []) {
$this->cors = new CorsService($config);
}
}
/**
* @param list<string>|null $arguments
*
* @return ResponseInterface|string|void
*/
public function before(RequestInterface $request, $arguments = null)
{
if (! $request instanceof IncomingRequest) {
return;
}
$this->createCorsService($arguments);
if (! $this->cors->isPreflightRequest($request)) {
return;
}
/** @var ResponseInterface $response */
$response = service('response');
$response = $this->cors->handlePreflightRequest($request, $response);
// Always adds `Vary: Access-Control-Request-Method` header for cacheability.
// If there is an intermediate cache server such as a CDN, if a plain
// OPTIONS request is sent, it may be cached. But valid preflight requests
// have this header, so it will be cached separately.
$response->appendHeader('Vary', 'Access-Control-Request-Method');
return $response;
}
/**
* @param list<string>|null $arguments
*/
private function createCorsService(?array $arguments): void
{
$this->cors ??= ($arguments === null) ? CorsService::factory()
: CorsService::factory($arguments[0]);
}
/**
* @param list<string>|null $arguments
*
* @return ResponseInterface|void
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
if (! $request instanceof IncomingRequest) {
return;
}
$this->createCorsService($arguments);
// Always adds `Vary: Access-Control-Request-Method` header for cacheability.
// If there is an intermediate cache server such as a CDN, if a plain
// OPTIONS request is sent, it may be cached. But valid preflight requests
// have this header, so it will be cached separately.
if ($request->is('OPTIONS')) {
$response->appendHeader('Vary', 'Access-Control-Request-Method');
}
return $this->cors->addResponseHeaders($request, $response);
}
}
TokenAuth.php
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter Shield.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Shield\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens;
/**
* Access Token Authentication Filter.
*
* Personal Access Token authentication for web applications.
*/
class TokenAuth implements FilterInterface
{
/**
* Do whatever processing this filter needs to do.
* By default, it should not return anything during
* normal execution. However, when an abnormal state
* is found, it should return an instance of
* CodeIgniter\HTTP\Response. If it does, script
* execution will end and that Response will be
* sent back to the client, allowing for error pages,
* redirects, etc.
*
* @param array|null $arguments
*
* @return RedirectResponse|void
*/
public function before(RequestInterface $request, $arguments = null)
{
if (! $request instanceof IncomingRequest) {
return;
}
/** @var AccessTokens $authenticator */
$authenticator = auth('tokens')->getAuthenticator();
$result = $authenticator->attempt([
'token' => $request->getHeaderLine(setting('Auth.authenticatorHeader')['tokens'] ?? 'Authorization'),
]);
if (! $result->isOK() || (! empty($arguments) && $result->extraInfo()->tokenCant($arguments[0]))) {
return service('response')
->setStatusCode(Response::HTTP_UNAUTHORIZED)
->setJson(['message' => lang('Auth.badToken')]);
}
if (setting('Auth.recordActiveDate')) {
$authenticator->recordActiveDate();
}
// Block inactive users when Email Activation is enabled
$user = $authenticator->getUser();
if ($user !== null && ! $user->isActivated()) {
$authenticator->logout();
return service('response')
->setStatusCode(Response::HTTP_FORBIDDEN)
->setJson(['message' => lang('Auth.activationBlocked')]);
}
}
/**
* We don't have anything to do here.
*
* @param array|null $arguments
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
{
}
}