Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST: disallow overriding "credential" in table sessions #10345

Merged
merged 1 commit into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
REST: disallow overriding "credential" in table sessions
See #10256 for context.

This change disallows overriding the "credential" property
in table sessions, by introducing an allow-list of
auth-related properties that can be overridden in such
situations.

Only the "token" property and properties used to exchange
one token for another ("urn:ietf:params:oauth:token-type:*")
are now allowed.
  • Loading branch information
adutra committed Jun 5, 2024
commit f9d20a04ee34c9e02f666471474640073af69bac
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableSet;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
import org.apache.iceberg.relocated.com.google.common.collect.Maps;
import org.apache.iceberg.rest.auth.AuthConfig;
Expand Down Expand Up @@ -124,6 +125,13 @@ public class RESTSessionCatalog extends BaseViewSessionCatalog
OAuth2Properties.SAML2_TOKEN_TYPE,
OAuth2Properties.SAML1_TOKEN_TYPE);

// Auth-related properties that are allowed to be passed to the table session
private static final Set<String> TABLE_SESSION_ALLOW_LIST =
ImmutableSet.<String>builder()
.add(OAuth2Properties.TOKEN)
.addAll(TOKEN_PREFERENCE_ORDER)
.build();

private final Function<Map<String, String>, RESTClient> clientBuilder;
private final BiFunction<SessionContext, Map<String, String>, FileIO> ioBuilder;
private Cache<String, AuthSession> sessions = null;
Expand Down Expand Up @@ -922,7 +930,14 @@ private FileIO tableFileIO(SessionContext context, Map<String, String> config) {
}

private AuthSession tableSession(Map<String, String> tableConf, AuthSession parent) {
Pair<String, Supplier<AuthSession>> newSession = newSession(tableConf, tableConf, parent);
Map<String, String> credentials = Maps.newHashMapWithExpectedSize(tableConf.size());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this change still allows overriding credential from the SessionContext which I believe is the desired behavior. But I'm happy to also apply the restriction to SessionContext as well.

for (String prop : tableConf.keySet()) {
if (TABLE_SESSION_ALLOW_LIST.contains(prop)) {
credentials.put(prop, tableConf.get(prop));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: newline after the closing }


Pair<String, Supplier<AuthSession>> newSession = newSession(credentials, tableConf, parent);
if (null == newSession) {
return parent;
}
Expand Down
74 changes: 38 additions & 36 deletions core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -902,9 +902,9 @@ public void testTableCredential(String oauth2ServerUri) {
testTableAuth(
"catalog",
ImmutableMap.of("urn:ietf:params:oauth:token-type:id_token", "id-token"),
ImmutableMap.of("credential", "table-user:secret"),
ImmutableMap.of("credential", "table-user:secret"), // will be ignored
ImmutableMap.of("Authorization", "Bearer token-exchange-token:sub=id-token,act=catalog"),
ImmutableMap.of("Authorization", "Bearer token-exchange-token:sub=id-token,act=catalog"),
ImmutableMap.of("Authorization", "Bearer client-credentials-token:sub=table-user"),
oauth2ServerUri);
}

Expand Down Expand Up @@ -1354,9 +1354,9 @@ public void testTableAuth(
eq(expectedContextHeaders),
any());

// if the table returned a bearer token, there will be no token request
if (!tableConfig.containsKey("token")) {
// client credentials or token exchange to get a table token
// if the table returned a bearer token or a credential, there will be no token request
if (!tableConfig.containsKey("token") && !tableConfig.containsKey("credential")) {
// token exchange to get a table token
Mockito.verify(adapter, times(1))
.execute(
eq(HTTPMethod.POST),
Expand All @@ -1368,38 +1368,40 @@ public void testTableAuth(
any());
}

// automatic refresh when metadata is accessed after commit
Mockito.verify(adapter)
.execute(
eq(HTTPMethod.GET),
eq("v1/namespaces/ns/tables/table"),
any(),
any(),
eq(LoadTableResponse.class),
eq(expectedTableHeaders),
any());

// load table from catalog
Mockito.verify(adapter)
.execute(
eq(HTTPMethod.GET),
eq("v1/namespaces/ns/tables/table"),
any(),
any(),
eq(LoadTableResponse.class),
eq(expectedContextHeaders),
any());
if (expectedContextHeaders.equals(expectedTableHeaders)) {
// load table from catalog + refresh loaded table
Mockito.verify(adapter, times(2))
.execute(
eq(HTTPMethod.GET),
eq("v1/namespaces/ns/tables/table"),
any(),
any(),
eq(LoadTableResponse.class),
eq(expectedTableHeaders),
any());
} else {
// load table from catalog
Mockito.verify(adapter)
.execute(
eq(HTTPMethod.GET),
eq("v1/namespaces/ns/tables/table"),
any(),
any(),
eq(LoadTableResponse.class),
eq(expectedContextHeaders),
any());

// refresh loaded table
Mockito.verify(adapter)
.execute(
eq(HTTPMethod.GET),
eq("v1/namespaces/ns/tables/table"),
any(),
any(),
eq(LoadTableResponse.class),
eq(expectedTableHeaders),
any());
// refresh loaded table
Mockito.verify(adapter)
.execute(
eq(HTTPMethod.GET),
eq("v1/namespaces/ns/tables/table"),
any(),
any(),
eq(LoadTableResponse.class),
eq(expectedTableHeaders),
any());
}
}

@ParameterizedTest
Expand Down