diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..33920489 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,50 @@ +name: Java SDK Matrix CI + +on: + push: + branches-ignore: + - staging-test + pull_request: + +jobs: + build: + name: Test ${{ matrix.module }} on JDK ${{ matrix.java }} + runs-on: ubuntu-latest + + strategy: + matrix: + java: ['8'] + module: [ 'core', 'http5', 'taglib' ] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v4 + with: + distribution: 'adopt' + java-version: ${{ matrix.java }} + + - name: Clean Gradle plugin cache + run: | + rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ matrix.java }}-${{ matrix.module }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Create test subaccount + run: ./gradlew createTestSubAccount -PmoduleName=${{ matrix.module }} + + - name: Load CLOUDINARY_URL and run ciTest + run: | + source tools/cloudinary_url.txt + ./gradlew -DCLOUDINARY_URL=$CLOUDINARY_URL ciTest -p cloudinary-${{ matrix.module }} -i \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8a1fec22..00000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -language: java -dist: trusty - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - -jdk: - - oraclejdk8 - - oraclejdk9 - - oraclejdk11 - - openjdk8 - - openjdk10 - -env: - - MODULE=core - - MODULE=http5 - -branches: - except: - - staging-test - -before_script: ./gradlew createTestSubAccount -PmoduleName=${MODULE} - -# ciTest is configured to skip the various timeout tests that don't work in travis -script: source tools/cloudinary_url.txt && ./gradlew -DCLOUDINARY_URL=$CLOUDINARY_URL ciTest -p cloudinary-${MODULE} -i - - -notifications: - email: - recipients: - - sdk_developers@cloudinary.com diff --git a/CHANGELOG.md b/CHANGELOG.md index ba03059e..f473f0ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +2.3.0 / 2025-06-18 +================== +* Fix API parameters signature +* Fix build single resource params +* Add skip backup parameter to delete folder api + 2.2.0 / 2025-02-02 ================== diff --git a/README.md b/README.md index f5659de0..1ed82876 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ The cloudinary_java library is available in [Maven Central](https://mvnrepositor com.cloudinary cloudinary-http45 - 2.2.0 + 2.3.0 ``` diff --git a/cloudinary-core/src/main/java/com/cloudinary/Api.java b/cloudinary-core/src/main/java/com/cloudinary/Api.java index 0cbab208..c84be0d6 100644 --- a/cloudinary-core/src/main/java/com/cloudinary/Api.java +++ b/cloudinary-core/src/main/java/com/cloudinary/Api.java @@ -162,7 +162,7 @@ public ApiResponse resourceByAssetID(String assetId, Map options) throws Excepti if(options.get("fields") != null) { options.put("fields", StringUtils.join(ObjectUtils.asArray(options.get("fields")), ",")); } - Map params = ObjectUtils.only(options, "tags", "context", "moderations", "fields"); + Map params = buildResourceDetailParams(options); ApiResponse response = callApi(HttpMethod.GET, Arrays.asList("resources", assetId), params, options); return response; } @@ -209,15 +209,19 @@ public ApiResponse resource(String public_id, Map options) throws Exception { if (options == null) options = ObjectUtils.emptyMap(); String resourceType = ObjectUtils.asString(options.get("resource_type"), "image"); String type = ObjectUtils.asString(options.get("type"), "upload"); + Map params = buildResourceDetailParams(options); - ApiResponse response = callApi(HttpMethod.GET, Arrays.asList("resources", resourceType, type, public_id), - ObjectUtils.only(options, "exif", "colors", "faces", "coordinates", - "image_metadata", "pages", "phash", "max_results", "quality_analysis", "cinemagraph_analysis", - "accessibility_analysis", "versions", "media_metadata", "derived_next_cursor"), options); + ApiResponse response = callApi(HttpMethod.GET, Arrays.asList("resources", resourceType, type, public_id), params, options); return response; } + private Map buildResourceDetailParams(Map options) { + return ObjectUtils.only(options, "exif", "colors", "faces", "coordinates", + "image_metadata", "pages", "phash", "max_results", "quality_analysis", "cinemagraph_analysis", + "accessibility_analysis", "versions", "media_metadata", "derived_next_cursor"); + } + public ApiResponse update(String public_id, Map options) throws Exception { if (options == null) options = ObjectUtils.emptyMap(); String resourceType = ObjectUtils.asString(options.get("resource_type"), "image"); @@ -660,8 +664,10 @@ public ApiResponse updateResourcesAccessModeByTag(String accessMode, String tag, * @throws Exception When the folder isn't empty or doesn't exist. */ public ApiResponse deleteFolder(String folder, Map options) throws Exception { + if (options == null || options.isEmpty()) options = ObjectUtils.asMap(); List uri = Arrays.asList("folders", folder); - return callApi(HttpMethod.DELETE, uri, Collections.emptyMap(), options); + Map params = ObjectUtils.only(options, "skip_backup"); + return callApi(HttpMethod.DELETE, uri, params, options); } /** diff --git a/cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java b/cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java index 19c0c3b8..869e20b6 100644 --- a/cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java +++ b/cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java @@ -32,7 +32,7 @@ public class Cloudinary { public final static String AKAMAI_SHARED_CDN = "res.cloudinary.com"; public final static String SHARED_CDN = AKAMAI_SHARED_CDN; - public final static String VERSION = "2.2.0"; + public final static String VERSION = "2.3.0"; static String USER_AGENT_PREFIX = "CloudinaryJava"; public final static String USER_AGENT_JAVA_VERSION = "(Java " + System.getProperty("java.version") + ")"; @@ -130,8 +130,8 @@ public String signedPreloadedImage(Map result) { + (result.containsKey("format") ? "." + result.get("format") : "") + "#" + result.get("signature"); } - public String apiSignRequest(Map paramsToSign, String apiSecret) { - return Util.produceSignature(paramsToSign, apiSecret, config.signatureAlgorithm); + public String apiSignRequest(Map paramsToSign, String apiSecret, int signatureVersion) { + return Util.produceSignature(paramsToSign, apiSecret, config.signatureAlgorithm, signatureVersion); } /** @@ -206,7 +206,7 @@ public void signRequest(Map params, Map options) if (apiSecret == null) throw new IllegalArgumentException("Must supply api_secret"); Util.clearEmpty(params); - params.put("signature", this.apiSignRequest(params, apiSecret)); + params.put("signature", this.apiSignRequest(params, apiSecret, this.config.signatureVersion)); params.put("api_key", apiKey); } diff --git a/cloudinary-core/src/main/java/com/cloudinary/Configuration.java b/cloudinary-core/src/main/java/com/cloudinary/Configuration.java index 07280d89..7586ae46 100644 --- a/cloudinary-core/src/main/java/com/cloudinary/Configuration.java +++ b/cloudinary-core/src/main/java/com/cloudinary/Configuration.java @@ -21,6 +21,7 @@ public class Configuration { public final static String USER_AGENT = "cld-android-" + VERSION; public static final boolean DEFAULT_IS_LONG_SIGNATURE = false; public static final SignatureAlgorithm DEFAULT_SIGNATURE_ALGORITHM = SignatureAlgorithm.SHA1; + public static final int DEFAULT_SIGNATURE_VERSION = 2; private static final String CONFIG_PROP_SIGNATURE_ALGORITHM = "signature_algorithm"; @@ -48,6 +49,7 @@ public class Configuration { public boolean forceVersion = true; public boolean longUrlSignature = DEFAULT_IS_LONG_SIGNATURE; public SignatureAlgorithm signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM; + public int signatureVersion = DEFAULT_SIGNATURE_VERSION; public String oauthToken = null; public Boolean analytics; public Configuration() { @@ -75,6 +77,7 @@ private Configuration( boolean forceVersion, boolean longUrlSignature, SignatureAlgorithm signatureAlgorithm, + int signatureVersion, String oauthToken, boolean analytics) { this.cloudName = cloudName; @@ -98,6 +101,7 @@ private Configuration( this.forceVersion = forceVersion; this.longUrlSignature = longUrlSignature; this.signatureAlgorithm = signatureAlgorithm; + this.signatureVersion = signatureVersion; this.oauthToken = oauthToken; this.analytics = analytics; } @@ -140,6 +144,7 @@ public void update(Map config) { } this.longUrlSignature = ObjectUtils.asBoolean(config.get("long_url_signature"), DEFAULT_IS_LONG_SIGNATURE); this.signatureAlgorithm = SignatureAlgorithm.valueOf(ObjectUtils.asString(config.get(CONFIG_PROP_SIGNATURE_ALGORITHM), DEFAULT_SIGNATURE_ALGORITHM.name())); + this.signatureVersion = ObjectUtils.asInteger(config.get("signature_version"), DEFAULT_SIGNATURE_VERSION); this.oauthToken = (String) config.get("oauth_token"); } @@ -173,6 +178,7 @@ public Map asMap() { map.put("properties", new HashMap(properties)); map.put("long_url_signature", longUrlSignature); map.put(CONFIG_PROP_SIGNATURE_ALGORITHM, signatureAlgorithm.toString()); + map.put("signature_version", signatureVersion); map.put("oauth_token", oauthToken); map.put("analytics", analytics); return map; @@ -206,6 +212,7 @@ public Configuration(Configuration other) { this.properties.putAll(other.properties); this.longUrlSignature = other.longUrlSignature; this.signatureAlgorithm = other.signatureAlgorithm; + this.signatureVersion = other.signatureVersion; this.oauthToken = other.oauthToken; this.analytics = other.analytics; } @@ -320,6 +327,7 @@ public static class Builder { private boolean forceVersion = true; private boolean longUrlSignature = DEFAULT_IS_LONG_SIGNATURE; private SignatureAlgorithm signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM; + private int signatureVersion = DEFAULT_SIGNATURE_VERSION; private String oauthToken = null; private boolean analytics; @@ -360,6 +368,7 @@ public Configuration build() { forceVersion, longUrlSignature, signatureAlgorithm, + signatureVersion, oauthToken, analytics); configuration.clientHints = clientHints; @@ -500,6 +509,11 @@ public Builder setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { return this; } + public Builder setSignatureVersion(int signatureVersion) { + this.signatureVersion = signatureVersion; + return this; + } + public Builder setOAuthToken(String oauthToken) { this.oauthToken = oauthToken; return this; @@ -535,6 +549,7 @@ public Builder from(Configuration other) { this.forceVersion = other.forceVersion; this.longUrlSignature = other.longUrlSignature; this.signatureAlgorithm = other.signatureAlgorithm; + this.signatureVersion = other.signatureVersion; this.oauthToken = other.oauthToken; this.analytics = other.analytics; return this; diff --git a/cloudinary-core/src/main/java/com/cloudinary/Util.java b/cloudinary-core/src/main/java/com/cloudinary/Util.java index f81da3cc..4f15c220 100644 --- a/cloudinary-core/src/main/java/com/cloudinary/Util.java +++ b/cloudinary-core/src/main/java/com/cloudinary/Util.java @@ -366,8 +366,8 @@ public static byte[] getUTF8Bytes(String string) { * @param apiSecret secret value * @return hex-string representation of signature calculated based on provided parameters map and secret */ - public static String produceSignature(Map paramsToSign, String apiSecret) { - return produceSignature(paramsToSign, apiSecret, SignatureAlgorithm.SHA1); + public static String produceSignature(Map paramsToSign, String apiSecret, int signatureVersion) { + return produceSignature(paramsToSign, apiSecret, SignatureAlgorithm.SHA1, signatureVersion); } /** @@ -384,22 +384,42 @@ public static String produceSignature(Map paramsToSign, String a * @param signatureAlgorithm type of hashing algorithm to use for calculation of HMAC * @return hex-string representation of signature calculated based on provided parameters map and secret */ - public static String produceSignature(Map paramsToSign, String apiSecret, SignatureAlgorithm signatureAlgorithm) { - Collection params = new ArrayList(); - for (Map.Entry param : new TreeMap(paramsToSign).entrySet()) { - if (param.getValue() instanceof Collection) { - params.add(param.getKey() + "=" + StringUtils.join((Collection) param.getValue(), ",")); - } else if (param.getValue() instanceof Object[]) { - params.add(param.getKey() + "=" + StringUtils.join((Object[]) param.getValue(), ",")); - } else { - if (StringUtils.isNotBlank(param.getValue())) { - params.add(param.getKey() + "=" + param.getValue().toString()); - } + public static String produceSignature(Map paramsToSign, String apiSecret, SignatureAlgorithm signatureAlgorithm, int signatureVersion) { + Collection flattenedParams = flattenAndSanitizeParams(paramsToSign, signatureVersion); + String toSign = StringUtils.join(flattenedParams, "&") + apiSecret; + byte[] hash = Util.hash(toSign, signatureAlgorithm); + return StringUtils.encodeHexString(hash); + } + + private static Collection flattenAndSanitizeParams(Map paramsToSign, int signatureVersion) { + Collection params = new ArrayList<>(); + + for (Map.Entry entry : new TreeMap<>(paramsToSign).entrySet()) { + Object value = entry.getValue(); + String rawValue = null; + + if (value instanceof Collection) { + rawValue = StringUtils.join((Collection) value, ","); + } else if (value instanceof Object[]) { + rawValue = StringUtils.join((Object[]) value, ","); + } else if (value != null && StringUtils.isNotBlank(value.toString())) { + rawValue = value.toString(); + } + + if (rawValue != null) { + String sanitizedValue = (signatureVersion == 2) + ? escapeAmpersand(rawValue) + : rawValue; + + params.add(entry.getKey() + "=" + sanitizedValue); } } - String to_sign = StringUtils.join(params, "&"); - byte[] hash = Util.hash(to_sign + apiSecret, signatureAlgorithm); - return StringUtils.encodeHexString(hash); + + return params; + } + + private static String escapeAmpersand(String input) { + return input.replace("&", "%26"); } /** diff --git a/cloudinary-core/src/main/java/com/cloudinary/api/signing/ApiResponseSignatureVerifier.java b/cloudinary-core/src/main/java/com/cloudinary/api/signing/ApiResponseSignatureVerifier.java index 1dbae00d..f6d7da67 100644 --- a/cloudinary-core/src/main/java/com/cloudinary/api/signing/ApiResponseSignatureVerifier.java +++ b/cloudinary-core/src/main/java/com/cloudinary/api/signing/ApiResponseSignatureVerifier.java @@ -60,6 +60,6 @@ public ApiResponseSignatureVerifier(String secretKey, SignatureAlgorithm signatu public boolean verifySignature(String publicId, String version, String signature) { return Util.produceSignature(ObjectUtils.asMap( "public_id", emptyIfNull(publicId), - "version", emptyIfNull(version)), secretKey, signatureAlgorithm).equals(signature); + "version", emptyIfNull(version)), secretKey, signatureAlgorithm, 1).equals(signature); } } diff --git a/cloudinary-core/src/test/java/com/cloudinary/test/CloudinaryTest.java b/cloudinary-core/src/test/java/com/cloudinary/test/CloudinaryTest.java index 18064976..c40a3ea2 100644 --- a/cloudinary-core/src/test/java/com/cloudinary/test/CloudinaryTest.java +++ b/cloudinary-core/src/test/java/com/cloudinary/test/CloudinaryTest.java @@ -1438,14 +1438,14 @@ public void testCloudinaryUrlEmptyScheme() { @Test public void testApiSignRequestSHA1() { cloudinary.config.signatureAlgorithm = SignatureAlgorithm.SHA1; - String signature = cloudinary.apiSignRequest(ObjectUtils.asMap("cloud_name", "dn6ot3ged", "timestamp", 1568810420, "username", "user@cloudinary.com"), "hdcixPpR2iKERPwqvH6sHdK9cyac"); + String signature = cloudinary.apiSignRequest(ObjectUtils.asMap("cloud_name", "dn6ot3ged", "timestamp", 1568810420, "username", "user@cloudinary.com"), "hdcixPpR2iKERPwqvH6sHdK9cyac", cloudinary.config.signatureVersion); assertEquals("14c00ba6d0dfdedbc86b316847d95b9e6cd46d94", signature); } @Test public void testApiSignRequestSHA256() { cloudinary.config.signatureAlgorithm = SignatureAlgorithm.SHA256; - String signature = cloudinary.apiSignRequest(ObjectUtils.asMap("cloud_name", "dn6ot3ged", "timestamp", 1568810420, "username", "user@cloudinary.com"), "hdcixPpR2iKERPwqvH6sHdK9cyac"); + String signature = cloudinary.apiSignRequest(ObjectUtils.asMap("cloud_name", "dn6ot3ged", "timestamp", 1568810420, "username", "user@cloudinary.com"), "hdcixPpR2iKERPwqvH6sHdK9cyac", cloudinary.config.signatureVersion); assertEquals("45ddaa4fa01f0c2826f32f669d2e4514faf275fe6df053f1a150e7beae58a3bd", signature); } diff --git a/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractApiTest.java b/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractApiTest.java index 5c854d75..90e90fa7 100644 --- a/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractApiTest.java +++ b/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractApiTest.java @@ -1368,4 +1368,39 @@ public void testAllowDerivedNextCursor() throws Exception { cloudinary.uploader().destroy(publicId, Collections.singletonMap("invalidate", true)); } } + + @Test + public void testSignatureWithEscapingCharacters() { + String API_SIGN_REQUEST_CLOUD_NAME = "dn6ot3ged"; + String API_SIGN_REQUEST_TEST_SECRET = "hdcixPpR2iKERPwqvH6sHdK9cyac"; + + Map paramsWithAmpersand = new HashMap<>(); + paramsWithAmpersand.put("cloud_name", API_SIGN_REQUEST_CLOUD_NAME); + paramsWithAmpersand.put("timestamp", 1568810420); + paramsWithAmpersand.put("notification_url", "https://fake.com/callback?a=1&tags=hello,world"); + + String signatureWithAmpersand = Util.produceSignature(paramsWithAmpersand, API_SIGN_REQUEST_TEST_SECRET, cloudinary.config.signatureVersion); + + Map paramsSmuggled = new HashMap<>(); + paramsSmuggled.put("cloud_name", API_SIGN_REQUEST_CLOUD_NAME); + paramsSmuggled.put("timestamp", 1568810420); + paramsSmuggled.put("notification_url", "https://fake.com/callback?a=1"); + paramsSmuggled.put("tags", "hello,world"); + + String signatureSmuggled = Util.produceSignature(paramsSmuggled, API_SIGN_REQUEST_TEST_SECRET, cloudinary.config.signatureVersion); + + assertNotEquals(signatureWithAmpersand, signatureSmuggled, + "Signatures should be different to prevent parameter smuggling"); + + String expectedSignature = "4fdf465dd89451cc1ed8ec5b3e314e8a51695704"; + assertEquals(expectedSignature, signatureWithAmpersand); + + String expectedSmuggledSignature = "7b4e3a539ff1fa6e6700c41b3a2ee77586a025f9"; + assertEquals(expectedSmuggledSignature, signatureSmuggled); + + String versionOneSignature = Util.produceSignature(paramsSmuggled, API_SIGN_REQUEST_TEST_SECRET, 1); + + assertEquals(expectedSmuggledSignature, versionOneSignature); + + } } diff --git a/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractFoldersApiTest.java b/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractFoldersApiTest.java index a0478bad..a8835046 100644 --- a/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractFoldersApiTest.java +++ b/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractFoldersApiTest.java @@ -85,4 +85,17 @@ public void testSubFolderWithParams() throws Exception { ApiResponse result = api.deleteFolder(rootFolderName, null); assertTrue(((List) result.get("deleted")).contains(rootFolderName)); } + + @Test + public void testDeleteFolderWithSkipBackup() throws Exception { + //Create + String rootFolderName = "deleteFolderWithSkipBackup" + SUFFIX; + assertTrue((Boolean) api.createFolder(rootFolderName, null).get("success")); + + //Delete + ApiResponse result = api.deleteFolder(rootFolderName, ObjectUtils.asMap("skip_backup", "true")); + assertTrue(((List) result.get("deleted")).contains(rootFolderName)); + + + } } diff --git a/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractUploaderTest.java b/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractUploaderTest.java index 5de797fa..794c926a 100644 --- a/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractUploaderTest.java +++ b/cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractUploaderTest.java @@ -104,7 +104,7 @@ public void testUtf8Upload() throws IOException { Map to_sign = new HashMap(); to_sign.put("public_id", result.get("public_id")); to_sign.put("version", ObjectUtils.asString(result.get("version"))); - String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret); + String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion); assertEquals(result.get("signature"), expected_signature); } @@ -131,7 +131,7 @@ public void testUpload() throws IOException { Map to_sign = new HashMap(); to_sign.put("public_id", result.get("public_id")); to_sign.put("version", ObjectUtils.asString(result.get("version"))); - String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret); + String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion); assertEquals(result.get("signature"), expected_signature); } @@ -167,7 +167,7 @@ public void testUploadUrl() throws IOException { Map to_sign = new HashMap(); to_sign.put("public_id", result.get("public_id")); to_sign.put("version", ObjectUtils.asString(result.get("version"))); - String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret); + String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion); assertEquals(result.get("signature"), expected_signature); } @@ -179,7 +179,7 @@ public void testUploadLargeUrl() throws IOException { Map to_sign = new HashMap(); to_sign.put("public_id", result.get("public_id")); to_sign.put("version", ObjectUtils.asString(result.get("version"))); - String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret); + String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion); assertEquals(result.get("signature"), expected_signature); } @@ -191,7 +191,7 @@ public void testUploadDataUri() throws IOException { Map to_sign = new HashMap(); to_sign.put("public_id", result.get("public_id")); to_sign.put("version", ObjectUtils.asString(result.get("version"))); - String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret); + String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion); assertEquals(result.get("signature"), expected_signature); } diff --git a/gradle.properties b/gradle.properties index aaea4f1d..0180f74f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ developerEmail=info@cloudinary.com # These two properties must use these exact names to be compatible with 'gradle install' plugin. group=com.cloudinary -version=2.2.0 +version=2.3.0 gnsp.disableApplyOnlyOnRootProjectEnforcement=true pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy