Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into feature/shibui-1740
Browse files Browse the repository at this point in the history
  • Loading branch information
chasegawa committed Jul 28, 2021
2 parents 8f5dc7a + 987f423 commit 994c04e
Show file tree
Hide file tree
Showing 19 changed files with 444 additions and 316 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ public UserService(RoleRepository roleRepository, UserRepository userRepository)
@Override
@Transactional
public void afterPropertiesSet() {
// SHIBUI-1740 migration task
userRepository.findAll().forEach(user -> {
if (user.getGroupId() == null) {
save(user); // this will ensure group is set as the default user group for those users without a group set
save(user); // this will ensure group is set as the default user group
}
});
}
Expand Down Expand Up @@ -111,11 +110,20 @@ public Group getCurrentUserGroup() {
}
}

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;
}

public boolean isAuthorizedFor(Group objectGroup) {
String objectGroupId = objectGroup == null ? Group.ADMIN_GROUP.getResourceId() : objectGroup.getResourceId();
return isAuthorizedFor(objectGroupId);
}

public boolean isAuthorizedFor(String objectGroupResourceId) {
switch (getCurrentUserAccess()) { // no user returns NONE
case ADMIN:
Expand All @@ -129,7 +137,7 @@ public boolean isAuthorizedFor(String objectGroupResourceId) {
return false;
}
}

/**
* Creating users should always have a group. If the user isn't assigned to a group, create one based on their name.
* If the user has the ADMIN role, they are always solely assigned to the admin group.
Expand Down Expand Up @@ -179,7 +187,7 @@ public User save(User user) {

return userRepository.save(user);
}

/**
* Given a user with a defined User.role, update the User.roles collection with that role.
*
Expand Down
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 994c04e

Please sign in to comment.