Skip to content

Commit

Permalink
Merge pull request #8 from aws/feature/configurable-trusted-domains
Browse files Browse the repository at this point in the history
Allow specifying which host patterns to trust in the constructor
  • Loading branch information
jeskew committed Sep 3, 2015
2 parents d623adb + 9e32576 commit c9fce76
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 12 deletions.
6 changes: 6 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@
<directory>./tests</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
</phpunit>
29 changes: 22 additions & 7 deletions src/MessageValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,33 @@ class MessageValidator
*/
private $certClient;

/** @var string */
private $hostPattern;

/**
* @var string A pattern that will match all regional SNS endpoints, e.g.:
* - sns.<region>.amazonaws.com (AWS)
* - sns.us-gov-west-1.amazonaws.com (AWS GovCloud)
* - sns.cn-north-1.amazonaws.com.cn (AWS China)
*/
private static $defaultHostPattern
= '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';

/**
* Constructs the Message Validator object and ensures that openssl is
* installed.
*
* @param callable $certClient Callable used to download the certificate.
* Should have the following function signature:
* `function (string $certUrl) : string $certContent`
*
* @throws \RuntimeException If openssl is not installed
* @param string $hostNamePattern
*/
public function __construct(callable $certClient = null)
{
public function __construct(
callable $certClient = null,
$hostNamePattern = ''
) {
$this->certClient = $certClient ?: 'file_get_contents';
$this->hostPattern = $hostNamePattern ?: self::$defaultHostPattern;
}

/**
Expand Down Expand Up @@ -125,14 +142,12 @@ public function getStringToSign(Message $message)
*/
private function validateUrl($url)
{
// The cert URL must be https, a .pem, and match the following pattern.
static $hostPattern = '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';
$parsed = parse_url($url);
if (empty($parsed['scheme'])
|| empty($parsed['host'])
|| $parsed['scheme'] !== 'https'
|| substr($url, -4) !== '.pem'
|| !preg_match($hostPattern, $parsed['host'])
|| !preg_match($this->hostPattern, $parsed['host'])
) {
throw new InvalidSnsMessageException(
'The certificate is located on an invalid domain.'
Expand Down
73 changes: 69 additions & 4 deletions tests/MessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,42 @@ public function testGetters()
}
}

public function testFactorySucceedsWithGoodData()
public function testIterable()
{
$this->assertInstanceOf('Aws\Sns\Message', new Message($this->messageData));
$message = new Message($this->messageData);

$this->assertInstanceOf('Traversable', $message);
foreach ($message as $key => $value) {
$this->assertTrue(isset($this->messageData[$key]));
$this->assertEquals($value, $this->messageData[$key]);
}
}

/**
* @dataProvider messageTypeProvider
*
* @param string $messageType
*/
public function testConstructorSucceedsWithGoodData($messageType)
{
$this->assertInstanceOf('Aws\Sns\Message', new Message(
['Type' => $messageType] + $this->messageData
));
}

public function messageTypeProvider()
{
return [
['Notification'],
['SubscriptionConfirmation'],
['UnsubscribeConfirmation'],
];
}

/**
* @expectedException \InvalidArgumentException
*/
public function testFactoryFailsWithNoType()
public function testConstructorFailsWithNoType()
{
$data = $this->messageData;
unset($data['Type']);
Expand All @@ -49,11 +76,37 @@ public function testFactoryFailsWithNoType()
/**
* @expectedException \InvalidArgumentException
*/
public function testFactoryFailsWithMissingData()
public function testConstructorFailsWithMissingData()
{
new Message(['Type' => 'Notification']);
}

/**
* @expectedException \InvalidArgumentException
*/
public function testRequiresTokenAndSubscribeUrlForSubscribeMessage()
{
new Message(
['Type' => 'SubscriptionConfirmation'] + array_diff_key(
$this->messageData,
array_flip(['Token', 'SubscribeURL'])
)
);
}

/**
* @expectedException \InvalidArgumentException
*/
public function testRequiresTokenAndSubscribeUrlForUnsubscribeMessage()
{
new Message(
['Type' => 'UnsubscribeConfirmation'] + array_diff_key(
$this->messageData,
array_flip(['Token', 'SubscribeURL'])
)
);
}

public function testCanCreateFromRawPost()
{
$_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE'] = 'Notification';
Expand Down Expand Up @@ -87,4 +140,16 @@ public function testCreateFromRawPostFailsWithMissingData()
Message::fromRawPostData();
unset($_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE']);
}

public function testArrayAccess()
{
$message = new Message($this->messageData);

$this->assertInstanceOf('ArrayAccess', $message);
$message['foo'] = 'bar';
$this->assertTrue(isset($message['foo']));
$this->assertTrue($message['foo'] === 'bar');
unset($message['foo']);
$this->assertFalse(isset($message['foo']));
}
}
30 changes: 29 additions & 1 deletion tests/MessageValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
namespace Aws\Sns;

/**
* @covers MessageValidator
* @covers Aws\Sns\MessageValidator
*/
class MessageValidatorTest extends \PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -52,6 +52,19 @@ public function testValidateFailsWhenSignatureVersionIsInvalid()
* @expectedExceptionMessage The certificate is located on an invalid domain.
*/
public function testValidateFailsWhenCertUrlInvalid()
{
$validator = new MessageValidator();
$message = $this->getTestMessage([
'SigningCertURL' => 'https://foo.amazonaws.com/bar.pem',
]);
$validator->validate($message);
}

/**
* @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
* @expectedExceptionMessage The certificate is located on an invalid domain.
*/
public function testValidateFailsWhenCertUrlNotAPemFile()
{
$validator = new MessageValidator();
$message = $this->getTestMessage([
Expand All @@ -60,6 +73,21 @@ public function testValidateFailsWhenCertUrlInvalid()
$validator->validate($message);
}

public function testValidatesAgainstCustomDomains()
{
$validator = new MessageValidator(
function () {
return self::$certificate;
},
'/^(foo|bar).example.com$/'
);
$message = $this->getTestMessage([
'SigningCertURL' => 'https://foo.example.com/baz.pem',
]);
$message['Signature'] = $this->getSignature($validator->getStringToSign($message));
$this->assertTrue($validator->isValid($message));
}

/**
* @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
* @expectedExceptionMessage Cannot get the public key from the certificate.
Expand Down

0 comments on commit c9fce76

Please sign in to comment.