Resolve recommended-profile Critical and High findings, retain narrow analyzer exceptions, and update the edge-broker WebSocket dependency to a non-vulnerable release.
192 lines
7.4 KiB
PHP
192 lines
7.4 KiB
PHP
<?php
|
|
|
|
app_require('traits/economic_endpoint_t.php');
|
|
app_require('modules/economic/economic_m.php');
|
|
|
|
use traits\economic_endpoint_t;
|
|
|
|
if (!class_exists('EconomicEndpointGrantFallbackProbe')) {
|
|
class EconomicEndpointGrantFallbackProbe
|
|
{
|
|
use economic_endpoint_t {
|
|
resolve_agreement_grant_token as public resolveAgreementGrantToken;
|
|
resolve_app_secret_token as public resolveAppSecretToken;
|
|
assert_successful_response as public assertSuccessfulResponse;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!class_exists('EconomicMLegacyGrantFallbackProbe')) {
|
|
class EconomicMLegacyGrantFallbackProbe extends economic_m
|
|
{
|
|
public function __construct()
|
|
{
|
|
// No-op: tests inject private token fields directly.
|
|
}
|
|
|
|
public function configureTokens(string $appSecretToken, string $primaryGrant, string $secondaryGrant = ''): void
|
|
{
|
|
$this->setPrivateTokenField('app_token', $appSecretToken);
|
|
$this->setPrivateTokenField('appAccessGrant', $primaryGrant);
|
|
$this->setPrivateTokenField('appAccessGrant2', $secondaryGrant);
|
|
}
|
|
|
|
public function resolveAgreementGrantToken(bool $authToken2): string
|
|
{
|
|
return $this->resolve_agreement_grant_token($authToken2);
|
|
}
|
|
|
|
public function resolveAppSecretToken(): string
|
|
{
|
|
return $this->resolve_app_secret_token();
|
|
}
|
|
|
|
public function assertSuccessfulResponse(int $httpStatusCode, string|false $response): string
|
|
{
|
|
return $this->assert_successful_response($httpStatusCode, $response);
|
|
}
|
|
|
|
private function setPrivateTokenField(string $fieldName, string $value): void
|
|
{
|
|
$reflection = new ReflectionProperty(economic_m::class, $fieldName);
|
|
$reflection->setValue($this, $value);
|
|
}
|
|
}
|
|
}
|
|
|
|
beforeEach(function (): void {
|
|
global $ECONOMIC_API;
|
|
|
|
$ECONOMIC_API = [
|
|
'app_secret_token' => 'test-secret',
|
|
'app_access_grant' => 'primary-grant',
|
|
'app_access_grant2' => 'secondary-grant',
|
|
];
|
|
});
|
|
|
|
it('uses grant #2 in economic_endpoint_t when explicitly requested and configured', function (): void {
|
|
$probe = new EconomicEndpointGrantFallbackProbe();
|
|
|
|
expect($probe->resolveAgreementGrantToken(true))->toBe('secondary-grant');
|
|
expect($probe->resolveAgreementGrantToken(false))->toBe('primary-grant');
|
|
});
|
|
|
|
it('falls back to grant #1 in economic_endpoint_t when grant #2 is missing', function (): void {
|
|
global $ECONOMIC_API;
|
|
$ECONOMIC_API['app_access_grant2'] = '';
|
|
|
|
$probe = new EconomicEndpointGrantFallbackProbe();
|
|
|
|
expect($probe->resolveAgreementGrantToken(true))->toBe('primary-grant');
|
|
expect($probe->resolveAgreementGrantToken(false))->toBe('primary-grant');
|
|
});
|
|
|
|
it('throws for missing required primary grant in economic_endpoint_t', function (): void {
|
|
global $ECONOMIC_API;
|
|
$ECONOMIC_API['app_access_grant'] = '';
|
|
|
|
$probe = new EconomicEndpointGrantFallbackProbe();
|
|
|
|
expect(fn() => $probe->resolveAgreementGrantToken(false))
|
|
->toThrow(RuntimeException::class, 'Missing e-conomic agreement grant token');
|
|
});
|
|
|
|
it('throws for missing app secret in economic_endpoint_t', function (): void {
|
|
global $ECONOMIC_API;
|
|
$ECONOMIC_API['app_secret_token'] = '';
|
|
|
|
$probe = new EconomicEndpointGrantFallbackProbe();
|
|
|
|
expect(fn() => $probe->resolveAppSecretToken())
|
|
->toThrow(RuntimeException::class, 'Missing e-conomic app secret token');
|
|
});
|
|
|
|
it('uses grant #2 in legacy economic_m when available', function (): void {
|
|
$probe = new EconomicMLegacyGrantFallbackProbe();
|
|
$probe->configureTokens('test-secret', 'primary-grant', 'secondary-grant');
|
|
|
|
expect($probe->resolveAgreementGrantToken(true))->toBe('secondary-grant');
|
|
expect($probe->resolveAgreementGrantToken(false))->toBe('primary-grant');
|
|
});
|
|
|
|
it('falls back to grant #1 in legacy economic_m when grant #2 is missing', function (): void {
|
|
$probe = new EconomicMLegacyGrantFallbackProbe();
|
|
$probe->configureTokens('test-secret', 'primary-grant', '');
|
|
|
|
expect($probe->resolveAgreementGrantToken(true))->toBe('primary-grant');
|
|
});
|
|
|
|
it('throws for missing required primary grant in legacy economic_m', function (): void {
|
|
$probe = new EconomicMLegacyGrantFallbackProbe();
|
|
$probe->configureTokens('test-secret', '', 'secondary-grant');
|
|
|
|
expect(fn() => $probe->resolveAgreementGrantToken(false))
|
|
->toThrow(RuntimeException::class, 'Missing e-conomic agreement grant token');
|
|
});
|
|
|
|
it('throws for missing app secret in legacy economic_m', function (): void {
|
|
$probe = new EconomicMLegacyGrantFallbackProbe();
|
|
$probe->configureTokens('', 'primary-grant', 'secondary-grant');
|
|
|
|
expect(fn() => $probe->resolveAppSecretToken())
|
|
->toThrow(RuntimeException::class, 'Missing e-conomic app secret token');
|
|
});
|
|
|
|
it('throws deterministic upstream exceptions for non-2xx endpoint trait responses', function (): void {
|
|
$probe = new EconomicEndpointGrantFallbackProbe();
|
|
$errorPayload = json_encode([
|
|
'message' => 'Could not parse query string filter.',
|
|
'errors' => ['Filtering is not allowed on property \'invalidField\'.'],
|
|
'logId' => 'abc123',
|
|
'httpStatusCode' => 400,
|
|
]);
|
|
|
|
expect(fn() => $probe->assertSuccessfulResponse(400, $errorPayload))
|
|
->toThrow(RuntimeException::class, 'Could not parse query string filter.');
|
|
|
|
expect(fn() => $probe->assertSuccessfulResponse(502, 'Gateway timeout'))
|
|
->toThrow(RuntimeException::class, 'HTTP 502');
|
|
});
|
|
|
|
it('throws deterministic upstream exceptions for non-2xx legacy economic_m responses', function (): void {
|
|
$probe = new EconomicMLegacyGrantFallbackProbe();
|
|
$probe->configureTokens('test-secret', 'primary-grant', 'secondary-grant');
|
|
$errorPayload = json_encode([
|
|
'message' => 'Could not parse query string filter.',
|
|
'errors' => ['Filtering is not allowed on property \'invalidField\'.'],
|
|
'logId' => 'abc123',
|
|
'httpStatusCode' => 400,
|
|
]);
|
|
|
|
expect(fn() => $probe->assertSuccessfulResponse(400, $errorPayload))
|
|
->toThrow(RuntimeException::class, 'Could not parse query string filter.');
|
|
|
|
expect(fn() => $probe->assertSuccessfulResponse(500, '<html>server error</html>'))
|
|
->toThrow(RuntimeException::class, 'HTTP 500');
|
|
});
|
|
|
|
it('returns raw payload unchanged on successful HTTP statuses', function (): void {
|
|
$endpointProbe = new EconomicEndpointGrantFallbackProbe();
|
|
$legacyProbe = new EconomicMLegacyGrantFallbackProbe();
|
|
$legacyProbe->configureTokens('test-secret', 'primary-grant', 'secondary-grant');
|
|
|
|
$payload = '{"collection":[{"customerNumber":1000}],"pagination":{"results":1}}';
|
|
|
|
expect($endpointProbe->assertSuccessfulResponse(200, $payload))->toBe($payload);
|
|
expect($legacyProbe->assertSuccessfulResponse(200, $payload))->toBe($payload);
|
|
});
|
|
|
|
it('bounds e-conomic curl calls below the PHP request timeout', function (): void {
|
|
$legacyContent = file_get_contents(app_path('modules/economic/economic_m.php'));
|
|
$endpointContent = file_get_contents(app_path('traits/economic_endpoint_t.php'));
|
|
|
|
expect($legacyContent)->not->toBeFalse()
|
|
->and($endpointContent)->not->toBeFalse();
|
|
|
|
foreach ([(string)$legacyContent, (string)$endpointContent] as $content) {
|
|
expect($content)->toContain('CURLOPT_CONNECTTIMEOUT => 3')
|
|
->and($content)->toContain('CURLOPT_TIMEOUT => 30')
|
|
->and($content)->not->toContain('CURLOPT_TIMEOUT => 0');
|
|
}
|
|
});
|