Skip to content

Commit

Permalink
Merged in SHIBUI-1029 (pull request #282)
Browse files Browse the repository at this point in the history
SHIBUI-1029
  • Loading branch information
Bill Smith authored and Jonathan Johnson committed Jan 28, 2019
2 parents 3c27f47 + 7856be1 commit 7931624
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
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.apache.commons.lang3.RandomStringUtils;
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;
Expand All @@ -25,6 +24,8 @@
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;

/**
Expand All @@ -40,32 +41,51 @@ public class AddNewUserFilter implements Filter {
private RoleRepository roleRepository;
private EmailService emailService;

public AddNewUserFilter(UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) {
private Pac4jConfigurationProperties pac4jConfigurationProperties;

private Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping;

public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.emailService = emailService;
this.pac4jConfigurationProperties = pac4jConfigurationProperties;
saml2ProfileMapping = this.pac4jConfigurationProperties.getSaml2ProfileMapping();
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

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(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
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 = getAttributeFromProfile(profile, "username");
if (username != null) {
Optional<User> 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);
try {
emailService.sendNewUserMail(username);
} catch (MessagingException e) {
Expand All @@ -87,6 +107,28 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
public void destroy() {
}

private String getAttributeFromProfile(SAML2Profile profile, String stringKey) {
String attribute = null;
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.
}
List<String> attributeList = (List<String>) 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, Pac4jConfigurationProperties pac4jConfigurationProperties) {
return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, pac4jConfigurationProperties);
}

@Configuration
Expand Down Expand Up @@ -57,12 +57,14 @@ public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigu
private UserRepository userRepository;
private RoleRepository roleRepository;
private EmailService emailService;
private Pac4jConfigurationProperties pac4jConfigurationProperties;

public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) {
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.pac4jConfigurationProperties = pac4jConfigurationProperties;
}

@Override
Expand All @@ -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(pac4jConfigurationProperties, userRepository, roleRepository, emailService), SecurityFilter.class);

http.authorizeRequests().anyRequest().fullyAuthenticated();

Expand Down
3 changes: 2 additions & 1 deletion pac4j-module/src/main/resources/META-INF/spring.factories
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
net.unicon.shibui.pac4j.Pac4jConfiguration,\
net.unicon.shibui.pac4j.WebSecurity,\
net.unicon.shibui.pac4j.Pac4jConfigurationProperties
net.unicon.shibui.pac4j.Pac4jConfigurationProperties,\
net.unicon.shibui.pac4j.CustomPropertiesConfiguration
7 changes: 7 additions & 0 deletions pac4j-module/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
shibui:
pac4j:
saml2ProfileMapping:
username: urn:oid:0.9.2342.19200300.100.1.3
firstName: givenName
lastName: sn
email: mail
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ 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.security.core.Authentication
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.context.SecurityContextHolder
Expand All @@ -18,6 +22,8 @@ import javax.servlet.http.HttpServletResponse
/**
* @author Bill Smith (wsmith@unicon.net)
*/
@SpringBootTest(classes = [Pac4jConfigurationProperties])
@EnableConfigurationProperties([Pac4jConfigurationProperties])
class AddNewUserFilterTests extends Specification {

UserRepository userRepository = Mock()
Expand All @@ -30,18 +36,33 @@ class AddNewUserFilterTests extends Specification {

SecurityContext securityContext = Mock()
Authentication authentication = Mock()
SAML2Profile saml2Profile = Mock()

@Autowired
Pac4jConfigurationProperties pac4jConfigurationProperties

Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping

@Subject
AddNewUserFilter addNewUserFilter = new AddNewUserFilter(userRepository, roleRepository, emailService)
AddNewUserFilter addNewUserFilter

def setup() {
SecurityContextHolder.setContext(securityContext)
securityContext.getAuthentication() >> authentication
authentication.getPrincipal() >> saml2Profile

addNewUserFilter = new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, emailService)
saml2ProfileMapping = pac4jConfigurationProperties.saml2ProfileMapping
}

def "new users are redirected"() {
given:
authentication.getName() >> 'newUser'
['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'))

Expand All @@ -50,14 +71,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("/unsecured/error.html")
}

def "existing users are not redirected"() {
given:
authentication.getName() >> 'existingUser'
saml2Profile.getAttribute(saml2ProfileMapping.getUsername()) >> ['existingUser']
userRepository.findByUsername('existingUser') >> Optional.of(new User().with {
it.username = 'existingUser'
it.roles = [new Role('ROLE_USER')]
Expand Down

0 comments on commit 7931624

Please sign in to comment.