Skip to content

Commit 86eb1aa

Browse files
committed
Merge pull request #2 from eugene-dounar/matcher
Introduce exact matching
2 parents e4800b8 + 2d0f00f commit 86eb1aa

File tree

5 files changed

+135
-43
lines changed

5 files changed

+135
-43
lines changed

phpunit.xml.dist

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit
4+
processIsolation="false"
5+
bootstrap="tests/bootstrap.php">
6+
<testsuites>
7+
<testsuite name="Test Suite">
8+
<directory suffix="Test.php">tests</directory>
9+
</testsuite>
10+
</testsuites>
11+
</phpunit>

src/Dflydev/Stack/Firewall.php

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ class Firewall implements HttpKernelInterface
1515
public function __construct(HttpKernelInterface $app, array $options = [])
1616
{
1717
$this->app = $app;
18-
$this->firewall = $options['firewall'];
18+
$this->matcher = new Firewall\Matcher($options['firewall']);
1919
unset($options['firewall']);
2020
$this->options = $options;
2121
}
2222

2323
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
2424
{
25-
$firewall = static::matchFirewall($request, $this->firewall);
25+
$firewall = $this->matcher->match($request);
2626

2727
if (null === $firewall) {
2828
// If no firewall is matched we can delegate immediately.
@@ -42,45 +42,4 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ
4242
->handle($request, $type, $catch);
4343
}
4444

45-
/**
46-
* Left public currently so we can test this by itself; eventually would
47-
* maybe like to make this a service that can be swapped out via
48-
* configuration? Not sure what to do with it, really.
49-
*/
50-
public static function matchFirewall(Request $request, array $firewalls)
51-
{
52-
if (!$firewalls) {
53-
// By default we should firewall the root request and not allow
54-
// anonymous requests. (will force challenge immediately)
55-
$firewalls = [
56-
['path' => '/']
57-
];
58-
}
59-
60-
$sortedFirewalls = [];
61-
foreach ($firewalls as $firewall) {
62-
if (!isset($firewall['anonymous'])) {
63-
$firewall['anonymous'] = false;
64-
}
65-
66-
if (isset($sortedFirewalls[$firewall['path']])) {
67-
throw new \InvalidArgumentException("Path '".$firewall['path']."' specified more than one time.");
68-
}
69-
70-
$sortedFirewalls[$firewall['path']] = $firewall;
71-
}
72-
73-
// We want to sort things by more specific paths first. This will
74-
// ensure that for instance '/' is never captured before any other
75-
// firewalled paths.
76-
krsort($sortedFirewalls);
77-
78-
foreach ($sortedFirewalls as $path => $firewall) {
79-
if (0 === strpos($request->getPathInfo(), $path)) {
80-
return $firewall;
81-
}
82-
}
83-
84-
return null;
85-
}
8645
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Dflydev\Stack\Firewall;
4+
5+
use Symfony\Component\HttpFoundation\Request;
6+
7+
class Matcher
8+
{
9+
private $firewalls = [];
10+
11+
public function __construct(array $firewalls)
12+
{
13+
if (!$firewalls) {
14+
// By default we should firewall the root request and not allow
15+
// anonymous requests. (will force challenge immediately)
16+
$firewalls = [
17+
['path' => '/']
18+
];
19+
}
20+
21+
foreach ($firewalls as $firewall) {
22+
// Set default values
23+
$firewall = $firewall + [
24+
'anonymous' => false,
25+
'exact_match' => false,
26+
'method' => null
27+
];
28+
$this->firewalls[] = $firewall;
29+
}
30+
31+
// We want to sort things by more specific paths first. This will
32+
// ensure that for instance '/' is never captured before any other
33+
// firewalled paths.
34+
uasort($this->firewalls, function($a, $b) {
35+
if ($a['path'] === $b['path']) {
36+
return 0;
37+
}
38+
return -($a > $b ? 1 : -1);
39+
});
40+
}
41+
42+
/**
43+
* Find the matching path
44+
*/
45+
public function match(Request $request)
46+
{
47+
foreach ($this->firewalls as $firewall) {
48+
if ($firewall['method'] !== null && $request->getMethod() !== $firewall['method']) {
49+
continue;
50+
}
51+
52+
if ($firewall['exact_match']) {
53+
if ($request->getPathInfo() === $firewall['path']) {
54+
return $firewall;
55+
}
56+
} elseif (0 === strpos($request->getPathInfo(), $firewall['path'])) {
57+
return $firewall;
58+
}
59+
}
60+
61+
return null;
62+
}
63+
}

tests/bootstrap.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
$loader = require __DIR__ . '/../vendor/autoload.php';
4+
$loader->add('unit\\', __DIR__);
5+
$loader->add('functional\\', __DIR__);

tests/unit/MatcherTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace unit;
4+
5+
use Symfony\Component\HttpFoundation\Request;
6+
7+
class MatcherTest extends \PHPUnit_Framework_TestCase
8+
{
9+
public function testPathMatching()
10+
{
11+
$this->matcher = new \Dflydev\Stack\Firewall\Matcher([
12+
['path' => '/'],
13+
['path' => '/users/', 'exact_match' => true],
14+
['path' => '/messages/', 'method' => 'POST'],
15+
['path' => '/cats/', 'method' => 'PUT', 'exact_match' => true],
16+
]);
17+
18+
$this->assertMatch('/', Request::create('/test'));
19+
$this->assertMatch('/', Request::create('/'));
20+
21+
$this->assertMatch('/users/', Request::create('/users/'));
22+
$this->assertMatch('/', Request::create('/users/123')); // no matching for exact_match
23+
24+
$this->assertMatch('/messages/', Request::create('/messages/123', 'POST'));
25+
$this->assertMatch('/messages/', Request::create('/messages/', 'POST'));
26+
$this->assertMatch('/', Request::create('/messages/', 'GET'));
27+
28+
$this->assertMatch('/cats/', Request::create('/cats/', 'PUT'));
29+
$this->assertMatch('/', Request::create('/cats/123', 'PUT'));
30+
$this->assertMatch('/', Request::create('/cats/', 'DELETE'));
31+
$this->assertMatch('/', Request::create('/cats/123', 'DELETE'));
32+
}
33+
34+
public function testNotMatching()
35+
{
36+
$matcher = new \Dflydev\Stack\Firewall\Matcher([['path' => '/api/']]);
37+
$this->assertNull($matcher->match(Request::create('/blah')));
38+
}
39+
40+
public function testSpecificMethodMatchingOrder()
41+
{
42+
$this->matcher = new \Dflydev\Stack\Firewall\Matcher([
43+
['path' => '/users/', 'method' => 'POST'],
44+
['path' => '/users/'],
45+
]);
46+
47+
$this->assertMatch('/users/', Request::create('/users/123'));
48+
}
49+
50+
protected function assertMatch($expected, $request)
51+
{
52+
$this->assertEquals($expected, $this->matcher->match($request)['path']);
53+
}
54+
}

0 commit comments

Comments
 (0)