Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
uses: shivammathur/setup-php@master
with:
php-version: ${{ matrix.php-versions }}
tools: composer:v1
tools: composer:v2

- name: Check PHP Version
run: php -v
Expand Down
17 changes: 12 additions & 5 deletions src/Rox/Core/Configuration/ConfigurationParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,15 @@ public function __construct(
* @param array $configData
* @return bool
*/
protected function isVerifiedSignature($configData)
protected function isVerifiedSignature($configData, $apiKey = null)
{
if ($this->_roxOptions->isSignatureDisabled()) {
// Check if signature verification is explicitly disabled
if ($this->_roxOptions !== null && $this->_roxOptions->isSignatureDisabled()) {
return true;
}

// Auto-disable signature verification for CBP/Unify keys (they don't send signature_v0)
if ($apiKey !== null && \Rox\Core\Utils\ApiKeyHelpers::isCBPApiKey($apiKey)) {
return true;
}

Expand Down Expand Up @@ -107,16 +113,17 @@ public function parse(ConfigurationFetchResult $fetchResult, SdkSettingsInterfac
{
try {
$json = $fetchResult->getParsedData();
$apiKey = $sdkSettings !== null ? $sdkSettings->getApiKey() : null;

if (!$this->isVerifiedSignature($json)) {
if (!$this->isVerifiedSignature($json, $apiKey)) {
$this->_configurationFetchedInvoker->invokeWithError(FetcherError::SignatureVerificationError);
$this->_errorReporter->report(
"Failed to validate signature",
new Exception(
sprintf(
"Data : %s Signature : %s",
(string) $json["data"],
(string) $json["signature_v0"]
(string) ($json["data"] ?? ""),
(string) ($json["signature_v0"] ?? "")
)
)
);
Expand Down
3 changes: 0 additions & 3 deletions src/Rox/Server/Rox.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,6 @@ public static function setup($apiKey, RoxOptions $roxOptions = null)
try {
if (!$roxOptions) {
$roxOptionsBuilder = new RoxOptionsBuilder();
if (ApiKeyHelpers::isCBPApiKey($apiKey)) {
$roxOptionsBuilder->setDisableSignatureVerification(true);
}
$roxOptions = new RoxOptions($roxOptionsBuilder);
}

Expand Down
66 changes: 64 additions & 2 deletions tests/Rox/Core/Configuration/ConfigurationParserTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function testWillReturnNullWhenUnexpectedException()
"nodata":"{\"application\":\"12345\",\"targetGroups\":[{\"condition\":\"eq(true,true)\",\"_id\":\"12345\"},{\"_id\":\"123456\",\"condition\":\"eq(true,true)\"}],\"experiments\":[{\"deploymentConfiguration\":{\"condition\":\"ifThen(and(true, true)\"},\"featureFlags\":[{\"name\":\"FeatureFlags.isFeatureFlagsEnabled\"}],\"archived\":false,\"name\":\"Feature Flags Drawer Item\",\"_id\":\"1\"},{\"deploymentConfiguration\":{\"condition\":\"ifThen(and(true, true)\"},\"featureFlags\":[{\"name\":\"Invitations.isInvitationsEnabled\"}],\"archived\":false,\"name\":\"Enable Modern Invitations\",\"_id\":\"2\"}] } ",
"signature_v0":"K/bEQCkRXa6+uFr5H2jCRCaVgmtsTwbgfrFGVJ9NebfMH8CgOhCDIvF4TM1Vyyl0bGS9a4r4Qgi/g63NDBWk0ZbRrKAUkVG56V3/bI2GDHxFvRNrNbiPmFv/wmLLuwgh1mdzU0EwLG4M7yXoNXtMr6Jli8t4xfBOaWW1g0QpASkiWa7kdTamVip/1QygyUuhX5hOyUMpy4Ny9Hi/QPvVBn6GDMxQtxpLfTavU9cBly2D7Ex8Z7sUUOKeoEJcdsoF1QzH14XvA2HQSICESz7D/uld0PNdG0tMj9NlAZfki8eY2KuUe/53Z0Og5WrqQUxiAdPuJoZr6+kSqlASZrrkYw==",
"signed_date":"2018-01-09T19:02:00.720Z"
}
}
EOT;

$configFetchResult = new ConfigurationFetchResult(json_decode($json, true), ConfigurationSource::CDN);
Expand All @@ -110,7 +110,9 @@ public function testWillReturnNullWhenUnexpectedException()
$this->assertNull($conf);

$this->assertNotNull($this->_cfiEvent);
$this->assertEquals(FetcherError::Unknown, $this->_cfiEvent->getErrorDetails());
// Fixed: Now properly returns SignatureVerificationError when data field is missing
// instead of letting it throw an exception that gets caught as Unknown
$this->assertEquals(FetcherError::SignatureVerificationError, $this->_cfiEvent->getErrorDetails());
}

public function testWillReturnNullWhenWrongSignature()
Expand Down Expand Up @@ -202,4 +204,64 @@ public function testWillParseExperimentsAndTargetGroups()

$this->assertNull($this->_cfiEvent);
}

public function testWillReturnNullWhenSignatureMissingAndVerificationNotDisabled()
{
$json = <<<EOT
{
"data":"{\"application\":\"12345\", \"targetGroups\": [], \"experiments\": []}",
"signed_date":"2018-01-09T19:02:00.720Z"
}
EOT;

$this->_mockedOptions->shouldReceive('isSignatureDisabled')
->andReturn(false);

$configFetchResult = new ConfigurationFetchResult(json_decode($json, true), ConfigurationSource::API);

$cp = new ConfigurationParser($this->_sf, $this->_kf, $this->_errRe, $this->_cfi, $this->_mockedOptions);
$this->assertNull($cp->parse($configFetchResult, $this->_sdk));
$this->assertNotNull($this->_cfiEvent);
$this->assertEquals(FetcherError::SignatureVerificationError, $this->_cfiEvent->getErrorDetails());
}

public function testWillParseSuccessfullyWhenSignatureMissingButVerificationDisabled()
{
$json = <<<EOT
{
"data":"{\"application\":\"12345\", \"targetGroups\": [], \"experiments\": []}",
"signed_date":"2018-01-09T19:02:00.720Z"
}
EOT;

$this->_mockedOptions->shouldReceive('isSignatureDisabled')
->andReturn(true);

$configFetchResult = new ConfigurationFetchResult(json_decode($json, true), ConfigurationSource::API);

$cp = new ConfigurationParser($this->_sf, $this->_kf, $this->_errRe, $this->_cfi, $this->_mockedOptions);
$conf = $cp->parse($configFetchResult, $this->_sdk);

$this->assertNotNull($conf);
$this->assertEquals(count($conf->getTargetGroups()), 0);
$this->assertEquals(count($conf->getExperiments()), 0);
}

public function testWillHandleNullRoxOptionsGracefully()
{
$json = <<<EOT
{
"data":"{\"application\":\"12345\", \"targetGroups\": [], \"experiments\": []}",
"signed_date":"2018-01-09T19:02:00.720Z"
}
EOT;

$configFetchResult = new ConfigurationFetchResult(json_decode($json, true), ConfigurationSource::API);

// Pass null for roxOptions - this should not crash
$cp = new ConfigurationParser($this->_sf, $this->_kf, $this->_errRe, $this->_cfi, null);

// Should return null because signature is missing and we can't check if verification is disabled
$this->assertNull($cp->parse($configFetchResult, $this->_sdk));
}
}
Loading