Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -983,8 +983,8 @@ public enum ConfVars {
" NOSASL: Raw transport" +
" JWT: JSON Web Token authentication via JWT token. Only supported in Http/Https mode"),
THRIFT_METASTORE_AUTHENTICATION_JWT_JWKS_URL("metastore.authentication.jwt.jwks.url",
"hive.metastore.authentication.jwt.jwks.url", "", "File URL from where URLBasedJWKSProvider "
+ "in metastore server will try to load JWKS to match a JWT sent in HTTP request header. Used only when "
"hive.metastore.authentication.jwt.jwks.url", "", "File URL from where "
+ "metastore server will try to load JWKS to match a JWT sent in HTTP request header. Used only when "
+ "Hive metastore server is running in JWT auth mode"),
METASTORE_CUSTOM_AUTHENTICATION_CLASS("metastore.custom.authentication.class",
"hive.metastore.custom.authentication.class",
Expand Down Expand Up @@ -1873,8 +1873,56 @@ public enum ConfVars {
" positive value will be used as-is."
),
CATALOG_SERVLET_AUTH("metastore.catalog.servlet.auth",
"hive.metastore.catalog.servlet.auth", "jwt", new StringSetValidator("none", "simple", "jwt"),
"HMS Catalog servlet authentication method (none, simple, or jwt)."
"hive.metastore.catalog.servlet.auth", "jwt", new StringSetValidator("none", "simple", "jwt", "oauth2"),
"HMS Catalog servlet authentication method (none, simple, jwt, or oauth2)."
),
CATALOG_SERVLET_AUTH_OAUTH2_ISSUER("metastore.catalog.servlet.auth.oauth2.issuer",
"hive.metastore.catalog.servlet.auth.oauth2.issuer", "",
"The authorization server's identifier, which is a URL. This is required when you use " +
"metastore.catalog.servlet.auth=oauth2"
),
CATALOG_SERVLET_AUTH_OAUTH2_AUDIENCE("metastore.catalog.servlet.auth.oauth2.audience",
"hive.metastore.catalog.servlet.auth.oauth2.audience", "",
"The acceptable name in the audience(aud) claim. This is required when you use " +
"metastore.catalog.servlet.auth=oauth2"
),
CATALOG_SERVLET_AUTH_OAUTH2_VALIDATION_METHOD("metastore.catalog.servlet.auth.oauth2.validation.method",
"hive.metastore.catalog.servlet.auth.oauth2.validation.method", "jwt",
new StringSetValidator("jwt", "introspection"),
"How to evaluate an access token. When your authorization server issues opaque tokens or you need " +
"to consider additional security requirements such as token revocations, use introspection."
),
CATALOG_SERVLET_AUTH_OAUTH2_CLIENT_ID("metastore.catalog.servlet.auth.oauth2.client.id",
"hive.metastore.catalog.servlet.auth.oauth2.client.id", "",
"The client ID to authenticate HMS, as a resource server, to the introspection endpoint. This is required to " +
"use metastore.catalog.servlet.auth.oauth2.validation.method=introspection."
),
CATALOG_SERVLET_AUTH_OAUTH2_CLIENT_SECRET("metastore.catalog.servlet.auth.oauth2.client.secret",
"hive.metastore.catalog.servlet.auth.oauth2.client.secret", "",
"The client secret to authenticate HMS, as a resource server, to the introspection endpoint. This is " +
"required to use metastore.catalog.servlet.auth.oauth2.validation.method=introspection."
),
CATALOG_SERVLET_AUTH_OAUTH2_INTROSPECTION_CACHE_EXPIRY(
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Quickly checking, OAuth2Properties.TOKEN_EXPIRES_IN_MS is used for an Iceberg client to refresh an access token, meaning the process (2) below. CATALOG_SERVLET_AUTH_OAUTH2_INTROSPECTION_CACHE_EXPIRY is used to cache the response of (4).
image

Copy link
Member

@deniskuzZ deniskuzZ Sep 25, 2025

Choose a reason for hiding this comment

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

in our case HMS is both Client and a Resource Server, right?
Image Sep 25, 2025, 11_50_14 AM

Copy link
Member

@deniskuzZ deniskuzZ Sep 25, 2025

Choose a reason for hiding this comment

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

btw, don't we need to define these as well or that would be client's(caller) responsibility
https://datatracker.ietf.org/doc/html/rfc8414#section-2

  • authorization_endpoint
    redirects end-users to authenticate.

  • token_endpoint – REQUIRED unless the AS supports only implicit.
    to exchange codes for tokens.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The entire authorization flow is decomposed into two steps: one to obtain an Access Token and another to access protected resources using the Access Token. I will skip explaining the first step because HMS is not involved at all. Anyway, a client exchanges some credentials for an Access Token with a limited scope and expiration. authorization_endpoint and token_endpoint are used there; That's why we don't need to configure them.
In the second step, HMS accepts and validates the Access Token. Token Introspection is a method to verify the latest state of Access Tokens through the network. CATALOG_SERVLET_AUTH_OAUTH2_CLIENT_ID and CATALOG_SERVLET_AUTH_OAUTH2_CLIENT_SECRET are used here so that only HMS can validate the access token and get the details.

image

Copy link
Member

Choose a reason for hiding this comment

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

Thanks! Then I understood it right.
But that would mean we need to add access token retrieval and refresh handling to the Hive RestCatalog OAuth2 client.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case of Iceberg REST, iceberg-core is responsible for it. In the case of Thrift over REST(Hive REST v2), as you say, IMetastoreClient needs to take the new responsibility, as you say. That's why I could not add metastore.authentication=oauth2 in this pull request. I can add it if someone is willing to have it, though.

"metastore.catalog.servlet.auth.oauth2.introspection.cache.expiry",
"hive.metastore.catalog.servlet.auth.oauth2.introspection.cache.expiry", 60, TimeUnit.SECONDS,
"The expiry time of the token introspection cache. Set to 0 to disable caching."
),
CATALOG_SERVLET_AUTH_OAUTH2_INTROSPECTION_CACHE_SIZE(
"metastore.catalog.servlet.auth.oauth2.introspection.cache.num",
"hive.metastore.catalog.servlet.auth.oauth2.introspection.cache.num", 1000L,
"The number of entries of the token introspection cache."
),
CATALOG_SERVLET_AUTH_OAUTH2_PRINCIPAL_MAPPER_REGEX_FIELD(
"metastore.catalog.servlet.auth.oauth2.principal.mapper.regex.username.field",
"hive.metastore.catalog.servlet.auth.oauth2.principal.mapper.regex.username.field", "sub",
"The claim name including a username. This is effective when you use RegexPrincipalMapper. For example, if " +
"you want to resolve a user name from the email claim, set this to email."
),
CATALOG_SERVLET_AUTH_OAUTH2_PRINCIPAL_MAPPER_REGEX_PATTERN(
"metastore.catalog.servlet.auth.oauth2.principal.mapper.regex.username.pattern",
"hive.metastore.catalog.servlet.auth.oauth2.principal.mapper.regex.username.pattern", "(.*)",
"The pattern to extract a user name. This is effective when you use RegexPrincipalMapper. For example, if " +
"you want to extract a user name from the local part of the email claim, set this to (.*)@example.com."
),
ICEBERG_CATALOG_SERVLET_PATH("metastore.iceberg.catalog.servlet.path",
"hive.metastore.iceberg.catalog.servlet.path", "iceberg",
Expand Down
10 changes: 10 additions & 0 deletions standalone-metastore/metastore-rest-catalog/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,16 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
import org.apache.iceberg.rest.responses.ListTablesResponse;
import org.apache.iceberg.rest.responses.LoadTableResponse;
import org.apache.iceberg.rest.responses.LoadViewResponse;
import org.apache.iceberg.rest.responses.OAuthTokenResponse;
import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
import org.apache.iceberg.util.Pair;
import org.apache.iceberg.util.PropertyUtil;
Expand Down Expand Up @@ -104,15 +103,6 @@ public class HMSCatalogAdapter implements RESTClient {
.put(CommitStateUnknownException.class, 500)
.buildOrThrow();

private static final String URN_OAUTH_TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange";
private static final String URN_OAUTH_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token";
private static final String GRANT_TYPE = "grant_type";
private static final String CLIENT_CREDENTIALS = "client_credentials";
private static final String BEARER = "Bearer";
private static final String CLIENT_ID = "client_id";
private static final String ACTOR_TOKEN = "actor_token";
private static final String SUBJECT_TOKEN = "subject_token";

private final Catalog catalog;
private final SupportsNamespaces asNamespaceCatalog;
private final ViewCatalog asViewCatalog;
Expand All @@ -127,8 +117,6 @@ public HMSCatalogAdapter(Catalog catalog) {
}

enum Route {
TOKENS(HTTPMethod.POST, "v1/oauth/tokens", null),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The initial specification of Iceberg REST allows the Iceberg REST Catalog to act as an Authorization Server, which is why the list contains this endpoint. As this does not follow security best practices, it will be removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

It will be removed with Iceberg spec 2.0; at this point, it may be deprecated but should not be removed yet, should it ?

Copy link
Contributor Author

@okumin okumin Sep 30, 2025

Choose a reason for hiding this comment

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

Yes, it should.
The token endpoint is not of a Resource Server but of an Authorization Server, i.e., typically Identity Provider such as Keycloak or Okta. The token endpoint is responsible for issuing a secure(e.g., cryptographically signed) Access Token.
Our endpoint is just a copied and pasted from iceberg-core, which is almost like an echo server(no authentication happens). It is possible to embed the Authorization Server roles in HMS; however, we would need to implement an RFC 6749, RFC 9068, or another RFC-compliant Authorization Server in that case. Otherwise, no user exists(who wants OAuth 2 that allows unauthenticated access?).

private OAuthTokenResponse tokens(Object body) {
@SuppressWarnings("unchecked")
Map<String, String> request = (Map<String, String>) castRequest(Map.class, body);
String grantType = request.get(GRANT_TYPE);
switch (grantType) {
case CLIENT_CREDENTIALS:
return OAuthTokenResponse.builder()
.withToken("client-credentials-token:sub=" + request.get(CLIENT_ID))
.withIssuedTokenType(URN_OAUTH_ACCESS_TOKEN)
.withTokenType(BEARER)
.build();
case URN_OAUTH_TOKEN_EXCHANGE:
String actor = request.get(ACTOR_TOKEN);
String token =
String.format(
"token-exchange-token:sub=%s%s",
request.get(SUBJECT_TOKEN), actor != null ? ",act=" + actor : "");
return OAuthTokenResponse.builder()
.withToken(token)
.withIssuedTokenType(URN_OAUTH_ACCESS_TOKEN)
.withTokenType(BEARER)
.build();

SEPARATE_AUTH_TOKENS_URI(HTTPMethod.POST, "https://auth-server.com/token", null),
CONFIG(HTTPMethod.GET, "v1/config", null),
LIST_NAMESPACES(HTTPMethod.GET, ResourcePaths.V1_NAMESPACES, null),
CREATE_NAMESPACE(HTTPMethod.POST, ResourcePaths.V1_NAMESPACES, CreateNamespaceRequest.class),
Expand Down Expand Up @@ -240,35 +228,6 @@ private ConfigResponse config() {
return castResponse(ConfigResponse.class, ConfigResponse.builder().withEndpoints(endpoints).build());
}

private OAuthTokenResponse tokens(Object body) {
@SuppressWarnings("unchecked")
Map<String, String> request = (Map<String, String>) castRequest(Map.class, body);
String grantType = request.get(GRANT_TYPE);
switch (grantType) {
case CLIENT_CREDENTIALS:
return OAuthTokenResponse.builder()
.withToken("client-credentials-token:sub=" + request.get(CLIENT_ID))
.withIssuedTokenType(URN_OAUTH_ACCESS_TOKEN)
.withTokenType(BEARER)
.build();

case URN_OAUTH_TOKEN_EXCHANGE:
String actor = request.get(ACTOR_TOKEN);
String token =
String.format(
"token-exchange-token:sub=%s%s",
request.get(SUBJECT_TOKEN), actor != null ? ",act=" + actor : "");
return OAuthTokenResponse.builder()
.withToken(token)
.withIssuedTokenType(URN_OAUTH_ACCESS_TOKEN)
.withTokenType(BEARER)
.build();

default:
throw new UnsupportedOperationException("Unsupported grant_type: " + grantType);
}
}

private ListNamespacesResponse listNamespaces(Map<String, String> vars) {
Namespace namespace;
if (vars.containsKey("parent")) {
Expand Down Expand Up @@ -469,9 +428,6 @@ private <T extends RESTResponse> T handleRequest(
counter.inc();
}
switch (route) {
case TOKENS:
return (T) tokens(body);

case CONFIG:
return (T) config();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.iceberg.rest;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.servlet.http.HttpServlet;
Expand Down Expand Up @@ -100,7 +102,9 @@ private Catalog createCatalog() {
*/
private HttpServlet createServlet(Catalog catalog) {
String authType = MetastoreConf.getVar(configuration, ConfVars.CATALOG_SERVLET_AUTH);
ServletSecurity security = new ServletSecurity(AuthType.fromString(authType), configuration);
// Iceberg REST client uses "catalog" by default
Copy link
Member

Choose a reason for hiding this comment

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

could you please elaborate on the scope? what are the other values?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The scope of OAuth 2 refers to the range of access from a client to protected resources. For example, let's say a web service (Client) wants to access the name, icon, and email (Protected Resources) of your (Resource Owner) Google account. The web service does not need to access your items in Google Photos, nor does it need to update Google's profiles. In this case, you can allow the web service to access only a limited set of resources.
image

The Iceberg client uses only "catalog" to give access to all Iceberg-related endpoints. We may protect HMS Thrift over HTTP with OAuth 2 and grant the "metastore" scope in the future. In that case, a user can create an access token that can use the HMS API but can not use Iceberg REST, or vice versa.

Copy link
Member

Choose a reason for hiding this comment

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

do you think catalog-read and catalog-write scopes would be useful? This way we could restrict some of the clients from modifying the metadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is an excellent question. It would be helpful. For example, it would allow you to give the read-only permission to a BI tool. I've not found the best practice for HMS and Iceberg users. I found some samples:

Copy link
Member

Choose a reason for hiding this comment

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

that's probably needs more design thinking so we could handle it later

List<String> scopes = Collections.singletonList("catalog");
ServletSecurity security = new ServletSecurity(AuthType.fromString(authType), configuration, req -> scopes);
return security.proxy(new HMSCatalogServlet(new HMSCatalogAdapter(catalog)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
package org.apache.iceberg.rest;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.util.Map;
import java.util.Optional;
Expand All @@ -31,7 +29,6 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
import org.apache.iceberg.relocated.com.google.common.io.CharStreams;
import org.apache.iceberg.rest.HMSCatalogAdapter.Route;
import org.apache.iceberg.rest.HTTPRequest.HTTPMethod;
import org.apache.iceberg.rest.responses.ErrorResponse;
Expand Down Expand Up @@ -152,10 +149,6 @@ static ServletRequestContext from(HttpServletRequest request) throws IOException
if (route.requestClass() != null) {
requestBody =
RESTObjectMapper.mapper().readValue(request.getReader(), route.requestClass());
} else if (route == Route.TOKENS) {
try (Reader reader = new InputStreamReader(request.getInputStream())) {
requestBody = RESTUtil.decodeFormData(CharStreams.toString(reader));
}
}

Map<String, String> queryParams =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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.iceberg.rest;

import java.util.Map;
import org.apache.hadoop.hive.metastore.ServletSecurity.AuthType;
import org.apache.hadoop.hive.metastore.annotation.MetastoreCheckinTest;
import org.apache.iceberg.exceptions.NotAuthorizedException;
import org.apache.iceberg.rest.extension.HiveRESTCatalogServerExtension;
import org.junit.experimental.categories.Category;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

@Category(MetastoreCheckinTest.class)
class TestRESTCatalogOAuth2Jwt extends BaseRESTCatalogTests {
@RegisterExtension
private static final HiveRESTCatalogServerExtension REST_CATALOG_EXTENSION =
HiveRESTCatalogServerExtension.builder(AuthType.OAUTH2).build();

@Override
protected Map<String, String> getDefaultClientConfiguration() {
return Map.of(
"uri", REST_CATALOG_EXTENSION.getRestEndpoint(),
"rest.auth.type", "oauth2",
"oauth2-server-uri", REST_CATALOG_EXTENSION.getOAuth2TokenEndpoint(),
"credential", REST_CATALOG_EXTENSION.getOAuth2ClientCredential()
);
}

@Test
void testWithAccessToken() {
Map<String, String> properties = Map.of(
"uri", REST_CATALOG_EXTENSION.getRestEndpoint(),
"rest.auth.type", "oauth2",
"token", REST_CATALOG_EXTENSION.getOAuth2AccessToken()
);
Assertions.assertFalse(RCKUtils.initCatalogClient(properties).listNamespaces().isEmpty());
}

@Test
void testWithWrongCredential() {
Map<String, String> properties = Map.of(
"uri", REST_CATALOG_EXTENSION.getRestEndpoint(),
"rest.auth.type", "oauth2",
"oauth2-server-uri", REST_CATALOG_EXTENSION.getOAuth2TokenEndpoint(),
"credential", "dummy:dummy"
);
NotAuthorizedException error = Assertions.assertThrows(NotAuthorizedException.class,
() -> RCKUtils.initCatalogClient(properties));
Assertions.assertEquals("Not authorized: invalid_client: Invalid client or Invalid client credentials",
error.getMessage());
}

@Test
void testWithWrongAccessToken() {
Map<String, String> properties = Map.of(
"uri", REST_CATALOG_EXTENSION.getRestEndpoint(),
"rest.auth.type", "oauth2",
"token", "invalid"
);
NotAuthorizedException error = Assertions.assertThrows(NotAuthorizedException.class,
() -> RCKUtils.initCatalogClient(properties));
Assertions.assertEquals("Not authorized: Authentication error: Invalid JWT serialization: Missing dot delimiter(s)",
error.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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.iceberg.rest;

import static org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars.CATALOG_SERVLET_AUTH_OAUTH2_VALIDATION_METHOD;

import java.util.Map;
import org.apache.hadoop.hive.metastore.ServletSecurity.AuthType;
import org.apache.hadoop.hive.metastore.annotation.MetastoreCheckinTest;
import org.apache.iceberg.exceptions.NotAuthorizedException;
import org.apache.iceberg.rest.extension.HiveRESTCatalogServerExtension;
import org.junit.experimental.categories.Category;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

@Category(MetastoreCheckinTest.class)
class TestRESTCatalogOAuth2TokenIntrospection extends BaseRESTCatalogTests {
@RegisterExtension
private static final HiveRESTCatalogServerExtension REST_CATALOG_EXTENSION =
HiveRESTCatalogServerExtension.builder(AuthType.OAUTH2)
.configure(CATALOG_SERVLET_AUTH_OAUTH2_VALIDATION_METHOD.getVarname(), "introspection").build();

@Override
protected Map<String, String> getDefaultClientConfiguration() {
return Map.of(
"uri", REST_CATALOG_EXTENSION.getRestEndpoint(),
"rest.auth.type", "oauth2",
"oauth2-server-uri", REST_CATALOG_EXTENSION.getOAuth2TokenEndpoint(),
"credential", REST_CATALOG_EXTENSION.getOAuth2ClientCredential()
);
}

@Test
void testWithAccessToken() {
Map<String, String> properties = Map.of(
"uri", REST_CATALOG_EXTENSION.getRestEndpoint(),
"rest.auth.type", "oauth2",
"token", REST_CATALOG_EXTENSION.getOAuth2AccessToken()
);
Assertions.assertFalse(RCKUtils.initCatalogClient(properties).listNamespaces().isEmpty());
}

@Test
void testWithWrongCredential() {
Map<String, String> properties = Map.of(
"uri", REST_CATALOG_EXTENSION.getRestEndpoint(),
"rest.auth.type", "oauth2",
"oauth2-server-uri", REST_CATALOG_EXTENSION.getOAuth2TokenEndpoint(),
"credential", "dummy:dummy"
);
NotAuthorizedException error = Assertions.assertThrows(NotAuthorizedException.class,
() -> RCKUtils.initCatalogClient(properties));
Assertions.assertEquals("Not authorized: invalid_client: Invalid client or Invalid client credentials",
error.getMessage());
}

@Test
void testWithWrongAccessToken() {
Map<String, String> properties = Map.of(
"uri", REST_CATALOG_EXTENSION.getRestEndpoint(),
"rest.auth.type", "oauth2",
"token", "invalid"
);
NotAuthorizedException error = Assertions.assertThrows(NotAuthorizedException.class,
() -> RCKUtils.initCatalogClient(properties));
Assertions.assertEquals("Not authorized: Authentication error: The token is not active",
error.getMessage());
}
}
Loading