@@ -78,7 +78,8 @@ def setUp(self):
7878 cname = None , # for these tests without actual upload, we ignore cname
7979 api_key = "a" , api_secret = "b" ,
8080 secure_distribution = None ,
81- private_cdn = False )
81+ private_cdn = False ,
82+ signature_version = 2 )
8283
8384 def __test_cloudinary_url (self , public_id = TEST_ID , options = None , expected_url = None , expected_options = None ):
8485 if expected_options is None :
@@ -1460,31 +1461,31 @@ def test_api_sign_request_prevents_parameter_smuggling(self):
14601461 """Should prevent parameter smuggling via & characters in parameter values"""
14611462 # Test with notification_url containing & characters
14621463 params_with_ampersand = {
1463- "cloud_name" : API_SIGN_REQUEST_CLOUD_NAME ,
1464+ "cloud_name" : API_SIGN_REQUEST_CLOUD_NAME ,
14641465 "timestamp" : 1568810420 ,
14651466 "notification_url" : "https://fake.com/callback?a=1&tags=hello,world"
14661467 }
1467-
1468+
14681469 signature_with_ampersand = api_sign_request (params_with_ampersand , API_SIGN_REQUEST_TEST_SECRET )
1469-
1470+
14701471 # Test that attempting to smuggle parameters by splitting the notification_url fails
14711472 params_smuggled = {
14721473 "cloud_name" : API_SIGN_REQUEST_CLOUD_NAME ,
1473- "timestamp" : 1568810420 ,
1474+ "timestamp" : 1568810420 ,
14741475 "notification_url" : "https://fake.com/callback?a=1" ,
14751476 "tags" : "hello,world" # This would be smuggled if & encoding didn't work
14761477 }
1477-
1478+
14781479 signature_smuggled = api_sign_request (params_smuggled , API_SIGN_REQUEST_TEST_SECRET )
1479-
1480+
14801481 # The signatures should be different, proving that parameter smuggling is prevented
14811482 self .assertNotEqual (signature_with_ampersand , signature_smuggled ,
14821483 "Signatures should be different to prevent parameter smuggling" )
1483-
1484+
14841485 # Verify the expected signature for the properly encoded case
14851486 expected_signature = "4fdf465dd89451cc1ed8ec5b3e314e8a51695704"
14861487 self .assertEqual (expected_signature , signature_with_ampersand )
1487-
1488+
14881489 # Verify the expected signature for the smuggled parameters case
14891490 expected_smuggled_signature = "7b4e3a539ff1fa6e6700c41b3a2ee77586a025f9"
14901491 self .assertEqual (expected_smuggled_signature , signature_smuggled )
@@ -1493,23 +1494,23 @@ def test_api_sign_request_signature_versions(self):
14931494 """Should use signature version 1 (without parameter encoding) for backward compatibility"""
14941495 public_id_with_ampersand = 'tests/logo&version=2'
14951496 test_version = 1
1496-
1497+
14971498 expected_signature_v1 = api_sign_request (
14981499 {'public_id' : public_id_with_ampersand , 'version' : test_version },
14991500 API_SIGN_REQUEST_TEST_SECRET ,
15001501 cloudinary .utils .SIGNATURE_SHA1 ,
15011502 signature_version = 1
15021503 )
1503-
1504+
15041505 expected_signature_v2 = api_sign_request (
15051506 {'public_id' : public_id_with_ampersand , 'version' : test_version },
15061507 API_SIGN_REQUEST_TEST_SECRET ,
15071508 cloudinary .utils .SIGNATURE_SHA1 ,
15081509 signature_version = 2
15091510 )
1510-
1511+
15111512 self .assertNotEqual (expected_signature_v1 , expected_signature_v2 )
1512-
1513+
15131514 # verify_api_response_signature should use version 1 for backward compatibility
15141515 with patch ('cloudinary.config' , return_value = cloudinary .config (api_secret = API_SIGN_REQUEST_TEST_SECRET )):
15151516 self .assertTrue (
@@ -1519,7 +1520,7 @@ def test_api_sign_request_signature_versions(self):
15191520 expected_signature_v1
15201521 )
15211522 )
1522-
1523+
15231524 self .assertFalse (
15241525 verify_api_response_signature (
15251526 public_id_with_ampersand ,
@@ -1528,6 +1529,68 @@ def test_api_sign_request_signature_versions(self):
15281529 )
15291530 )
15301531
1532+ def test_signature_version_config_support (self ):
1533+ """Should use signature_version from config and produce different signatures for v1 vs v2"""
1534+ # Use params with & characters to show the encoding difference between versions
1535+ params = {'public_id' : 'test&image' , 'notification_url' : 'https://example.com/callback?param=value&other=data' }
1536+
1537+ # Test with config signature_version = 1
1538+ cloudinary .config ().signature_version = 1
1539+
1540+ # Test sign_request function uses config values
1541+ options_with_config = {'api_key' : 'test_key' , 'api_secret' : API_SIGN_REQUEST_TEST_SECRET }
1542+ signed_params_config_v1 = cloudinary .utils .sign_request (params .copy (), options_with_config )
1543+
1544+ # Test explicit signature version
1545+ options_explicit_v1 = options_with_config .copy ()
1546+ options_explicit_v1 ['signature_version' ] = 1
1547+ signed_params_explicit_v1 = cloudinary .utils .sign_request (params .copy (), options_explicit_v1 )
1548+
1549+ self .assertEqual (signed_params_config_v1 ['signature' ], signed_params_explicit_v1 ['signature' ])
1550+
1551+ # Test with config signature_version = 2
1552+ cloudinary .config ().signature_version = 2
1553+
1554+ signed_params_config_v2 = cloudinary .utils .sign_request (params .copy (), options_with_config )
1555+
1556+ options_explicit_v2 = options_with_config .copy ()
1557+ options_explicit_v2 ['signature_version' ] = 2
1558+ signed_params_explicit_v2 = cloudinary .utils .sign_request (params .copy (), options_explicit_v2 )
1559+
1560+ self .assertEqual (signed_params_config_v2 ['signature' ], signed_params_explicit_v2 ['signature' ])
1561+
1562+ # Verify that v1 and v2 actually produce different signatures due to parameter encoding
1563+ self .assertNotEqual (signed_params_config_v1 ['signature' ], signed_params_config_v2 ['signature' ],
1564+ "Signature v1 and v2 should be different for parameters with & characters" )
1565+
1566+ def test_sign_request_with_signature_version (self ):
1567+ """Should support signature_version parameter in sign_request function"""
1568+ params = {'public_id' : 'test_image' , 'version' : 1234 }
1569+ options = {'api_key' : 'test_key' , 'api_secret' : API_SIGN_REQUEST_TEST_SECRET }
1570+
1571+ # Test with signature_version in options
1572+ options_v1 = options .copy ()
1573+ options_v1 ['signature_version' ] = 1
1574+ signed_params_v1 = cloudinary .utils .sign_request (params .copy (), options_v1 )
1575+
1576+ options_v2 = options .copy ()
1577+ options_v2 ['signature_version' ] = 2
1578+ signed_params_v2 = cloudinary .utils .sign_request (params .copy (), options_v2 )
1579+
1580+ # The signatures should be different for different versions (for params with & characters)
1581+ # For these simple params without & they might be the same, but let's test the structure
1582+ self .assertIn ('signature' , signed_params_v1 )
1583+ self .assertIn ('signature' , signed_params_v2 )
1584+ self .assertIn ('api_key' , signed_params_v1 )
1585+ self .assertIn ('api_key' , signed_params_v2 )
1586+
1587+ # Test that signature_version is passed through correctly
1588+ expected_sig_v1 = api_sign_request (params , API_SIGN_REQUEST_TEST_SECRET , cloudinary .utils .SIGNATURE_SHA1 , 1 )
1589+ expected_sig_v2 = api_sign_request (params , API_SIGN_REQUEST_TEST_SECRET , cloudinary .utils .SIGNATURE_SHA1 , 2 )
1590+
1591+ self .assertEqual (signed_params_v1 ['signature' ], expected_sig_v1 )
1592+ self .assertEqual (signed_params_v2 ['signature' ], expected_sig_v2 )
1593+
15311594
15321595if __name__ == '__main__' :
15331596 unittest .main ()
0 commit comments