This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new e396992da6 [oss] Fix blob presigned URL validation when 
fs.oss.sld.enabled is set (#10230)
e396992da6 is described below

commit e396992da6d8c0112a014fb5249cbd0399d91f85
Author: Eunbin Son <[email protected]>
AuthorDate: Sat Oct 3 18:05:54 2026 +0900

    [oss] Fix blob presigned URL validation when fs.oss.sld.enabled is set 
(#10230)
---
 .../org/apache/paimon/jindo/JindoFileIOTest.java   |   2 +
 .../org/apache/paimon/oss/OSSBlobPresigner.java    |   8 +-
 .../java/org/apache/paimon/oss/OSSFileIOTest.java  | 104 +++++++++++++++++++++
 3 files changed, 112 insertions(+), 2 deletions(-)

diff --git 
a/paimon-filesystems/paimon-jindo/src/test/java/org/apache/paimon/jindo/JindoFileIOTest.java
 
b/paimon-filesystems/paimon-jindo/src/test/java/org/apache/paimon/jindo/JindoFileIOTest.java
index 06b430a2a2..17ee5e6d5b 100644
--- 
a/paimon-filesystems/paimon-jindo/src/test/java/org/apache/paimon/jindo/JindoFileIOTest.java
+++ 
b/paimon-filesystems/paimon-jindo/src/test/java/org/apache/paimon/jindo/JindoFileIOTest.java
@@ -27,6 +27,7 @@ import org.apache.paimon.options.Options;
 import org.apache.paimon.utils.Pair;
 
 import com.aliyun.jindodata.common.JindoHadoopSystem;
+import com.aliyun.oss.ClientConfiguration;
 import com.aliyun.oss.HttpMethod;
 import com.aliyun.oss.OSSClient;
 import com.aliyun.oss.model.ObjectMetadata;
@@ -124,6 +125,7 @@ public class JindoFileIOTest {
                 "paimon-blob-descriptor-sha256", 
sha256Hex(descriptor.serialize()));
         when(client.headObject(eq("bucket"), 
contains("_bloburl_"))).thenReturn(metadata);
         
when(client.getEndpoint()).thenReturn(URI.create("https://oss.example.com";));
+        when(client.getClientConfiguration()).thenReturn(new 
ClientConfiguration());
         when(client.generatePresignedUrl(eq("bucket"), anyString(), any(), 
eq(HttpMethod.GET)))
                 .thenAnswer(
                         invocation ->
diff --git 
a/paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSBlobPresigner.java
 
b/paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSBlobPresigner.java
index 55f9089e32..c47e7de875 100644
--- 
a/paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSBlobPresigner.java
+++ 
b/paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSBlobPresigner.java
@@ -219,11 +219,15 @@ public final class OSSBlobPresigner {
     private static void validatePresignedUrl(
             OSSClient client, URL url, String bucket, String targetKey) throws 
Exception {
         URI endpoint = client.getEndpoint();
-        String expectedHost = bucket + "." + endpoint.getHost();
+        // With fs.oss.sld.enabled the client signs path-style URLs: the host 
is the endpoint and
+        // the bucket becomes the first path segment, so bucket and key are 
still both checked.
+        boolean pathStyle = client.getClientConfiguration().isSLDEnabled();
+        String expectedHost = pathStyle ? endpoint.getHost() : bucket + "." + 
endpoint.getHost();
+        String expectedPath = pathStyle ? "/" + bucket + "/" + targetKey : "/" 
+ targetKey;
         if (!"https".equalsIgnoreCase(endpoint.getScheme())
                 || !"https".equalsIgnoreCase(url.getProtocol())
                 || !expectedHost.equalsIgnoreCase(url.getHost())
-                || !("/" + targetKey).equals(url.toURI().getPath())) {
+                || !expectedPath.equals(url.toURI().getPath())) {
             throw new IOException("OSS client generated a presigned URL for an 
invalid target.");
         }
     }
diff --git 
a/paimon-filesystems/paimon-oss-impl/src/test/java/org/apache/paimon/oss/OSSFileIOTest.java
 
b/paimon-filesystems/paimon-oss-impl/src/test/java/org/apache/paimon/oss/OSSFileIOTest.java
index e801d25f81..629b14cdfc 100644
--- 
a/paimon-filesystems/paimon-oss-impl/src/test/java/org/apache/paimon/oss/OSSFileIOTest.java
+++ 
b/paimon-filesystems/paimon-oss-impl/src/test/java/org/apache/paimon/oss/OSSFileIOTest.java
@@ -93,6 +93,7 @@ public class OSSFileIOTest {
         copied.setPartNumber(1);
         copied.setETag("etag");
         when(client.uploadPartCopy(any())).thenReturn(copied);
+        when(client.getClientConfiguration()).thenReturn(new 
ClientConfiguration());
         
when(client.getEndpoint()).thenReturn(URI.create("https://oss-cn-hangzhou.aliyuncs.com";));
         when(client.generatePresignedUrl(eq("bucket"), anyString(), any(), 
eq(HttpMethod.GET)))
                 .thenAnswer(
@@ -158,6 +159,7 @@ public class OSSFileIOTest {
                 new 
BlobDescriptor("oss://bucket/table/-internal.aliyuncs.com/source.blob", 0, 1);
         when(client.headObject(eq("bucket"), contains("_bloburl_")))
                 .thenReturn(matchingMetadata(descriptor));
+        when(client.getClientConfiguration()).thenReturn(new 
ClientConfiguration());
         when(client.getEndpoint())
                 
.thenReturn(URI.create("https://oss-cn-hangzhou-internal.aliyuncs.com";));
         when(client.generatePresignedUrl(eq("bucket"), anyString(), any(), 
eq(HttpMethod.GET)))
@@ -337,11 +339,112 @@ public class OSSFileIOTest {
                 
"https://bucket.oss-cn-hangzhou.aliyuncs.com/table/_bloburl_hash";);
     }
 
+    @Test
+    public void testCreateBlobPresignedUrlAcceptsPathStyleWhenSldEnabled() 
throws Exception {
+        BlobDescriptor descriptor = new 
BlobDescriptor("oss://bucket/table/source.blob", 0, 1);
+        String key = "table/_bloburl_" + sha256Hex(descriptor.serialize());
+        for (String endpoint :
+                new String[] {
+                    "https://oss-cn-hangzhou.aliyuncs.com";,
+                    "https://oss-cn-hangzhou-internal.aliyuncs.com";,
+                    "https://ep-abc.oss.cn-hangzhou.privatelink.aliyuncs.com";
+                }) {
+            String signed = endpoint + "/bucket/" + key + "?Signature=test";
+            assertThat(presignWithSldMock(endpoint, descriptor, 
signed)).isEqualTo(signed);
+        }
+
+        // The bucket is still verified, now as the first path segment.
+        assertThatThrownBy(
+                        () ->
+                                presignWithSldMock(
+                                        "https://oss-cn-hangzhou.aliyuncs.com";,
+                                        descriptor,
+                                        
"https://oss-cn-hangzhou.aliyuncs.com/other/"; + key))
+                .isInstanceOf(java.io.IOException.class)
+                .hasMessageContaining("invalid target");
+    }
+
+    @Test
+    public void 
testCreateBlobPresignedUrlAcceptsSdkPathStyleUrlWithEncodedKey() throws 
Exception {
+        BlobDescriptor descriptor =
+                new BlobDescriptor("oss://bucket/table/a 
b/\uD55C/source.blob", 0, 1);
+
+        URL url = new 
URL(presignWithSldClient("https://oss-cn-hangzhou.aliyuncs.com";, descriptor));
+
+        assertThat(url.getHost()).isEqualTo("oss-cn-hangzhou.aliyuncs.com");
+        assertThat(url.toURI().getPath())
+                .isEqualTo(
+                        "/bucket/table/a b/\uD55C/_bloburl_" + 
sha256Hex(descriptor.serialize()));
+    }
+
+    @Test
+    public void 
testCreateBlobPresignedUrlAcceptsSdkPathStyleUrlWithEndpointPort()
+            throws Exception {
+        BlobDescriptor descriptor = new 
BlobDescriptor("oss://bucket/table/source.blob", 0, 1);
+
+        URL url =
+                new URL(
+                        presignWithSldClient(
+                                "https://oss-cn-hangzhou.aliyuncs.com:8443";, 
descriptor));
+
+        assertThat(url.getHost()).isEqualTo("oss-cn-hangzhou.aliyuncs.com");
+        assertThat(url.getPort()).isEqualTo(8443);
+        assertThat(url.getPath())
+                .isEqualTo("/bucket/table/_bloburl_" + 
sha256Hex(descriptor.serialize()));
+    }
+
+    private static String presignWithSldMock(
+            String endpoint, BlobDescriptor descriptor, String generatedUrl) 
throws Exception {
+        OSSClient client = mock(OSSClient.class);
+        when(client.headObject(eq("bucket"), contains("_bloburl_")))
+                .thenReturn(matchingMetadata(descriptor));
+        ClientConfiguration configuration = new ClientConfiguration();
+        configuration.setSLDEnabled(true);
+        when(client.getClientConfiguration()).thenReturn(configuration);
+        when(client.getEndpoint()).thenReturn(URI.create(endpoint));
+        when(client.generatePresignedUrl(eq("bucket"), anyString(), any(), 
eq(HttpMethod.GET)))
+                .thenReturn(presignedUrl(generatedUrl));
+        return new TestOSSFileIO(client)
+                .createBlobPresignedUrl(
+                        new Path("oss://bucket/table"), descriptor, 
Duration.ofMinutes(5));
+    }
+
+    /**
+     * Presigns with the URL the SDK itself signs once {@code 
fs.oss.sld.enabled} has turned on
+     * second-level domain mode, so the validator is checked against the real 
path-style output.
+     */
+    private static String presignWithSldClient(String endpoint, BlobDescriptor 
descriptor)
+            throws Exception {
+        OSSClient signer = hadoopStyleClient(endpoint);
+        try {
+            signer.getClientConfiguration().setSLDEnabled(true);
+            OSSClient client = mock(OSSClient.class);
+            when(client.headObject(eq("bucket"), contains("_bloburl_")))
+                    .thenReturn(matchingMetadata(descriptor));
+            
when(client.getClientConfiguration()).thenReturn(signer.getClientConfiguration());
+            when(client.getEndpoint()).thenReturn(signer.getEndpoint());
+            when(client.generatePresignedUrl(eq("bucket"), anyString(), any(), 
eq(HttpMethod.GET)))
+                    .thenAnswer(
+                            invocation ->
+                                    signer.generatePresignedUrl(
+                                            "bucket",
+                                            invocation.getArgument(1),
+                                            invocation.getArgument(2),
+                                            HttpMethod.GET));
+            return new TestOSSFileIO(client)
+                    .createBlobPresignedUrl(
+                            new Path("oss://bucket/table"), descriptor, 
Duration.ofMinutes(5));
+        } finally {
+            signer.shutdown();
+        }
+    }
+
     private static void assertInvalidPresignedUrl(String endpoint, String 
generatedUrl) {
         OSSClient client = mock(OSSClient.class);
         BlobDescriptor descriptor = new 
BlobDescriptor("oss://bucket/table/source.blob", 0, 1);
         when(client.headObject(eq("bucket"), contains("_bloburl_")))
                 .thenReturn(matchingMetadata(descriptor));
+        when(client.getClientConfiguration()).thenReturn(new 
ClientConfiguration());
         when(client.getEndpoint()).thenReturn(URI.create(endpoint));
         when(client.generatePresignedUrl(eq("bucket"), anyString(), any(), 
eq(HttpMethod.GET)))
                 .thenReturn(presignedUrl(generatedUrl));
@@ -407,6 +510,7 @@ public class OSSFileIOTest {
     }
 
     private static void stubPresigning(OSSClient client) {
+        when(client.getClientConfiguration()).thenReturn(new 
ClientConfiguration());
         
when(client.getEndpoint()).thenReturn(URI.create("https://oss-cn-hangzhou.aliyuncs.com";));
         when(client.generatePresignedUrl(eq("bucket"), anyString(), any(), 
eq(HttpMethod.GET)))
                 .thenAnswer(

Reply via email to