Skip to content

Commit 9f91ae5

Browse files
authored
Make host comparison case-insensitive, fix substring comparison (#15)
1 parent 2f3d457 commit 9f91ae5

File tree

4 files changed

+92
-15
lines changed

4 files changed

+92
-15
lines changed

src/HostComparer.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace webignition\Guzzle\Middleware\HttpAuthentication;
4+
5+
class HostComparer
6+
{
7+
public function isHostMatch(string $requestHost, string $authenticationHost)
8+
{
9+
$requestHost = strtolower($requestHost);
10+
$authenticationHost = strtolower($authenticationHost);
11+
12+
return $requestHost === $authenticationHost
13+
|| preg_match('*' . preg_quote($authenticationHost, '*') . '$*i', $requestHost) > 0;
14+
}
15+
}

src/HttpAuthenticationMiddleware.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
class HttpAuthenticationMiddleware
88
{
9+
/**
10+
* @var HostComparer
11+
*/
12+
private $hostComparer;
13+
914
/**
1015
* @var string
1116
*/
@@ -14,13 +19,18 @@ class HttpAuthenticationMiddleware
1419
/**
1520
* @var string
1621
*/
17-
private $credentials = null;
22+
private $credentials = '';
1823

1924
/**
2025
* @var null string
2126
*/
2227
private $host = null;
2328

29+
public function __construct(HostComparer $hostComparer)
30+
{
31+
$this->hostComparer = $hostComparer;
32+
}
33+
2434
public function setType(string $type)
2535
{
2636
$this->type = $type;
@@ -59,12 +69,7 @@ public function __invoke(callable $handler): callable
5969
return $handler($request, $options);
6070
}
6171

62-
$requestHost = $request->getHeaderLine('host');
63-
64-
$hasHostMatch = $requestHost === $this->host
65-
&& preg_match('/' . preg_quote($this->host, '//') . '$/', $requestHost) > 0;
66-
67-
if (!$hasHostMatch) {
72+
if (!$this->hostComparer->isHostMatch($request->getHeaderLine('host'), $this->host)) {
6873
return $handler($request, $options);
6974
}
7075

tests/HostComparerTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/** @noinspection PhpDocSignatureInspection */
3+
4+
namespace webignition\Guzzle\Middleware\HttpAuthentication\Tests;
5+
6+
use webignition\Guzzle\Middleware\HttpAuthentication\HostComparer;
7+
8+
class HostComparerTest extends \PHPUnit\Framework\TestCase
9+
{
10+
/**
11+
* @var HostComparer
12+
*/
13+
private $hostComparer;
14+
15+
protected function setUp()
16+
{
17+
parent::setUp();
18+
19+
$this->hostComparer = new HostComparer();
20+
}
21+
22+
/**
23+
* @dataProvider isHostMatchDataProvider
24+
*/
25+
public function testIsHostMatch(string $requestHost, string $authenticationHost, bool $expectedIsHostMatch)
26+
{
27+
$this->assertEquals($expectedIsHostMatch, $this->hostComparer->isHostMatch($requestHost, $authenticationHost));
28+
}
29+
30+
public function isHostMatchDataProvider(): array
31+
{
32+
return [
33+
'lowercase equality' => [
34+
'requestHost' => 'example.com',
35+
'authenticationHost' => 'example.com',
36+
'expectedIsHostMatch' => true,
37+
],
38+
'uppercase equality' => [
39+
'requestHost' => 'EXAMPLE.COM',
40+
'authenticationHost' => 'EXAMPLE.COM',
41+
'expectedIsHostMatch' => true,
42+
],
43+
'authentication host is ending substring of request host' => [
44+
'requestHost' => 'subdomain.example.com',
45+
'authenticationHost' => 'example.com',
46+
'expectedIsHostMatch' => true,
47+
],
48+
'simple inequality' => [
49+
'requestHost' => 'example.com',
50+
'authenticationHost' => 'example.org',
51+
'expectedIsHostMatch' => false,
52+
],
53+
'authentication host is middle substring of request host' => [
54+
'requestHost' => 'subdomain.example.com.org',
55+
'authenticationHost' => 'example.com',
56+
'expectedIsHostMatch' => false,
57+
],
58+
];
59+
}
60+
}

tests/HttpAuthenticationMiddlewareTest.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
/** @noinspection PhpDocSignatureInspection */
23

34
namespace webignition\Guzzle\Middleware\HttpAuthentication\Tests;
45

@@ -7,6 +8,7 @@
78
use webignition\Guzzle\Middleware\HttpAuthentication\AuthorizationType;
89
use webignition\Guzzle\Middleware\HttpAuthentication\AuthorizationHeader;
910
use webignition\Guzzle\Middleware\HttpAuthentication\CredentialsFactory;
11+
use webignition\Guzzle\Middleware\HttpAuthentication\HostComparer;
1012
use webignition\Guzzle\Middleware\HttpAuthentication\HttpAuthenticationMiddleware;
1113

1214
class HttpAuthenticationMiddlewareTest extends \PHPUnit\Framework\TestCase
@@ -25,16 +27,11 @@ protected function setUp()
2527
{
2628
parent::setUp();
2729

28-
$this->httpAuthenticationMiddleware = new HttpAuthenticationMiddleware();
30+
$this->httpAuthenticationMiddleware = new HttpAuthenticationMiddleware(new HostComparer());
2931
}
3032

3133
/**
3234
* @dataProvider invokeAuthorizationNotSetDataProvider
33-
*
34-
* @param string $requestHost
35-
* @param string|null $type
36-
* @param string|null $credentials
37-
* @param string|null $host
3835
*/
3936
public function testInvokeAuthorizationNotSet(
4037
string $requestHost,
@@ -181,13 +178,13 @@ function ($returnedRequest, $returnedOptions) use ($originalRequest, $options) {
181178
/**
182179
* @return MockInterface|RequestInterface
183180
*/
184-
private function createOriginalRequest(): RequestInterface
181+
private function createOriginalRequest(string $host = self::HOST): RequestInterface
185182
{
186183
$request = \Mockery::mock(RequestInterface::class);
187184
$request
188185
->shouldReceive('getHeaderLine')
189186
->with('host')
190-
->andReturn(self::HOST);
187+
->andReturn($host);
191188

192189
return $request;
193190
}

0 commit comments

Comments
 (0)