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

Optional debug logging #173

Merged
merged 5 commits into from Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
92 changes: 85 additions & 7 deletions EventListener/CorsListener.php
Expand Up @@ -12,6 +12,8 @@
namespace Nelmio\CorsBundle\EventListener;

use Nelmio\CorsBundle\Options\ResolverInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
Expand Down Expand Up @@ -41,54 +43,82 @@ class CorsListener
/** @var ResolverInterface */
protected $configurationResolver;

public function __construct(ResolverInterface $configurationResolver)
/** @var LoggerInterface */
private $logger;

public function __construct(ResolverInterface $configurationResolver, ?LoggerInterface $logger = null)
{
$this->configurationResolver = $configurationResolver;

if (null === $logger) {
$logger = new NullLogger();
}
$this->logger = $logger;
}

public function onKernelRequest(RequestEvent $event): void
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
$this->logger->debug('Not a master type request, skipping CORS checks.');

return;
}

$request = $event->getRequest();

if (!$options = $this->configurationResolver->getOptions($request)) {
$this->logger->debug('Could not get options for request, skipping CORS checks.');
return;
}

// if the "forced_allow_origin_value" option is set, add a listener which will set or override the "Access-Control-Allow-Origin" header
if (!empty($options['forced_allow_origin_value'])) {
$this->logger->debug(sprintf(
"The 'forced_allow_origin_value' option is set to '%s', adding a listener to set or override the 'Access-Control-Allow-Origin' header.",
$options['forced_allow_origin_value']
));

$request->attributes->set(self::SHOULD_FORCE_ORIGIN_ATTR, true);
}

// skip if not a CORS request
if (!$request->headers->has('Origin')) {
$this->logger->debug("Request does not have 'Origin' header, skipping CORS.");

return;
}

if ($options['skip_same_as_origin'] && $request->headers->get('Origin') === $request->getSchemeAndHttpHost()) {
$this->logger->debug("The 'Origin' header of the request equals the scheme and host the request was sent to, skipping CORS.");

return;
}

// perform preflight checks
if ('OPTIONS' === $request->getMethod() && $request->headers->has('Access-Control-Request-Method')) {
$this->logger->debug("Request is a preflight check, setting event response now.");

$event->setResponse($this->getPreflightResponse($request, $options));

return;
}

if (!$this->checkOrigin($request, $options)) {
$this->logger->debug("Origin check failed.");

return;
}

$this->logger->debug("Origin is allowed, proceed with adding CORS response headers.");

$request->attributes->set(self::SHOULD_ALLOW_ORIGIN_ATTR, true);
}

public function onKernelResponse(ResponseEvent $event): void
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
$this->logger->debug("Not a master type request, skip adding CORS response headers.");

return;
}

Expand All @@ -98,26 +128,43 @@ public function onKernelResponse(ResponseEvent $event): void
$shouldForceOrigin = $request->attributes->getBoolean(self::SHOULD_FORCE_ORIGIN_ATTR);

if (!$shouldAllowOrigin && !$shouldForceOrigin) {
$this->logger->debug("The origin should not be allowed and not be forced, skip adding CORS response headers.");

return;
}

if (!$options = $this->configurationResolver->getOptions($request)) {
$this->logger->debug("Could not resolve options for request, skip adding CORS response headers.");

return;
}

if ($shouldAllowOrigin) {
$response = $event->getResponse();
// add CORS response headers
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
$origin = $request->headers->get('Origin');

$this->logger->debug(sprintf("Setting 'Access-Control-Allow-Origin' response header to '%s'.", $origin));

$response->headers->set('Access-Control-Allow-Origin', $origin);

if ($options['allow_credentials']) {
$this->logger->debug("Setting 'Access-Control-Allow-Credentials' to 'true'.");

$response->headers->set('Access-Control-Allow-Credentials', 'true');
}
if ($options['expose_headers']) {
$response->headers->set('Access-Control-Expose-Headers', strtolower(implode(', ', $options['expose_headers'])));
$headers = strtolower(implode(', ', $options['expose_headers']));

$this->logger->debug(sprintf("Setting 'Access-Control-Expose-Headers' response header to '%s'.", $headers));

$response->headers->set('Access-Control-Expose-Headers', $headers);
}
}

if ($shouldForceOrigin) {
$this->logger->debug(sprintf("Setting 'Access-Control-Allow-Origin' response header to '%s'.", $options['forced_allow_origin_value']));

$event->getResponse()->headers->set('Access-Control-Allow-Origin', $options['forced_allow_origin_value']);
}
}
Expand All @@ -128,34 +175,53 @@ protected function getPreflightResponse(Request $request, array $options): Respo
$response->setVary(['Origin']);

if ($options['allow_credentials']) {
$this->logger->debug("Setting 'Access-Control-Allow-Credentials' response header to 'true'.");

$response->headers->set('Access-Control-Allow-Credentials', 'true');
}
if ($options['allow_methods']) {
$response->headers->set('Access-Control-Allow-Methods', implode(', ', $options['allow_methods']));
$methods = implode(', ', $options['allow_methods']);

$this->logger->debug(sprintf("Setting 'Access-Control-Allow-Methods' response header to '%s'.", $methods));

$response->headers->set('Access-Control-Allow-Methods', $methods);
}
if ($options['allow_headers']) {
$headers = $this->isWildcard($options, 'allow_headers')
? $request->headers->get('Access-Control-Request-Headers')
: implode(', ', $options['allow_headers']);

if ($headers) {
$this->logger->debug(sprintf("Setting 'Access-Control-Allow-Headers' response header to '%s'.", $headers));

$response->headers->set('Access-Control-Allow-Headers', $headers);
}
}
if ($options['max_age']) {
$this->logger->debug(sprintf("Setting 'Access-Control-Max-Age' response header to '%d'.", $options['max_age']));

$response->headers->set('Access-Control-Max-Age', $options['max_age']);
}

if (!$this->checkOrigin($request, $options)) {
$this->logger->debug("Removing 'Access-Control-Allow-Origin' response header.");

$response->headers->remove('Access-Control-Allow-Origin');

return $response;
}

$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
$origin = $request->headers->get('Origin');

$this->logger->debug(sprintf("Setting 'Access-Control-Allow-Origin' response header to '%s'", $origin));

$response->headers->set('Access-Control-Allow-Origin', $origin);

// check request method
if (!in_array(strtoupper($request->headers->get('Access-Control-Request-Method')), $options['allow_methods'], true)) {
$method = strtoupper($request->headers->get('Access-Control-Request-Method'));
if (!in_array($method, $options['allow_methods'], true)) {
$this->logger->debug(sprintf("Method '%s' is not allowed.", $method));

$response->setStatusCode(405);

return $response;
Expand Down Expand Up @@ -203,22 +269,34 @@ protected function checkOrigin(Request $request, array $options): bool
if ($options['origin_regex'] === true) {
// origin regex matching
foreach ($options['allow_origin'] as $originRegexp) {
$this->logger->debug(sprintf("Matching origin regex '%s' to origin '%s'.", $originRegexp, $origin));

if (preg_match('{'.$originRegexp.'}i', $origin)) {
$this->logger->debug(sprintf("Origin regex '%s' matches origin '%s'.", $originRegexp, $origin));

return true;
}
}
} else {
// old origin matching
if (in_array($origin, $options['allow_origin'])) {
$this->logger->debug(sprintf("Origin '%s' is allowed.", $origin));

return true;
}
}

$this->logger->debug(sprintf("Origin '%s' is not allowed.", $origin));

return false;
}

private function isWildcard(array $options, string $option): bool
{
return $options[$option] === true || (is_array($options[$option]) && in_array('*', $options[$option]));
$result = $options[$option] === true || (is_array($options[$option]) && in_array('*', $options[$option]));

$this->logger->debug(sprintf("Option '%s' is %s a wildcard.", $option, $result ? '' : 'not'));

return $result;
}
}
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -15,7 +15,8 @@
}
],
"require": {
"symfony/framework-bundle": "^4.4 || ^5.4 || ^6.0"
"symfony/framework-bundle": "^4.4 || ^5.4 || ^6.0",
"psr/log": "^1.0"
},
"require-dev": {
"mockery/mockery": "^1.2",
Expand Down