From 29d948d8a0a5f65d52d199178c913a30da3794ba Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 24 Jan 2019 17:52:24 -0700 Subject: [PATCH 1/4] [SHIBUI-1029] Custom user attributes mapping WIP. Need to deal with some casting issues. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 41 ++++++++++++------- .../pac4j/CustomPropertiesConfiguration.java | 26 ++++++++++++ .../net/unicon/shibui/pac4j/WebSecurity.java | 10 +++-- .../main/resources/META-INF/spring.factories | 3 +- .../src/main/resources/application.yml | 6 +++ 5 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java create mode 100644 pac4j-module/src/main/resources/application.yml diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java index 5c5bf6e12..d30acf5bd 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java @@ -7,14 +7,11 @@ import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; import edu.internet2.tier.shibboleth.admin.ui.service.EmailService; -import org.apache.commons.lang.RandomStringUtils; -import org.apache.http.entity.ContentType; +import org.pac4j.saml.profile.SAML2Profile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.bcrypt.BCrypt; import javax.mail.MessagingException; import javax.servlet.Filter; @@ -25,6 +22,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Map; import java.util.Optional; /** @@ -40,32 +38,47 @@ public class AddNewUserFilter implements Filter { private RoleRepository roleRepository; private EmailService emailService; - public AddNewUserFilter(UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { + private CustomPropertiesConfiguration customPropertiesConfiguration; + + private Map saml2ProfileMapping; + + public AddNewUserFilter(CustomPropertiesConfiguration customPropertiesConfiguration, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { this.userRepository = userRepository; this.roleRepository = roleRepository; this.emailService = emailService; + this.customPropertiesConfiguration = customPropertiesConfiguration; + saml2ProfileMapping = this.customPropertiesConfiguration.getSaml2ProfileMapping(); } @Override public void init(FilterConfig filterConfig) throws ServletException { } + private User buildAndPersistNewUserFromProfile(Map attributes) { + Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE)); + roleRepository.save(noRole); + + User user = new User(); + user.getRoles().add(noRole); + user.setUsername((String) attributes.get(saml2ProfileMapping.get("username"))); + user.setFirstName((String) attributes.get(saml2ProfileMapping.get("firstName"))); + user.setLastName((String) attributes.get(saml2ProfileMapping.get("lastName"))); + user.setEmailAddress((String) attributes.get(saml2ProfileMapping.get("email"))); + userRepository.save(user); + return user; + } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - String username = authentication.getName(); + SAML2Profile profile = (SAML2Profile) authentication.getPrincipal(); + if (profile != null) { + String username = (String) profile.getAttribute(saml2ProfileMapping.get("username")); if (username != null) { Optional persistedUser = userRepository.findByUsername(username); User user; if (!persistedUser.isPresent()) { - user = new User(); - user.setUsername(username); - user.setPassword(BCrypt.hashpw(RandomStringUtils.randomAlphanumeric(20), BCrypt.gensalt())); - Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE)); - roleRepository.save(noRole); - user.getRoles().add(noRole); - userRepository.save(user); + user = buildAndPersistNewUserFromProfile(profile.getAttributes()); try { emailService.sendNewUserMail(username); } catch (MessagingException e) { diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java new file mode 100644 index 000000000..9d0f745b6 --- /dev/null +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java @@ -0,0 +1,26 @@ +package net.unicon.shibui.pac4j; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Component +@ConfigurationProperties(prefix="custom") +public class CustomPropertiesConfiguration { + + private Map saml2ProfileMapping; + + public Map getSaml2ProfileMapping() { + return saml2ProfileMapping; + } + + public void setSaml2ProfileMapping(Map saml2ProfileMapping) { + this.saml2ProfileMapping = saml2ProfileMapping; + } +} diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java index e0e156eec..421dffe63 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java @@ -20,8 +20,8 @@ @AutoConfigureOrder(-1) public class WebSecurity { @Bean("webSecurityConfig") - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { - return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService); + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, CustomPropertiesConfiguration customPropertiesConfiguration) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, customPropertiesConfiguration); } @Configuration @@ -57,12 +57,14 @@ public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigu private UserRepository userRepository; private RoleRepository roleRepository; private EmailService emailService; + private CustomPropertiesConfiguration customPropertiesConfiguration; - public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, CustomPropertiesConfiguration customPropertiesConfiguration) { this.config = config; this.userRepository = userRepository; this.roleRepository = roleRepository; this.emailService = emailService; + this.customPropertiesConfiguration = customPropertiesConfiguration; } @Override @@ -72,7 +74,7 @@ protected void configure(HttpSecurity http) throws Exception { final CallbackFilter callbackFilter = new CallbackFilter(this.config); http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) .addFilterBefore(securityFilter, BasicAuthenticationFilter.class) - .addFilterAfter(new AddNewUserFilter(userRepository, roleRepository, emailService), SecurityFilter.class); + .addFilterAfter(new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService), SecurityFilter.class); http.authorizeRequests().anyRequest().fullyAuthenticated(); diff --git a/pac4j-module/src/main/resources/META-INF/spring.factories b/pac4j-module/src/main/resources/META-INF/spring.factories index 90f792d84..f46ede845 100644 --- a/pac4j-module/src/main/resources/META-INF/spring.factories +++ b/pac4j-module/src/main/resources/META-INF/spring.factories @@ -1,4 +1,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ net.unicon.shibui.pac4j.Pac4jConfiguration,\ net.unicon.shibui.pac4j.WebSecurity,\ - net.unicon.shibui.pac4j.Pac4jConfigurationProperties \ No newline at end of file + net.unicon.shibui.pac4j.Pac4jConfigurationProperties,\ + net.unicon.shibui.pac4j.CustomPropertiesConfiguration \ No newline at end of file diff --git a/pac4j-module/src/main/resources/application.yml b/pac4j-module/src/main/resources/application.yml new file mode 100644 index 000000000..7ffeb8bf9 --- /dev/null +++ b/pac4j-module/src/main/resources/application.yml @@ -0,0 +1,6 @@ +custom: + saml2ProfileMapping: + username: urn:oid:0.9.2342.19200300.100.1.3 + firstname: givenName + lastname: sn + email: mail \ No newline at end of file From 0d35c923a5b75a47e136dacc5a3b147b382cef0c Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 25 Jan 2019 11:07:27 -0700 Subject: [PATCH 2/4] [SHIBUI-1029] More WIP, trying to get test properties to populate. --- .../unicon/shibui/pac4j/AddNewUserFilterTests.groovy | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy index 98c5e99f3..526785d72 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -5,9 +5,13 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.User import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import edu.internet2.tier.shibboleth.admin.ui.service.EmailService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.annotation.ComponentScan import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContext import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.test.context.ContextConfiguration import spock.lang.Specification import spock.lang.Subject @@ -18,6 +22,9 @@ import javax.servlet.http.HttpServletResponse /** * @author Bill Smith (wsmith@unicon.net) */ +@SpringBootTest +@ComponentScan("net.unicon.shibui.pac4j") +@ContextConfiguration(classes=[CustomPropertiesConfiguration]) class AddNewUserFilterTests extends Specification { UserRepository userRepository = Mock() @@ -31,8 +38,10 @@ class AddNewUserFilterTests extends Specification { SecurityContext securityContext = Mock() Authentication authentication = Mock() + CustomPropertiesConfiguration customPropertiesConfiguration + @Subject - AddNewUserFilter addNewUserFilter = new AddNewUserFilter(userRepository, roleRepository, emailService) + AddNewUserFilter addNewUserFilter = new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService) def setup() { SecurityContextHolder.setContext(securityContext) From 127bfcf405808fbb07dd37960dc3660e253f9435 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 25 Jan 2019 12:28:26 -0700 Subject: [PATCH 3/4] [SHIBUI-1029] Updated tests with support for new custom user mappings. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 37 ++++++++++++++----- .../pac4j/CustomPropertiesConfiguration.java | 4 +- .../src/main/resources/application.yml | 4 +- .../shibui/pac4j/AddNewUserFilterTests.groovy | 28 +++++++++----- 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java index d30acf5bd..af369937f 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java @@ -7,11 +7,13 @@ import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; import edu.internet2.tier.shibboleth.admin.ui.service.EmailService; +import org.apache.commons.lang3.RandomStringUtils; import org.pac4j.saml.profile.SAML2Profile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCrypt; import javax.mail.MessagingException; import javax.servlet.Filter; @@ -22,6 +24,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -54,18 +57,22 @@ public AddNewUserFilter(CustomPropertiesConfiguration customPropertiesConfigurat public void init(FilterConfig filterConfig) throws ServletException { } - private User buildAndPersistNewUserFromProfile(Map attributes) { + private User buildAndPersistNewUserFromProfile(SAML2Profile profile) { Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE)); roleRepository.save(noRole); User user = new User(); user.getRoles().add(noRole); - user.setUsername((String) attributes.get(saml2ProfileMapping.get("username"))); - user.setFirstName((String) attributes.get(saml2ProfileMapping.get("firstName"))); - user.setLastName((String) attributes.get(saml2ProfileMapping.get("lastName"))); - user.setEmailAddress((String) attributes.get(saml2ProfileMapping.get("email"))); - userRepository.save(user); - return user; + user.setUsername(getAttributeFromProfile(profile, "username")); + user.setPassword(BCrypt.hashpw(RandomStringUtils.randomAlphanumeric(20), BCrypt.gensalt())); + user.setFirstName(getAttributeFromProfile(profile, "firstName")); + user.setLastName(getAttributeFromProfile(profile, "lastName")); + user.setEmailAddress(getAttributeFromProfile(profile, "email")); + User persistedUser = userRepository.save(user); + if (logger.isDebugEnabled()) { + logger.debug("Persisted new user:\n" + user); + } + return persistedUser; } @Override @@ -73,12 +80,12 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); SAML2Profile profile = (SAML2Profile) authentication.getPrincipal(); if (profile != null) { - String username = (String) profile.getAttribute(saml2ProfileMapping.get("username")); + String username = getAttributeFromProfile(profile, "username"); if (username != null) { Optional persistedUser = userRepository.findByUsername(username); User user; if (!persistedUser.isPresent()) { - user = buildAndPersistNewUserFromProfile(profile.getAttributes()); + user = buildAndPersistNewUserFromProfile(profile); try { emailService.sendNewUserMail(username); } catch (MessagingException e) { @@ -100,6 +107,18 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha public void destroy() { } + private String getAttributeFromProfile(SAML2Profile profile, String stringKey) { + String mappingKey = saml2ProfileMapping.get(stringKey); + List attributeList = (List) profile.getAttribute(mappingKey); + String attribute = null; + if (attributeList.size() > 0) { + if (attributeList.size() != 1) { + logger.warn(String.format("More than one attribute was found for key [%s]", stringKey)); + } + attribute = attributeList.get(0); + } + return attribute; + } private byte[] getJsonResponseBytes(ErrorResponse eErrorResponse) throws IOException { String errorResponseJson = new ObjectMapper().writeValueAsString(eErrorResponse); return errorResponseJson.getBytes(); diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java index 9d0f745b6..793b43793 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java @@ -1,10 +1,9 @@ package net.unicon.shibui.pac4j; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; -import java.util.HashMap; import java.util.Map; /** @@ -12,6 +11,7 @@ */ @Component @ConfigurationProperties(prefix="custom") +@EnableConfigurationProperties public class CustomPropertiesConfiguration { private Map saml2ProfileMapping; diff --git a/pac4j-module/src/main/resources/application.yml b/pac4j-module/src/main/resources/application.yml index 7ffeb8bf9..ae30333c7 100644 --- a/pac4j-module/src/main/resources/application.yml +++ b/pac4j-module/src/main/resources/application.yml @@ -1,6 +1,6 @@ custom: saml2ProfileMapping: username: urn:oid:0.9.2342.19200300.100.1.3 - firstname: givenName - lastname: sn + firstName: givenName + lastName: sn email: mail \ No newline at end of file diff --git a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy index 526785d72..652b38783 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -5,13 +5,13 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.User import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import edu.internet2.tier.shibboleth.admin.ui.service.EmailService +import org.pac4j.saml.profile.SAML2Profile import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.test.context.SpringBootTest -import org.springframework.context.annotation.ComponentScan import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContext import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.test.context.ContextConfiguration import spock.lang.Specification import spock.lang.Subject @@ -22,9 +22,8 @@ import javax.servlet.http.HttpServletResponse /** * @author Bill Smith (wsmith@unicon.net) */ -@SpringBootTest -@ComponentScan("net.unicon.shibui.pac4j") -@ContextConfiguration(classes=[CustomPropertiesConfiguration]) +@SpringBootTest(classes = [CustomPropertiesConfiguration]) +@EnableConfigurationProperties([CustomPropertiesConfiguration]) class AddNewUserFilterTests extends Specification { UserRepository userRepository = Mock() @@ -37,20 +36,31 @@ class AddNewUserFilterTests extends Specification { SecurityContext securityContext = Mock() Authentication authentication = Mock() + SAML2Profile saml2Profile = Mock() + @Autowired CustomPropertiesConfiguration customPropertiesConfiguration + Map userAttributeMapping + @Subject - AddNewUserFilter addNewUserFilter = new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService) + AddNewUserFilter addNewUserFilter def setup() { SecurityContextHolder.setContext(securityContext) securityContext.getAuthentication() >> authentication + authentication.getPrincipal() >> saml2Profile + + addNewUserFilter = new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService) + userAttributeMapping = customPropertiesConfiguration.saml2ProfileMapping } def "new users are redirected"() { given: - authentication.getName() >> 'newUser' + saml2Profile.getAttribute(userAttributeMapping.get('username')) >> ['newUser'] + saml2Profile.getAttribute(userAttributeMapping.get('firstName')) >> ['New'] + saml2Profile.getAttribute(userAttributeMapping.get('lastName')) >> ['User'] + saml2Profile.getAttribute(userAttributeMapping.get('email')) >> ['newuser@institution.edu'] userRepository.findByUsername('newUser') >> Optional.empty() roleRepository.findByName('ROLE_NONE') >> Optional.of(new Role('ROLE_NONE')) @@ -59,14 +69,14 @@ class AddNewUserFilterTests extends Specification { then: 1 * roleRepository.save(_) - 1 * userRepository.save(_) + 1 * userRepository.save(_ as User) >> { User user -> user } 1 * emailService.sendNewUserMail('newUser') 1 * response.sendRedirect("/static.html") } def "existing users are not redirected"() { given: - authentication.getName() >> 'existingUser' + saml2Profile.getAttribute(userAttributeMapping.get('username')) >> ['existingUser'] userRepository.findByUsername('existingUser') >> Optional.of(new User().with { it.username = 'existingUser' it.roles = [new Role('ROLE_USER')] From 7856be12667b1c2967d2324d6d06db595c63bdce Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 25 Jan 2019 15:35:14 -0700 Subject: [PATCH 4/4] [SHIBUI-1029] Consolidated properties into Pac4jConfigurationProperties. Refactored tests and filter to use new properties. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 36 ++++++++----- .../pac4j/CustomPropertiesConfiguration.java | 26 ---------- .../shibui/pac4j/Pac4jConfiguration.java | 1 + .../pac4j/Pac4jConfigurationProperties.java | 50 +++++++++++++++++++ .../net/unicon/shibui/pac4j/WebSecurity.java | 12 ++--- .../src/main/resources/application.yml | 13 ++--- .../shibui/pac4j/AddNewUserFilterTests.groovy | 24 +++++---- 7 files changed, 100 insertions(+), 62 deletions(-) delete mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java index af369937f..27275870d 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java @@ -41,16 +41,16 @@ public class AddNewUserFilter implements Filter { private RoleRepository roleRepository; private EmailService emailService; - private CustomPropertiesConfiguration customPropertiesConfiguration; + private Pac4jConfigurationProperties pac4jConfigurationProperties; - private Map saml2ProfileMapping; + private Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping; - public AddNewUserFilter(CustomPropertiesConfiguration customPropertiesConfiguration, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { + public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { this.userRepository = userRepository; this.roleRepository = roleRepository; this.emailService = emailService; - this.customPropertiesConfiguration = customPropertiesConfiguration; - saml2ProfileMapping = this.customPropertiesConfiguration.getSaml2ProfileMapping(); + this.pac4jConfigurationProperties = pac4jConfigurationProperties; + saml2ProfileMapping = this.pac4jConfigurationProperties.getSaml2ProfileMapping(); } @Override @@ -108,17 +108,27 @@ public void destroy() { } private String getAttributeFromProfile(SAML2Profile profile, String stringKey) { - String mappingKey = saml2ProfileMapping.get(stringKey); - List attributeList = (List) profile.getAttribute(mappingKey); String attribute = null; - if (attributeList.size() > 0) { - if (attributeList.size() != 1) { - logger.warn(String.format("More than one attribute was found for key [%s]", stringKey)); - } - attribute = attributeList.get(0); + switch (stringKey) { + case "username": + attribute = saml2ProfileMapping.getUsername(); + break; + case "firstName": + attribute = saml2ProfileMapping.getFirstName(); + break; + case "lastName": + attribute = saml2ProfileMapping.getLastName(); + break; + case "email": + attribute = saml2ProfileMapping.getEmail(); + break; + default: + // do we care? Not yet. } - return attribute; + List attributeList = (List) profile.getAttribute(attribute); + return attributeList.size() < 1 ? null : attributeList.get(0); } + private byte[] getJsonResponseBytes(ErrorResponse eErrorResponse) throws IOException { String errorResponseJson = new ObjectMapper().writeValueAsString(eErrorResponse); return errorResponseJson.getBytes(); diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java deleted file mode 100644 index 793b43793..000000000 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.unicon.shibui.pac4j; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Bill Smith (wsmith@unicon.net) - */ -@Component -@ConfigurationProperties(prefix="custom") -@EnableConfigurationProperties -public class CustomPropertiesConfiguration { - - private Map saml2ProfileMapping; - - public Map getSaml2ProfileMapping() { - return saml2ProfileMapping; - } - - public void setSaml2ProfileMapping(Map saml2ProfileMapping) { - this.saml2ProfileMapping = saml2ProfileMapping; - } -} diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java index 1c28b1ee1..5ee638178 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java @@ -22,6 +22,7 @@ public Config config(final Pac4jConfigurationProperties pac4jConfigurationProper saml2ClientConfiguration.setServiceProviderMetadataPath(pac4jConfigurationProperties.getServiceProviderMetadataPath()); saml2ClientConfiguration.setForceServiceProviderMetadataGeneration(pac4jConfigurationProperties.isForceServiceProviderMetadataGeneration()); saml2ClientConfiguration.setWantsAssertionsSigned(pac4jConfigurationProperties.isWantAssertionsSigned()); + saml2ClientConfiguration.setAttributeAsId(pac4jConfigurationProperties.getSaml2ProfileMapping().getUsername()); final SAML2Client saml2Client = new SAML2Client(saml2ClientConfiguration); saml2Client.setName("Saml2Client"); diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java index afb1369a1..cf36d8e65 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java @@ -1,10 +1,12 @@ package net.unicon.shibui.pac4j; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "shibui.pac4j") +@EnableConfigurationProperties public class Pac4jConfigurationProperties { private String keystorePath = "/tmp/samlKeystore.jks"; private String keystorePassword = "changeit"; @@ -16,6 +18,46 @@ public class Pac4jConfigurationProperties { private boolean forceServiceProviderMetadataGeneration = false; private String callbackUrl; private boolean wantAssertionsSigned = true; + private SAML2ProfileMapping saml2ProfileMapping; + + public static class SAML2ProfileMapping { + private String username; + private String email; + private String firstName; + private String lastName; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + } public String getKeystorePath() { return keystorePath; @@ -96,4 +138,12 @@ public boolean isWantAssertionsSigned() { public void setWantAssertionsSigned(boolean wantAssertionsSigned) { this.wantAssertionsSigned = wantAssertionsSigned; } + + public SAML2ProfileMapping getSaml2ProfileMapping() { + return saml2ProfileMapping; + } + + public void setSaml2ProfileMapping(SAML2ProfileMapping saml2ProfileMapping) { + this.saml2ProfileMapping = saml2ProfileMapping; + } } diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java index 421dffe63..f0c183175 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java @@ -20,8 +20,8 @@ @AutoConfigureOrder(-1) public class WebSecurity { @Bean("webSecurityConfig") - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, CustomPropertiesConfiguration customPropertiesConfiguration) { - return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, customPropertiesConfiguration); + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, Pac4jConfigurationProperties pac4jConfigurationProperties) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, pac4jConfigurationProperties); } @Configuration @@ -57,14 +57,14 @@ public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigu private UserRepository userRepository; private RoleRepository roleRepository; private EmailService emailService; - private CustomPropertiesConfiguration customPropertiesConfiguration; + private Pac4jConfigurationProperties pac4jConfigurationProperties; - public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, CustomPropertiesConfiguration customPropertiesConfiguration) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, Pac4jConfigurationProperties pac4jConfigurationProperties) { this.config = config; this.userRepository = userRepository; this.roleRepository = roleRepository; this.emailService = emailService; - this.customPropertiesConfiguration = customPropertiesConfiguration; + this.pac4jConfigurationProperties = pac4jConfigurationProperties; } @Override @@ -74,7 +74,7 @@ protected void configure(HttpSecurity http) throws Exception { final CallbackFilter callbackFilter = new CallbackFilter(this.config); http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) .addFilterBefore(securityFilter, BasicAuthenticationFilter.class) - .addFilterAfter(new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService), SecurityFilter.class); + .addFilterAfter(new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, emailService), SecurityFilter.class); http.authorizeRequests().anyRequest().fullyAuthenticated(); diff --git a/pac4j-module/src/main/resources/application.yml b/pac4j-module/src/main/resources/application.yml index ae30333c7..4042fd939 100644 --- a/pac4j-module/src/main/resources/application.yml +++ b/pac4j-module/src/main/resources/application.yml @@ -1,6 +1,7 @@ -custom: - saml2ProfileMapping: - username: urn:oid:0.9.2342.19200300.100.1.3 - firstName: givenName - lastName: sn - email: mail \ No newline at end of file +shibui: + pac4j: + saml2ProfileMapping: + username: urn:oid:0.9.2342.19200300.100.1.3 + firstName: givenName + lastName: sn + email: mail \ No newline at end of file diff --git a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy index 652b38783..ac470ea25 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -22,8 +22,8 @@ import javax.servlet.http.HttpServletResponse /** * @author Bill Smith (wsmith@unicon.net) */ -@SpringBootTest(classes = [CustomPropertiesConfiguration]) -@EnableConfigurationProperties([CustomPropertiesConfiguration]) +@SpringBootTest(classes = [Pac4jConfigurationProperties]) +@EnableConfigurationProperties([Pac4jConfigurationProperties]) class AddNewUserFilterTests extends Specification { UserRepository userRepository = Mock() @@ -39,9 +39,9 @@ class AddNewUserFilterTests extends Specification { SAML2Profile saml2Profile = Mock() @Autowired - CustomPropertiesConfiguration customPropertiesConfiguration + Pac4jConfigurationProperties pac4jConfigurationProperties - Map userAttributeMapping + Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping @Subject AddNewUserFilter addNewUserFilter @@ -51,16 +51,18 @@ class AddNewUserFilterTests extends Specification { securityContext.getAuthentication() >> authentication authentication.getPrincipal() >> saml2Profile - addNewUserFilter = new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService) - userAttributeMapping = customPropertiesConfiguration.saml2ProfileMapping + addNewUserFilter = new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, emailService) + saml2ProfileMapping = pac4jConfigurationProperties.saml2ProfileMapping } def "new users are redirected"() { given: - saml2Profile.getAttribute(userAttributeMapping.get('username')) >> ['newUser'] - saml2Profile.getAttribute(userAttributeMapping.get('firstName')) >> ['New'] - saml2Profile.getAttribute(userAttributeMapping.get('lastName')) >> ['User'] - saml2Profile.getAttribute(userAttributeMapping.get('email')) >> ['newuser@institution.edu'] + ['Username': 'newUser', + 'FirstName': 'New', + 'LastName': 'User', + 'Email': 'newuser@institution.edu'].each { key, value -> + saml2Profile.getAttribute(saml2ProfileMapping."get${key}"()) >> [value] + } userRepository.findByUsername('newUser') >> Optional.empty() roleRepository.findByName('ROLE_NONE') >> Optional.of(new Role('ROLE_NONE')) @@ -76,7 +78,7 @@ class AddNewUserFilterTests extends Specification { def "existing users are not redirected"() { given: - saml2Profile.getAttribute(userAttributeMapping.get('username')) >> ['existingUser'] + saml2Profile.getAttribute(saml2ProfileMapping.getUsername()) >> ['existingUser'] userRepository.findByUsername('existingUser') >> Optional.of(new User().with { it.username = 'existingUser' it.roles = [new Role('ROLE_USER')]