Skip to content

Commit c9fce76

Browse files
committed
Merge pull request #8 from aws/feature/configurable-trusted-domains
Allow specifying which host patterns to trust in the constructor
2 parents d623adb + 9e32576 commit c9fce76

File tree

4 files changed

+126
-12
lines changed

4 files changed

+126
-12
lines changed

phpunit.xml.dist

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@
66
<directory>./tests</directory>
77
</testsuite>
88
</testsuites>
9+
10+
<filter>
11+
<whitelist>
12+
<directory suffix=".php">src/</directory>
13+
</whitelist>
14+
</filter>
915
</phpunit>

src/MessageValidator.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,33 @@ class MessageValidator
1515
*/
1616
private $certClient;
1717

18+
/** @var string */
19+
private $hostPattern;
20+
21+
/**
22+
* @var string A pattern that will match all regional SNS endpoints, e.g.:
23+
* - sns.<region>.amazonaws.com (AWS)
24+
* - sns.us-gov-west-1.amazonaws.com (AWS GovCloud)
25+
* - sns.cn-north-1.amazonaws.com.cn (AWS China)
26+
*/
27+
private static $defaultHostPattern
28+
= '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';
29+
1830
/**
31+
* Constructs the Message Validator object and ensures that openssl is
32+
* installed.
33+
*
1934
* @param callable $certClient Callable used to download the certificate.
2035
* Should have the following function signature:
2136
* `function (string $certUrl) : string $certContent`
22-
*
23-
* @throws \RuntimeException If openssl is not installed
37+
* @param string $hostNamePattern
2438
*/
25-
public function __construct(callable $certClient = null)
26-
{
39+
public function __construct(
40+
callable $certClient = null,
41+
$hostNamePattern = ''
42+
) {
2743
$this->certClient = $certClient ?: 'file_get_contents';
44+
$this->hostPattern = $hostNamePattern ?: self::$defaultHostPattern;
2845
}
2946

3047
/**
@@ -125,14 +142,12 @@ public function getStringToSign(Message $message)
125142
*/
126143
private function validateUrl($url)
127144
{
128-
// The cert URL must be https, a .pem, and match the following pattern.
129-
static $hostPattern = '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';
130145
$parsed = parse_url($url);
131146
if (empty($parsed['scheme'])
132147
|| empty($parsed['host'])
133148
|| $parsed['scheme'] !== 'https'
134149
|| substr($url, -4) !== '.pem'
135-
|| !preg_match($hostPattern, $parsed['host'])
150+
|| !preg_match($this->hostPattern, $parsed['host'])
136151
) {
137152
throw new InvalidSnsMessageException(
138153
'The certificate is located on an invalid domain.'

tests/MessageTest.php

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,42 @@ public function testGetters()
3131
}
3232
}
3333

34-
public function testFactorySucceedsWithGoodData()
34+
public function testIterable()
3535
{
36-
$this->assertInstanceOf('Aws\Sns\Message', new Message($this->messageData));
36+
$message = new Message($this->messageData);
37+
38+
$this->assertInstanceOf('Traversable', $message);
39+
foreach ($message as $key => $value) {
40+
$this->assertTrue(isset($this->messageData[$key]));
41+
$this->assertEquals($value, $this->messageData[$key]);
42+
}
43+
}
44+
45+
/**
46+
* @dataProvider messageTypeProvider
47+
*
48+
* @param string $messageType
49+
*/
50+
public function testConstructorSucceedsWithGoodData($messageType)
51+
{
52+
$this->assertInstanceOf('Aws\Sns\Message', new Message(
53+
['Type' => $messageType] + $this->messageData
54+
));
55+
}
56+
57+
public function messageTypeProvider()
58+
{
59+
return [
60+
['Notification'],
61+
['SubscriptionConfirmation'],
62+
['UnsubscribeConfirmation'],
63+
];
3764
}
3865

3966
/**
4067
* @expectedException \InvalidArgumentException
4168
*/
42-
public function testFactoryFailsWithNoType()
69+
public function testConstructorFailsWithNoType()
4370
{
4471
$data = $this->messageData;
4572
unset($data['Type']);
@@ -49,11 +76,37 @@ public function testFactoryFailsWithNoType()
4976
/**
5077
* @expectedException \InvalidArgumentException
5178
*/
52-
public function testFactoryFailsWithMissingData()
79+
public function testConstructorFailsWithMissingData()
5380
{
5481
new Message(['Type' => 'Notification']);
5582
}
5683

84+
/**
85+
* @expectedException \InvalidArgumentException
86+
*/
87+
public function testRequiresTokenAndSubscribeUrlForSubscribeMessage()
88+
{
89+
new Message(
90+
['Type' => 'SubscriptionConfirmation'] + array_diff_key(
91+
$this->messageData,
92+
array_flip(['Token', 'SubscribeURL'])
93+
)
94+
);
95+
}
96+
97+
/**
98+
* @expectedException \InvalidArgumentException
99+
*/
100+
public function testRequiresTokenAndSubscribeUrlForUnsubscribeMessage()
101+
{
102+
new Message(
103+
['Type' => 'UnsubscribeConfirmation'] + array_diff_key(
104+
$this->messageData,
105+
array_flip(['Token', 'SubscribeURL'])
106+
)
107+
);
108+
}
109+
57110
public function testCanCreateFromRawPost()
58111
{
59112
$_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE'] = 'Notification';
@@ -87,4 +140,16 @@ public function testCreateFromRawPostFailsWithMissingData()
87140
Message::fromRawPostData();
88141
unset($_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE']);
89142
}
143+
144+
public function testArrayAccess()
145+
{
146+
$message = new Message($this->messageData);
147+
148+
$this->assertInstanceOf('ArrayAccess', $message);
149+
$message['foo'] = 'bar';
150+
$this->assertTrue(isset($message['foo']));
151+
$this->assertTrue($message['foo'] === 'bar');
152+
unset($message['foo']);
153+
$this->assertFalse(isset($message['foo']));
154+
}
90155
}

tests/MessageValidatorTest.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
namespace Aws\Sns;
33

44
/**
5-
* @covers MessageValidator
5+
* @covers Aws\Sns\MessageValidator
66
*/
77
class MessageValidatorTest extends \PHPUnit_Framework_TestCase
88
{
@@ -52,6 +52,19 @@ public function testValidateFailsWhenSignatureVersionIsInvalid()
5252
* @expectedExceptionMessage The certificate is located on an invalid domain.
5353
*/
5454
public function testValidateFailsWhenCertUrlInvalid()
55+
{
56+
$validator = new MessageValidator();
57+
$message = $this->getTestMessage([
58+
'SigningCertURL' => 'https://foo.amazonaws.com/bar.pem',
59+
]);
60+
$validator->validate($message);
61+
}
62+
63+
/**
64+
* @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
65+
* @expectedExceptionMessage The certificate is located on an invalid domain.
66+
*/
67+
public function testValidateFailsWhenCertUrlNotAPemFile()
5568
{
5669
$validator = new MessageValidator();
5770
$message = $this->getTestMessage([
@@ -60,6 +73,21 @@ public function testValidateFailsWhenCertUrlInvalid()
6073
$validator->validate($message);
6174
}
6275

76+
public function testValidatesAgainstCustomDomains()
77+
{
78+
$validator = new MessageValidator(
79+
function () {
80+
return self::$certificate;
81+
},
82+
'/^(foo|bar).example.com$/'
83+
);
84+
$message = $this->getTestMessage([
85+
'SigningCertURL' => 'https://foo.example.com/baz.pem',
86+
]);
87+
$message['Signature'] = $this->getSignature($validator->getStringToSign($message));
88+
$this->assertTrue($validator->isValid($message));
89+
}
90+
6391
/**
6492
* @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
6593
* @expectedExceptionMessage Cannot get the public key from the certificate.

0 commit comments

Comments
 (0)