diff --git a/backend/build.gradle b/backend/build.gradle index 63170b7fa..1ef670fa5 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -7,6 +7,7 @@ plugins { id 'io.franzbecker.gradle-lombok' version '1.13' id 'com.palantir.docker' version '0.20.1' id 'com.palantir.docker-run' version '0.20.1' + id 'com.avast.gradle.docker-compose' version '0.8.0' } apply plugin: 'io.spring.dependency-management' @@ -110,7 +111,7 @@ dependencies { } // spring boot auto-config starters - ['starter-web', 'starter-data-jpa', 'starter-security', 'starter-actuator', 'devtools', 'starter-webflux'].each { + ['starter-web', 'starter-data-jpa', 'starter-security', 'starter-actuator', 'devtools', 'starter-webflux', 'starter-thymeleaf', 'starter-mail'].each { compile "org.springframework.boot:spring-boot-${it}" } // TODO: figure out what this should really be @@ -313,4 +314,20 @@ task runChecker << { sleep 5000 } } -} \ No newline at end of file +} + +/* + * Docker Compose (gradle-docker-compose-plugin) settings. + * Used to start and stop docker containers before running tests. + */ +apply plugin: 'docker-compose' +dockerCompose { + useComposeFiles = ['./src/test/docker-files/docker-compose.yml'] + captureContainersOutput = true + waitForTcpPorts = false +} + +test { + dependsOn 'composeUp' + finalizedBy 'composeDown' +} 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 index a225d7271..dcf255601 100644 --- 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 @@ -11,6 +11,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadat import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository 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.repository.UserRepository import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions import org.springframework.context.annotation.Bean @@ -24,17 +25,35 @@ import javax.annotation.PostConstruct @Profile('dev') class DevConfig { private final UserRepository adminUserRepository + private final RoleRepository roleRepository private final MetadataResolverRepository metadataResolverRepository - DevConfig(UserRepository adminUserRepository, MetadataResolverRepository metadataResolverRepository) { + DevConfig(UserRepository adminUserRepository, MetadataResolverRepository metadataResolverRepository, RoleRepository roleRepository) { this.adminUserRepository = adminUserRepository this.metadataResolverRepository = metadataResolverRepository + this.roleRepository = roleRepository } @Transactional @PostConstruct void createDevUsers() { + if (roleRepository.count() == 0) { + def roles = [new Role().with { + name = 'ROLE_ADMIN' + it + }, new Role().with { + name = 'ROLE_USER' + it + }, new Role().with { + name = 'ROLE_NONE' + it + }] + roles.each { + roleRepository.save(it) + } + } + roleRepository.flush() if (adminUserRepository.count() == 0) { def users = [new User().with { username = 'admin' @@ -42,7 +61,7 @@ class DevConfig { firstName = 'Joe' lastName = 'Doe' emailAddress = 'joe@institution.edu' - roles.add(new Role(name: 'ROLE_ADMIN')) + roles.add(roleRepository.findByName('ROLE_ADMIN').get()) it }, new User().with { username = 'nonadmin' @@ -50,13 +69,21 @@ class DevConfig { firstName = 'Peter' lastName = 'Vandelay' emailAddress = 'peter@institution.edu' - roles.add(new Role(name: 'ROLE_USER')) + roles.add(roleRepository.findByName('ROLE_USER').get()) + it + }, new User().with { + username = 'admin2' + password = '{noop}anotheradmin' + firstName = 'Rand' + lastName = 'al\'Thor' + emailAddress = 'rand@institution.edu' + roles.add(roleRepository.findByName('ROLE_ADMIN').get()) it }] users.each { adminUserRepository.save(it) } - + adminUserRepository.flush() } } diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrap.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrap.groovy index 7a74a2ca9..f9ab4ffb0 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrap.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrap.groovy @@ -34,6 +34,7 @@ class UserBootstrap { new CSVReader(new InputStreamReader(shibUIConfiguration.userBootstrapResource.inputStream)).each { it -> def (username, password, firstName, lastName, roleName) = it def role = roleRepository.findByName(roleName).orElse(new Role(name: roleName)) + roleRepository.saveAndFlush(role) def user = userRepository.findByUsername(username).orElse(new User(username: username)).with { it.password = password it.firstName = firstName diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EmailConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EmailConfiguration.java new file mode 100644 index 000000000..aa11a2076 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EmailConfiguration.java @@ -0,0 +1,107 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration; + +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import edu.internet2.tier.shibboleth.admin.ui.service.EmailService; +import edu.internet2.tier.shibboleth.admin.ui.service.EmailServiceImpl; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.mail.javamail.JavaMailSender; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.spring5.SpringTemplateEngine; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; +import org.thymeleaf.templateresolver.ITemplateResolver; + +import java.util.Collections; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Configuration +@ConfigurationProperties("shibui.mail") +public class EmailConfiguration { + + private static final String EMAIL_TEMPLATE_ENCODING = "UTF-8"; + + //Configured via @ConfigurationProperties (using setter method) with 'shibui.mail.text-email-template-path-prefix' property and + // default value set here if that property is not explicitly set in application.properties + @Setter + private String textEmailTemplatePathPrefix = "/mail/text/"; + + //Configured via @ConfigurationProperties (using setter method) with 'shibui.mail.html-email-template-path-prefix' property and + // default value set here if that property is not explicitly set in application.properties + @Setter + private String htmlEmailTemplatePathPrefix = "/mail/html/"; + + //Configured via @ConfigurationProperties (using setter method) with 'shibui.mail.system-email-address' property and + // default value set here if that property is not explicitly set in application.properties + @Setter + private String systemEmailAddress = "doNotReply@shibui.org"; + + @Autowired + private JavaMailSender javaMailSender; + + @Autowired + private UserRepository userRepository; + + @Bean + public ResourceBundleMessageSource emailMessageSource() { + final ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("mail/mailMessages"); + return messageSource; + } + + @Bean + public TemplateEngine textEmailTemplateEngine() { + final SpringTemplateEngine templateEngine = new SpringTemplateEngine(); + templateEngine.addTemplateResolver(textTemplateResolver()); + templateEngine.setTemplateEngineMessageSource(emailMessageSource()); + return templateEngine; + } + + @Bean + public TemplateEngine htmlEmailTemplateEngine() { + final SpringTemplateEngine templateEngine = new SpringTemplateEngine(); + templateEngine.addTemplateResolver(htmlTemplateResolver()); + templateEngine.setTemplateEngineMessageSource(emailMessageSource()); + return templateEngine; + } + + private ITemplateResolver textTemplateResolver() { + final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); + templateResolver.setOrder(1); + templateResolver.setResolvablePatterns(Collections.singleton("*")); + templateResolver.setPrefix(textEmailTemplatePathPrefix); + templateResolver.setSuffix(".txt"); + templateResolver.setTemplateMode(TemplateMode.TEXT); + templateResolver.setCharacterEncoding(EMAIL_TEMPLATE_ENCODING); + templateResolver.setCacheable(false); + return templateResolver; + } + + private ITemplateResolver htmlTemplateResolver() { + final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); + templateResolver.setOrder(1); + templateResolver.setResolvablePatterns(Collections.singleton("*")); + templateResolver.setPrefix(htmlEmailTemplatePathPrefix); + templateResolver.setSuffix(".html"); + templateResolver.setTemplateMode(TemplateMode.HTML); + templateResolver.setCharacterEncoding(EMAIL_TEMPLATE_ENCODING); + templateResolver.setCacheable(false); + return templateResolver; + } + + @Bean + public EmailService emailService() { + return new EmailServiceImpl(javaMailSender, + emailMessageSource(), + textEmailTemplateEngine(), + htmlEmailTemplateEngine(), + systemEmailAddress, + userRepository); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java index 3f85175e1..fa458816a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java @@ -3,6 +3,7 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration; import edu.internet2.tier.shibboleth.admin.ui.security.model.Role; import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; +import edu.internet2.tier.shibboleth.admin.ui.service.EmailService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Role.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Role.java index c7f1112b3..41acabdca 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Role.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Role.java @@ -45,7 +45,7 @@ public Role(String name, int rank) { //Ignore properties annotation here is to prevent stack overflow recursive error during JSON serialization @JsonIgnoreProperties("roles") - @ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER) + @ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY) private Set users = new HashSet<>(); } 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 edb7c542a..1a36ffdcc 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 @@ -10,9 +10,9 @@ import lombok.ToString; import org.apache.commons.lang.StringUtils; -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; @@ -50,7 +50,7 @@ public class User extends AbstractAuditable { private String role; @JsonIgnore - @ManyToMany(cascade = CascadeType.PERSIST) + @ManyToMany(fetch = FetchType.EAGER) @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/UserRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserRepository.java index f19ceb1b7..380e8501a 100644 --- 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 @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; +import java.util.Set; /** * Spring Data repository to manage entities of type {@link User}. @@ -13,4 +14,5 @@ public interface UserRepository extends JpaRepository { Optional findByUsername(String username); + Set findByRoles_Name(String roleName); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailService.java new file mode 100644 index 000000000..a4ace393f --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailService.java @@ -0,0 +1,13 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import javax.mail.MessagingException; +import java.util.Locale; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public interface EmailService { + void sendMail(String emailTemplate, String fromAddress, String[] recipients, String subject, Locale locale) throws MessagingException; + void sendNewUserMail(String newUsername) throws MessagingException; + String[] getSystemAdminEmailAddresses(); +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImpl.java new file mode 100644 index 000000000..6e8d20f28 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImpl.java @@ -0,0 +1,77 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class EmailServiceImpl implements EmailService { + private static final Logger logger = LoggerFactory.getLogger(EmailServiceImpl.class); + + private final String systemEmailAddress; + private JavaMailSender emailSender; + private ResourceBundleMessageSource emailMessageSource; + private TemplateEngine textEmailTemplateEngine; + private TemplateEngine htmlEmailTemplateEngine; + private UserRepository userRepository; + + public EmailServiceImpl(JavaMailSender emailSender, + ResourceBundleMessageSource emailMessageSource, + TemplateEngine textEmailTemplateEngine, + TemplateEngine htmlEmailTemplateEngine, + String systemEmailAddress, + UserRepository userRepository) { + this.emailSender = emailSender; + this.emailMessageSource = emailMessageSource; + this.textEmailTemplateEngine = textEmailTemplateEngine; + this.htmlEmailTemplateEngine = htmlEmailTemplateEngine; + this.systemEmailAddress = systemEmailAddress; + this.userRepository = userRepository; + } + + public void sendMail(String emailTemplate, String fromAddress, String[] recipients, String subject, Locale locale) throws MessagingException { + Context context = new Context(locale); + // TODO: set things to be replaced in the email template here + + MimeMessage mimeMessage = this.emailSender.createMimeMessage(); + MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true,"UTF-8"); + message.setSubject(subject); + message.setFrom(fromAddress); + message.setTo(recipients); + + String textContent = textEmailTemplateEngine.process(emailTemplate, context); + String htmlContent = htmlEmailTemplateEngine.process(emailTemplate, context); + message.setText(textContent, htmlContent); + + emailSender.send(mimeMessage); + } + + public void sendNewUserMail(String newUsername) throws MessagingException { + String subject = String.format("User Access Request for %s", newUsername); + sendMail("new-user", systemEmailAddress, getSystemAdminEmailAddresses(), subject, Locale.getDefault()); + } + + public String[] getSystemAdminEmailAddresses() { + Set systemAdmins = userRepository.findByRoles_Name("ROLE_ADMIN"); + if (systemAdmins == null || systemAdmins.size() == 0) { + //TODO: Should this be an exception? + logger.warn("No users with ROLE_ADMIN were found! Check your configuration!"); + systemAdmins = new HashSet<>(); + } + return systemAdmins.stream().map(User::getEmailAddress).distinct().toArray(String[]::new); + } +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 81a0d09dd..6e999d002 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -65,3 +65,15 @@ shibui.nameid-filter-ui-schema-location=classpath:nameid-filter.schema.json # Set the following property to periodically write out metadata providers configuration. There is no default value; the following is just an example # shibui.metadataProviders.target=file:/opt/shibboleth-idp/conf/shibui-metadata-providers.xml # shibui.metadataProviders.taskRunRate=30000 + +# Email configuration (local mailhog) +spring.mail.host=localhost +spring.mail.port=1025 +spring.mail.username=username +spring.mail.password=password +spring.mail.properties.mail.smtp.auth=false +spring.mail.properties.mail.smtp.starttls.enable=false + +shibui.mail.text-email-template-path-prefix=/mail/text/ +shibui.mail.html.email-template-path-prefix=/mail/html/ +shibui.mail.system-email-address=doNotReply@shibui.org diff --git a/backend/src/main/resources/mail/html/new-user.html b/backend/src/main/resources/mail/html/new-user.html new file mode 100644 index 000000000..83d22f22a --- /dev/null +++ b/backend/src/main/resources/mail/html/new-user.html @@ -0,0 +1,4 @@ + + New User Email + The user identified in the subject has requested access to SHIBUI. + \ No newline at end of file diff --git a/backend/src/main/resources/mail/text/new-user.txt b/backend/src/main/resources/mail/text/new-user.txt new file mode 100644 index 000000000..2cff1e571 --- /dev/null +++ b/backend/src/main/resources/mail/text/new-user.txt @@ -0,0 +1 @@ +The user identified in the subject has requested access to SHIBUI. \ No newline at end of file diff --git a/backend/src/test/docker-files/docker-compose.yml b/backend/src/test/docker-files/docker-compose.yml new file mode 100644 index 000000000..d065da0d4 --- /dev/null +++ b/backend/src/test/docker-files/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3" +services: + + mailhog: + image: mailhog/mailhog:latest + ports: + - 1025:1025 + - 8025:8025 + container_name: mailhog + networks: + - backend + +networks: + backend: diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy index 1796e3b70..a01645a09 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy @@ -20,6 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.io.ClassPathResource +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.mail.javamail.JavaMailSenderImpl @Configuration class TestConfiguration { @@ -35,6 +37,15 @@ class TestConfiguration { this.metadataResolverRepository = metadataResolverRepository } + @Bean + JavaMailSender javaMailSender() { + return new JavaMailSenderImpl().with { + it.host = 'localhost' + it.port = 1025 + it + } + } + @Bean MetadataResolver metadataResolver() { ChainingMetadataResolver metadataResolver = new OpenSamlChainingMetadataResolver() diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy new file mode 100644 index 000000000..8960f807e --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy @@ -0,0 +1,70 @@ +package edu.internet2.tier.shibboleth.admin.ui.service + +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.DevConfig +import edu.internet2.tier.shibboleth.admin.ui.configuration.EmailConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.core.env.Environment +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import spock.lang.Ignore +import spock.lang.Specification + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@SpringBootTest +@DataJpaTest +@ContextConfiguration(classes=[CoreShibUiConfiguration, EmailConfiguration, TestConfiguration, InternationalizationConfiguration, SearchConfiguration, DevConfig]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@ActiveProfiles(["no-auth", "dev"]) +class EmailServiceImplTests extends Specification { + + @Autowired + EmailService emailService + + @Autowired + Environment env + + JsonSlurper jsonSlurper = new JsonSlurper() + + // Ignoring until we can figure out how to get this to pass on Jenkins + @Ignore + def "emailService can successfully send an email"() { + given: + + def mailhogHost = 'localhost' + def mailhogPort = '8025' + + def newUserName = 'foobar' + def expectedToAddresses = emailService.systemAdminEmailAddresses + def expectedFromAddress = env.getProperty('shibui.mail.system-email-address') + def expectedNewUserEmailSubject = "User Access Request for ${newUserName}" + def expectedTextEmailBody = new File(this.class.getResource('/mail/text/new-user.txt').toURI()).text + + when: + emailService.sendNewUserMail("foobar") + def getEmailsURL = new URL("http://${mailhogHost}:${mailhogPort}/api/v2/messages") + def resultJson = jsonSlurper.parse(getEmailsURL) + println(JsonOutput.prettyPrint(JsonOutput.toJson(resultJson))) + + then: + // There's a bunch of other noise in here that changes per email + resultJson.total == 1 + expectedToAddresses.size() == resultJson.items[0].To.size() + expectedToAddresses.join(', ') == resultJson.items[0].Content.Headers.To[0] + expectedFromAddress == resultJson.items[0].From.Mailbox + '@' + resultJson.items[0].From.Domain + expectedNewUserEmailSubject == resultJson.items[0].Content.Headers.Subject[0] + resultJson.items[0].Content.Body.contains(expectedTextEmailBody) + } +}