Skip to content

Commit

Permalink
Merged in feature/shibui-1774 (pull request #497)
Browse files Browse the repository at this point in the history
Feature/shibui 1774

Approved-by: Jonathan Johnson
Approved-by: Bill Smith
  • Loading branch information
chasegawa authored and Jonathan Johnson committed Jul 27, 2021
2 parents 42792ca + 9813176 commit 987f423
Show file tree
Hide file tree
Showing 19 changed files with 440 additions and 311 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,13 @@ public User getCurrentUser() {
}
return user;
}

public Set<String> getUserRoles(String username) {
Optional<User> user = userRepository.findByUsername(username);
HashSet<String> result = new HashSet<>();
if (user.isPresent() ) {
user.get().getRoles().forEach(role -> result.add(role.getName()));
}
return result;
}
}
12 changes: 8 additions & 4 deletions pac4j-module/build.gradle
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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> 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> emailService) {
public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserRepository userRepository, RoleRepository roleRepository, Matcher matcher, Optional<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 {
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<Role> 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<User> 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<User> 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<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();
public void init(FilterConfig filterConfig) throws ServletException {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

}
Original file line number Diff line number Diff line change
@@ -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<SAML2Profile> {
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<UserProfile> generate(WebContext context, SessionStore sessionStore, UserProfile profile) {
Optional<User> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<String> {

private static final String ANONYMOUS = "anonymousUser";
Expand Down
Loading

0 comments on commit 987f423

Please sign in to comment.