This is an automated email from the ASF dual-hosted git repository.
kumaab pushed a commit to branch ranger-2.10
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/ranger-2.10 by this push:
new f7f746f63 RANGER-5795: Support pluggable token supplier in Ranger REST
client to obtain and refresh bearer tokens dynamically (#1227)
f7f746f63 is described below
commit f7f746f63f746a147575b27c061f1a797584f32a
Author: Abhishek Kumar <[email protected]>
AuthorDate: Mon Sep 14 11:41:40 2026 -0700
RANGER-5795: Support pluggable token supplier in Ranger REST client to
obtain and refresh bearer tokens dynamically (#1227)
(cherry picked from commit ad5eddafe829f7bd378b0d45e391b2bded145ed7)
---
.../destination/RangerAuditServerDestination.java | 4 +-
.../ranger/admin/client/RangerAdminRESTClient.java | 14 +-
...tJwtProvider.java => DefaultTokenSupplier.java} | 12 +-
.../apache/ranger/plugin/authn/JwtProvider.java | 24 ---
.../plugin/policyengine/RangerPluginContext.java | 84 +++++++++--
.../ranger/plugin/service/RangerBasePlugin.java | 6 +-
.../ranger/plugin/util/RangerRESTClient.java | 14 +-
.../plugin/authn/TestDefaultTokenSupplier.java | 52 +++++++
.../TestRangerPluginContextTokenSupplier.java | 164 +++++++++++++++++++++
.../ranger/plugin/util/TestRangerRESTClient.java | 62 ++++++++
10 files changed, 377 insertions(+), 59 deletions(-)
diff --git
a/agents-audit/dest-auditserver/src/main/java/org/apache/ranger/audit/destination/RangerAuditServerDestination.java
b/agents-audit/dest-auditserver/src/main/java/org/apache/ranger/audit/destination/RangerAuditServerDestination.java
index 802e96113..a46f8f5c7 100644
---
a/agents-audit/dest-auditserver/src/main/java/org/apache/ranger/audit/destination/RangerAuditServerDestination.java
+++
b/agents-audit/dest-auditserver/src/main/java/org/apache/ranger/audit/destination/RangerAuditServerDestination.java
@@ -28,7 +28,7 @@
import org.apache.ranger.audit.model.AuditEventBase;
import org.apache.ranger.audit.model.AuthzAuditEvent;
import org.apache.ranger.audit.provider.MiscUtil;
-import org.apache.ranger.plugin.authn.DefaultJwtProvider;
+import org.apache.ranger.plugin.authn.DefaultTokenSupplier;
import org.apache.ranger.plugin.util.RangerRESTClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -90,7 +90,7 @@ public void init(Properties props, String propPrefix) {
this.restClient = new RangerRESTClient(url, sslConfigFileName, config);
if (AUTH_TYPE_JWT.equalsIgnoreCase(authType)) {
- this.restClient.setJwtProvider(new
DefaultJwtProvider("ranger.plugin.policy.rest.client", config));
+ this.restClient.setTokenSupplier(new
DefaultTokenSupplier("ranger.plugin.policy.rest.client", config));
}
this.restClient.setRestClientConnTimeOutMs(connTimeoutMs);
diff --git
a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
index ceae18bc4..ffa52aa1b 100644
---
a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
+++
b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
@@ -30,7 +30,6 @@
import org.apache.ranger.audit.provider.MiscUtil;
import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
import org.apache.ranger.authorization.utils.StringUtil;
-import org.apache.ranger.plugin.authn.JwtProvider;
import org.apache.ranger.plugin.model.RangerRole;
import org.apache.ranger.plugin.util.*;
import org.slf4j.Logger;
@@ -43,6 +42,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
public class RangerAdminRESTClient extends AbstractRangerAdminClient {
private static final Logger LOG =
LoggerFactory.getLogger(RangerAdminRESTClient.class);
@@ -53,7 +53,7 @@ public class RangerAdminRESTClient extends
AbstractRangerAdminClient {
private String serviceNameUrlParam;
private String pluginId;
private String clusterName;
- private JwtProvider jwtProvider;
+ private Supplier<String> tokenSupplier;
private RangerRESTClient restClient;
private RangerRESTUtils restUtils = new RangerRESTUtils();
private boolean supportsPolicyDeltas;
@@ -766,11 +766,11 @@ public boolean isAuthenticationEnabled() {
return (restClient != null && restClient.isAuthFilterPresent())
|| super.isAuthenticationEnabled();
}
- public void setJwtProvider(JwtProvider jwtProvider) {
- this.jwtProvider = jwtProvider;
+ public void setTokenSupplier(Supplier<String> tokenSupplier) {
+ this.tokenSupplier = tokenSupplier;
if (restClient != null) {
- restClient.setJwtProvider(jwtProvider);
+ restClient.setTokenSupplier(tokenSupplier);
}
}
@@ -780,8 +780,8 @@ private void init(String url, String sslConfigFileName, int
restClientConnTimeOu
}
restClient = new RangerRESTClient(url, sslConfigFileName,
config);
- if (jwtProvider != null) {
- restClient.setJwtProvider(jwtProvider);
+ if (tokenSupplier != null) {
+ restClient.setTokenSupplier(tokenSupplier);
}
restClient.setRestClientConnTimeOutMs(restClientConnTimeOutMs);
restClient.setRestClientReadTimeOutMs(restClientReadTimeOutMs);
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultJwtProvider.java
b/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultTokenSupplier.java
similarity index 91%
rename from
agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultJwtProvider.java
rename to
agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultTokenSupplier.java
index 3c5b1a698..25ee5d956 100644
---
a/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultJwtProvider.java
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultTokenSupplier.java
@@ -29,9 +29,10 @@
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
+import java.util.function.Supplier;
-public class DefaultJwtProvider implements JwtProvider {
- private static final Logger LOG =
LoggerFactory.getLogger(DefaultJwtProvider.class);
+public class DefaultTokenSupplier implements Supplier<String> {
+ private static final Logger LOG =
LoggerFactory.getLogger(DefaultTokenSupplier.class);
public static final String JWT_SOURCE = ".jwt.source";
public static final String JWT_ENV = ".jwt.env";
@@ -39,6 +40,9 @@ public class DefaultJwtProvider implements JwtProvider {
public static final String JWT_CRED_FILE = ".jwt.cred.file";
public static final String JWT_CRED_ALIAS = ".jwt.cred.alias";
+ // class name of a Supplier<String> implementation; defaults to
DefaultTokenSupplier if JWT_SOURCE is configured
+ public static final String JWT_SUPPLIER = ".jwt.supplier";
+
private final String jwtEnvVar;
private final String jwtFilePath;
private final String jwtCredFilePath;
@@ -48,7 +52,7 @@ public class DefaultJwtProvider implements JwtProvider {
private volatile String jwt;
- public DefaultJwtProvider(String propertyPrefix, Configuration config) {
+ public DefaultTokenSupplier(String propertyPrefix, Configuration config) {
String jwtSrc = config.get(propertyPrefix + JWT_SOURCE);
if (jwtSrc == null) {
@@ -87,7 +91,7 @@ public DefaultJwtProvider(String propertyPrefix,
Configuration config) {
}
@Override
- public String getJwt() {
+ public String get() {
if (StringUtils.isNotEmpty(jwtEnvVar)) {
jwt = System.getenv(jwtEnvVar);
} else if (StringUtils.isNotEmpty(jwtFilePath)) {
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/authn/JwtProvider.java
b/agents-common/src/main/java/org/apache/ranger/plugin/authn/JwtProvider.java
deleted file mode 100644
index 1c9f0bd10..000000000
---
a/agents-common/src/main/java/org/apache/ranger/plugin/authn/JwtProvider.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.ranger.plugin.authn;
-
-public interface JwtProvider {
- String getJwt();
-}
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPluginContext.java
b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPluginContext.java
index 8b763748e..f52dd8692 100644
---
a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPluginContext.java
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPluginContext.java
@@ -20,11 +20,11 @@
package org.apache.ranger.plugin.policyengine;
import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.conf.Configuration;
import org.apache.ranger.admin.client.RangerAdminClient;
import org.apache.ranger.admin.client.RangerAdminRESTClient;
import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
-import org.apache.ranger.plugin.authn.DefaultJwtProvider;
-import org.apache.ranger.plugin.authn.JwtProvider;
+import org.apache.ranger.plugin.authn.DefaultTokenSupplier;
import org.apache.ranger.plugin.model.RangerPolicy;
import org.apache.ranger.plugin.resourcematcher.RangerResourceMatcher;
import org.apache.ranger.plugin.service.RangerAuthContext;
@@ -32,15 +32,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Supplier;
public class RangerPluginContext {
private static final Logger LOG =
LoggerFactory.getLogger(RangerPluginContext.class);
private final RangerPluginConfig
config;
- private JwtProvider
jwtProvider;
+ private Supplier<String>
tokenSupplier;
private RangerAuthContext
authContext;
private RangerAuthContextListener
authContextListener;
private RangerAdminClient
adminClient;
@@ -49,8 +51,8 @@ public class RangerPluginContext {
public RangerPluginContext(RangerPluginConfig config) {
- this.config = config;
- this.jwtProvider = new
DefaultJwtProvider(config.getPropertyPrefix() + ".policy.rest.client", config);
+ this.config = config;
+ this.tokenSupplier =
getTokenSupplier(config.getPropertyPrefix() + ".policy.rest.client", config);
}
public RangerPluginConfig getConfig() { return config; }
@@ -174,8 +176,8 @@ public RangerAdminClient
createAdminClient(RangerPluginConfig pluginConfig) {
if(ret == null) {
ret = new RangerAdminRESTClient();
- if (jwtProvider != null) {
- ((RangerAdminRESTClient)
ret).setJwtProvider(jwtProvider);
+ if (tokenSupplier != null) {
+ ((RangerAdminRESTClient)
ret).setTokenSupplier(tokenSupplier);
}
}
@@ -190,16 +192,74 @@ public RangerAdminClient
createAdminClient(RangerPluginConfig pluginConfig) {
return ret;
}
- public void registerJWTProvider(JwtProvider jwtProvider) {
- this.jwtProvider = jwtProvider;
+ public void registerTokenSupplier(Supplier<String> tokenSupplier) {
+ this.tokenSupplier = tokenSupplier;
RangerAdminRESTClient restClient = (adminClient instanceof
RangerAdminRESTClient) ? (RangerAdminRESTClient) adminClient : null;
if (restClient != null) {
- restClient.setJwtProvider(jwtProvider);
+ restClient.setTokenSupplier(tokenSupplier);
}
}
- public JwtProvider getJwtProvider() {
- return jwtProvider;
+ public Supplier<String> getTokenSupplier() {
+ return tokenSupplier;
+ }
+
+ private static Supplier<String> getTokenSupplier(String propertyPrefix,
RangerPluginConfig config) {
+ String providerProp = propertyPrefix +
DefaultTokenSupplier.JWT_SUPPLIER;
+ String clzName = config.get(providerProp);
+ final Supplier<String> ret;
+
+ if (StringUtils.isNotBlank(clzName)) {
+ LOG.info("Using Token supplier [{}], config: [{}]",
clzName, providerProp);
+
+ if
(DefaultTokenSupplier.class.getName().equals(clzName)) {
+ ret = new DefaultTokenSupplier(propertyPrefix,
config);
+ } else {
+ ret = getCustomTokenSupplier(clzName, config,
providerProp);
+ }
+ } else if (isJwtSourceConfigured(propertyPrefix, config)) {
+ ret = new DefaultTokenSupplier(propertyPrefix, config);
+ } else {
+ LOG.debug("No token supplier configured, config: [{}]",
providerProp);
+
+ ret = null;
+ }
+
+ return ret;
+ }
+
+ private static boolean isJwtSourceConfigured(String propertyPrefix,
RangerPluginConfig config) {
+ return StringUtils.isNotBlank(config.get(propertyPrefix +
DefaultTokenSupplier.JWT_SOURCE));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Supplier<String> getCustomTokenSupplier(String clzName,
RangerPluginConfig config, String providerProp) {
+ final Class<?> clz;
+
+ /* a misconfigured supplier is rejected: Throwable covers
LinkageError/NoClassDefFoundError from an incomplete classpath */
+ try {
+ clz = Class.forName(clzName);
+ } catch (Throwable excp) {
+ throw new IllegalArgumentException(providerProp + "=" +
clzName + ": failed to load token supplier class", excp);
+ }
+
+ if (!Supplier.class.isAssignableFrom(clz)) {
+ throw new IllegalArgumentException(providerProp + "=" +
clzName + ": class does not implement " + Supplier.class.getName());
+ }
+
+ try {
+ try {
+ /* prefer a constructor that accepts the Ranger
Configuration */
+ Constructor<?> ctor =
clz.getDeclaredConstructor(Configuration.class);
+
+ return (Supplier<String>)
ctor.newInstance(config);
+ } catch (NoSuchMethodException excp) {
+ /* fall back to the no-argument constructor */
+ return (Supplier<String>)
clz.getDeclaredConstructor().newInstance();
+ }
+ } catch (Throwable excp) {
+ throw new IllegalArgumentException(providerProp + "=" +
clzName + ": failed to instantiate token supplier", excp);
+ }
}
}
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
index 970473889..b90b2a520 100644
---
a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.util.*;
+import java.util.function.Supplier;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
@@ -34,7 +35,6 @@
import org.apache.ranger.authorization.hadoop.config.RangerAuditConfig;
import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
import org.apache.ranger.authorization.utils.StringUtil;
-import org.apache.ranger.plugin.authn.JwtProvider;
import org.apache.ranger.plugin.contextenricher.RangerAdminUserStoreRetriever;
import org.apache.ranger.plugin.contextenricher.RangerUserStoreEnricher;
import org.apache.ranger.plugin.model.RangerBaseModelObject;
@@ -216,8 +216,8 @@ public static AuditHandler getAuditProvider(String
serviceName) {
return ret;
}
- public void registerJwtProvider(JwtProvider jwtProvider) {
- pluginContext.registerJWTProvider(jwtProvider);
+ public void registerTokenSupplier(Supplier<String> tokenSupplier) {
+ pluginContext.registerTokenSupplier(tokenSupplier);
}
public String getServiceType() {
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
index aa7773612..4830bbfed 100644
---
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
@@ -37,6 +37,7 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
@@ -57,7 +58,6 @@
import org.apache.ranger.authorization.hadoop.utils.RangerCredentialProvider;
import org.apache.ranger.authorization.utils.JsonUtils;
import org.apache.ranger.authorization.utils.StringUtil;
-import org.apache.ranger.plugin.authn.JwtProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -119,7 +119,7 @@ public class RangerRESTClient {
private volatile Client client;
private volatile Client cookieAuthClient;
- private JwtProvider jwtProvider;
+ private volatile Supplier<String> tokenSupplier;
private ClientFilter jwtAuthFilter;
private ClientFilter basicAuthFilter;
private volatile String basicAuthHeader;
@@ -194,8 +194,8 @@ public void setBasicAuthInfo(String username, String
password) {
setBasicAuthFilter(username, password);
}
- public void setJwtProvider(JwtProvider jwtProvider) {
- this.jwtProvider = jwtProvider;
+ public void setTokenSupplier(Supplier<String> tokenSupplier) {
+ this.tokenSupplier = tokenSupplier;
setJWTFilter();
resetClient();
@@ -301,13 +301,13 @@ private Client buildClient() {
}
private void setJWTFilter() {
- JwtProvider jwtProvider = this.jwtProvider;
- if (jwtProvider != null) {
+ Supplier<String> provider = tokenSupplier;
+ if (provider != null) {
LOG.info("Registering JWT auth header in REST client");
jwtAuthFilter = new ClientFilter() {
@Override
public ClientResponse handle(ClientRequest
clientRequest) throws ClientHandlerException {
- String jwt =
StringUtils.trimToNull(jwtProvider.getJwt());
+ String jwt =
StringUtils.trimToNull(provider.get());
/* without a token, fall back to basic
auth when it is configured */
if (jwt != null) {
diff --git
a/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultTokenSupplier.java
b/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultTokenSupplier.java
new file mode 100644
index 000000000..809031215
--- /dev/null
+++
b/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultTokenSupplier.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ranger.plugin.authn;
+
+import org.apache.hadoop.conf.Configuration;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TestDefaultTokenSupplier {
+ @Test
+ public void defaultTokenSupplierIsSupplierAndReadsTokenFromFile() throws
Exception {
+
assertTrue(Supplier.class.isAssignableFrom(DefaultTokenSupplier.class),
"DefaultTokenSupplier should implement Supplier<String>");
+
+ File jwtFile = File.createTempFile("ranger-jwt", ".token");
+
+ jwtFile.deleteOnExit();
+
+ Files.write(jwtFile.toPath(), "file-jwt-token".getBytes());
+
+ Configuration config = new Configuration();
+
+ config.set("test.prefix" + DefaultTokenSupplier.JWT_SOURCE, "file");
+ config.set("test.prefix" + DefaultTokenSupplier.JWT_FILE,
jwtFile.getAbsolutePath());
+
+ Supplier<String> provider = new DefaultTokenSupplier("test.prefix",
config);
+
+ assertEquals("file-jwt-token", provider.get());
+ }
+}
diff --git
a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java
b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java
new file mode 100644
index 000000000..0ebe4afbd
--- /dev/null
+++
b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java
@@ -0,0 +1,164 @@
+/*
+ * 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.ranger.plugin.policyengine;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.authn.DefaultTokenSupplier;
+import org.junit.jupiter.api.Test;
+
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestRangerPluginContextTokenSupplier {
+ @Test
+ public void noTokenSupplierWhenNotConfigured() {
+ RangerPluginConfig config = newConfig();
+ RangerPluginContext ctx = new RangerPluginContext(config);
+
+ assertNull(ctx.getTokenSupplier());
+ }
+
+ @Test
+ public void usesDefaultProviderWhenJwtSourceConfigured() {
+ RangerPluginConfig config = newConfig();
+
+ config.set(jwtSourceProperty(config), "env");
+
+ RangerPluginContext ctx = new RangerPluginContext(config);
+
+ assertEquals(DefaultTokenSupplier.class,
ctx.getTokenSupplier().getClass());
+ }
+
+ @Test
+ public void usesDefaultProviderWhenExplicitlyConfigured() {
+ RangerPluginConfig config = newConfig();
+
+ config.set(tokenSupplierProperty(config),
DefaultTokenSupplier.class.getName());
+
+ RangerPluginContext ctx = new RangerPluginContext(config);
+
+ assertEquals(DefaultTokenSupplier.class,
ctx.getTokenSupplier().getClass());
+ }
+
+ @Test
+ public void usesCustomProviderWithNoArgConstructor() {
+ RangerPluginConfig config = newConfig();
+
+ config.set(tokenSupplierProperty(config),
NoArgTokenSupplier.class.getName());
+
+ RangerPluginContext ctx = new RangerPluginContext(config);
+
+ assertEquals(NoArgTokenSupplier.class,
ctx.getTokenSupplier().getClass());
+ assertEquals("no-arg-token", ctx.getTokenSupplier().get());
+ }
+
+ @Test
+ public void usesCustomProviderWithConfigConstructor() {
+ RangerPluginConfig config = newConfig();
+
+ config.set(tokenSupplierProperty(config),
ConfigTokenSupplier.class.getName());
+
+ RangerPluginContext ctx = new RangerPluginContext(config);
+
+ assertEquals(ConfigTokenSupplier.class,
ctx.getTokenSupplier().getClass());
+ assertEquals("config-token", ctx.getTokenSupplier().get());
+ }
+
+ @Test
+ public void throwsForClassThatIsNotSupplier() {
+ RangerPluginConfig config = newConfig();
+
+ config.set(tokenSupplierProperty(config),
NotASupplier.class.getName());
+ config.set(jwtSourceProperty(config), "env");
+
+ assertThrows(IllegalArgumentException.class, () -> new
RangerPluginContext(config));
+ }
+
+ @Test
+ public void throwsForUnknownClass() {
+ RangerPluginConfig config = newConfig();
+
+ config.set(tokenSupplierProperty(config),
"org.apache.ranger.NoSuchTokenSupplier");
+ config.set(jwtSourceProperty(config), "env");
+
+ assertThrows(IllegalArgumentException.class, () -> new
RangerPluginContext(config));
+ }
+
+ @Test
+ public void throwsWhenProviderConstructorThrows() {
+ RangerPluginConfig config = newConfig();
+
+ config.set(tokenSupplierProperty(config),
FailingTokenSupplier.class.getName());
+ config.set(jwtSourceProperty(config), "env");
+
+ assertThrows(IllegalArgumentException.class, () -> new
RangerPluginContext(config));
+ }
+
+ public static class NoArgTokenSupplier implements Supplier<String> {
+ @Override
+ public String get() {
+ return "no-arg-token";
+ }
+ }
+
+ public static class ConfigTokenSupplier implements Supplier<String> {
+ public ConfigTokenSupplier(Configuration config) {
+ /* custom providers may accept the Ranger Configuration */
+ }
+
+ @Override
+ public String get() {
+ return "config-token";
+ }
+ }
+
+ public static class NotASupplier {
+ public String get() {
+ return "not-a-supplier";
+ }
+ }
+
+ public static class FailingTokenSupplier implements Supplier<String> {
+ public FailingTokenSupplier() {
+ throw new IllegalStateException("token source unavailable");
+ }
+
+ @Override
+ public String get() {
+ return "never-returned";
+ }
+ }
+
+ private RangerPluginConfig newConfig() {
+ return new RangerPluginConfig("hive", "test-service", "test-app",
"cl1", "on-perm", new RangerPolicyEngineOptions());
+ }
+
+ private String tokenSupplierProperty(RangerPluginConfig config) {
+ return config.getPropertyPrefix() + ".policy.rest.client" +
DefaultTokenSupplier.JWT_SUPPLIER;
+ }
+
+ private String jwtSourceProperty(RangerPluginConfig config) {
+ return config.getPropertyPrefix() + ".policy.rest.client" +
DefaultTokenSupplier.JWT_SOURCE;
+ }
+}
diff --git
a/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerRESTClient.java
b/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerRESTClient.java
index 9d837a2c4..2ab5b7eb5 100644
---
a/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerRESTClient.java
+++
b/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerRESTClient.java
@@ -19,11 +19,21 @@
package org.apache.ranger.plugin.util;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.hadoop.conf.Configuration;
import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
import org.apache.ranger.plugin.policyengine.RangerPolicyEngineOptions;
import org.apache.ranger.plugin.service.RangerBasePlugin;
import org.junit.jupiter.api.Test;
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -50,4 +60,56 @@ public void testPluginInit_WithValidUrl_Succeeds() {
plugin.init();
assertNotNull(plugin, "RangerBasePlugin should be initialized
successfully");
}
+
+ @Test
+ public void jwtProviderInjectsBearerAuthorizationHeader() throws Exception
{
+ assertEquals("Bearer test-jwt-token", captureAuthHeader(() ->
"test-jwt-token", null, null));
+ }
+
+ @Test
+ public void tokenSupplierWithoutTokenFallsBackToBasicAuth() throws
Exception {
+ assertEquals("Basic dXNlcjE6cGFzc3dvcmQx", captureAuthHeader(() ->
null, "user1", "password1"));
+ }
+
+ private String captureAuthHeader(Supplier<String> tokenSupplier, String
username, String password) throws Exception {
+ AtomicReference<String> capturedAuthHeader = new AtomicReference<>();
+ HttpServer httpServer = HttpServer.create(new
InetSocketAddress(0), 0);
+
+ httpServer.createContext("/", exchange -> {
+ List<String> values =
exchange.getRequestHeaders().get("Authorization");
+
+ if (values != null && !values.isEmpty()) {
+ capturedAuthHeader.set(values.get(0));
+ }
+
+ exchange.sendResponseHeaders(200, -1);
+ exchange.close();
+ });
+
+ httpServer.start();
+
+ try {
+ String serverUrl = "http://localhost:" +
httpServer.getAddress().getPort();
+ Configuration conf = new Configuration();
+ RangerRESTClient client = new RangerRESTClient(serverUrl, null,
conf);
+
+ if (username != null) {
+ client.setBasicAuthInfo(username, password);
+ }
+
+ // custom token provider is now a plain
java.util.function.Supplier<String> (no dependency on Ranger interfaces)
+ client.setTokenSupplier(tokenSupplier);
+
+ assertTrue(client.isAuthFilterPresent(), "JWT auth filter should
be present when a token supplier is registered");
+
+ ClientResponse response = client.get("/test",
Collections.emptyMap());
+
+ assertEquals(200, response.getStatus());
+ response.close();
+
+ return capturedAuthHeader.get();
+ } finally {
+ httpServer.stop(0);
+ }
+ }
}