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);
+        }
+    }
 }

Reply via email to