diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java index 3adca2b75..a2d11a1ab 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java @@ -61,4 +61,13 @@ public User getCurrentUser() { } return user; } + + public Set getUserRoles(String username) { + Optional user = userRepository.findByUsername(username); + HashSet result = new HashSet<>(); + if (user.isPresent() ) { + user.get().getRoles().forEach(role -> result.add(role.getName())); + } + return result; + } } diff --git a/pac4j-module/build.gradle b/pac4j-module/build.gradle index 2d952a4c8..a84d2fe11 100644 --- a/pac4j-module/build.gradle +++ b/pac4j-module/build.gradle @@ -1,8 +1,9 @@ plugins { id 'groovy' id 'jacoco' - id 'org.springframework.boot' version '2.1.5.RELEASE' apply false + id 'org.springframework.boot' version '2.4.2' apply false id 'io.spring.dependency-management' version '1.0.7.RELEASE' + id 'io.freefair.lombok' version '5.3.0' } sourceCompatibility = 11 @@ -22,12 +23,15 @@ dependencyManagement { } } +generateLombokConfig.enabled = false + dependencies { compileOnly project(':backend') - compile "org.pac4j:spring-security-pac4j:4.0.0" - compile "org.pac4j:pac4j-core:3.3.0" - compile "org.pac4j:pac4j-saml:3.3.0", { + compile "org.pac4j:spring-security-pac4j:6.0.0" // pac4j is "off" - spring 6.0.0 here uses 5.1 core, thus differences in versions + compile "org.pac4j:pac4j-core:5.1.0" + compile "org.pac4j:pac4j-http:5.1.0" + compile "org.pac4j:pac4j-saml:5.1.0", { // opensaml libraries are provided exclude group: 'org.opensaml' } 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 54cb2950d..c379690c5 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,7 +7,12 @@ 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.core.context.JEEContext; +import org.pac4j.core.context.session.JEESessionStore; +import org.pac4j.core.matching.matcher.Matcher; +import org.pac4j.core.profile.CommonProfile; import org.pac4j.saml.profile.SAML2Profile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,116 +27,102 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.transaction.Transactional; + import java.io.IOException; import java.util.List; import java.util.Optional; -/** - * @author Bill Smith (wsmith@unicon.net) - */ -public class AddNewUserFilter implements Filter { - - private static final Logger logger = LoggerFactory.getLogger(AddNewUserFilter.class); +import lombok.extern.slf4j.Slf4j; +@Slf4j +public class AddNewUserFilter implements Filter { private static final String ROLE_NONE = "ROLE_NONE"; - private UserRepository userRepository; - private RoleRepository roleRepository; private Optional emailService; - private Pac4jConfigurationProperties pac4jConfigurationProperties; + private RoleRepository roleRepository; + private Pac4jConfigurationProperties.SimpleProfileMapping simpleProfileMapping; + private UserRepository userRepository; + private Matcher matcher; - private Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping; - - public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserRepository userRepository, RoleRepository roleRepository, Optional emailService) { + public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserRepository userRepository, RoleRepository roleRepository, Matcher matcher, Optional 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 { + this.matcher = matcher; + simpleProfileMapping = this.pac4jConfigurationProperties.getSimpleProfileMapping(); } - private User buildAndPersistNewUserFromProfile(SAML2Profile profile) { - Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE)); - roleRepository.save(noRole); + @Transactional + private User buildAndPersistNewUserFromProfile(CommonProfile profile) { + Optional noRole = roleRepository.findByName(ROLE_NONE); + Role newUserRole; + if (noRole.isEmpty()) { + newUserRole = new Role(ROLE_NONE); + newUserRole = roleRepository.save(newUserRole); + } + newUserRole = noRole.get(); User user = new User(); - user.getRoles().add(noRole); - user.setUsername(getAttributeFromProfile(profile, "username")); + user.getRoles().add(newUserRole); + user.setUsername(profile.getUsername()); 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.setFirstName(profile.getFirstName()); + user.setLastName(profile.getFamilyName()); + user.setEmailAddress(profile.getEmail()); User persistedUser = userRepository.save(user); - if (logger.isDebugEnabled()) { - logger.debug("Persisted new user:\n" + user); + if (log.isDebugEnabled()) { + log.debug("Persisted new user:\n" + user); } return persistedUser; } + @Override + public void destroy() { + } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + JEEContext context = new JEEContext((HttpServletRequest)request, (HttpServletResponse)response); + if (!matcher.matches(context, JEESessionStore.INSTANCE)) { + return; + } Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - SAML2Profile profile = (SAML2Profile) authentication.getPrincipal(); - if (profile != null) { - String username = getAttributeFromProfile(profile, "username"); - if (username != null) { - Optional persistedUser = userRepository.findByUsername(username); - User user; - if (!persistedUser.isPresent()) { - user = buildAndPersistNewUserFromProfile(profile); - emailService.ifPresent(e -> { - try { - e.sendNewUserMail(username); - } catch (MessagingException e1) { - logger.warn(String.format("Unable to send new user email for user [%s]", username), e); - } - }); - } else { - user = persistedUser.get(); - } - if (user.getRole().equals(ROLE_NONE)) { - ((HttpServletResponse) response).sendRedirect("/unsecured/error.html"); - } else { - chain.doFilter(request, response); // else, user is in the system already, carry on + if (authentication != null) { + CommonProfile profile = (CommonProfile) authentication.getPrincipal(); + if (profile != null) { + String username = profile.getUsername(); + if (username != null) { + Optional persistedUser = userRepository.findByUsername(username); + User user; + if (persistedUser.isEmpty()) { + user = buildAndPersistNewUserFromProfile(profile); + emailService.ifPresent(e -> { + try { + e.sendNewUserMail(username); + } + catch (MessagingException e1) { + log.warn(String.format("Unable to send new user email for user [%s]", username), e); + } + }); + } else { + user = persistedUser.get(); + } + if (user.getRole().equals(ROLE_NONE)) { + ((HttpServletResponse) response).sendRedirect("/unsecured/error.html"); + } else { + chain.doFilter(request, response); // else, user is in the system already, carry on + } } } } } @Override - 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 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(); + public void init(FilterConfig filterConfig) throws ServletException { } } diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/BetterSAML2Profile.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/BetterSAML2Profile.java index 58d16a9ec..17a5edd64 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/BetterSAML2Profile.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/BetterSAML2Profile.java @@ -2,22 +2,40 @@ import org.pac4j.saml.profile.SAML2Profile; +import net.unicon.shibui.pac4j.Pac4jConfigurationProperties.SimpleProfileMapping; + import java.util.Collection; public class BetterSAML2Profile extends SAML2Profile { - private final String usernameAttribute; + private SimpleProfileMapping profileMapping; + + public BetterSAML2Profile(final SimpleProfileMapping simpleProfileMapping) { + this.profileMapping = simpleProfileMapping; + } - public BetterSAML2Profile(final String usernameAttribute) { - this.usernameAttribute = usernameAttribute; + @Override + public String getEmail() { + return (String) getAttribute(profileMapping.getEmail()); + } + + @Override + public String getFamilyName() { + return (String) getAttribute(profileMapping.getLastName()); + } + + @Override + public String getFirstName() { + return (String) getAttribute(profileMapping.getFirstName()); } @Override public String getUsername() { - Object username = getAttribute(usernameAttribute); + Object username = getAttribute(profileMapping.getUsername()); if (username instanceof Collection) { - return (String) ((Collection)username).toArray()[0]; + return (String) ((Collection) username).toArray()[0]; } else { return (String) username; } } + } diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/SAML2ModelAuthorizationGenerator.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/LocalUserProfileAuthorizationGenerator.java similarity index 52% rename from pac4j-module/src/main/java/net/unicon/shibui/pac4j/SAML2ModelAuthorizationGenerator.java rename to pac4j-module/src/main/java/net/unicon/shibui/pac4j/LocalUserProfileAuthorizationGenerator.java index 165477627..1f0f0cba1 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/SAML2ModelAuthorizationGenerator.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/LocalUserProfileAuthorizationGenerator.java @@ -1,24 +1,26 @@ package net.unicon.shibui.pac4j; -import edu.internet2.tier.shibboleth.admin.ui.security.model.User; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import java.util.Optional; + import org.pac4j.core.authorization.generator.AuthorizationGenerator; import org.pac4j.core.context.WebContext; -import org.pac4j.saml.profile.SAML2Profile; +import org.pac4j.core.context.session.SessionStore; +import org.pac4j.core.profile.UserProfile; -import java.util.Optional; +import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; -public class SAML2ModelAuthorizationGenerator implements AuthorizationGenerator { +public class LocalUserProfileAuthorizationGenerator implements AuthorizationGenerator { private final UserRepository userRepository; - public SAML2ModelAuthorizationGenerator(UserRepository userRepository) { + public LocalUserProfileAuthorizationGenerator(UserRepository userRepository) { this.userRepository = userRepository; } @Override - public SAML2Profile generate(WebContext context, SAML2Profile profile) { + public Optional generate(WebContext context, SessionStore sessionStore, UserProfile profile) { Optional user = userRepository.findByUsername(profile.getUsername()); - user.ifPresent( u -> profile.addRole(u.getRole())); - return profile; + user.ifPresent( u -> profile.addRole(u.getRole()) ); + return Optional.of(profile); } } diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java index 47332d43b..81f573fe1 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java @@ -1,11 +1,13 @@ package net.unicon.shibui.pac4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.domain.AuditorAware; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import java.util.Optional; +@ConditionalOnProperty(name = "shibui.pac4j-enabled", havingValue = "true") public class Pac4jAuditorAware implements AuditorAware { private static final String ANONYMOUS = "anonymousUser"; 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 d96e4b352..5e173f151 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 @@ -1,50 +1,137 @@ package net.unicon.shibui.pac4j; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import org.apache.commons.lang3.StringUtils; import org.pac4j.core.client.Clients; import org.pac4j.core.config.Config; +import org.pac4j.core.context.WebContext; +import org.pac4j.core.context.session.SessionStore; +import org.pac4j.core.credentials.Credentials; +import org.pac4j.core.credentials.TokenCredentials; +import org.pac4j.core.credentials.authenticator.Authenticator; +import org.pac4j.core.exception.CredentialsException; +import org.pac4j.core.matching.matcher.PathMatcher; +import org.pac4j.core.profile.CommonProfile; import org.pac4j.core.profile.definition.CommonProfileDefinition; +import org.pac4j.http.client.direct.HeaderClient; import org.pac4j.saml.client.SAML2Client; -import org.pac4j.saml.client.SAML2ClientConfiguration; +import org.pac4j.saml.config.SAML2Configuration; import org.pac4j.saml.credentials.authenticator.SAML2Authenticator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.server.ErrorPage; +import org.springframework.boot.web.server.ErrorPageRegistrar; +import org.springframework.boot.web.server.ErrorPageRegistry; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; + +import com.google.common.collect.Lists; + +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; +import lombok.extern.slf4j.Slf4j; +/** + * Configuration setup here following readme from - https://github.com/pac4j/spring-security-pac4j/tree/5.0.x + * NOTE: matchers are now done as part of the config and have been moved over from the WebSecurity.java class of this package + * @see http://www.pac4j.org/docs/config.html + */ @Configuration @ConditionalOnProperty(name = "shibui.pac4j-enabled", havingValue = "true") +@Slf4j public class Pac4jConfiguration { + public final static String PAC4J_CLIENT_NAME = "shibUIAuthClient"; + + @Autowired + private UserService userService; + + /** + * Custom class that ensures we add the user's roles to the information when doing SAML2 auth + */ @Bean - public SAML2ModelAuthorizationGenerator saml2ModelAuthorizationGenerator(UserRepository userRepository) { - return new SAML2ModelAuthorizationGenerator(userRepository); + public LocalUserProfileAuthorizationGenerator saml2ModelAuthorizationGenerator(UserRepository userRepository) { + return new LocalUserProfileAuthorizationGenerator(userRepository); } + + @Bean(name = "pac4j-config") + public Config config(final Pac4jConfigurationProperties pac4jConfigProps, + final LocalUserProfileAuthorizationGenerator saml2ModelAuthorizationGenerator) { + log.info("**** Configuring PAC4J "); + final Config config = new Config(); + final Clients clients = new Clients(pac4jConfigProps.getCallbackUrl()); + + // configure the matcher for bypassing auth checks + PathMatcher pm = new PathMatcher(); + pm.setExcludedPaths(Lists.newArrayList("/favicon.ico", "/unsecured/**/*", "/assets/**/*.png", "/static/**/*")); + config.addMatcher("exclude-paths-matcher", pm); - @Bean - public Config config(final Pac4jConfigurationProperties pac4jConfigurationProperties, final SAML2ModelAuthorizationGenerator saml2ModelAuthorizationGenerator) { - final SAML2ClientConfiguration saml2ClientConfiguration = new SAML2ClientConfiguration(); - saml2ClientConfiguration.setKeystorePath(pac4jConfigurationProperties.getKeystorePath()); - saml2ClientConfiguration.setKeystorePassword(pac4jConfigurationProperties.getKeystorePassword()); - saml2ClientConfiguration.setPrivateKeyPassword(pac4jConfigurationProperties.getPrivateKeyPassword()); - saml2ClientConfiguration.setIdentityProviderMetadataPath(pac4jConfigurationProperties.getIdentityProviderMetadataPath()); - saml2ClientConfiguration.setMaximumAuthenticationLifetime(pac4jConfigurationProperties.getMaximumAuthenticationLifetime()); - saml2ClientConfiguration.setServiceProviderEntityId(pac4jConfigurationProperties.getServiceProviderEntityId()); - 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"); - saml2Client.addAuthorizationGenerator(saml2ModelAuthorizationGenerator); - - SAML2Authenticator saml2Authenticator = new SAML2Authenticator(saml2ClientConfiguration.getAttributeAsId(), saml2ClientConfiguration.getMappedAttributes()); - saml2Authenticator.setProfileDefinition(new CommonProfileDefinition<>(p -> new BetterSAML2Profile(pac4jConfigurationProperties.getSaml2ProfileMapping().getUsername()))); - saml2Client.setAuthenticator(saml2Authenticator); - - final Clients clients = new Clients(pac4jConfigurationProperties.getCallbackUrl(), saml2Client); - - final Config config = new Config(clients); + // Configure the client + switch (pac4jConfigProps.getTypeOfAuth()) { + case "SAML2": { + log.info("**** Configuring PAC4J SAML2"); + final SAML2Configuration saml2Config = new SAML2Configuration(); + saml2Config.setKeystorePath(pac4jConfigProps.getKeystorePath()); + saml2Config.setKeystorePassword(pac4jConfigProps.getKeystorePassword()); + saml2Config.setPrivateKeyPassword(pac4jConfigProps.getPrivateKeyPassword()); + saml2Config.setIdentityProviderMetadataPath(pac4jConfigProps.getIdentityProviderMetadataPath()); + saml2Config.setMaximumAuthenticationLifetime(pac4jConfigProps.getMaximumAuthenticationLifetime()); + saml2Config.setServiceProviderEntityId(pac4jConfigProps.getServiceProviderEntityId()); + saml2Config.setServiceProviderMetadataPath(pac4jConfigProps.getServiceProviderMetadataPath()); + saml2Config.setForceServiceProviderMetadataGeneration(pac4jConfigProps.isForceServiceProviderMetadataGeneration()); + saml2Config.setWantsAssertionsSigned(pac4jConfigProps.isWantAssertionsSigned()); + saml2Config.setAttributeAsId(pac4jConfigProps.getSimpleProfileMapping().getUsername()); + //saml2Config.setPostLogoutURL(pac4jConfigProps.getPostLogoutURL()); // consideration needed? + //saml2Config.setSpLogoutRequestBindingType(pac4jConfigProps.getSpLogoutRequestBindingType()); + + final SAML2Client saml2Client = new SAML2Client(saml2Config); + saml2Client.setName("Saml2Client"); + saml2Client.addAuthorizationGenerator(saml2ModelAuthorizationGenerator); + SAML2Authenticator saml2Authenticator = new SAML2Authenticator(saml2Config.getAttributeAsId(), saml2Config.getMappedAttributes()); + saml2Authenticator.setProfileDefinition(new CommonProfileDefinition(p -> new BetterSAML2Profile(pac4jConfigProps.getSimpleProfileMapping()))); + saml2Client.setAuthenticator(saml2Authenticator); + + saml2Client.setName(PAC4J_CLIENT_NAME); + clients.setClients(saml2Client); + } + case "HEADER": { + log.info("**** Configuring PAC4J Header Client"); + HeaderClient headerClient = new HeaderClient(pac4jConfigProps.getAuthenticationHeader(), + new Authenticator() { + @Override + public void validate(Credentials credentials, WebContext context, SessionStore sessionStore) { + if (credentials instanceof TokenCredentials) { + TokenCredentials creds = (TokenCredentials) credentials; + String token = creds.getToken(); + if (StringUtils.isAllBlank(token)) { + throw new CredentialsException("Supplied token value in header was missing or blank"); + } + } else { + throw new CredentialsException("Invalid Credentials object generated by HeaderClient"); + } + final CommonProfile profile = new CommonProfile(); + String token = ((TokenCredentials)credentials).getToken(); + profile.setId(token); + profile.addAttribute("username", token); + profile.setRoles(userService.getUserRoles(token)); + credentials.setUserProfile(profile); + } + }); + headerClient.setName(PAC4J_CLIENT_NAME); + clients.setClients(headerClient); + } + } + config.setClients(clients); return config; } + + @Bean + public ErrorPageRegistrar errorPageRegistrar() { + return this::registerErrorPages; + } + + private void registerErrorPages(ErrorPageRegistry registry) { + registry.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unsecured/error.html")); + registry.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN, "/unsecured/error.html")); + } } 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 47defac65..977164958 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,151 +1,47 @@ package net.unicon.shibui.pac4j; +import javax.servlet.Filter; + +import org.pac4j.springframework.security.web.CallbackFilter; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; +import lombok.Getter; +import lombok.Setter; + @Component @ConfigurationProperties(prefix = "shibui.pac4j") @EnableConfigurationProperties @ConditionalOnProperty(name = "shibui.pac4j-enabled", havingValue = "true") +@Getter +@Setter public class Pac4jConfigurationProperties { - private String keystorePath = "/tmp/samlKeystore.jks"; - private String keystorePassword = "changeit"; - private String privateKeyPassword = "changeit"; + + final static String DEFAULT_AUTH_HEADER = "REMOTE_USER"; + private String authenticationHeader = DEFAULT_AUTH_HEADER; + private String callbackUrl; + private boolean forceServiceProviderMetadataGeneration = false; private String identityProviderMetadataPath = "/tmp/idp-metadata.xml"; + private String keystorePassword = "changeit"; + private String keystorePath = "/tmp/samlKeystore.jks"; private int maximumAuthenticationLifetime = 3600; + private String privateKeyPassword = "changeit"; + private SimpleProfileMapping simpleProfileMapping; private String serviceProviderEntityId = "https://unicon.net/shibui"; private String serviceProviderMetadataPath = "/tmp/sp-metadata.xml"; - private boolean forceServiceProviderMetadataGeneration = false; - private String callbackUrl; - private boolean wantAssertionsSigned = true; - private SAML2ProfileMapping saml2ProfileMapping; + private String typeOfAuth = "SAML2"; - public static class SAML2ProfileMapping { - private String username; + private boolean wantAssertionsSigned = true; + + @Getter + @Setter + public static class SimpleProfileMapping { 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; - } - - public void setKeystorePath(String keystorePath) { - this.keystorePath = keystorePath; - } - - public String getKeystorePassword() { - return keystorePassword; - } - - public void setKeystorePassword(String keystorePassword) { - this.keystorePassword = keystorePassword; - } - - public String getPrivateKeyPassword() { - return privateKeyPassword; - } - - public void setPrivateKeyPassword(String privateKeyPassword) { - this.privateKeyPassword = privateKeyPassword; - } - - public String getIdentityProviderMetadataPath() { - return identityProviderMetadataPath; - } - - public void setIdentityProviderMetadataPath(String identityProviderMetadataPath) { - this.identityProviderMetadataPath = identityProviderMetadataPath; - } - - public int getMaximumAuthenticationLifetime() { - return maximumAuthenticationLifetime; - } - - public void setMaximumAuthenticationLifetime(int maximumAuthenticationLifetime) { - this.maximumAuthenticationLifetime = maximumAuthenticationLifetime; - } - - public String getServiceProviderEntityId() { - return serviceProviderEntityId; - } - - public void setServiceProviderEntityId(String serviceProviderEntityId) { - this.serviceProviderEntityId = serviceProviderEntityId; - } - - public String getServiceProviderMetadataPath() { - return serviceProviderMetadataPath; - } - - public void setServiceProviderMetadataPath(String serviceProviderMetadataPath) { - this.serviceProviderMetadataPath = serviceProviderMetadataPath; - } - - public boolean isForceServiceProviderMetadataGeneration() { - return forceServiceProviderMetadataGeneration; - } - - public void setForceServiceProviderMetadataGeneration(boolean forceServiceProviderMetadataGeneration) { - this.forceServiceProviderMetadataGeneration = forceServiceProviderMetadataGeneration; - } - - public String getCallbackUrl() { - return callbackUrl; - } - - public void setCallbackUrl(String callbackUrl) { - this.callbackUrl = callbackUrl; - } - - public boolean isWantAssertionsSigned() { - return wantAssertionsSigned; - } - - public void setWantAssertionsSigned(boolean wantAssertionsSigned) { - this.wantAssertionsSigned = wantAssertionsSigned; - } - - public SAML2ProfileMapping getSaml2ProfileMapping() { - return saml2ProfileMapping; - } - - public void setSaml2ProfileMapping(SAML2ProfileMapping saml2ProfileMapping) { - this.saml2ProfileMapping = saml2ProfileMapping; - } + private String username; + } } + 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 907cc6963..b16af86ba 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 @@ -4,9 +4,14 @@ 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.StringUtils; import org.pac4j.core.config.Config; +import org.pac4j.core.matching.matcher.Matcher; +import org.pac4j.core.matching.matcher.PathMatcher; import org.pac4j.springframework.security.web.CallbackFilter; import org.pac4j.springframework.security.web.SecurityFilter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -19,49 +24,23 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.firewall.StrictHttpFirewall; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import javax.swing.text.html.Option; import java.util.Optional; +import javax.servlet.Filter; + @Configuration @AutoConfigureOrder(-1) @ConditionalOnProperty(name = "shibui.pac4j-enabled", havingValue = "true") @AutoConfigureAfter(EmailConfiguration.class) - -public class WebSecurity { +public class WebSecurity { @Bean("webSecurityConfig") - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, Optional emailService, Pac4jConfigurationProperties pac4jConfigurationProperties) { - return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, pac4jConfigurationProperties); - } - - @Configuration - @Order(0) - @ConditionalOnProperty(name = "shibui.pac4j-enabled", havingValue = "true") - public static class FaviconSecurityConfiguration extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http.antMatcher("/favicon.ico").authorizeRequests().antMatchers("/favicon.ico").permitAll(); - } - } - - @Configuration - @Order(1) - @ConditionalOnProperty(name = "shibui.pac4j-enabled", havingValue = "true") - public static class UnsecuredSecurityConfiguration extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http.antMatcher("/unsecured/**/*").authorizeRequests().antMatchers("/unsecured/**/*").permitAll(); - } - } - - @Configuration - @Order(2) - @ConditionalOnProperty(name = "shibui.pac4j-enabled", havingValue = "true") - public static class ErrorSecurityConfiguration extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http.antMatcher("/error").authorizeRequests().antMatchers("/error").permitAll(); - } + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, + RoleRepository roleRepository, Optional emailService, + Pac4jConfigurationProperties pac4jConfigurationProperties) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, + pac4jConfigurationProperties); } @Order(100) @@ -72,7 +51,8 @@ public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigu private Optional emailService; private Pac4jConfigurationProperties pac4jConfigurationProperties; - public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, Optional emailService, Pac4jConfigurationProperties pac4jConfigurationProperties) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, + Optional emailService, Pac4jConfigurationProperties pac4jConfigurationProperties) { this.config = config; this.userRepository = userRepository; this.roleRepository = roleRepository; @@ -82,28 +62,47 @@ public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository use @Override protected void configure(HttpSecurity http) throws Exception { - final SecurityFilter securityFilter = new SecurityFilter(this.config, "Saml2Client"); - - final CallbackFilter callbackFilter = new CallbackFilter(this.config); - http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) - .addFilterBefore(securityFilter, BasicAuthenticationFilter.class) - .addFilterAfter(new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, emailService), SecurityFilter.class); - - http.authorizeRequests().anyRequest().fullyAuthenticated(); - + http.authorizeRequests().antMatchers("/unsecured/**/*").permitAll(); + + // add filter based on auth type + http.antMatcher("/**").addFilterBefore(getFilter(config, pac4jConfigurationProperties.getTypeOfAuth()), BasicAuthenticationFilter.class); + + // add the new user filter + http.addFilterAfter(new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, getPathMatcher("exclude-paths-matcher") , emailService), SecurityFilter.class); + + http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> response.sendRedirect("/unsecured/error.html")); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); - http.csrf().disable(); http.headers().frameOptions().disable(); } + private Matcher getPathMatcher(String name) { + return config.getMatchers().get(name); + } + + private Filter getFilter(Config config2, String typeOfAuth) { + switch (typeOfAuth) { + case "SAML2": + return new CallbackFilter(this.config); + case "HEADER": + final SecurityFilter securityFilterForHeader = new SecurityFilter(this.config, Pac4jConfiguration.PAC4J_CLIENT_NAME); + securityFilterForHeader.setMatchers("exclude-paths-matcher"); + return securityFilterForHeader; + } + return null; // This will cause a runtime error + } + @Override public void configure(org.springframework.security.config.annotation.web.builders.WebSecurity web) throws Exception { super.configure(web); StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); + firewall.setAllowUrlEncodedDoubleSlash(true); web.httpFirewall(firewall); + + // These don't need to be secured + web.ignoring().antMatchers("/favicon.ico", "/unsecured/**/*", "/assets/**/*.png", "/static/**/*", "/**/*.css"); } } diff --git a/pac4j-module/src/main/resources/application.yml b/pac4j-module/src/main/resources/application.yml index 4042fd939..ebba4fcd9 100644 --- a/pac4j-module/src/main/resources/application.yml +++ b/pac4j-module/src/main/resources/application.yml @@ -1,6 +1,6 @@ shibui: pac4j: - saml2ProfileMapping: + simpleProfileMapping: username: urn:oid:0.9.2342.19200300.100.1.3 firstName: givenName lastName: sn diff --git a/pac4j-module/src/test/docker/conf-header/application.yml b/pac4j-module/src/test/docker/conf-header/application.yml new file mode 100644 index 000000000..0534c57f8 --- /dev/null +++ b/pac4j-module/src/test/docker/conf-header/application.yml @@ -0,0 +1,15 @@ +shibui: + user-bootstrap-resource: file:/conf/users.csv + roles: ROLE_ADMIN,ROLE_NONE,ROLE_USER,ROLE_PONY + pac4j-enabled: true + pac4j: + type-of-auth: HEADER + authentication-header: REMOTE_USER + +logging: + level: + org.pac4j: "TRACE" + org.opensaml: "INFO" +server.tomcat: + remote-ip-header: x-forwarded-for + protocol-header: x-forwarded-proto diff --git a/pac4j-module/src/test/docker/conf-header/users.csv b/pac4j-module/src/test/docker/conf-header/users.csv new file mode 100644 index 000000000..fcd838992 --- /dev/null +++ b/pac4j-module/src/test/docker/conf-header/users.csv @@ -0,0 +1 @@ +admin,{noop}password,test,test,ROLE_ADMIN,test@example.com diff --git a/pac4j-module/src/test/docker/conf/application.yml b/pac4j-module/src/test/docker/conf/application.yml index 90ddce36a..b49782897 100644 --- a/pac4j-module/src/test/docker/conf/application.yml +++ b/pac4j-module/src/test/docker/conf/application.yml @@ -31,7 +31,7 @@ shibui: forceServiceProviderMetadataGeneration: true callbackUrl: "https://localhost:8443/callback" maximumAuthenticationLifetime: 3600000 - saml2ProfileMapping: + simpleProfileMapping: username: urn:oid:0.9.2342.19200300.100.1.1 firstName: urn:oid:2.5.4.42 lastName: urn:oid:2.5.4.4 diff --git a/pac4j-module/src/test/docker/docker-compose-header.yml b/pac4j-module/src/test/docker/docker-compose-header.yml new file mode 100644 index 000000000..0b978fcda --- /dev/null +++ b/pac4j-module/src/test/docker/docker-compose-header.yml @@ -0,0 +1,44 @@ +version: "3.7" + +services: + reverse-proxy: + image: library/traefik:v2.2 + command: + - "--api.insecure=true" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web-secure.address=:443" + - "--providers.file.directory=/configuration/" + - "--providers.file.watch=true" + # - "--log.level=DEBUG" + networks: + - reverse-proxy + ports: + - "80:80" + - "8080:8080" + - "443:443" + - "8443:8443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./reverse-proxy/:/configuration/ + - ./reverse-proxy/certs/:/certs/ + shibui: + image: unicon/shibui + entrypoint: ["/usr/bin/java", "-Dspring.profiles.active=dev", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.war"] + labels: + - "traefik.http.routers.shibui.rule=Host(`shibui.unicon.local`)" + - "traefik.http.services.shibui.loadbalancer.server.port=8080" + - "traefik.http.routers.shibui.tls=true" + - "traefik.docker.network=docker_reverse-proxy" + - "traefik.enable=true" + - "traefik.http.middlewares.header.headers.customRequestHeaders.REMOTE_USER=admin" + - "traefik.http.routers.shibui.middlewares=header" + ports: + - 5005:5005 + networks: + - reverse-proxy + volumes: + - ./conf-header:/conf + - ./conf-header/application.yml:/application.yml +networks: + reverse-proxy: diff --git a/pac4j-module/src/test/docker/reverse-proxy/certs/star.unicon.local.crt b/pac4j-module/src/test/docker/reverse-proxy/certs/star.unicon.local.crt new file mode 100644 index 000000000..8d4592789 --- /dev/null +++ b/pac4j-module/src/test/docker/reverse-proxy/certs/star.unicon.local.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCDCCAfCgAwIBAgIJANdpvkovSXs6MA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNV +BAMMDioudW5pY29uLmxvY2FsMB4XDTIwMDUyNzE3MzE1OVoXDTMwMDUyNzE3MzE1 +OVowGTEXMBUGA1UEAwwOKi51bmljb24ubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCh9zduMpOqWDPfx5vHSBoWDwk44cc8XdFCD7nqi9EPHewO +jAKCVihWKGa3OX6kQ4g6VIE7PCqdvtjq6eJ54FKuJ7FiFQweuaUXlXx5tdiWYfVe ++BV7jaLJGy0iniPgx8Pu9ajQOOINcmLwixYkTe+OBfeMgeivk0+o58/Lmkl2FyAv +IvGA2Glxf8QtxQwHgtQLU3aHRlgi6YHSIxolvX+CuDvj4xsgLBdCphYkJtUXmaKO +frQRxnHVxUUptWptqbwJPxrOMnIIdteRnduo+/i0RZ33+tC5W61Cr2uyoDGUzmKE +HKgfTMxLeqJLtm+sB74xmI3Fsnq3qfWajZLqUyz5AgMBAAGjUzBRMB0GA1UdDgQW +BBROeSN15J2wAW9OMZAdPIz84DEOsjAfBgNVHSMEGDAWgBROeSN15J2wAW9OMZAd +PIz84DEOsjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQASo38W +sqw8vijAs3DSYUCjFjlAvlwjsXU0y4IpelmgYSWD0Lqfb9/5jEu06F8hzLMSzmAL +4UKIX6TwtLQb3HLvYObbjhaSSYG0Tl702dANW499QlHF/gYsCb9C6oA+5jzfHd7Y +LS8bUv6gZkC3rP1E0kCGLJGrtvdMniAPuZ8W25SFedpR0iR7+d+lg9oOBsgjkWGc +eKNtSJvic5dyZQCDlEzXVa3lyBOrawW9J/Sqhm0v3Tar1CVKoeXU3QV/bx7s8TWH +YvaxQ82dOsGwZDMmPHhnJQDIQqjt2H9RJjrJy440RwyLiDr1A3/AJxrqpz98QONG +FOhuJQXDH3YGDP7B +-----END CERTIFICATE----- diff --git a/pac4j-module/src/test/docker/reverse-proxy/certs/star.unicon.local.key b/pac4j-module/src/test/docker/reverse-proxy/certs/star.unicon.local.key new file mode 100644 index 000000000..204200812 --- /dev/null +++ b/pac4j-module/src/test/docker/reverse-proxy/certs/star.unicon.local.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh9zduMpOqWDPf +x5vHSBoWDwk44cc8XdFCD7nqi9EPHewOjAKCVihWKGa3OX6kQ4g6VIE7PCqdvtjq +6eJ54FKuJ7FiFQweuaUXlXx5tdiWYfVe+BV7jaLJGy0iniPgx8Pu9ajQOOINcmLw +ixYkTe+OBfeMgeivk0+o58/Lmkl2FyAvIvGA2Glxf8QtxQwHgtQLU3aHRlgi6YHS +IxolvX+CuDvj4xsgLBdCphYkJtUXmaKOfrQRxnHVxUUptWptqbwJPxrOMnIIdteR +nduo+/i0RZ33+tC5W61Cr2uyoDGUzmKEHKgfTMxLeqJLtm+sB74xmI3Fsnq3qfWa +jZLqUyz5AgMBAAECggEAD5EuT7YmwTmnCrwpudaxQyuAzGnO93tg8IOIcAWuO1C5 +7pAAcbyMNfO+I6AwDuO6xTh4D1RyGCVOvg1qyiIIcFq6Tt4NAyr+tiyVATG8NoAF +0HHxxqVPXxrwlKI0epdYuq/74L+G4pn67nm/0A95leH28BdQYQe8oVRegYg7xVC+ +hFNQATLro1NXPfOtujn47/HjIHBBd2Or6dgj/Jh0niFg6Ts3LCgoxZVJeOVTqFht +26tpyMygeE2UpDIDlJjpO+YWTBIMV2i5MuoDAjQDlunaWyrz+Uo4LHaqGrBKkqri +3qhcWI0mGDIw4JPupNStpKXsOBdDCobi80taaKIIgQKBgQDVgVLOAKJu2xMH3/k/ +Fkj1GOugJ+cFvNiXZbwsxc3T5v5InoBeit4MoHSbATFt5EM4oxRD0LOAnkkdJAS3 +2k3nwQkBxR+AWc/9Tugxtyg84oaMrQ3kgwVMWEyFfDjtnAXzJ/vhUvkLaQXKRuW4 +Bpmd0BfZTVmnD6o7KAjCVoBVsQKBgQDCM9CIG6rJ7gxkd0tOOfIXcReG8jGve1tG +8WDg6JhdL/ITBx4y6m9ez/P9BNn9BJixCbkkcwvRro/pCYZG7rNWQsr/9AxiUk/a +91oaGyiPhe6ly5/HlaIyUBPejr3clPL29UFe7CEWiuc/DdhuIrblWziSaX5K8n9d +C85ECkZ1yQKBgDPgfHI5jT5KVNFxBmrhy1Bigb4kc+nc/POscJCgb2axlc+nU6Gl +NFb1FeAj5hLBh+PXHDFuIG98Bl/iRJM3o/5P1RRtBT52lCcEaT7LNie1EuRTmDCR +9VObkNxOVrbFVlzCtl0FgCXZmuKPX3nbgK/rxB+0v7fsAnzlOVufEQfBAoGAUSkQ +ZGGTrdJzkWUdrrKez1cvKvF6/EiEbBCimTv7uS45b+RQpZy+MJSd9kcZxxktqwdS +Pb5q2RWoBQ8689f8UPeXGQLoTgryXqNzH2fsOpcRlm6T9vg9EePPefqTtGT3aWg0 +CrSKW35viDWEFvshDpdh/CZkZnQT8FHnwR2+NqkCgYEAh9ZygpT21hZGMPUjk8j8 +Qo8+K33p08+Sf4LsSQMy9uq5+rcZ3/0S/bZeSHSQgVxba1S61WvE3MGz779f3A8U +GKUnCR9HfZpbw2ziVzKsJUqfq9q1bsy3LSJUNfJfEJWshSh8DNCyDv/y4HON53Il +yOQugjsEbN6Be3D0FBCFYlc= +-----END PRIVATE KEY----- diff --git a/pac4j-module/src/test/docker/reverse-proxy/configuration/certificates.yml b/pac4j-module/src/test/docker/reverse-proxy/configuration/certificates.yml new file mode 100644 index 000000000..88abe448d --- /dev/null +++ b/pac4j-module/src/test/docker/reverse-proxy/configuration/certificates.yml @@ -0,0 +1,9 @@ +tls: + certificates: + - certFile: /certs/star.unicon.local.crt + keyFile: /certs/star.unicon.local.key + stores: + default: + defaultCertificate: + certFile: /certs/star.unicon.local.crt + keyFile: /certs/star.unicon.local.key \ 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 6c44f5699..d15b70f0a 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,6 +5,9 @@ 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.core.matching.matcher.PathMatcher +import org.pac4j.core.profile.CommonProfile import org.pac4j.saml.profile.SAML2Profile import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.context.properties.EnableConfigurationProperties @@ -17,6 +20,7 @@ import spock.lang.Subject import javax.servlet.FilterChain import javax.servlet.ServletRequest +import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse /** @@ -30,7 +34,7 @@ class AddNewUserFilterTests extends Specification { RoleRepository roleRepository = Mock() EmailService emailService = Mock() - ServletRequest request = Mock() + HttpServletRequest request = Mock() HttpServletResponse response = Mock() FilterChain chain = Mock() @@ -41,7 +45,7 @@ class AddNewUserFilterTests extends Specification { @Autowired Pac4jConfigurationProperties pac4jConfigurationProperties - Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping + Pac4jConfigurationProperties.SimpleProfileMapping profileMapping @Subject AddNewUserFilter addNewUserFilter @@ -51,8 +55,8 @@ class AddNewUserFilterTests extends Specification { securityContext.getAuthentication() >> authentication authentication.getPrincipal() >> saml2Profile - addNewUserFilter = new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, Optional.of(emailService)) - saml2ProfileMapping = pac4jConfigurationProperties.saml2ProfileMapping + addNewUserFilter = new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, new PathMatcher(), Optional.of(emailService)) + profileMapping = pac4jConfigurationProperties.simpleProfileMapping } def "new users are redirected"() { @@ -61,8 +65,9 @@ class AddNewUserFilterTests extends Specification { 'FirstName': 'New', 'LastName': 'User', 'Email': 'newuser@institution.edu'].each { key, value -> - saml2Profile.getAttribute(saml2ProfileMapping."get${key}"()) >> [value] + saml2Profile.getAttribute(profileMapping."get${key}"()) >> [value] } + saml2Profile.getUsername() >> "newUser" userRepository.findByUsername('newUser') >> Optional.empty() roleRepository.findByName('ROLE_NONE') >> Optional.of(new Role('ROLE_NONE')) @@ -70,7 +75,7 @@ class AddNewUserFilterTests extends Specification { addNewUserFilter.doFilter(request, response, chain) then: - 1 * roleRepository.save(_) + 0 * roleRepository.save(_) 1 * userRepository.save(_ as User) >> { User user -> user } 1 * emailService.sendNewUserMail('newUser') 1 * response.sendRedirect("/unsecured/error.html") @@ -78,7 +83,7 @@ class AddNewUserFilterTests extends Specification { def "existing users are not redirected"() { given: - saml2Profile.getAttribute(saml2ProfileMapping.getUsername()) >> ['existingUser'] + saml2Profile.getUsername() >> "existingUser" userRepository.findByUsername('existingUser') >> Optional.of(new User().with { it.username = 'existingUser' it.roles = [new Role('ROLE_USER')] diff --git a/ui/public/unsecured/error.html b/ui/public/unsecured/error.html index 5535dc452..4d200efa8 100644 --- a/ui/public/unsecured/error.html +++ b/ui/public/unsecured/error.html @@ -6,7 +6,7 @@ - +