diff --git a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java index ef10779f83..f59ebdea0b 100644 --- a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java +++ b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java @@ -57,6 +57,7 @@ import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -69,6 +70,8 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import static org.apache.ranger.security.web.filter.RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED; + @Component @Transactional public class SessionMgr { @@ -97,10 +100,19 @@ public class SessionMgr { @Autowired StringUtil stringUtil; + boolean ssoEnabled; + boolean headerAuthEnabled; + public SessionMgr() { logger.debug("SessionManager created"); } + @PostConstruct + public void init() { + ssoEnabled = PropertiesUtil.getBooleanProperty("ranger.sso.enabled", false); + headerAuthEnabled = PropertiesUtil.getBooleanProperty(PROP_HEADER_AUTH_ENABLED, false); + } + public UserSessionBase processSuccessLogin(int authType, String userAgent, HttpServletRequest httpRequest) { boolean newSessionCreation = true; UserSessionBase userSession = null; @@ -523,18 +535,23 @@ protected XXAuthSession storeAuthSession(XXAuthSession gjAuthSession) { } private void getSSOSpnegoAuthCheckForAPI(String currentLoginId, HttpServletRequest request) { - RangerSecurityContext context = RangerContextHolder.getSecurityContext(); - UserSessionBase session = context != null ? context.getUserSession() : null; - boolean ssoEnabled = session != null ? session.isSSOEnabled() : PropertiesUtil.getBooleanProperty("ranger.sso.enabled", false); - XXPortalUser gjUser = daoManager.getXXPortalUser().findByLoginId(currentLoginId); + XXPortalUser gjUser = daoManager.getXXPortalUser().findByLoginId(currentLoginId); - if (gjUser == null && ((request.getAttribute("spnegoEnabled") != null && (boolean) request.getAttribute("spnegoEnabled")) || (ssoEnabled) || bizUtil.isHealthCheckUser(currentLoginId))) { - logger.debug("User : {} doesn't exist in Ranger DB So creating user as it's SSO or Spnego authenticated", currentLoginId); + if (gjUser == null) { + if (headerAuthEnabled || bizUtil.isHealthCheckUser(currentLoginId)) { + logger.debug("User : {} doesn't exist in Ranger DB. Creating user as it's header-auth authenticated", currentLoginId); - if (bizUtil.isHealthCheckUser(currentLoginId)) { xUserMgr.createServiceConfigUserSynchronously(currentLoginId); } else { - xUserMgr.createServiceConfigUser(currentLoginId); + RangerSecurityContext context = RangerContextHolder.getSecurityContext(); + UserSessionBase session = context != null ? context.getUserSession() : null; + boolean ssoEnabled = session != null ? session.isSSOEnabled() : this.ssoEnabled; + + if (ssoEnabled || (request.getAttribute("spnegoEnabled") != null && (boolean) request.getAttribute("spnegoEnabled"))) { + logger.debug("User : {} doesn't exist in Ranger DB. Creating user as it's SSO or Spnego", currentLoginId); + + xUserMgr.createServiceConfigUser(currentLoginId); + } } } } diff --git a/security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java b/security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java index edc18e1338..fa014a10f0 100755 --- a/security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java +++ b/security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java @@ -50,6 +50,7 @@ import org.apache.ranger.db.XXUserDao; import org.apache.ranger.db.XXUserPermissionDao; import org.apache.ranger.entity.XXAuditMap; +import org.apache.ranger.entity.XXAuthSession; import org.apache.ranger.entity.XXGroup; import org.apache.ranger.entity.XXGroupPermission; import org.apache.ranger.entity.XXGroupUser; @@ -79,6 +80,10 @@ import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil; import org.apache.ranger.plugin.util.PasswordUtils.PasswordGenerator; import org.apache.ranger.plugin.util.RangerUserStore; +import org.apache.ranger.security.context.RangerContextHolder; +import org.apache.ranger.security.context.RangerSecurityContext; +import org.apache.ranger.security.web.filter.RangerAuthenticationToken; +import org.apache.ranger.security.web.filter.RangerHeaderPreAuthFilter; import org.apache.ranger.service.RangerPolicyService; import org.apache.ranger.service.XPortalUserService; import org.apache.ranger.service.XResourceService; @@ -110,6 +115,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; @@ -3782,6 +3790,72 @@ private void updateUserStoreVersion(String label) { } } + /** + * Resolves the roles to assign to a freshly auto-provisioned external user. When header-based + * authentication is enabled and the current request was authenticated through the trusted proxy, + * the (validated) roles carried in the {@link RangerAuthenticationToken} authorities are used. + * Returns {@code null} otherwise, in which case the caller falls back to the default ROLE_USER. + */ + private List getHeaderAuthRoles() { + List ret = null; + + if (PropertiesUtil.getBooleanProperty(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED, false)) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + if (auth instanceof RangerAuthenticationToken && ((RangerAuthenticationToken) auth).getAuthType() == XXAuthSession.AUTH_TYPE_TRUSTED_PROXY) { + List roles = new ArrayList<>(); + + for (GrantedAuthority authority : auth.getAuthorities()) { + String role = (authority != null) ? StringUtils.trimToNull(authority.getAuthority()) : null; + + if (role != null && RangerConstants.VALID_USER_ROLE_LIST.contains(role)) { + roles.add(role); + } + } + + if (!roles.isEmpty()) { + ret = roles; + } + } + } + + return ret; + } + + /** + * Creates the portal user with the supplied roles. When the roles originate from the trusted + * roles header ({@code trustedHeaderRoles}), the creation runs under a trusted system session + * (an admin session with no interactive user attached) so that non-public roles such as + * ROLE_SYS_ADMIN carried in the header are permitted during provisioning. No real user account + * is impersonated and the previous security context is always restored. + */ + private XXPortalUser createExternalPortalUser(XXPortalUser xXPortalUser, List roleList, boolean trustedHeaderRoles) { + if (!trustedHeaderRoles) { + return userMgr.createUser(xXPortalUser, RangerCommonEnums.STATUS_ENABLED, roleList); + } + + RangerSecurityContext context = RangerContextHolder.getSecurityContext(); + UserSessionBase originalSession = context != null ? context.getUserSession() : null; + + if (context == null) { + context = new RangerSecurityContext(); + + RangerContextHolder.setSecurityContext(context); + } + + try { + UserSessionBase systemSession = new UserSessionBase(); + + systemSession.setUserAdmin(true); + + context.setUserSession(systemSession); + + return userMgr.createUser(xXPortalUser, RangerCommonEnums.STATUS_ENABLED, roleList); + } finally { + context.setUserSession(originalSession); + } + } + private class ExternalUserCreator implements Runnable { private final String userName; @@ -3807,18 +3881,24 @@ private void createExternalUser() { vXPortalUser.setLoginId(userName); vXPortalUser.setUserSource(RangerCommonEnums.USER_EXTERNAL); - ArrayList roleList = new ArrayList<>(); + List headerRoles = getHeaderAuthRoles(); + boolean trustedHeaderRoles = CollectionUtils.isNotEmpty(headerRoles); + ArrayList roleList = new ArrayList<>(); - roleList.add(RangerConstants.ROLE_USER); + if (trustedHeaderRoles) { + roleList.addAll(headerRoles); + } else { + roleList.add(RangerConstants.ROLE_USER); + } vXPortalUser.setUserRoleList(roleList); xXPortalUser = userMgr.mapVXPortalUserToXXPortalUser(vXPortalUser); try { - xXPortalUser = userMgr.createUser(xXPortalUser, RangerCommonEnums.STATUS_ENABLED, roleList); + xXPortalUser = createExternalPortalUser(xXPortalUser, roleList, trustedHeaderRoles); - logger.debug("createExternalUser(): Successfully created user in x_portal_user table {}", xXPortalUser.getLoginId()); + logger.debug("createExternalUser(): Successfully created user {} in x_portal_user table with roles {}", xXPortalUser.getLoginId(), roleList); } catch (Exception ex) { throw new RuntimeException("Failed to create user " + userName + " in x_portal_user table. retrying", ex); } diff --git a/security-admin/src/main/java/org/apache/ranger/common/RangerConstants.java b/security-admin/src/main/java/org/apache/ranger/common/RangerConstants.java index 3a8e33ee9f..63146e383a 100644 --- a/security-admin/src/main/java/org/apache/ranger/common/RangerConstants.java +++ b/security-admin/src/main/java/org/apache/ranger/common/RangerConstants.java @@ -31,12 +31,8 @@ public class RangerConstants extends RangerCommonEnums { // Default Roles public static final String ROLE_SYS_ADMIN = "ROLE_SYS_ADMIN"; public static final String ROLE_ADMIN = "ROLE_ADMIN"; - public static final String ROLE_INTEGRATOR = "ROLE_INTEGRATOR"; - public static final String ROLE_DATA_ANALYST = "ROLE_DATA_ANALYST"; - public static final String ROLE_BIZ_MGR = "ROLE_BIZ_MGR"; public static final String ROLE_KEY_ADMIN = "ROLE_KEY_ADMIN"; public static final String ROLE_USER = "ROLE_USER"; - public static final String ROLE_ANON = "ROLE_ANON"; public static final String ROLE_OTHER = "ROLE_OTHER"; public static final String GROUP_PUBLIC = "public"; public static final String ROLE_ADMIN_AUDITOR = "ROLE_ADMIN_AUDITOR"; 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 index 800bf92f57..4352519a1f 100644 --- 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 @@ -21,6 +21,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.ranger.biz.UserMgr; import org.apache.ranger.common.PropertiesUtil; +import org.apache.ranger.common.RangerConstants; import org.apache.ranger.entity.XXAuthSession; import org.apache.ranger.plugin.util.SpiffeIdUtil; import org.slf4j.Logger; @@ -45,7 +46,10 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class RangerHeaderPreAuthFilter extends GenericFilterBean { private static final Logger LOG = LoggerFactory.getLogger(RangerHeaderPreAuthFilter.class); @@ -54,10 +58,30 @@ public class RangerHeaderPreAuthFilter extends GenericFilterBean { public static final String PROP_USERNAME_HEADER_NAME = "ranger.admin.authn.header.username"; public static final String PROP_SPIFFE_HEADER_NAME = "ranger.admin.authn.header.spiffe"; public static final String PROP_REQUEST_ID_HEADER_NAME = "ranger.admin.authn.header.requestid"; + public static final String PROP_ROLES_HEADER_NAME = "ranger.admin.authn.header.roles"; + + /** + * External-facing role names accepted in the configured roles header, mapped to Ranger's + * internal role constants (see {@link RangerConstants#VALID_USER_ROLE_LIST}). + */ + private static final Map EXTERNAL_ROLE_TO_RANGER_ROLE; + + static { + Map roleMap = new HashMap<>(); + + roleMap.put("RANGER_ROLE_ADMIN", RangerConstants.ROLE_SYS_ADMIN); + roleMap.put("RANGER_ROLE_AUDITOR", RangerConstants.ROLE_ADMIN_AUDITOR); + roleMap.put("RANGER_ROLE_USER", RangerConstants.ROLE_USER); + roleMap.put("RANGER_ROLE_KEY_ADMIN", RangerConstants.ROLE_KEY_ADMIN); + roleMap.put("RANGER_ROLE_KEY_ADMIN_AUDITOR", RangerConstants.ROLE_KEY_ADMIN_AUDITOR); + + EXTERNAL_ROLE_TO_RANGER_ROLE = Collections.unmodifiableMap(roleMap); + } private boolean headerAuthEnabled; private String userNameHeaderName; private List spiffeHeaderNames; + private String rolesHeaderName; @Autowired UserMgr userMgr; @@ -69,6 +93,7 @@ protected void initialize() { if (headerAuthEnabled) { userNameHeaderName = PropertiesUtil.getProperty(PROP_USERNAME_HEADER_NAME); spiffeHeaderNames = SpiffeIdUtil.parseHeaderNames(PropertiesUtil.getProperty(PROP_SPIFFE_HEADER_NAME)); + rolesHeaderName = PropertiesUtil.getProperty(PROP_ROLES_HEADER_NAME); if (StringUtils.isBlank(userNameHeaderName) && spiffeHeaderNames.isEmpty()) { LOG.warn("Disabling header-based authentication, as neither {} nor {} is set", PROP_USERNAME_HEADER_NAME, PROP_SPIFFE_HEADER_NAME); @@ -88,7 +113,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String username = resolvePrincipal(httpRequest); if (StringUtils.isNotBlank(username)) { - List grantedAuthorities = getAuthoritiesFromRanger(username); + List grantedAuthorities = getAuthorities(httpRequest, username); final UserDetails principal = new User(username, "", grantedAuthorities); RangerAuthenticationToken authToken = new RangerAuthenticationToken(principal, grantedAuthorities, XXAuthSession.AUTH_TYPE_TRUSTED_PROXY); @@ -135,14 +160,82 @@ private String resolvePrincipal(HttpServletRequest httpRequest) { return null; } + /** + * Resolves the authorities to assign to the authenticated user. When the trusted proxy + * supplies roles via the configured roles header, those roles are honored; otherwise the + * roles persisted for the user in the Ranger DB are used. + */ + private List getAuthorities(HttpServletRequest httpRequest, String username) { + List ret = getAuthoritiesFromHeader(httpRequest); + + if (ret == null || ret.isEmpty()) { + ret = getAuthoritiesFromRanger(username); + } + + return ret == null ? Collections.emptyList() : ret; + } + + /** + * Loads authorities from the configured roles header. External-facing role names + * ({@code RANGER_ROLE_ADMIN}, {@code RANGER_ROLE_AUDITOR}, etc.) are mapped to Ranger's + * internal role constants before being added to the authentication token. Internal role + * names from {@link RangerConstants#VALID_USER_ROLE_LIST} are also accepted; any other + * value is ignored. + */ + private List getAuthoritiesFromHeader(HttpServletRequest httpRequest) { + List ret = null; + + if (StringUtils.isNotBlank(rolesHeaderName)) { + String rolesHeaderValue = httpRequest.getHeader(rolesHeaderName); + + if (StringUtils.isNotBlank(rolesHeaderValue)) { + ret = new ArrayList<>(); + + for (String role : rolesHeaderValue.split(",")) { + String trimmedRole = StringUtils.trimToNull(role); + + if (trimmedRole != null) { + String rangerRole = resolveRoleFromHeader(trimmedRole); + + if (rangerRole != null) { + ret.add(new SimpleGrantedAuthority(rangerRole)); + } else { + LOG.warn("Ignoring unrecognized role '{}' received in header '{}'", trimmedRole, rolesHeaderName); + } + } + } + } + } + + return ret; + } + + /** + * Maps an external-facing role name from the roles header to a Ranger internal role constant, + * or returns the value unchanged when it is already a recognized internal role name. + */ + private String resolveRoleFromHeader(String headerRole) { + String ret = EXTERNAL_ROLE_TO_RANGER_ROLE.get(headerRole); + + if (ret == null) { + if (RangerConstants.VALID_USER_ROLE_LIST.contains(headerRole)) { + ret = headerRole; + } + } + + return ret; + } + /** * Loads authorities from Ranger DB */ private List getAuthoritiesFromRanger(String username) { - List ret = new ArrayList<>(); + List ret = null; Collection roleList = userMgr.getRolesByLoginId(username); - if (roleList != null) { + if (roleList != null && !roleList.isEmpty()) { + ret = new ArrayList<>(); + for (String role : roleList) { if (StringUtils.isNotBlank(role)) { ret.add(new SimpleGrantedAuthority(role)); 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 180e54f82f..330d9f69b6 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 @@ -332,6 +332,11 @@ ranger.admin.authn.header.requestid + + ranger.admin.authn.header.roles + + Name of the HTTP header (e.g. X-Forwarded-Roles) carrying a comma-separated list of roles to assign to the authenticated user. Accepted values are role names RANGER_ROLE_ADMIN, RANGER_ROLE_AUDITOR, RANGER_ROLE_USER, RANGER_ROLE_KEY_ADMIN, and RANGER_ROLE_KEY_ADMIN_AUDITOR. Values that are not recognized are ignored. + ranger.admin.spiffe.as.username.enabled false 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 index 5103d836be..d70135035b 100644 --- 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 @@ -68,6 +68,7 @@ public void tearDown() { PropertiesUtil.getPropertiesMap().remove(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME); PropertiesUtil.getPropertiesMap().remove(RangerHeaderPreAuthFilter.PROP_SPIFFE_HEADER_NAME); PropertiesUtil.getPropertiesMap().remove(RangerHeaderPreAuthFilter.PROP_REQUEST_ID_HEADER_NAME); + PropertiesUtil.getPropertiesMap().remove(RangerHeaderPreAuthFilter.PROP_ROLES_HEADER_NAME); } @Test @@ -372,4 +373,201 @@ public void testDoFilter_enabled_existingAuthenticatedContext_doesNotOverrideAut verify(userMgr, never()).getRolesByLoginId(anyString()); assertEquals(existingAuth, SecurityContextHolder.getContext().getAuthentication()); } + + @Test + public void testDoFilter_enabled_withExternalRolesHeader_mapsToInternalRoles() throws Exception { + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED, "true"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME, "X-Forwarded-User"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_ROLES_HEADER_NAME, "X-Forwarded-Roles"); + + RangerHeaderPreAuthFilter filter = new RangerHeaderPreAuthFilter(); + UserMgr userMgr = mock(UserMgr.class); + + filter.userMgr = userMgr; + filter.initialize(); + + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + when(request.getHeader("X-Forwarded-User")).thenReturn("joeuser"); + when(request.getHeader("X-Forwarded-Roles")).thenReturn("RANGER_ROLE_ADMIN, RANGER_ROLE_USER"); + + 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); + + verify(userMgr, never()).getRolesByLoginId(anyString()); + } + + @Test + public void testDoFilter_enabled_withRolesHeader_setsAuthenticationFromHeaderRoles() throws Exception { + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED, "true"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME, "X-Forwarded-User"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_ROLES_HEADER_NAME, "X-Forwarded-Roles"); + + RangerHeaderPreAuthFilter filter = new RangerHeaderPreAuthFilter(); + UserMgr userMgr = mock(UserMgr.class); + + filter.userMgr = userMgr; + filter.initialize(); + + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + when(request.getHeader("X-Forwarded-User")).thenReturn("joeuser"); + when(request.getHeader("X-Forwarded-Roles")).thenReturn("ROLE_SYS_ADMIN, ROLE_USER"); + + 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); + + // roles came from the trusted header, so the Ranger DB must not be consulted + verify(userMgr, never()).getRolesByLoginId(anyString()); + } + + @Test + public void testDoFilter_enabled_rolesHeaderWithUnknownRoles_ignoresInvalidAndKeepsValid() throws Exception { + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED, "true"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME, "X-Forwarded-User"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_ROLES_HEADER_NAME, "X-Forwarded-Roles"); + + RangerHeaderPreAuthFilter filter = new RangerHeaderPreAuthFilter(); + UserMgr userMgr = mock(UserMgr.class); + + filter.userMgr = userMgr; + filter.initialize(); + + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + when(request.getHeader("X-Forwarded-User")).thenReturn("joeuser"); + when(request.getHeader("X-Forwarded-Roles")).thenReturn("ROLE_ADMIN_AUDITOR, ROLE_BOGUS, , ROLE_USER"); + + FilterChain chain = new FilterChain() { + @Override + public void doFilter(ServletRequest req, ServletResponse res) { + org.springframework.security.core.Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + assertNotNull(auth); + + Collection authorities = auth.getAuthorities(); + assertEquals(2, authorities.size()); + assertTrue(authorities.stream().anyMatch(a -> "ROLE_ADMIN_AUDITOR".equals(a.toString()))); + assertTrue(authorities.stream().anyMatch(a -> "ROLE_USER".equals(a.toString()))); + } + }; + + filter.doFilter(request, response, chain); + + verify(userMgr, never()).getRolesByLoginId(anyString()); + } + + @Test + public void testDoFilter_enabled_rolesHeaderWithNoValidRoles_fallsBackToRangerDbRoles() throws Exception { + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED, "true"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME, "X-Forwarded-User"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_ROLES_HEADER_NAME, "X-Forwarded-Roles"); + + RangerHeaderPreAuthFilter filter = new RangerHeaderPreAuthFilter(); + UserMgr userMgr = mock(UserMgr.class); + + filter.userMgr = userMgr; + filter.initialize(); + + when(userMgr.getRolesByLoginId("joeuser")).thenReturn(Collections.singletonList("ROLE_USER")); + + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + when(request.getHeader("X-Forwarded-User")).thenReturn("joeuser"); + when(request.getHeader("X-Forwarded-Roles")).thenReturn("ROLE_BOGUS"); + + FilterChain chain = new FilterChain() { + @Override + public void doFilter(ServletRequest req, ServletResponse res) { + org.springframework.security.core.Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + assertNotNull(auth); + + Collection authorities = auth.getAuthorities(); + assertEquals(1, authorities.size()); + assertTrue(authorities.stream().anyMatch(a -> "ROLE_USER".equals(a.toString()))); + } + }; + + filter.doFilter(request, response, chain); + + verify(userMgr).getRolesByLoginId("joeuser"); + } + + @Test + public void testDoFilter_enabled_rolesHeaderConfiguredButAbsent_fallsBackToRangerDbRoles() throws Exception { + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED, "true"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME, "X-Forwarded-User"); + PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_ROLES_HEADER_NAME, "X-Forwarded-Roles"); + + RangerHeaderPreAuthFilter filter = new RangerHeaderPreAuthFilter(); + UserMgr userMgr = mock(UserMgr.class); + + filter.userMgr = userMgr; + filter.initialize(); + + 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-Forwarded-User")).thenReturn("joeuser"); + // roles header configured but not present in the request + + FilterChain chain = new FilterChain() { + @Override + public void doFilter(ServletRequest req, ServletResponse res) { + org.springframework.security.core.Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + assertNotNull(auth); + + 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); + + verify(userMgr).getRolesByLoginId("joeuser"); + } }