Skip to content
This repository has been archived by the owner on Dec 28, 2020. It is now read-only.

Commit

Permalink
Added support for excluded routes (#56)
Browse files Browse the repository at this point in the history
* Added support for excluded routes

* Fixed spec test

* Documentation update for excluded routes

* Fixed BC break and w/s issues

* Re-ordered constructor arguments

* Fixed key in routing.yml

* Set correct response code in test for invalid csrf

* Rollback the status code

* Added /protected-resource/excluded to behat bootstrap

Also renamed /allowed to /excluded for clarity

* Readme fixes
  • Loading branch information
leevigraham authored and dunglas committed Nov 5, 2018
1 parent 5c7e4d8 commit 4d50e23
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 4 deletions.
12 changes: 11 additions & 1 deletion DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,17 @@ public function getConfigTreeBuilder()
->end()
->end()
->end()
->end();
->arrayNode('exclude')
->prototype('array')
->children()
->scalarNode('path')->defaultFalse()->end()
->scalarNode('route')->defaultFalse()->end()
->scalarNode('host')->defaultFalse()->end()
->arrayNode('methods')->prototype('scalar')->end()->end()
->end()
->end()
->end()
->end();

return $treeBuilder;
}
Expand Down
1 change: 1 addition & 0 deletions DependencyInjection/DunglasAngularCsrfExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('dunglas_angular_csrf.cookie.set_on', $config['cookie']['set_on']);
$container->setParameter('dunglas_angular_csrf.header.name', $config['header']['name']);
$container->setParameter('dunglas_angular_csrf.secure', $config['secure']);
$container->setParameter('dunglas_angular_csrf.exclude', $config['exclude']);

$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
Expand Down
11 changes: 10 additions & 1 deletion EventListener/AngularCsrfValidationListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,29 @@ class AngularCsrfValidationListener
* @var string
*/
protected $headerName;
/**
* @var array
*/
private $exclude;

/**
* @param AngularCsrfTokenManager $angularCsrfTokenManager
* @param RouteMatcherInterface $routeMatcher
* @param array $routes
* @param string $headerName
* @param array $exclude
*/
public function __construct(
AngularCsrfTokenManager $angularCsrfTokenManager,
RouteMatcherInterface $routeMatcher,
array $routes,
$headerName
$headerName,
array $exclude = array()
) {
$this->angularCsrfTokenManager = $angularCsrfTokenManager;
$this->routeMatcher = $routeMatcher;
$this->routes = $routes;
$this->exclude = $exclude;
$this->headerName = $headerName;
}

Expand All @@ -69,6 +76,8 @@ public function onKernelRequest(GetResponseEvent $event)
if (
HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()
||
$this->routeMatcher->match($event->getRequest(), $this->exclude)
||
!$this->routeMatcher->match($event->getRequest(), $this->routes)
) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,9 @@ public function csrfProtectedAction(Request $request)

return new Response('Success', 400);
}

public function csrfNonProtectedAction(Request $request)
{
return new Response('Success', 200);
}
}
2 changes: 2 additions & 0 deletions Features/Context/Fixtures/config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ dunglas_angular_csrf:
secure:
- { path: ^/resource, methods: [POST, PUT, PATCH, LINK] }
- { path: ^/protected-resource, methods: [POST, PUT, PATCH, LINK] }
exclude:
- { path: ^/protected-resource/excluded, methods: [POST, PUT, PATCH, LINK] }

services:
test.dunglas_angular_csrf.token_manager:
Expand Down
6 changes: 6 additions & 0 deletions Features/Context/Fixtures/config/routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ test_csrf_protected_form:
defaults: { _controller: TestBundle:Test:csrfProtected, _validate_csrf: true }
requirements:
_method: POST

test_csrf_non_protected:
path: /protected-resource/excluded
defaults: { _controller: TestBundle:Test:csrfNonProtected, _validate_csrf: true }
requirements:
_method: POST
4 changes: 4 additions & 0 deletions Features/securing_endpoints.feature
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ Feature: CSRF protection
Scenario: The form csrf token is not disabled in favor of header one if its invalid
Given I send POST request to "/protected-resource" with invalid csrf header
Then the response code should be 403

Scenario: We should access excluded page which is behind secure
Given I send POST request to "/protected-resource/excluded" with invalid csrf header
Then the response code should be 200
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ dunglas_angular_csrf:
- { path: ^/api, methods: [POST, PUT, PATCH, LINK] }
- { route: ^api_v2_ }
- { host: example.com, methods: [POST, PUT, PATCH, DELETE, LINK] }
# Collection of patterns to exclude
exclude:
- { path: ^/api/exclude, methods: [POST, PUT, PATCH, LINK] }
- { route: ^api_v2_exclude }
- { host: exclude-example.com, methods: [POST, PUT, PATCH, DELETE, LINK] }

```

Your app is now secured.
Expand Down Expand Up @@ -111,6 +117,9 @@ dunglas_angular_csrf:
# Patterns of URLs to check for a valid CSRF token
secure:
- { path: "^/url-pattern", route: "^route_name_pattern$", host: "example.com", methods: [GET, POST] }
# Patterns to exclude from secure routes
exclude:
- { path: "^/url-pattern/exclude", route: "^route_name_pattern$", host: "example.com", methods: [GET, POST] }
```

## Integration with the Symfony Form Component
Expand Down
1 change: 1 addition & 0 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<argument type="service" id="dunglas_angular_csrf.route_matcher" />
<argument>%dunglas_angular_csrf.secure%</argument>
<argument>%dunglas_angular_csrf.header.name%</argument>
<argument>%dunglas_angular_csrf.exclude%</argument>

<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="12" />
</service>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public function it_loads(ContainerBuilder $container, ParameterBagInterface $par
array('path' => '/bruay', 'route' => false, 'host' => false, 'methods' => array('HEAD', 'LINK')),
array('path' => false, 'route' => false, 'host' => 'example.com', 'methods' => array()),
),
'exclude' => array(
array('path' => '^/lille/excluded', 'route' => false, 'host' => false, 'methods' => array()),
),
),
);

Expand All @@ -68,6 +71,7 @@ public function it_loads(ContainerBuilder $container, ParameterBagInterface $par
$container->setParameter('dunglas_angular_csrf.cookie.set_on', $configs['dunglas_angular_csrf']['cookie']['set_on'])->shouldBeCalled();
$container->setParameter('dunglas_angular_csrf.header.name', $configs['dunglas_angular_csrf']['header']['name'])->shouldBeCalled();
$container->setParameter('dunglas_angular_csrf.secure', $configs['dunglas_angular_csrf']['secure'])->shouldBeCalled();
$container->setParameter('dunglas_angular_csrf.exclude', $configs['dunglas_angular_csrf']['exclude'])->shouldBeCalled();
$container->setDefinition('dunglas_angular_csrf.token_manager', Argument::type('Symfony\Component\DependencyInjection\Definition'))->shouldBeCalled();
$container->setDefinition('dunglas_angular_csrf.route_matcher', Argument::type('Symfony\Component\DependencyInjection\Definition'))->shouldBeCalled();
$container->setDefinition('dunglas_angular_csrf.validation_listener', Argument::type('Symfony\Component\DependencyInjection\Definition'))->shouldBeCalled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@ class AngularCsrfValidationListenerSpec extends ObjectBehavior
const INVALID_TOKEN = 'invalid';

private $routes = array('^/secured');
private $excluded = array('^/secured/excluded');
private $secureValidRequest;
private $secureInvalidRequest;
private $unsecureRequest;
private $excludedSecureRequest;

public function let(
AngularCsrfTokenManager $tokenManager,
RouteMatcherInterface $routeMatcher,
Request $secureValidRequest,
Request $secureInvalidRequest,
Request $unsecureRequest,
Request $excludedSecureRequest,
HeaderBag $validHeaders,
HeaderBag $invalidHeaders
) {
Expand All @@ -52,12 +55,24 @@ public function let(
$this->secureInvalidRequest->headers = $invalidHeaders;

$this->unsecureRequest = $unsecureRequest;
$this->excludedSecureRequest = $excludedSecureRequest;

$routeMatcher->match($this->secureValidRequest, $this->routes)->willReturn(true);
$routeMatcher->match($this->secureValidRequest, $this->excluded)->willReturn(false);
$routeMatcher->match($this->secureInvalidRequest, $this->routes)->willReturn(true);
$routeMatcher->match($this->secureInvalidRequest, $this->excluded)->willReturn(false);
$routeMatcher->match($this->unsecureRequest, $this->routes)->willReturn(false);

$this->beConstructedWith($tokenManager, $routeMatcher, $this->routes, self::HEADER_NAME);
$routeMatcher->match($this->unsecureRequest, $this->excluded)->willReturn(false);
$routeMatcher->match($this->excludedSecureRequest, $this->routes)->willReturn(true);
$routeMatcher->match($this->excludedSecureRequest, $this->excluded)->willReturn(true);

$this->beConstructedWith(
$tokenManager,
$routeMatcher,
$this->routes,
self::HEADER_NAME,
$this->excluded
);
}

public function it_is_initializable()
Expand Down Expand Up @@ -97,4 +112,12 @@ public function it_does_not_secure_when_it_does_not(GetResponseEvent $event)

$this->onKernelRequest($event);
}

public function it_does_not_secure_when_it_is_excluded(GetResponseEvent $event)
{
$event->getRequestType()->willReturn(HttpKernelInterface::MASTER_REQUEST);
$event->getRequest()->willReturn($this->excludedSecureRequest);

$this->onKernelRequest($event);
}
}

0 comments on commit 4d50e23

Please sign in to comment.