diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy new file mode 100644 index 000000000..2106b52a2 --- /dev/null +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy @@ -0,0 +1,35 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration + +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.UserRepository +import org.springframework.context.annotation.Profile +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional + +import javax.annotation.PostConstruct + +@Component +@Profile('dev') +class DevConfig { + private final UserRepository adminUserRepository + + DevConfig(UserRepository adminUserRepository) { + this.adminUserRepository = adminUserRepository + } + + @Transactional + @PostConstruct + void createDevAdminUsers() { + if (adminUserRepository.count() == 0) { + def user = new User().with { + username = 'admin' + password = '{noop}adminpass' + roles.add(new Role(name: 'ROLE_ADMIN')) + it + } + + adminUserRepository.save(user) + } + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java index e78e8503d..1dcdc6ce7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java @@ -1,9 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration.auto; import edu.internet2.tier.shibboleth.admin.ui.security.DefaultAuditorAware; -import edu.internet2.tier.shibboleth.admin.ui.security.model.AdminRole; -import edu.internet2.tier.shibboleth.admin.ui.security.model.AdminUser; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.AdminUserRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; import edu.internet2.tier.shibboleth.admin.ui.security.springsecurity.AdminUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -24,10 +22,6 @@ import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.StrictHttpFirewall; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import javax.annotation.PostConstruct; /** * Web security configuration. @@ -46,7 +40,7 @@ public class WebSecurityConfig { private String defaultPassword; @Autowired - private AdminUserRepository adminUserRepository; + private UserRepository userRepository; @Bean public HttpFirewall allowUrlEncodedSlashHttpFirewall() { @@ -80,11 +74,11 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { if (defaultPassword != null && !"".equals(defaultPassword)) { auth .inMemoryAuthentication() - .withUser("user") + .withUser("root") .password(defaultPassword) - .roles("USER"); + .roles("ADMIN"); } - auth.userDetailsService(adminUserService(adminUserRepository)).passwordEncoder(passwordEncoder); + auth.userDetailsService(adminUserService(userRepository)).passwordEncoder(passwordEncoder); } @Override @@ -103,8 +97,8 @@ public AuditorAware defaultAuditorAware() { @Bean @Profile("!no-auth") - public AdminUserService adminUserService(AdminUserRepository adminUserRepository) { - return new AdminUserService(adminUserRepository); + public AdminUserService adminUserService(UserRepository userRepository) { + return new AdminUserService(userRepository); } @Bean @@ -124,32 +118,5 @@ public void configure(WebSecurity web) throws Exception { } }; } - - @Component - @Profile("dev") - public static class SampleAdminUsersCreator { - - @Autowired - AdminUserRepository adminUserRepository; - - @Transactional - @PostConstruct - public void createSampleAdminUsers() { - if (adminUserRepository.count() == 0L) { - AdminRole role = new AdminRole(); - role.setName("ROLE_ADMIN"); - AdminUser user = new AdminUser(); - user.setUsername("admin"); - user.setPassword("{noop}adminpass"); - - //The complexity of managing bi-directional many-to-many. TODO: encapsulate this association - //managing logic into domain model itself - role.getAdmins().add(user); - user.getRoles().add(role); - - adminUserRepository.save(user); - } - } - } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/AdminRole.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Role.java similarity index 80% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/AdminRole.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Role.java index faa7e3167..20564093d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/AdminRole.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Role.java @@ -24,14 +24,14 @@ @NoArgsConstructor @Getter @Setter -@EqualsAndHashCode(callSuper = true, exclude = "admins") -@ToString(exclude = "admins") -public class AdminRole extends AbstractAuditable { +@EqualsAndHashCode(callSuper = true, exclude = "users") +@ToString(exclude = "users") +public class Role extends AbstractAuditable { @Column(unique = true) private String name; @ManyToMany(cascade = CascadeType.ALL, mappedBy = "roles", fetch = FetchType.EAGER) - private Set admins = new HashSet<>(); + private Set users = new HashSet<>(); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/AdminUser.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java similarity index 78% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/AdminUser.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java index 0294c14bb..9b24cf946 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/AdminUser.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java @@ -7,7 +7,6 @@ import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; @@ -25,7 +24,7 @@ @Setter @EqualsAndHashCode(callSuper = true, exclude = "roles") @ToString(exclude = "roles") -public class AdminUser extends AbstractAuditable { +public class User extends AbstractAuditable { @Column(nullable = false, unique = true) private String username; @@ -40,6 +39,6 @@ public class AdminUser extends AbstractAuditable { @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) @ManyToMany(cascade = CascadeType.ALL) - @JoinTable(name = "adminuser_role", joinColumns = @JoinColumn(name = "admin_user_id"), inverseJoinColumns = @JoinColumn(name = "admin_role_id")) - private Set roles = new HashSet<>(); + @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) + private Set roles = new HashSet<>(); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/AdminRoleRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/AdminRoleRepository.java deleted file mode 100644 index 99672f57d..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/AdminRoleRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.repository; - -import edu.internet2.tier.shibboleth.admin.ui.security.model.AdminRole; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -/** - * Spring Data repository to manage entities of type {@link AdminRole}. - * - * @author Dmitriy Kopylenko - */ -public interface AdminRoleRepository extends JpaRepository { - - Optional findByName(final String name); -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/AdminUserRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/AdminUserRepository.java deleted file mode 100644 index 559156e99..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/AdminUserRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.repository; - -import edu.internet2.tier.shibboleth.admin.ui.security.model.AdminUser; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -/** - * Spring Data repository to manage entities of type {@link AdminUser}. - * - * @author Dmitriy Kopylenko - */ -public interface AdminUserRepository extends JpaRepository { - - Optional findByUsername(String username); -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/RoleRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/RoleRepository.java new file mode 100644 index 000000000..120f2938e --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/RoleRepository.java @@ -0,0 +1,16 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.repository; + +import edu.internet2.tier.shibboleth.admin.ui.security.model.Role; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +/** + * Spring Data repository to manage entities of type {@link Role}. + * + * @author Dmitriy Kopylenko + */ +public interface RoleRepository extends JpaRepository { + + Optional findByName(final String name); +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserRepository.java new file mode 100644 index 000000000..f19ceb1b7 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserRepository.java @@ -0,0 +1,16 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.repository; + +import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +/** + * Spring Data repository to manage entities of type {@link User}. + * + * @author Dmitriy Kopylenko + */ +public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserService.java index c1f1c06b8..a2cab06e2 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserService.java @@ -1,12 +1,11 @@ package edu.internet2.tier.shibboleth.admin.ui.security.springsecurity; -import edu.internet2.tier.shibboleth.admin.ui.security.model.AdminRole; -import edu.internet2.tier.shibboleth.admin.ui.security.model.AdminUser; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.AdminUserRepository; +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.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -23,17 +22,17 @@ @RequiredArgsConstructor public class AdminUserService implements UserDetailsService { - private final AdminUserRepository adminUserRepository; + private final UserRepository userRepository; @Override @Transactional(readOnly = true) public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - AdminUser user = adminUserRepository + User user = userRepository .findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException(String.format("User [%s] is not found", username))); Set grantedAuthorities = user.getRoles().stream() - .map(AdminRole::getName) + .map(Role::getName) .map(SimpleGrantedAuthority::new) .collect(toSet()); @@ -42,7 +41,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx throw new UsernameNotFoundException(String.format("No roles are defined for user [%s]", username)); } - return new User(user.getUsername(), user.getPassword(), grantedAuthorities); + return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy index 40b92e561..e20b5d8f8 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy @@ -1,12 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.security.springsecurity -import edu.internet2.tier.shibboleth.admin.ui.security.repository.AdminRoleRepository -import edu.internet2.tier.shibboleth.admin.ui.security.repository.AdminUserRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest -import org.springframework.context.annotation.Profile import org.springframework.security.core.userdetails.UsernameNotFoundException -import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @@ -23,10 +21,10 @@ class AdminUserServiceTests extends Specification { AdminUserService adminUserService @Autowired - AdminRoleRepository adminRoleRepository + RoleRepository adminRoleRepository @Autowired - AdminUserRepository adminUserRepository + UserRepository adminUserRepository def "Loading existing admin user with admin role"() { diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 000000000..b8397ecd9 --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,8 @@ +# Security + +Security in the system is controlled by Spring Security. + +Currently, the following roles are recognized: + +1. `ADMIN` +1. `USER` \ No newline at end of file