Skip to content

Commit 2c96380

Browse files
Add signatureVersion config
1 parent 1c2ba09 commit 2c96380

File tree

4 files changed

+73
-11
lines changed

4 files changed

+73
-11
lines changed

src/Api/Utils/ApiUtils.php

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -242,16 +242,18 @@ public static function serializeResponsiveBreakpoints(?array $breakpoints): bool
242242
*
243243
* @internal
244244
*/
245-
public static function serializeQueryParams(array $parameters = []): string
245+
public static function serializeQueryParams(array $parameters = [], int $signatureVersion = 2): string
246246
{
247-
// URL encode & characters in values to prevent parameter smuggling
248-
$encodedParameters = ArrayUtils::mapAssoc(
249-
static fn($key, $value) => str_replace('&', '%26', $value),
250-
$parameters
251-
);
252-
247+
// Version 2: URL encode & characters in values to prevent parameter smuggling
248+
if ($signatureVersion >= 2) {
249+
$parameters = ArrayUtils::mapAssoc(
250+
static fn($key, $value) => str_replace('&', '%26', $value),
251+
$parameters
252+
);
253+
}
254+
253255
return ArrayUtils::implodeAssoc(
254-
$encodedParameters,
256+
$parameters,
255257
self::QUERY_STRING_OUTER_DELIMITER,
256258
self::QUERY_STRING_INNER_DELIMITER
257259
);
@@ -263,6 +265,7 @@ public static function serializeQueryParams(array $parameters = []): string
263265
* @param array $parameters Parameters to sign.
264266
* @param string $secret The API secret of the cloud.
265267
* @param string $signatureAlgorithm Signature algorithm
268+
* @param int $signatureVersion Signature version (1 or 2)
266269
*
267270
* @return string The signature.
268271
*
@@ -271,13 +274,14 @@ public static function serializeQueryParams(array $parameters = []): string
271274
public static function signParameters(
272275
array $parameters,
273276
string $secret,
274-
string $signatureAlgorithm = Utils::ALGO_SHA1
277+
string $signatureAlgorithm = Utils::ALGO_SHA1,
278+
int $signatureVersion = 2
275279
): string {
276280
$parameters = array_map(self::class . '::serializeSimpleApiParam', $parameters);
277281

278282
ksort($parameters);
279283

280-
$signatureContent = self::serializeQueryParams($parameters);
284+
$signatureContent = self::serializeQueryParams($parameters, $signatureVersion);
281285

282286
return Utils::sign($signatureContent, $secret, false, $signatureAlgorithm);
283287
}
@@ -293,7 +297,8 @@ public static function signRequest(?array &$parameters, CloudConfig $cloudConfig
293297
$parameters['signature'] = self::signParameters(
294298
$parameters,
295299
$cloudConfig->apiSecret,
296-
$cloudConfig->signatureAlgorithm
300+
$cloudConfig->signatureAlgorithm,
301+
$cloudConfig->signatureVersion
297302
);
298303
$parameters['api_key'] = $cloudConfig->apiKey;
299304
}

src/Configuration/CloudConfig.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* target="_blank">Get account details from the Cloudinary Console.</a>
2020
*
2121
* @property ?string $signatureAlgorithm By default, set to self::DEFAULT_SIGNATURE_ALGORITHM.
22+
* @property ?int $signatureVersion By default, set to self::DEFAULT_SIGNATURE_VERSION.
2223
*
2324
* @api
2425
*/
@@ -29,13 +30,15 @@ class CloudConfig extends BaseConfigSection
2930
public const CONFIG_NAME = 'cloud';
3031

3132
public const DEFAULT_SIGNATURE_ALGORITHM = Utils::ALGO_SHA1;
33+
public const DEFAULT_SIGNATURE_VERSION = 2;
3234

3335
// Supported parameters
3436
public const CLOUD_NAME = 'cloud_name';
3537
public const API_KEY = 'api_key';
3638
public const API_SECRET = 'api_secret';
3739
public const OAUTH_TOKEN = 'oauth_token';
3840
public const SIGNATURE_ALGORITHM = 'signature_algorithm';
41+
public const SIGNATURE_VERSION = 'signature_version';
3942

4043
/**
4144
* @var array of configuration keys that contain sensitive data that should not be exported (for example api key)
@@ -69,6 +72,11 @@ class CloudConfig extends BaseConfigSection
6972
*/
7073
protected ?string $signatureAlgorithm = null;
7174

75+
/**
76+
* Sets the signature version (2 by default).
77+
*/
78+
protected ?int $signatureVersion = null;
79+
7280
/**
7381
* Serialises configuration section to a string representation.
7482
*

src/Configuration/CloudConfigTrait.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ public function signatureAlgorithm(string $signatureAlgorithm): static
5959
return $this->setCloudConfig(CloudConfig::SIGNATURE_ALGORITHM, $signatureAlgorithm);
6060
}
6161

62+
/**
63+
* Sets the signature version.
64+
*
65+
* @param int $signatureVersion The signature version to use. (Can be 1 or 2).
66+
*
67+
* @return $this
68+
*
69+
* @api
70+
*/
71+
public function signatureVersion(int $signatureVersion): static
72+
{
73+
return $this->setCloudConfig(CloudConfig::SIGNATURE_VERSION, $signatureVersion);
74+
}
75+
6276
/**
6377
* Sets the Cloud configuration key with the specified value.
6478
*

tests/Unit/Utils/ApiUtilsTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,4 +389,39 @@ public function testApiSignRequestPreventsParameterSmuggling()
389389
$expectedSmuggledSignature = '7b4e3a539ff1fa6e6700c41b3a2ee77586a025f9';
390390
self::assertEquals($expectedSmuggledSignature, $signatureSmugggled);
391391
}
392+
393+
/**
394+
* Should apply the configured signature version from CloudConfig.
395+
*/
396+
public function testConfiguredSignatureVersionIsApplied()
397+
{
398+
$params = [
399+
'cloud_name' => self::API_SIGN_REQUEST_CLOUD_NAME,
400+
'timestamp' => 1568810420,
401+
'notification_url' => 'https://fake.com/callback?a=1&tags=hello,world'
402+
];
403+
404+
$config = new Configuration('cloudinary://key:' . self::API_SIGN_REQUEST_TEST_SECRET . '@test123');
405+
406+
// Test with signature version 1 (legacy behavior - no URL encoding)
407+
$config->cloud->signatureVersion = 1;
408+
$paramsV1 = $params;
409+
ApiUtils::signRequest($paramsV1, $config->cloud);
410+
$signatureV1 = $paramsV1['signature'];
411+
412+
// Test with signature version 2 (current behavior - with URL encoding)
413+
$config->cloud->signatureVersion = 2;
414+
$paramsV2 = $params;
415+
ApiUtils::signRequest($paramsV2, $config->cloud);
416+
$signatureV2 = $paramsV2['signature'];
417+
418+
// Signatures should be different, proving the version setting is applied
419+
self::assertNotEquals($signatureV1, $signatureV2,
420+
'Signature versions should produce different results');
421+
422+
// Version 2 should match the expected encoded signature
423+
$expectedV2Signature = '4fdf465dd89451cc1ed8ec5b3e314e8a51695704';
424+
self::assertEquals($expectedV2Signature, $signatureV2,
425+
'Version 2 should match expected encoded signature');
426+
}
392427
}

0 commit comments

Comments
 (0)