Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jakarta-validation-api = { module = "jakarta.validation:jakarta.validation-api",
jakarta-ws-rs-api = { module = "jakarta.ws.rs:jakarta.ws.rs-api", version = "4.0.0" }
javax-servlet-api = { module = "javax.servlet:javax.servlet-api", version = "4.0.1" }
junit-bom = { module = "org.junit:junit-bom", version = "5.13.4" }
keycloak-admin-client = { module = "org.keycloak:keycloak-admin-client", version = "26.0.6" }
logback-classic = { module = "ch.qos.logback:logback-classic", version = "1.5.18" }
micrometer-bom = { module = "io.micrometer:micrometer-bom", version = "1.15.3" }
microprofile-fault-tolerance-api = { module = "org.eclipse.microprofile.fault-tolerance:microprofile-fault-tolerance-api", version = "4.1.2" }
Expand All @@ -94,6 +95,7 @@ spark35-sql-scala212 = { module = "org.apache.spark:spark-sql_2.12", version.ref
swagger-annotations = { module = "io.swagger:swagger-annotations", version.ref = "swagger" }
swagger-jaxrs = { module = "io.swagger:swagger-jaxrs", version.ref = "swagger" }
testcontainers-bom = { module = "org.testcontainers:testcontainers-bom", version = "1.21.3" }
testcontainers-keycloak = { module = "com.github.dasniko:testcontainers-keycloak", version = "3.8.0" }
threeten-extra = { module = "org.threeten:threeten-extra", version = "1.8.0" }

[plugins]
Expand Down
1 change: 1 addition & 0 deletions integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
implementation(project(":polaris-api-management-model"))
implementation(project(":polaris-api-catalog-service"))

implementation(libs.jakarta.annotation.api)
implementation(libs.jakarta.ws.rs.api)
implementation(libs.guava)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.google.common.base.Joiner;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.Response;
import java.net.URI;
import java.util.ArrayList;
Expand All @@ -42,38 +41,17 @@
import org.apache.iceberg.rest.responses.ListNamespacesResponse;
import org.apache.iceberg.rest.responses.ListTablesResponse;
import org.apache.iceberg.rest.responses.LoadTableResponse;
import org.apache.iceberg.rest.responses.OAuthTokenResponse;

/**
* A simple, non-exhaustive set of helper methods for accessing the Iceberg REST API.
*
* @see PolarisClient#catalogApi(ClientCredentials)
* @see PolarisClient#catalogApi(String)
*/
public class CatalogApi extends RestApi {
public class CatalogApi extends PolarisRestApi {
public CatalogApi(Client client, PolarisApiEndpoints endpoints, String authToken, URI uri) {
super(client, endpoints, authToken, uri);
}

public String obtainToken(ClientCredentials credentials) {
try (Response response =
request("v1/oauth/tokens")
.post(
Entity.form(
new MultivaluedHashMap<>(
Map.of(
"grant_type",
"client_credentials",
"scope",
"PRINCIPAL_ROLE:ALL",
"client_id",
credentials.clientId(),
"client_secret",
credentials.clientSecret()))))) {
assertThat(response).returns(Response.Status.OK.getStatusCode(), Response::getStatus);
return response.readEntity(OAuthTokenResponse.class).token();
}
}

public void createNamespace(String catalogName, String namespaceName) {
try (Response response =
request("v1/{cat}/namespaces", Map.of("cat", catalogName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@
*/
package org.apache.polaris.service.it.env;

import org.apache.polaris.core.admin.model.PrincipalWithCredentialsCredentials;
import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;

/**
* This class holds credentials for accessing the test Polaris Server. An instance of this class
* representing an admin user is injected into test parameters by {@link
* PolarisIntegrationTestExtension}.
*/
public record ClientCredentials(String clientId, String clientSecret) {}
public record ClientCredentials(String clientId, String clientSecret) {

/**
* Creates a {@link ClientCredentials} from an instance of the Admin API model {@link
* PrincipalWithCredentialsCredentials}.
*/
public ClientCredentials(PrincipalWithCredentialsCredentials credentials) {
this(credentials.getClientId(), credentials.getClientSecret());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.polaris.service.it.env;

import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;

/**
Expand All @@ -27,4 +28,13 @@
*
* @see Server#adminCredentials()
*/
public record ClientPrincipal(String principalName, ClientCredentials credentials) {}
public record ClientPrincipal(String principalName, ClientCredentials credentials) {

/**
* Creates a {@link ClientPrincipal} from an instance of the Admin API model {@link
* PrincipalWithCredentials}.
*/
public ClientPrincipal(PrincipalWithCredentials principal) {
this(principal.getPrincipal().getName(), new ClientCredentials(principal.getCredentials()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
/**
* A simple, non-exhaustive set of helper methods for accessing the generic tables REST API
*
* @see PolarisClient#genericTableApi(ClientCredentials)
* @see PolarisClient#genericTableApi(String)
*/
public class GenericTableApi extends RestApi {
public class GenericTableApi extends PolarisRestApi {
GenericTableApi(Client client, PolarisApiEndpoints endpoints, String authToken, URI uri) {
super(client, endpoints, authToken, uri);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,15 @@
import java.util.Map;
import org.apache.iceberg.rest.RESTCatalog;
import org.apache.iceberg.rest.auth.OAuth2Properties;
import org.apache.polaris.core.admin.model.PrincipalWithCredentials;

public final class IcebergHelper {
private IcebergHelper() {}

public static RESTCatalog restCatalog(
PolarisClient client,
PolarisApiEndpoints endpoints,
PrincipalWithCredentials credentials,
String catalog,
Map<String, String> extraProperties) {
String authToken = client.obtainToken(credentials);
Map<String, String> extraProperties,
String authToken) {
RESTCatalog restCatalog = new RESTCatalog();

ImmutableMap.Builder<String, String> propertiesBuilder =
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
/**
* A simple, non-exhaustive set of helper methods for accessing the Polaris Management API.
*
* @see PolarisClient#managementApi(ClientCredentials)
* @see PolarisClient#managementApi(String)
*/
public class ManagementApi extends RestApi {
public class ManagementApi extends PolarisRestApi {
public ManagementApi(Client client, PolarisApiEndpoints endpoints, String authToken, URI uri) {
super(client, endpoints, authToken, uri);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.polaris.service.it.env;

import static org.assertj.core.api.Assertions.assertThat;

import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.Response;
import java.net.URI;
import java.util.Map;
import org.apache.iceberg.rest.responses.OAuthTokenResponse;

/**
* A simple facade to an OAuth2 token endpoint. It works with both Polaris internal token endpoint
* and with external identity providers.
*/
public class OAuth2Api extends RestApi {

private final String endpointPath;

public OAuth2Api(Client client, URI issuerUrl, String endpointPath) {
super(client, issuerUrl);
this.endpointPath = endpointPath;
}

public String obtainAccessToken(ClientCredentials credentials, String scope) {
return obtainAccessToken(
Map.of(
"grant_type",
"client_credentials",
"client_id",
credentials.clientId(),
"client_secret",
credentials.clientSecret(),
"scope",
scope));
}

public String obtainAccessToken(Map<String, String> requestBody) {
try (Response response =
request(endpointPath).post(Entity.form(new MultivaluedHashMap<>(requestBody)))) {
assertThat(response).returns(Response.Status.OK.getStatusCode(), Response::getStatus);
String token = response.readEntity(OAuthTokenResponse.class).token();
return token;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import jakarta.ws.rs.client.Client;
import java.net.URI;
import java.util.Map;
import java.util.Random;
import org.apache.iceberg.rest.RESTSerializers;
import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
Expand Down Expand Up @@ -73,7 +75,7 @@ public static ObjectMapper buildObjectMapper() {
/**
* This method should be used by test code to make top-level entity names. The purpose of this
* method is two-fold:
* <li>Identify top-level entities for latger clean-up by {@link #cleanUp(ClientCredentials)}.
* <li>Identify top-level entities for later clean-up by {@link #cleanUp(String)}.
* <li>Allow {@link PolarisServerManager}s to customize top-level entities per environment.
*/
public String newEntityName(String hint) {
Expand All @@ -84,70 +86,53 @@ public ManagementApi managementApi(String authToken) {
return new ManagementApi(client, endpoints, authToken, endpoints.managementApiEndpoint());
}

public ManagementApi managementApi(ClientCredentials credentials) {
return managementApi(obtainToken(credentials));
}

public ManagementApi managementApi(PrincipalWithCredentials principal) {
return managementApi(obtainToken(principal));
}

public CatalogApi catalogApi(PrincipalWithCredentials principal) {
return new CatalogApi(
client, endpoints, obtainToken(principal), endpoints.catalogApiEndpoint());
}

public CatalogApi catalogApi(ClientCredentials credentials) {
return new CatalogApi(
client, endpoints, obtainToken(credentials), endpoints.catalogApiEndpoint());
public CatalogApi catalogApi(String authToken) {
return new CatalogApi(client, endpoints, authToken, endpoints.catalogApiEndpoint());
}

public CatalogApi catalogApiPlain() {
return new CatalogApi(client, endpoints, null, endpoints.catalogApiEndpoint());
}

public GenericTableApi genericTableApi(PrincipalWithCredentials principal) {
return new GenericTableApi(
client, endpoints, obtainToken(principal), endpoints.catalogApiEndpoint());
public GenericTableApi genericTableApi(String authToken) {
return new GenericTableApi(client, endpoints, authToken, endpoints.catalogApiEndpoint());
}

public GenericTableApi genericTableApi(ClientCredentials credentials) {
return new GenericTableApi(
client, endpoints, obtainToken(credentials), endpoints.catalogApiEndpoint());
public PolicyApi policyApi(String authToken) {
return new PolicyApi(client, endpoints, authToken, endpoints.catalogApiEndpoint());
}

public PolicyApi policyApi(PrincipalWithCredentials principal) {
return new PolicyApi(client, endpoints, obtainToken(principal), endpoints.catalogApiEndpoint());
/** Requests an access token from the Polaris server for the given principal. */
public String obtainToken(PrincipalWithCredentials credentials) {
return obtainToken(new ClientPrincipal(credentials));
}

public PolicyApi policyApi(ClientCredentials credentials) {
return new PolicyApi(
client, endpoints, obtainToken(credentials), endpoints.catalogApiEndpoint());
/** Requests an access token from the Polaris server for the given principal. */
public String obtainToken(ClientPrincipal principal) {
return obtainToken(principal.credentials());
}

/**
* Requests an access token from the Polaris server for the client ID/secret pair that is part of
* the given principal data object.
*/
public String obtainToken(PrincipalWithCredentials principal) {
return obtainToken(
new ClientCredentials(
principal.getCredentials().getClientId(),
principal.getCredentials().getClientSecret()));
/** Requests an access token from the Polaris server for the given credentials. */
public String obtainToken(ClientCredentials credentials) {
OAuth2Api api = new OAuth2Api(client, endpoints.catalogApiEndpoint(), "v1/oauth/tokens");
return api.obtainAccessToken(credentials, "PRINCIPAL_ROLE:ALL");
}

/** Requests an access token from the Polaris server for the given {@link ClientCredentials}. */
public String obtainToken(ClientCredentials credentials) {
return polarisServerManager().accessManager(client).obtainAccessToken(endpoints, credentials);
/**
* Requests an access token from the authorization server denoted by the issuer URL and token
* endpoint path.
*/
public String obtainToken(URI issuerUrl, String endpointPath, Map<String, String> requestBody) {
return new OAuth2Api(client, issuerUrl, endpointPath).obtainAccessToken(requestBody);
}

private boolean ownedName(String name) {
public boolean ownedName(String name) {
return name != null && name.contains(clientId);
}

public void cleanUp(ClientCredentials credentials) {
ManagementApi managementApi = managementApi(credentials);
CatalogApi catalogApi = catalogApi(credentials);
public void cleanUp(String authToken) {
ManagementApi managementApi = managementApi(authToken);
CatalogApi catalogApi = catalogApi(authToken);

managementApi.listCatalogs().stream()
.filter(c -> ownedName(c.getName()))
Expand Down
Loading