diff --git a/README.md b/README.md index 07ce59ab0..40788dfe3 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The easiest way to do this in a servlet container is through the use of system p ## Authentication -Currently, the application is wired with very simple authentication. A password for the user `user` +Currently, the application is wired with very simple authentication. A password for the user `root` can be set with the `shibui.default-password` property. If none is set, a default password will be generated and logged: 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/ShibbolethUiApplication.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java index a1ccd6c25..b548b98d4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java @@ -3,14 +3,12 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.Profile; import org.springframework.context.event.EventListener; @@ -21,7 +19,7 @@ @SpringBootApplication @ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "edu.internet2.tier.shibboleth.admin.ui.configuration.auto.*")) -@EntityScan(basePackages = "edu.internet2.tier.shibboleth.admin.ui.domain") +@EntityScan(basePackages = {"edu.internet2.tier.shibboleth.admin.ui.domain", "edu.internet2.tier.shibboleth.admin.ui.security.model"}) @EnableJpaAuditing @EnableScheduling @EnableWebSecurity @@ -49,5 +47,4 @@ void showMetadataResolversResourceIds(ApplicationStartedEvent e) { .forEach(it -> System.out.println(String.format("MetadataResolver [%s: %s]", it.getName(), it.getResourceId()))); } } - } 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 f824ca8a5..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,6 +1,9 @@ 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.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; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -13,6 +16,8 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.StrictHttpFirewall; @@ -20,7 +25,7 @@ /** * Web security configuration. - * + *
* Workaround for slashes in URL from [https://stackoverflow.com/questions/48453980/spring-5-0-3-requestrejectedexception-the-request-was-rejected-because-the-url]
*/
@Configuration
@@ -34,6 +39,9 @@ public class WebSecurityConfig {
@Value("${shibui.default-password:}")
private String defaultPassword;
+ @Autowired
+ private UserRepository userRepository;
+
@Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
@@ -62,13 +70,15 @@ protected void configure(HttpSecurity http) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO: more configurable authentication
+ PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
if (defaultPassword != null && !"".equals(defaultPassword)) {
auth
.inMemoryAuthentication()
- .withUser("user").password(defaultPassword).roles("USER");
- } else {
- super.configure(auth);
+ .withUser("root")
+ .password(defaultPassword)
+ .roles("ADMIN");
}
+ auth.userDetailsService(adminUserService(userRepository)).passwordEncoder(passwordEncoder);
}
@Override
@@ -85,6 +95,12 @@ public AuditorAwareAdminUserService
+ *
+ * @author Dmitriy Kopylenko
+ */
+@SpringBootTest
+@ActiveProfiles('dev')
+class AdminUserServiceTests extends Specification {
+
+ @Autowired
+ AdminUserService adminUserService
+
+ @Autowired
+ RoleRepository adminRoleRepository
+
+ @Autowired
+ UserRepository adminUserRepository
+
+
+ def "Loading existing admin user with admin role"() {
+ given: 'Valid user with admin role is available (loaded by Spring Boot Listener in dev profile)'
+ def user = adminUserService.loadUserByUsername('admin')
+
+ expect:
+ user.username == 'admin'
+ user.password == '{noop}adminpass'
+ user.getAuthorities().size() == 1
+ user.getAuthorities()[0].authority == 'ROLE_ADMIN'
+ user.enabled
+ user.accountNonExpired
+ user.credentialsNonExpired
+ }
+
+ def "Loading NON-existing admin user with admin role"() {
+ when: 'Non-existent admin user is tried to be looked up'
+ adminUserService.loadUserByUsername('nonexisting')
+
+ then:
+ thrown UsernameNotFoundException
+ }
+}
diff --git a/docs/GETTINGSTARTED.md b/docs/GETTINGSTARTED.md
index 65da98a2c..eeed8fae3 100644
--- a/docs/GETTINGSTARTED.md
+++ b/docs/GETTINGSTARTED.md
@@ -46,7 +46,7 @@ The easiest way to do this in a servlet container is through the use of system p
## Authentication
-Currently, the application is wired with very simple authentication. A password for the user `user`
+Currently, the application is wired with very simple authentication. A password for the user `root`
can be set with the `shibui.default-password` property. If none is set, a default password
will be generated and logged:
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