From 30e7b0a5db66f2585def7391e687883a7ae377e3 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 13 Sep 2021 14:15:39 -0700 Subject: [PATCH] SHIBUI-2063 intermediate commit --- .../admin/ui/security/model/User.java | 11 ++++-- .../ui/security/service/IRolesService.java | 13 +++++-- .../ui/security/service/RolesServiceImpl.java | 38 ++++++++++++++++++- backend/src/main/resources/application.yml | 2 + .../unicon/shibui/pac4j/AddNewUserFilter.java | 28 +++++++------- .../shibui/pac4j/BetterSAML2Profile.java | 7 +++- .../shibui/pac4j/Pac4jConfiguration.java | 2 - .../pac4j/Pac4jConfigurationProperties.java | 2 + .../net/unicon/shibui/pac4j/WebSecurity.java | 15 ++++---- .../src/main/resources/application.yml | 27 ++++++++++++- 10 files changed, 110 insertions(+), 35 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java index cb769c662..76eb44899 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java @@ -113,12 +113,15 @@ public String getOwnerId() { public OwnerType getOwnerType() { return OwnerType.USER; } - + + /** + * @return the FIRST role found for the user or an exception if the user has no roles + */ public String getRole() { if (StringUtils.isBlank(this.role)) { Set roles = this.getRoles(); - if (roles.size() != 1) { - throw new RuntimeException(String.format("User with username [%s] has no role or does not have exactly one role!", this.getUsername())); + if (roles.isEmpty()) { + throw new RuntimeException(String.format("User with username [%s] has no roles", this.getUsername())); } this.role = roles.iterator().next().getName(); } @@ -145,4 +148,4 @@ public void setGroups(Set groups) { public void registerLoader(ILazyLoaderHelper lazyLoaderHelper) { this.lazyLoaderHelper = lazyLoaderHelper; } -} +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IRolesService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IRolesService.java index 26653d241..ac30d986a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IRolesService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IRolesService.java @@ -1,6 +1,8 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service; import java.util.List; +import java.util.Optional; +import java.util.Set; import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.security.exception.RoleDeleteException; @@ -11,12 +13,17 @@ public interface IRolesService { Role createRole(Role role) throws RoleExistsConflictException; - Role updateRole(Role role) throws EntityNotFoundException; - List findAll(); + Optional findByName(String roleNone); + Role findByResourceId(String resourceId) throws EntityNotFoundException; + Set getAndCreateAllRoles(Set roles); + void deleteDefinition(String resourceId) throws EntityNotFoundException, RoleDeleteException; -} + Role updateRole(Role role) throws EntityNotFoundException; + + void save(Role newUserRole); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/RolesServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/RolesServiceImpl.java index cfbe57fb0..373843781 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/RolesServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/RolesServiceImpl.java @@ -1,7 +1,9 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -42,6 +44,11 @@ public List findAll() { return roleRepository.findAll(); } + @Override + public Optional findByName(String roleName) { + return roleRepository.findByName(roleName); + } + @Override public Role findByResourceId(String resourceId) throws EntityNotFoundException { Optional found = roleRepository.findByResourceId(resourceId); @@ -51,6 +58,30 @@ public Role findByResourceId(String resourceId) throws EntityNotFoundException { return found.get(); } + @Override + public Set getAndCreateAllRoles(Set roleNames) { + HashSet result = new HashSet<>(); + if (roleNames.isEmpty()) { + Role r = getRoleNone(); + result.add(r); + return result; + } + roleNames.forEach(roleName -> { + Optional role = roleRepository.findByName(roleName); + result.add(role.orElseGet(() -> roleRepository.save(new Role(roleName)))); + }); + return result; + } + + private Role getRoleNone() { + Optional noRole = roleRepository.findByName("ROLE_NONE"); + if (noRole.isEmpty()) { + Role newUserRole = new Role("ROLE_NONE"); + return roleRepository.save(newUserRole); + } + return noRole.get(); + } + @Override public Role updateRole(Role role) throws EntityNotFoundException { Optional found = roleRepository.findByName(role.getName()); @@ -59,4 +90,9 @@ public Role updateRole(Role role) throws EntityNotFoundException { } return roleRepository.save(role); } -} + + @Override + public void save(Role newUserRole) { + roleRepository.save(newUserRole); + } +} \ No newline at end of file diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 74ae43689..4035d8549 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -14,11 +14,13 @@ # forceServiceProviderMetadataGeneration: false # callbackUrl: "https://localhost:8443/callback" # maximumAuthenticationLifetime: 3600000 +# requireAssertedRoleForNewUsers: false # saml2ProfileMapping: # username: urn:oid:0.9.2342.19200300.100.1.1 # firstname: urn:oid:2.5.4.42 # lastname: urn:oid:2.5.4.4 # email: urn:oid:0.9.2342.19200300.100.1.3 +# groups: urn:oid:1.3.6.1.4.1.5923.1.5.1.1 # attributeId - isMemberOf custom: attributes: 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 548380712..eb6f9c7ba 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 @@ -4,8 +4,8 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.model.Role; 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.service.IGroupService; +import edu.internet2.tier.shibboleth.admin.ui.security.service.IRolesService; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import edu.internet2.tier.shibboleth.admin.ui.service.EmailService; import lombok.extern.slf4j.Slf4j; @@ -17,6 +17,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.transaction.annotation.Transactional; import javax.mail.MessagingException; import javax.servlet.Filter; @@ -27,7 +28,6 @@ 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.ArrayList; import java.util.HashSet; @@ -43,13 +43,13 @@ public class AddNewUserFilter implements Filter { private IGroupService groupService; private Matcher matcher; private Pac4jConfigurationProperties pac4jConfigurationProperties; - private RoleRepository roleRepository; + private IRolesService rolesService; private Pac4jConfigurationProperties.SimpleProfileMapping simpleProfileMapping; private UserService userService; - public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserService userService, RoleRepository roleRepository, Matcher matcher, IGroupService groupService, Optional emailService) { + public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserService userService, IRolesService rolesService, Matcher matcher, IGroupService groupService, Optional emailService) { this.userService = userService; - this.roleRepository = roleRepository; + this.rolesService = rolesService; this.emailService = emailService; this.pac4jConfigurationProperties = pac4jConfigurationProperties; this.matcher = matcher; @@ -59,16 +59,10 @@ public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationPropertie @Transactional User buildAndPersistNewUserFromProfile(CommonProfile profile) { - Optional noRole = roleRepository.findByName(ROLE_NONE); - Role newUserRole; - if (noRole.isEmpty()) { - newUserRole = new Role(ROLE_NONE); - roleRepository.save(newUserRole); - } - newUserRole = noRole.get(); + Set userRoles = rolesService.getAndCreateAllRoles(profile.getRoles()); User user = new User(); - user.getRoles().add(newUserRole); + user.setRoles(userRoles); user.setUsername(profile.getUsername()); user.setPassword(BCrypt.hashpw(RandomStringUtils.randomAlphanumeric(20), BCrypt.gensalt())); user.setFirstName(profile.getFirstName()); @@ -90,11 +84,15 @@ User buildAndPersistNewUserFromProfile(CommonProfile profile) { } } - User persistedUser = userService.save(user); + // Don't save the user if the role required flag is on and the user has the default none role + if (pac4jConfigurationProperties.isRequireAssertedRoleForNewUsers() && user.getRole().equals(ROLE_NONE)) { + return user; + } + user = userService.save(user); if (log.isDebugEnabled()) { log.debug("Persisted new user:\n" + user); } - return persistedUser; + return user; } @Override 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 f948304cf..bbe33694c 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 @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.List; +import java.util.Set; public class BetterSAML2Profile extends SAML2Profile { private SimpleProfileMapping profileMapping; @@ -39,6 +40,10 @@ public List getGroups() { return (List) getAttribute(profileMapping.getGroups()); } + public Set getRoles() { + return (Set) getAttribute(profileMapping.getRoles()); + } + @Override public String getUsername() { Object username = getAttribute(profileMapping.getUsername()); @@ -49,4 +54,4 @@ public String getUsername() { } } -} +} \ No newline at end of file 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 cda47108b..37a33b5ae 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 @@ -107,8 +107,6 @@ public void validate(Credentials credentials, WebContext context) { 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.addAuthorizationGenerator(saml2ModelAuthorizationGenerator); 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 41f65d43b..30311ba84 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 @@ -24,6 +24,7 @@ public class Pac4jConfigurationProperties { private String keystorePath = "/tmp/samlKeystore.jks"; private int maximumAuthenticationLifetime = 3600; private String privateKeyPassword = "changeit"; + private boolean requireAssertedRoleForNewUsers = false; private SimpleProfileMapping simpleProfileMapping; private String serviceProviderEntityId = "https://unicon.net/shibui"; private String serviceProviderMetadataPath = "/tmp/sp-metadata.xml"; @@ -37,6 +38,7 @@ public static class SimpleProfileMapping { private String email; private String firstName; private String groups; + private String roles; private String lastName; 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 b9b14a168..17bc554ff 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 @@ -1,13 +1,12 @@ package net.unicon.shibui.pac4j; import edu.internet2.tier.shibboleth.admin.ui.configuration.auto.EmailConfiguration; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; +import edu.internet2.tier.shibboleth.admin.ui.security.service.IRolesService; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import edu.internet2.tier.shibboleth.admin.ui.service.EmailService; import static net.unicon.shibui.pac4j.Pac4jConfiguration.PAC4J_CLIENT_NAME; import org.pac4j.core.config.Config; - import org.pac4j.core.matching.Matcher; import org.pac4j.springframework.security.web.CallbackFilter; import org.pac4j.springframework.security.web.SecurityFilter; @@ -34,9 +33,9 @@ public class WebSecurity { @Bean("webSecurityConfig") public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserService userService, - RoleRepository roleRepository, Optional emailService, + IRolesService rolesService, Optional emailService, Pac4jConfigurationProperties pac4jConfigurationProperties, IGroupService groupService) { - return new Pac4jWebSecurityConfigurerAdapter(config, userService, roleRepository, emailService, groupService, + return new Pac4jWebSecurityConfigurerAdapter(config, userService, rolesService, emailService, groupService, pac4jConfigurationProperties); } @@ -46,14 +45,14 @@ public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigu private Optional emailService; private IGroupService groupService; private Pac4jConfigurationProperties pac4jConfigurationProperties; - private RoleRepository roleRepository; + private IRolesService rolesService; private UserService userService; - public Pac4jWebSecurityConfigurerAdapter(final Config config, UserService userService, RoleRepository roleRepository, + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserService userService, IRolesService rolesService, Optional emailService, IGroupService groupService, Pac4jConfigurationProperties pac4jConfigurationProperties) { this.config = config; this.userService = userService; - this.roleRepository = roleRepository; + this.rolesService = rolesService; this.emailService = emailService; this.groupService = groupService; this.pac4jConfigurationProperties = pac4jConfigurationProperties; @@ -69,7 +68,7 @@ protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/**").addFilterBefore(getFilter(config, pac4jConfigurationProperties.getTypeOfAuth()), BasicAuthenticationFilter.class); http.antMatcher("/**").addFilterBefore(securityFilter, BasicAuthenticationFilter.class); // add the new user filter - http.addFilterAfter(new AddNewUserFilter(pac4jConfigurationProperties, userService, roleRepository, getPathMatcher("exclude-paths-matcher"), groupService, emailService), SecurityFilter.class); + http.addFilterAfter(new AddNewUserFilter(pac4jConfigurationProperties, userService, rolesService, getPathMatcher("exclude-paths-matcher"), groupService, 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 4dd28dc1f..ef2d684d0 100644 --- a/pac4j-module/src/main/resources/application.yml +++ b/pac4j-module/src/main/resources/application.yml @@ -6,4 +6,29 @@ shibui: firstName: givenName lastName: sn email: mail - groups: urn:oid:1.3.6.1.4.1.5923.1.5.1.1 # attributeId - isMemberOf \ No newline at end of file + groups: urn:oid:1.3.6.1.4.1.5923.1.5.1.1 # attributeId - isMemberOf + roles: + +#shibui: +## Default password must be set for the default user to be configured and setup +# default-rootuser:root +## need to include the encoding for the password - be sure to quote the entire value as shown +# default-password: "{noop}foopassword" +# pac4j-enabled: true +# pac4j: +# keystorePath: "/etc/shibui/samlKeystore.jks" +# keystorePassword: "changeit" +# privateKeyPassword: "changeit" +# serviceProviderEntityId: "https://idp.example.com/shibui" +# serviceProviderMetadataPath: "/etc/shibui/sp-metadata.xml" +# identityProviderMetadataPath: "/etc/shibui/idp-metadata.xml" +# forceServiceProviderMetadataGeneration: false +# callbackUrl: "https://localhost:8443/callback" +# maximumAuthenticationLifetime: 3600000 +# requireAssertedRoleForNewUsers: false +# saml2ProfileMapping: +# username: urn:oid:0.9.2342.19200300.100.1.1 +# firstname: urn:oid:2.5.4.42 +# lastname: urn:oid:2.5.4.4 +# email: urn:oid:0.9.2342.19200300.100.1.3 +# groups: urn:oid:1.3.6.1.4.1.5923.1.5.1.1 # attributeId - isMemberOf \ No newline at end of file