This is an automated email from the ASF dual-hosted git repository.
abhi pushed a commit to branch ranger-2.8
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/ranger-2.8 by this push:
new d40fac0b6 RANGER-5499: Add support for header based authentication
(#873)
d40fac0b6 is described below
commit d40fac0b695007394dab90be0b8384f2c51aea03
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)
---
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);
+ }
+ }
+}