This is an automated email from the ASF dual-hosted git repository.

abhi pushed a commit to branch ranger-2.9
in repository https://gitbox.apache.org/repos/asf/ranger.git

commit 1e12062734992e5de38a8b910e0183c31af2d2c9
Author: Abhishek Kumar <[email protected]>
AuthorDate: Wed Mar 18 12:25:53 2026 -0700

    RANGER-5499: Add support for header based authentication (#873)
    
    Co-authored-by: Madhan Neethiraj <[email protected]>
    Co-authored-by: Cursor AI (assisted) <[email protected]>
    (cherry picked from commit 2345c1d79d63f632bab237a8b08426d056b30f6c)
    (cherry picked from commit d40fac0b695007394dab90be0b8384f2c51aea03)
---
 mkdocs/.gitignore                                  |   1 +
 .../web/filter/RangerAuthenticationToken.java      |  55 +++++
 .../web/filter/RangerHeaderPreAuthFilter.java      | 117 ++++++++++
 .../RangerSecurityContextFormationFilter.java      |  49 ++--
 .../main/resources/conf.dist/ranger-admin-site.xml |  14 ++
 .../conf.dist/security-applicationContext.xml      |   4 +
 .../web/filter/TestRangerHeaderPreAuthFilter.java  | 179 +++++++++++++++
 .../TestRangerSecurityContextFormationFilter.java  | 250 +++++++++++++++++++++
 8 files changed, 653 insertions(+), 16 deletions(-)

diff --git a/mkdocs/.gitignore b/mkdocs/.gitignore
new file mode 100644
index 000000000..988107fe1
--- /dev/null
+++ b/mkdocs/.gitignore
@@ -0,0 +1 @@
+.cache/
\ No newline at end of file
diff --git 
a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerAuthenticationToken.java
 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerAuthenticationToken.java
new file mode 100644
index 000000000..ccf43a417
--- /dev/null
+++ 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerAuthenticationToken.java
@@ -0,0 +1,55 @@
+/*
+ * 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.security.web.filter;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+
+public class RangerAuthenticationToken extends AbstractAuthenticationToken {
+    private static final long serialVersionUID = 1L;
+
+    private final Object principal;
+    private final int    authType;
+
+    public RangerAuthenticationToken(UserDetails principal, Collection<? 
extends GrantedAuthority> authorities, int authType) {
+        super(authorities);
+
+        this.principal = principal;
+        this.authType  = authType;
+
+        super.setAuthenticated(true);
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return principal;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return null;
+    }
+
+    public int getAuthType() {
+        return authType;
+    }
+}
diff --git 
a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerHeaderPreAuthFilter.java
 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerHeaderPreAuthFilter.java
new file mode 100644
index 000000000..89bb78750
--- /dev/null
+++ 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerHeaderPreAuthFilter.java
@@ -0,0 +1,117 @@
+/*
+ * 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.security.web.filter;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ranger.biz.UserMgr;
+import org.apache.ranger.common.PropertiesUtil;
+import org.apache.ranger.entity.XXAuthSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import 
org.springframework.security.web.authentication.WebAuthenticationDetails;
+import org.springframework.web.filter.GenericFilterBean;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class RangerHeaderPreAuthFilter extends GenericFilterBean {
+    private static final Logger LOG = 
LoggerFactory.getLogger(RangerHeaderPreAuthFilter.class);
+
+    public static final String PROP_HEADER_AUTH_ENABLED        = 
"ranger.admin.authn.header.enabled";
+    public static final String PROP_USERNAME_HEADER_NAME       = 
"ranger.admin.authn.header.username";
+    public static final String PROP_REQUEST_ID_HEADER_NAME     = 
"ranger.admin.authn.header.requestid";
+
+    private boolean headerAuthEnabled;
+    private String  userNameHeaderName;
+
+    @Autowired
+    UserMgr userMgr;
+
+    @PostConstruct
+    public void initialize(FilterConfig filterConfig) throws ServletException {
+        headerAuthEnabled  = 
PropertiesUtil.getBooleanProperty(PROP_HEADER_AUTH_ENABLED, false);
+        userNameHeaderName = 
PropertiesUtil.getProperty(PROP_USERNAME_HEADER_NAME);
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain) throws IOException, ServletException {
+        HttpServletRequest  httpRequest  = (HttpServletRequest) request;
+
+        if (headerAuthEnabled) {
+            Authentication existingAuthn = 
SecurityContextHolder.getContext().getAuthentication();
+
+            if (existingAuthn == null || !existingAuthn.isAuthenticated()) {
+                String username = 
StringUtils.trimToNull(httpRequest.getHeader(userNameHeaderName));
+
+                if (StringUtils.isNotBlank(username)) {
+                    List<GrantedAuthority>    grantedAuthorities = 
getAuthoritiesFromRanger(username);
+                    final UserDetails         principal          = new 
User(username, "", grantedAuthorities);
+                    RangerAuthenticationToken authToken          = new 
RangerAuthenticationToken(principal, grantedAuthorities, 
XXAuthSession.AUTH_TYPE_TRUSTED_PROXY);
+
+                    authToken.setDetails(new 
WebAuthenticationDetails(httpRequest));
+
+                    
SecurityContextHolder.getContext().setAuthentication(authToken);
+
+                    LOG.debug("Authenticated request using trusted headers for 
user={}", username);
+                } else {
+                    LOG.debug("Username header '{}' is missing or empty in the 
request!", userNameHeaderName);
+                }
+            }
+        } else {
+            LOG.debug("Header-based authentication is disabled!");
+        }
+
+        chain.doFilter(request, response);
+    }
+
+    /**
+     * Loads authorities from Ranger DB
+     */
+    private List<GrantedAuthority> getAuthoritiesFromRanger(String username) {
+        List<GrantedAuthority> ret = new ArrayList<>();
+        Collection<String>    roleList = userMgr.getRolesByLoginId(username);
+
+        if (roleList != null) {
+            for (String role : roleList) {
+                if (StringUtils.isNotBlank(role)) {
+                    ret.add(new SimpleGrantedAuthority(role));
+                }
+            }
+        }
+
+        return ret;
+    }
+}
diff --git 
a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java
 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java
index 71d7af0d1..65706070f 100644
--- 
a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java
+++ 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java
@@ -32,6 +32,7 @@
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.ranger.biz.SessionMgr;
 import org.apache.ranger.biz.XUserMgr;
 import org.apache.ranger.common.GUIDUtil;
@@ -43,6 +44,8 @@
 import org.apache.ranger.security.context.RangerContextHolder;
 import org.apache.ranger.security.context.RangerSecurityContext;
 import org.apache.ranger.util.RestUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import 
org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.core.Authentication;
@@ -50,6 +53,7 @@
 import org.springframework.web.filter.GenericFilterBean;
 
 public class RangerSecurityContextFormationFilter extends GenericFilterBean {
+       private static final Logger LOG = 
LoggerFactory.getLogger(RangerSecurityContextFormationFilter.class);
 
        public static final String AKA_SC_SESSION_KEY = "AKA_SECURITY_CONTEXT";
        public static final String USER_AGENT = "User-Agent";
@@ -65,11 +69,13 @@ public class RangerSecurityContextFormationFilter extends 
GenericFilterBean {
 
        @Autowired
        GUIDUtil guidUtil;
-               
-       String testIP = null;
+
+       private final String testIP;
+       private final String requestIdHeaderName;
 
        public RangerSecurityContextFormationFilter() {
-               testIP = PropertiesUtil.getProperty("xa.env.ip");
+               this.testIP              = 
PropertiesUtil.getProperty("xa.env.ip");
+               this.requestIdHeaderName = 
PropertiesUtil.getProperty(RangerHeaderPreAuthFilter.PROP_REQUEST_ID_HEADER_NAME);
        }
 
        /*
@@ -113,14 +119,14 @@ public void doFilter(ServletRequest request, 
ServletResponse response,
                                requestContext.setUserAgent(userAgent);
                                requestContext.setDeviceType(httpUtil
                                                .getDeviceType(httpRequest));
-                               
requestContext.setServerRequestId(guidUtil.genGUID());
+                               
requestContext.setServerRequestId(getRequestId(auth, httpRequest));
                                
requestContext.setRequestURL(httpRequest.getRequestURI());
 
                                
requestContext.setClientTimeOffsetInMinute(clientTimeOffset);
                                context.setRequestContext(requestContext);
 
                                RangerContextHolder.setSecurityContext(context);
-                               int authType = getAuthType(httpRequest);
+                               int authType = getAuthType(auth, httpRequest);
                                UserSessionBase userSession = 
sessionMgr.processSuccessLogin(
                                                authType, userAgent, 
httpRequest);
 
@@ -159,25 +165,36 @@ private void setupAdminOpContext(ServletRequest request) {
                }
        }
 
-       private int getAuthType(HttpServletRequest request) {
-               int authType;
+       private int getAuthType(Authentication auth, HttpServletRequest 
request) {
+               if (auth instanceof RangerAuthenticationToken) {
+                       return ((RangerAuthenticationToken) auth).getAuthType();
+               }
+
                Object ssoEnabledObj = request.getAttribute("ssoEnabled");
                Boolean ssoEnabled = ssoEnabledObj != null ? 
Boolean.valueOf(String.valueOf(ssoEnabledObj)) : 
PropertiesUtil.getBooleanProperty("ranger.sso.enabled", false);
 
                if (ssoEnabled) {
-                       authType = XXAuthSession.AUTH_TYPE_SSO;
+                       return XXAuthSession.AUTH_TYPE_SSO;
                } else if (request.getAttribute("spnegoEnabled") != null && 
Boolean.valueOf(String.valueOf(request.getAttribute("spnegoEnabled")))){
                        if (request.getAttribute("trustedProxyEnabled") != null 
&& 
Boolean.valueOf(String.valueOf(request.getAttribute("trustedProxyEnabled")))) {
-                               if (logger.isDebugEnabled()) {
-                                       logger.debug("Setting auth type as 
trusted proxy");
-                               }
-                               authType = 
XXAuthSession.AUTH_TYPE_TRUSTED_PROXY;
+                               LOG.debug("Setting auth type as trusted proxy");
+
+                               return XXAuthSession.AUTH_TYPE_TRUSTED_PROXY;
                        } else {
-                               authType = XXAuthSession.AUTH_TYPE_KERBEROS;
+                               return XXAuthSession.AUTH_TYPE_KERBEROS;
                        }
-               } else {
-                       authType = XXAuthSession.AUTH_TYPE_PASSWORD;
                }
-               return authType;
+
+               return XXAuthSession.AUTH_TYPE_PASSWORD;
+       }
+
+       private String getRequestId(Authentication auth, HttpServletRequest 
request) {
+               String ret = null;
+
+               if (requestIdHeaderName != null && auth instanceof 
RangerAuthenticationToken && ((RangerAuthenticationToken) auth).getAuthType() 
== XXAuthSession.AUTH_TYPE_TRUSTED_PROXY) {
+                       ret = 
StringUtils.trimToNull(request.getHeader(requestIdHeaderName));
+               }
+
+               return ret != null ? ret : guidUtil.genGUID();
        }
 }
diff --git a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml 
b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
index 2da6f1c43..d1fccc27d 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
@@ -271,6 +271,20 @@
                <value>Mozilla,chrome</value>
        </property>
        <!-- SSO Properties Ends-->
+       <!-- Trusted header auth properties start -->
+       <property>
+               <name>ranger.admin.authn.header.enabled</name>
+               <value>false</value>
+       </property>
+       <property>
+               <name>ranger.admin.authn.header.username</name>
+               <value>x-awc-username</value>
+       </property>
+       <property>
+               <name>ranger.admin.authn.header.requestid</name>
+               <value>x-awc-requestid</value>
+       </property>
+       <!-- Trusted header auth properties end -->
        <!-- Kerberos Properties starts-->      
        <property>
                <name>ranger.admin.kerberos.token.valid.seconds</name>
diff --git 
a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml 
b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
index 8c0ad7ea6..a1dcacd1a 100644
--- 
a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
+++ 
b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
@@ -62,6 +62,7 @@ 
http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd";>
                </security:headers>
                <security:session-management 
session-fixation-protection="newSession" />
                <intercept-url pattern="/**" access="isAuthenticated()"/>
+               <security:custom-filter position="PRE_AUTH_FILTER" 
ref="headerPreAuthFilter" />
                <custom-filter ref="ssoAuthenticationFilter" 
after="BASIC_AUTH_FILTER" />
                <security:custom-filter ref="rangerJwtAuthWrapper" 
before="SERVLET_API_SUPPORT_FILTER" />
                <security:custom-filter ref="krbAuthenticationFilter" 
after="SERVLET_API_SUPPORT_FILTER" />
@@ -113,6 +114,9 @@ 
http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd";>
        <beans:bean id="mdcFilter" 
class="org.apache.ranger.security.web.filter.RangerMDCFilter">
     </beans:bean>
 
+       <beans:bean id="headerPreAuthFilter" 
class="org.apache.ranger.security.web.filter.RangerHeaderPreAuthFilter">
+    </beans:bean>
+
     <beans:bean id="ssoAuthenticationFilter" 
class="org.apache.ranger.security.web.filter.RangerSSOAuthenticationFilter">
     </beans:bean>
        
diff --git 
a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerHeaderPreAuthFilter.java
 
b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerHeaderPreAuthFilter.java
new file mode 100644
index 000000000..2ed69a6fb
--- /dev/null
+++ 
b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerHeaderPreAuthFilter.java
@@ -0,0 +1,179 @@
+/*
+ * 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.security.web.filter;
+
+import org.apache.ranger.biz.UserMgr;
+import org.apache.ranger.common.PropertiesUtil;
+import org.apache.ranger.entity.XXAuthSession;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestRangerHeaderPreAuthFilter {
+    @BeforeEach
+    public void setUp() {
+        SecurityContextHolder.clearContext();
+    }
+
+    @AfterEach
+    public void tearDown() {
+        SecurityContextHolder.clearContext();
+
+        
PropertiesUtil.getPropertiesMap().remove(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED);
+        
PropertiesUtil.getPropertiesMap().remove(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME);
+        
PropertiesUtil.getPropertiesMap().remove(RangerHeaderPreAuthFilter.PROP_REQUEST_ID_HEADER_NAME);
+    }
+
+    @Test
+    public void testDoFilter_disabled_passesThrough() throws Exception {
+        RangerHeaderPreAuthFilter filter  = new RangerHeaderPreAuthFilter();
+        UserMgr                   userMgr = mock(UserMgr.class);
+
+        filter.userMgr = userMgr;
+        filter.initialize(null);
+
+        HttpServletRequest  request  = mock(HttpServletRequest.class);
+        HttpServletResponse response = mock(HttpServletResponse.class);
+        FilterChain         chain    = mock(FilterChain.class);
+
+        filter.doFilter(request, response, chain);
+
+        verify(chain).doFilter(request, response);
+        verify(userMgr, never()).getRolesByLoginId(anyString());
+        assertNull(SecurityContextHolder.getContext().getAuthentication());
+    }
+
+    @Test
+    public void testDoFilter_enabled_missingUsername_passesThrough() throws 
Exception {
+        
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED,
 "true");
+        
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME,
 "x-awc-username");
+
+        RangerHeaderPreAuthFilter filter  = new RangerHeaderPreAuthFilter();
+        UserMgr                   userMgr = mock(UserMgr.class);
+
+        filter.userMgr = userMgr;
+        filter.initialize(null);
+
+        HttpServletRequest  request  = mock(HttpServletRequest.class);
+        HttpServletResponse response = mock(HttpServletResponse.class);
+        FilterChain         chain    = mock(FilterChain.class);
+
+        // no username header — getHeader returns null by default
+
+        filter.doFilter(request, response, chain);
+
+        verify(chain).doFilter(request, response);
+        verify(userMgr, never()).getRolesByLoginId(anyString());
+        assertNull(SecurityContextHolder.getContext().getAuthentication());
+    }
+
+    @Test
+    public void 
testDoFilter_enabled_withUsername_setsAuthenticationFromRangerDbRoles() throws 
Exception {
+        
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED,
 "true");
+        
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME,
 "x-awc-username");
+
+        RangerHeaderPreAuthFilter filter  = new RangerHeaderPreAuthFilter();
+        UserMgr                   userMgr = mock(UserMgr.class);
+
+        filter.userMgr = userMgr;
+        filter.initialize(null);
+
+        
when(userMgr.getRolesByLoginId("joeuser")).thenReturn(Arrays.asList("ROLE_SYS_ADMIN",
 "ROLE_USER"));
+
+        HttpServletRequest  request  = mock(HttpServletRequest.class);
+        HttpServletResponse response = mock(HttpServletResponse.class);
+
+        when(request.getHeader("x-awc-username")).thenReturn("joeuser");
+
+        FilterChain chain = new FilterChain() {
+            @Override
+            public void doFilter(ServletRequest req, ServletResponse res) {
+                org.springframework.security.core.Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
+
+                assertNotNull(auth);
+                assertTrue(auth instanceof RangerAuthenticationToken);
+                RangerAuthenticationToken rangerAuth = 
(RangerAuthenticationToken) auth;
+                assertEquals(XXAuthSession.AUTH_TYPE_TRUSTED_PROXY, 
rangerAuth.getAuthType());
+                assertEquals("joeuser", auth.getName());
+
+                Collection<?> authorities = auth.getAuthorities();
+                assertEquals(2, authorities.size());
+                assertTrue(authorities.stream().anyMatch(a -> 
"ROLE_SYS_ADMIN".equals(a.toString())));
+                assertTrue(authorities.stream().anyMatch(a -> 
"ROLE_USER".equals(a.toString())));
+            }
+        };
+
+        filter.doFilter(request, response, chain);
+    }
+
+    @Test
+    public void 
testDoFilter_enabled_existingAuthenticatedContext_doesNotOverrideAuthentication()
 throws Exception {
+        
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED,
 "true");
+        
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME,
 "x-awc-username");
+
+        RangerHeaderPreAuthFilter filter  = new RangerHeaderPreAuthFilter();
+        UserMgr                   userMgr = mock(UserMgr.class);
+
+        filter.userMgr = userMgr;
+        filter.initialize(null);
+
+        UsernamePasswordAuthenticationToken existingAuth = new 
UsernamePasswordAuthenticationToken("existing-user", "pwd", 
Collections.singletonList(new SimpleGrantedAuthority("test-role")));
+
+        SecurityContextHolder.getContext().setAuthentication(existingAuth);
+
+        HttpServletRequest  request  = mock(HttpServletRequest.class);
+        HttpServletResponse response = mock(HttpServletResponse.class);
+        FilterChain         chain    = mock(FilterChain.class);
+
+        filter.doFilter(request, response, chain);
+
+        verify(chain).doFilter(request, response);
+        verify(userMgr, never()).getRolesByLoginId(anyString());
+        assertEquals(existingAuth, 
SecurityContextHolder.getContext().getAuthentication());
+    }
+}
diff --git 
a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerSecurityContextFormationFilter.java
 
b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerSecurityContextFormationFilter.java
new file mode 100644
index 000000000..ff9f059bc
--- /dev/null
+++ 
b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerSecurityContextFormationFilter.java
@@ -0,0 +1,250 @@
+/*
+ * 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.security.web.filter;
+
+import org.apache.ranger.biz.SessionMgr;
+import org.apache.ranger.biz.XUserMgr;
+import org.apache.ranger.common.GUIDUtil;
+import org.apache.ranger.common.HTTPUtil;
+import org.apache.ranger.common.PropertiesUtil;
+import org.apache.ranger.common.RangerCommonEnums;
+import org.apache.ranger.common.UserSessionBase;
+import org.apache.ranger.entity.XXAuthSession;
+import org.apache.ranger.security.context.RangerContextHolder;
+import org.apache.ranger.security.context.RangerSecurityContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import 
org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @generated by Cursor
+ * @description <Unit Test for TestRangerSecurityContextFormationFilter class>
+ */
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestRangerSecurityContextFormationFilter {
+    @AfterEach
+    public void tearDown() {
+        SecurityContextHolder.clearContext();
+        RangerContextHolder.resetSecurityContext();
+        RangerContextHolder.resetOpContext();
+    }
+
+    @Test
+    public void testDoFilter_setsSecurityHeadersAndCleansContext() throws 
IOException, ServletException {
+        RangerSecurityContextFormationFilter filter = new 
RangerSecurityContextFormationFilter();
+
+        // mock authenticated user to drive context creation path
+        GrantedAuthority auth = new SimpleGrantedAuthority("ROLE_USER");
+        Authentication authentication = new 
AnonymousAuthenticationToken("key", "principal", 
Collections.singletonList(auth));
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+
+        HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse res = Mockito.mock(HttpServletResponse.class);
+        FilterChain chain = Mockito.mock(FilterChain.class);
+
+        filter.doFilter(req, res, chain);
+
+        // Verify headers
+        verify(res).setHeader("Cache-Control", "no-cache, no-store, max-age=0, 
must-revalidate");
+        verify(res).setHeader("X-Frame-Options", "DENY");
+        verify(res).setHeader("X-XSS-Protection", "1; mode=block");
+        verify(res).setHeader("Strict-Transport-Security", "max-age=31536000; 
includeSubDomains; preload");
+        verify(res).setHeader("Content-Security-Policy",
+                "default-src 'none'; script-src 'self' 'unsafe-inline' 
'unsafe-eval'; connect-src 'self'; img-src 'self' data:; style-src 'self' 
'unsafe-inline';font-src 'self'");
+        verify(res).setHeader("X-Permitted-Cross-Domain-Policies", "none");
+
+        verify(chain).doFilter(any(ServletRequest.class), 
any(ServletResponse.class));
+
+        // filter should clean up thread locals
+        assertNull(RangerContextHolder.getSecurityContext());
+        assertNull(RangerContextHolder.getOpContext());
+    }
+
+    @Test
+    public void testDoFilter_setsCreatePrincipalsIfAbsentFlag() throws 
Exception {
+        RangerSecurityContextFormationFilter filter = new 
RangerSecurityContextFormationFilter();
+
+        HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse res = Mockito.mock(HttpServletResponse.class);
+
+        // Set anonymous auth to skip dependency calls but still execute
+        // setupAdminOpContext
+        GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_ANON");
+        Authentication anon = new AnonymousAuthenticationToken("k", "p", 
Collections.singletonList(ga));
+        SecurityContextHolder.getContext().setAuthentication(anon);
+
+        when(req.getParameter("createPrincipalsIfAbsent")).thenReturn("true");
+
+        FilterChain chain = new FilterChain() {
+            @Override
+            public void doFilter(ServletRequest servletRequest, 
ServletResponse servletResponse) {
+                Boolean flag = RangerContextHolder.getOpContext() != null
+                        ? 
RangerContextHolder.getOpContext().getCreatePrincipalsIfAbsent()
+                        : null;
+                assertEquals(Boolean.TRUE, flag);
+            }
+        };
+
+        filter.doFilter(req, res, chain);
+
+        verify(res, times(1)).setHeader("X-Frame-Options", "DENY");
+    }
+
+    @Test
+    public void testGetAuthType_reflectionVariants() throws Exception {
+        RangerSecurityContextFormationFilter filter = new 
RangerSecurityContextFormationFilter();
+        Method m = 
RangerSecurityContextFormationFilter.class.getDeclaredMethod("getAuthType", 
Authentication.class, HttpServletRequest.class);
+        m.setAccessible(true);
+
+        List<GrantedAuthority> authorities  = Collections.singletonList(new 
SimpleGrantedAuthority("ROLE_USER"));
+        UserDetails            userDetails  = new 
org.springframework.security.core.userdetails.User("u", "", authorities);
+        HttpServletRequest     emptyRequest = 
Mockito.mock(HttpServletRequest.class);
+
+        // Header-based trusted proxy — identified via 
RangerAuthenticationToken, no request attributes needed
+        assertEquals(XXAuthSession.AUTH_TYPE_TRUSTED_PROXY,
+                m.invoke(filter, new RangerAuthenticationToken(userDetails, 
authorities, XXAuthSession.AUTH_TYPE_TRUSTED_PROXY), emptyRequest));
+
+        // SSO — identified via request attribute 
(RangerSSOAuthenticationFilter sets UsernamePasswordAuthenticationToken)
+        HttpServletRequest reqSso = Mockito.mock(HttpServletRequest.class);
+        Mockito.when(reqSso.getAttribute("ssoEnabled")).thenReturn(true);
+        assertEquals(XXAuthSession.AUTH_TYPE_SSO,
+                m.invoke(filter, new 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken("u",
 "pwd", authorities), reqSso));
+
+        // Kerberos — identified via spnegoEnabled attribute
+        HttpServletRequest reqKrb = Mockito.mock(HttpServletRequest.class);
+        Mockito.when(reqKrb.getAttribute("ssoEnabled")).thenReturn(false);
+        Mockito.when(reqKrb.getAttribute("spnegoEnabled")).thenReturn(true);
+        
Mockito.when(reqKrb.getAttribute("trustedProxyEnabled")).thenReturn(false);
+        assertEquals(XXAuthSession.AUTH_TYPE_KERBEROS,
+                m.invoke(filter, new 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken("u",
 "pwd", authorities), reqKrb));
+
+        // Kerberos trusted proxy — both spnegoEnabled and trustedProxyEnabled
+        HttpServletRequest reqKrbTp = Mockito.mock(HttpServletRequest.class);
+        Mockito.when(reqKrbTp.getAttribute("ssoEnabled")).thenReturn(false);
+        Mockito.when(reqKrbTp.getAttribute("spnegoEnabled")).thenReturn(true);
+        
Mockito.when(reqKrbTp.getAttribute("trustedProxyEnabled")).thenReturn(true);
+        assertEquals(XXAuthSession.AUTH_TYPE_TRUSTED_PROXY,
+                m.invoke(filter, new 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken("u",
 "pwd", authorities), reqKrbTp));
+
+        // Password — no RangerAuthenticationToken, ssoEnabled explicitly 
false, no Kerberos attributes
+        HttpServletRequest reqPwd = Mockito.mock(HttpServletRequest.class);
+        Mockito.when(reqPwd.getAttribute("ssoEnabled")).thenReturn(false);
+        assertEquals(XXAuthSession.AUTH_TYPE_PASSWORD,
+                m.invoke(filter, new 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken("u",
 "pwd", authorities), reqPwd));
+    }
+
+    @Test
+    public void 
testDoFilter_authenticated_createsSecurityContextAndUserSession() throws 
Exception {
+        
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_REQUEST_ID_HEADER_NAME,
 "x-awc-requestid");
+
+        try {
+            RangerSecurityContextFormationFilter filter = new 
RangerSecurityContextFormationFilter();
+
+            SessionMgr sessionMgr = Mockito.mock(SessionMgr.class);
+            HTTPUtil   httpUtil   = Mockito.mock(HTTPUtil.class);
+            GUIDUtil   guidUtil   = Mockito.mock(GUIDUtil.class);
+            XUserMgr   xUserMgr   = Mockito.mock(XUserMgr.class);
+
+            filter.sessionMgr = sessionMgr;
+            filter.httpUtil   = httpUtil;
+            filter.guidUtil   = guidUtil;
+            filter.xUserMgr   = xUserMgr;
+
+            List<GrantedAuthority>   authorities    = 
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
+            UserDetails              userDetails    = new 
org.springframework.security.core.userdetails.User("user", "", authorities);
+            RangerAuthenticationToken authentication = new 
RangerAuthenticationToken(userDetails, authorities, 
XXAuthSession.AUTH_TYPE_TRUSTED_PROXY);
+
+            
SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            HttpServletRequest  req     = 
Mockito.mock(HttpServletRequest.class);
+            HttpServletResponse res     = 
Mockito.mock(HttpServletResponse.class);
+            HttpSession         session = Mockito.mock(HttpSession.class);
+
+            Mockito.when(req.getSession(false)).thenReturn(session);
+            
Mockito.when(session.getAttribute(RangerSecurityContextFormationFilter.AKA_SC_SESSION_KEY)).thenReturn(null);
+            
Mockito.when(req.getHeader(RangerSecurityContextFormationFilter.USER_AGENT)).thenReturn("Mozilla/5.0");
+            
Mockito.when(req.getHeader("x-awc-requestid")).thenReturn("awc-request-1");
+            Mockito.when(req.getRequestURI()).thenReturn("/secure");
+            
Mockito.when(httpUtil.getDeviceType(req)).thenReturn(RangerCommonEnums.DEVICE_BROWSER);
+
+            UserSessionBase userSession = Mockito.mock(UserSessionBase.class);
+
+            
Mockito.when(userSession.getClientTimeOffsetInMinute()).thenReturn(0);
+            Mockito.when(sessionMgr.processSuccessLogin(Mockito.anyInt(), 
Mockito.anyString(), Mockito.any(HttpServletRequest.class)))
+                    .thenReturn(userSession);
+
+            FilterChain chain = new FilterChain() {
+                @Override
+                public void doFilter(ServletRequest servletRequest, 
ServletResponse servletResponse) {
+                    RangerSecurityContext ctx = 
RangerContextHolder.getSecurityContext();
+
+                    assertNotNull(ctx);
+                    assertNotNull(ctx.getRequestContext());
+                    assertEquals("awc-request-1", 
ctx.getRequestContext().getServerRequestId());
+                    assertSame(userSession, ctx.getUserSession());
+                }
+            };
+
+            filter.doFilter(req, res, chain);
+
+            Mockito.verify(session, 
Mockito.times(1)).setAttribute(Mockito.eq(RangerSecurityContextFormationFilter.AKA_SC_SESSION_KEY),
 Mockito.any());
+            Mockito.verify(res).setHeader("X-Frame-Options", "DENY");
+            Mockito.verify(sessionMgr, 
Mockito.times(1)).processSuccessLogin(Mockito.anyInt(), Mockito.anyString(), 
Mockito.any(HttpServletRequest.class));
+            Mockito.verify(userSession, 
Mockito.times(1)).setClientTimeOffsetInMinute(Mockito.anyInt());
+
+            assertNull(RangerContextHolder.getSecurityContext());
+            assertNull(RangerContextHolder.getOpContext());
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(RangerHeaderPreAuthFilter.PROP_REQUEST_ID_HEADER_NAME);
+        }
+    }
+}


Reply via email to