|
20 | 20 |
|
21 | 21 | import static org.apache.polaris.service.it.env.PolarisClient.polarisClient; |
22 | 22 | import static org.assertj.core.api.Assertions.assertThat; |
| 23 | +import static org.assertj.core.api.Assertions.assertThatCode; |
23 | 24 | import static org.assertj.core.api.Assertions.assertThatThrownBy; |
24 | 25 | import static org.awaitility.Awaitility.await; |
25 | 26 |
|
| 27 | +import com.google.common.collect.ImmutableMap; |
26 | 28 | import jakarta.ws.rs.ProcessingException; |
27 | 29 | import jakarta.ws.rs.client.Entity; |
28 | 30 | import jakarta.ws.rs.client.Invocation; |
|
67 | 69 | import org.apache.polaris.core.admin.model.PolarisCatalog; |
68 | 70 | import org.apache.polaris.core.admin.model.PrincipalRole; |
69 | 71 | import org.apache.polaris.core.admin.model.StorageConfigInfo; |
| 72 | +import org.apache.polaris.core.config.BehaviorChangeConfiguration; |
70 | 73 | import org.apache.polaris.core.entity.CatalogEntity; |
71 | 74 | import org.apache.polaris.core.entity.PolarisEntityConstants; |
72 | 75 | import org.apache.polaris.service.it.env.ClientPrincipal; |
|
76 | 79 | import org.apache.polaris.service.it.env.RestApi; |
77 | 80 | import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension; |
78 | 81 | import org.assertj.core.api.InstanceOfAssertFactories; |
| 82 | +import org.assertj.core.api.ThrowableAssert; |
79 | 83 | import org.junit.jupiter.api.AfterAll; |
80 | 84 | import org.junit.jupiter.api.AfterEach; |
81 | 85 | import org.junit.jupiter.api.BeforeAll; |
|
84 | 88 | import org.junit.jupiter.api.TestInfo; |
85 | 89 | import org.junit.jupiter.api.extension.ExtendWith; |
86 | 90 | import org.junit.jupiter.api.io.TempDir; |
| 91 | +import org.junit.jupiter.params.ParameterizedTest; |
| 92 | +import org.junit.jupiter.params.provider.ValueSource; |
87 | 93 |
|
88 | 94 | /** |
89 | 95 | * @implSpec This test expects the server to be configured with the following features configured: |
@@ -186,11 +192,30 @@ private static void createCatalog( |
186 | 192 | String principalRoleName, |
187 | 193 | StorageConfigInfo storageConfig, |
188 | 194 | String defaultBaseLocation) { |
189 | | - CatalogProperties props = |
| 195 | + createCatalog( |
| 196 | + catalogName, |
| 197 | + catalogType, |
| 198 | + principalRoleName, |
| 199 | + storageConfig, |
| 200 | + defaultBaseLocation, |
| 201 | + ImmutableMap.of()); |
| 202 | + } |
| 203 | + |
| 204 | + private static void createCatalog( |
| 205 | + String catalogName, |
| 206 | + Catalog.TypeEnum catalogType, |
| 207 | + String principalRoleName, |
| 208 | + StorageConfigInfo storageConfig, |
| 209 | + String defaultBaseLocation, |
| 210 | + Map<String, String> additionalProperties) { |
| 211 | + CatalogProperties.Builder propsBuilder = |
190 | 212 | CatalogProperties.builder(defaultBaseLocation) |
191 | 213 | .addProperty( |
192 | | - CatalogEntity.REPLACE_NEW_LOCATION_PREFIX_WITH_CATALOG_DEFAULT_KEY, "file:/") |
193 | | - .build(); |
| 214 | + CatalogEntity.REPLACE_NEW_LOCATION_PREFIX_WITH_CATALOG_DEFAULT_KEY, "file:/"); |
| 215 | + for (var entry : additionalProperties.entrySet()) { |
| 216 | + propsBuilder.addProperty(entry.getKey(), entry.getValue()); |
| 217 | + } |
| 218 | + CatalogProperties props = propsBuilder.build(); |
194 | 219 | Catalog catalog = |
195 | 220 | catalogType.equals(Catalog.TypeEnum.INTERNAL) |
196 | 221 | ? PolarisCatalog.builder() |
@@ -641,4 +666,53 @@ public void testRequestBodyTooLarge() throws Exception { |
641 | 666 | }); |
642 | 667 | } |
643 | 668 | } |
| 669 | + |
| 670 | + @ParameterizedTest |
| 671 | + @ValueSource(booleans = {true, false}) |
| 672 | + public void testNamespaceOutsideCatalog(boolean allowNamespaceLocationEscape) throws IOException { |
| 673 | + String catalogName = client.newEntityName("testNamespaceOutsideCatalog_specificLocation"); |
| 674 | + String catalogLocation = baseLocation.resolve(catalogName + "/catalog").toString(); |
| 675 | + String badLocation = baseLocation.resolve(catalogName + "/ns").toString(); |
| 676 | + createCatalog( |
| 677 | + catalogName, |
| 678 | + Catalog.TypeEnum.INTERNAL, |
| 679 | + principalRoleName, |
| 680 | + FileStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.FILE) |
| 681 | + .setAllowedLocations(List.of(catalogLocation)) |
| 682 | + .build(), |
| 683 | + catalogLocation, |
| 684 | + ImmutableMap.of( |
| 685 | + BehaviorChangeConfiguration.ALLOW_NAMESPACE_CUSTOM_LOCATION.catalogConfig(), |
| 686 | + String.valueOf(allowNamespaceLocationEscape))); |
| 687 | + try (RESTSessionCatalog sessionCatalog = newSessionCatalog(catalogName)) { |
| 688 | + SessionCatalog.SessionContext sessionContext = SessionCatalog.SessionContext.createEmpty(); |
| 689 | + sessionCatalog.createNamespace(sessionContext, Namespace.of("good_namespace")); |
| 690 | + ThrowableAssert.ThrowingCallable createBadNamespace = |
| 691 | + () -> |
| 692 | + sessionCatalog.createNamespace( |
| 693 | + sessionContext, |
| 694 | + Namespace.of("bad_namespace"), |
| 695 | + ImmutableMap.of("location", badLocation)); |
| 696 | + if (!allowNamespaceLocationEscape) { |
| 697 | + assertThatThrownBy(createBadNamespace) |
| 698 | + .isInstanceOf(BadRequestException.class) |
| 699 | + .hasMessageContaining("custom location"); |
| 700 | + } else { |
| 701 | + assertThatCode(createBadNamespace).doesNotThrowAnyException(); |
| 702 | + } |
| 703 | + ThrowableAssert.ThrowingCallable createBadChildGoodParent = |
| 704 | + () -> |
| 705 | + sessionCatalog.createNamespace( |
| 706 | + sessionContext, |
| 707 | + Namespace.of("good_namespace", "bad_child"), |
| 708 | + ImmutableMap.of("location", badLocation)); |
| 709 | + if (!allowNamespaceLocationEscape) { |
| 710 | + assertThatThrownBy(createBadChildGoodParent) |
| 711 | + .isInstanceOf(BadRequestException.class) |
| 712 | + .hasMessageContaining("custom location"); |
| 713 | + } else { |
| 714 | + assertThatCode(createBadChildGoodParent).doesNotThrowAnyException(); |
| 715 | + } |
| 716 | + } |
| 717 | + } |
644 | 718 | } |
0 commit comments