From 51267bd052f5978a6f222f76a19ddc87ed10a5b2 Mon Sep 17 00:00:00 2001 From: Nicholas Blair <nicholas.blair@wisc.edu> Date: Tue, 13 Sep 2016 11:26:48 -0500 Subject: [PATCH] fix: correct handling of HttpServletRequest#getHeaders and tests Properly handles cases where container may return blank String values in the returned list. Tests were previously adding List objects as headers - need to add just String values. --- .../java/edu/wisc/uwss/UWUserDetailsImpl.java | 2 +- ...thenticatedUserDetailsAttributeMapper.java | 52 ++++++++--- ...ticatedUserDetailsAttributeMapperTest.java | 88 +++++++++++++++---- 3 files changed, 114 insertions(+), 28 deletions(-) diff --git a/uw-spring-security-core/src/main/java/edu/wisc/uwss/UWUserDetailsImpl.java b/uw-spring-security-core/src/main/java/edu/wisc/uwss/UWUserDetailsImpl.java index 2e73b58..00ed353 100644 --- a/uw-spring-security-core/src/main/java/edu/wisc/uwss/UWUserDetailsImpl.java +++ b/uw-spring-security-core/src/main/java/edu/wisc/uwss/UWUserDetailsImpl.java @@ -62,7 +62,7 @@ public class UWUserDetailsImpl extends User implements UWUserDetails, HasModifia * @param fullName * @param emailAddress * @param uddsMembership - * @param grantedAuthorities + * @param authorities */ public UWUserDetailsImpl(String pvi, String username, String password, String fullName, String emailAddress, diff --git a/uw-spring-security-core/src/main/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapper.java b/uw-spring-security-core/src/main/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapper.java index 20bf725..46e5473 100644 --- a/uw-spring-security-core/src/main/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapper.java +++ b/uw-spring-security-core/src/main/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapper.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -60,7 +61,7 @@ public interface PreauthenticatedUserDetailsAttributeMapper { private String identityProviderHeader = "Shib-Identity-Provider"; private String customLogoutPrefix = "/Shibboleth.sso/Logout?return="; private String customLogoutSuffix = "/logout/"; - private String manifestHeader = "ismemberof"; + private String manifestHeader = "isMemberOf"; private static final Logger logger = LoggerFactory.getLogger(Default.class); /** @@ -82,17 +83,9 @@ public interface PreauthenticatedUserDetailsAttributeMapper { String pvi = request.getHeader(pviHeader); String cn = request.getHeader(fullNameHeader); String emplid = request.getHeader(isisEmplidHeader); - Collection<String> uddsMembership = new ArrayList<>(); - Enumeration<String> uddsHeaders = request.getHeaders(uddsHeader); - if(uddsHeaders != null) { - uddsMembership = Collections.list(uddsHeaders); - } + Collection<String> uddsMembership = safeGetHeaders(request, uddsHeader); String email = request.getHeader(emailAddressHeader); - Collection<String> manifestGroups = new ArrayList<>(); - Enumeration<String> manifestHeaders = request.getHeaders(manifestHeader); - if(manifestHeaders != null) { - manifestGroups = Collections.list(manifestHeaders); - } + Collection<String> manifestGroups = safeGetHeaders(request, manifestHeader); UWUserDetailsImpl result = UWUserDetailsImpl.newInstance(pvi, uid, "", cn, email, uddsMembership, manifestGroups); result.setSource("edu.wisc.uwss.preauth"); result.setEppn(eppn); @@ -105,7 +98,42 @@ public interface PreauthenticatedUserDetailsAttributeMapper { logger.debug("mapUser constructed {} from headers in request", result); return result; } - + + /** + * {@link HttpServletRequest#getHeaders(String)} has 3 possible return values: + * + * <ul> + * <li>A non-empty {@link Enumeration} of {@link String} values for the header,</li> + * <li>An "empty" {@link Enumeration} of {@link String} values for the header,</li> + * <li>null, if the container does not allow access to the header(s).</li> + * </ul> + * + * Further complicating things is that some containers may return a non-empty {@link Enumeration} + * of {@link String} values, but those {@link String} values may be blank. For this particular case, + * {@link Collections#list(Enumeration)} is unsafe, because the returned list may include blank values. + * + * This method provides a safe way to get the values for the specified header as a non-null + * (but still potentially empty) {@link List}. + * + * @see HttpServletRequest#getHeaders(String) + * @see StringUtils#isNotBlank(CharSequence) + * @param request the HTTP request + * @param headerName the name of the multi-valued header + * @return a never null, but potentially empty, {@link List} of not blank values for the requested header + */ + protected List<String> safeGetHeaders(HttpServletRequest request, String headerName) { + Enumeration<String> enumeration = request.getHeaders(headerName); + if(enumeration == null) { + return Collections.emptyList(); + } + List<String> result = new ArrayList<>(); + for(String next : Collections.list(enumeration)){ + if(StringUtils.isNotBlank(next)) { + result.add(next); + } + } + return result; + } /** * * @param identityProviderHeaderValue the value of the identityProviderHeader diff --git a/uw-spring-security-core/src/test/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapperTest.java b/uw-spring-security-core/src/test/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapperTest.java index d7e86ee..9f6ee0d 100644 --- a/uw-spring-security-core/src/test/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapperTest.java +++ b/uw-spring-security-core/src/test/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapperTest.java @@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -18,6 +19,8 @@ import javax.servlet.http.HttpServletRequest; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import edu.wisc.uwss.UWUserDetails; import edu.wisc.uwss.preauth.PreauthenticatedUserDetailsAttributeMapper.Default; @@ -30,9 +33,10 @@ import edu.wisc.uwss.preauth.PreauthenticatedUserDetailsAttributeMapper.Default; public class PreauthenticatedUserDetailsAttributeMapperTest { private PreauthenticatedUserDetailsAttributeMapper.Default filter = new PreauthenticatedUserDetailsAttributeMapper.Default(); - + /** * Verify expected behaviour for {@link PreauthenticatedUserDetailsAttributeMapper#mapUser(HttpServletRequest)} + * for a simple case with 1 value for each expected header. */ @Test public void mapUser_success() { @@ -43,18 +47,16 @@ public class PreauthenticatedUserDetailsAttributeMapperTest { String name = "some body"; String email = "some.body@wisc.edu"; String emplid = "0000123456"; - List<String> uddsMembership = Collections.singletonList("udds1234"); - List<String> manifestGroups = Collections.singletonList("uw:domain:something"); request.addHeader("eppn", eppn); request.addHeader("wiscedupvi", pvi); request.addHeader("uid", uid); request.addHeader("cn", name); request.addHeader("mail", email); - request.addHeader("wisceduudds", uddsMembership); + request.addHeader("wisceduudds", "udds1234"); request.addHeader("wisceduisisemplid", emplid); request.addHeader("Shib-Identity-Provider", "https://logintest.wisc.edu/idp/shibboleth"); - request.addHeader("ismemberof",manifestGroups); + request.addHeader("isMemberOf", "uw:domain:something"); UWUserDetails result = filter.mapUser(request); @@ -64,37 +66,93 @@ public class PreauthenticatedUserDetailsAttributeMapperTest { assertEquals(pvi, result.getPvi()); assertEquals(name, result.getFullName()); assertEquals(email, result.getEmailAddress()); - assertEquals(uddsMembership, result.getUddsMembership()); + assertEquals(Collections.singletonList("udds1234"), result.getUddsMembership()); assertEquals(emplid, result.getIsisEmplid()); assertEquals("/Shibboleth.sso/Logout?return=https://logintest.wisc.edu/logout/", result.getCustomLogoutUrl()); assertEquals(1,result.getAuthorities().size()); - assertEquals(manifestGroups.toString(),result.getAuthorities().toString()); + assertTrue(result.getAuthorities().contains(new SimpleGrantedAuthority("uw:domain:something"))); } /** - * Verify that a user can have multiple Manifest groups in their {@link GrantedAuthority}s + * Verify expected behaviour for {@link PreauthenticatedUserDetailsAttributeMapper#mapUser(HttpServletRequest)} + * when the isMemberOf header is empty. + */ + @Test + public void mapUser_empty_isMemberOf() { + MockHttpServletRequest request = new MockHttpServletRequest(); + String uid = "somebody"; + String eppn = "somebody@wisc.edu"; + String pvi = "1234567"; + String name = "some body"; + String email = "some.body@wisc.edu"; + String emplid = "0000123456"; + List<String> uddsMembership = Collections.singletonList("udds1234"); + + request.addHeader("eppn", eppn); + request.addHeader("wiscedupvi", pvi); + request.addHeader("uid", uid); + request.addHeader("cn", name); + request.addHeader("mail", email); + request.addHeader("wisceduudds", uddsMembership); + request.addHeader("wisceduisisemplid", emplid); + request.addHeader("Shib-Identity-Provider", "https://logintest.wisc.edu/idp/shibboleth"); + + UWUserDetails result = filter.mapUser(request); + + assertNotNull(result); + assertEquals(uid, result.getUsername()); + assertEquals(eppn, result.getEppn()); + assertEquals(pvi, result.getPvi()); + assertEquals(name, result.getFullName()); + assertEquals(email, result.getEmailAddress()); + assertEquals(uddsMembership, result.getUddsMembership()); + assertEquals(emplid, result.getIsisEmplid()); + assertEquals("/Shibboleth.sso/Logout?return=https://logintest.wisc.edu/logout/", result.getCustomLogoutUrl()); + assertEquals(0, result.getAuthorities().size()); + } + /** + * Verify expected behaviour for {@link PreauthenticatedUserDetailsAttributeMapper#mapUser(HttpServletRequest)} + * when the isMemberOf header has multiple values. */ @Test public void mapUser_multipleManifestGroups() { MockHttpServletRequest request = new MockHttpServletRequest(); String uid = "somebody"; - ArrayList<String> manifestGroups = new ArrayList<>(); - manifestGroups.add("uw:domain:onegroup"); - manifestGroups.add("uw:domain:anothergroup"); request.addHeader("uid", uid); - request.addHeader("ismemberof",manifestGroups); + request.addHeader("isMemberOf", "uw:domain:onegroup"); + request.addHeader("isMemberOf", "uw:domain:anothergroup"); UWUserDetails result = filter.mapUser(request); assertEquals(uid, result.getUsername()); assertEquals(2,result.getAuthorities().size()); - for (GrantedAuthority authority : result.getAuthorities()) { - assertTrue(manifestGroups.contains(authority.getAuthority())); - } + assertTrue(result.getAuthorities().contains(new SimpleGrantedAuthority("uw:domain:onegroup"))); + assertTrue(result.getAuthorities().contains(new SimpleGrantedAuthority("uw:domain:anothergroup"))); + } + /** + * Verify expected behaviour for {@link PreauthenticatedUserDetailsAttributeMapper#mapUser(HttpServletRequest)} + * when the isMemberOf header has multiple values. + */ + @Test + public void mapUser_multipleudds() { + MockHttpServletRequest request = new MockHttpServletRequest(); + + String uid = "somebody"; + + request.addHeader("uid", uid); + request.addHeader("wisceduudds", "A061234"); + request.addHeader("wisceduudds", "B062345"); + + UWUserDetails result = filter.mapUser(request); + + assertEquals(uid, result.getUsername()); + assertEquals(2,result.getUddsMembership().size()); + assertTrue(result.getUddsMembership().contains("A061234")); + assertTrue(result.getUddsMembership().contains("B062345")); } /** * Verify behavior of {@link Default#toCustomLogoutUrl(String)} for -- GitLab