From 7356d8c9a97f36f2c53f2477f366fbf254c0a231 Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Fri, 14 Dec 2018 15:24:13 -0700 Subject: [PATCH 01/42] SHIBUI-1047: (Not Completed) Attempting to fix issue with using a decimal character in input type=number by using Regex as a way of validating 0-1 real number input; --- .../schema/provider/filebacked-http-reloading.schema.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json b/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json index de58d5137..6467d6c45 100644 --- a/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json +++ b/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json @@ -51,15 +51,16 @@ "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", - "type": "number", + "type": "string", "widget": { "id": "number", - "step": 0.01 + "step": "any" }, "placeholder": "label.real-number", "minimum": 0, "maximum": 1, - "default": null + "default": null, + "pattern": "^(0(\\.\\d+)?|1(\\.0+)?)?$" } } } From 99b715dbd9fd5083b728eb39db17b90dee39c331 Mon Sep 17 00:00:00 2001 From: Jj! Date: Tue, 8 Jan 2019 09:16:04 -0600 Subject: [PATCH 02/42] [SHIBUI-1029] update configuration and metadata --- .../src/test/docker/conf/application.yml | 2 +- .../src/test/docker/conf/idp-metadata.xml | 112 +++++++++++++----- 2 files changed, 83 insertions(+), 31 deletions(-) diff --git a/pac4j-module/src/test/docker/conf/application.yml b/pac4j-module/src/test/docker/conf/application.yml index b850afb54..41832e741 100644 --- a/pac4j-module/src/test/docker/conf/application.yml +++ b/pac4j-module/src/test/docker/conf/application.yml @@ -10,7 +10,7 @@ shibui: keystorePath: "/conf/samlKeystore.jks" keystorePassword: "changeit" privateKeyPassword: "changeit" - serviceProviderEntityId: "https://unicon.net/shibui" + serviceProviderEntityId: "https://unicon.net/dev/shibui" serviceProviderMetadataPath: "/conf/sp-metadata.xml" identityProviderMetadataPath: "/conf/idp-metadata.xml" forceServiceProviderMetadataGeneration: true diff --git a/pac4j-module/src/test/docker/conf/idp-metadata.xml b/pac4j-module/src/test/docker/conf/idp-metadata.xml index eddc5010c..0c0924b54 100644 --- a/pac4j-module/src/test/docker/conf/idp-metadata.xml +++ b/pac4j-module/src/test/docker/conf/idp-metadata.xml @@ -1,30 +1,82 @@ - - - - - - - MIIDdDCCAlygAwIBAgIGAVWm+BpSMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ -bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv -b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNzAx -MTQ1ODQ0WhcNMjEwNjMwMTQ1ODQ0WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN -TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx -CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAirwyeCS6SZpnYxprfXhpTNXwVfQC+J9OvBlJp8/7ngA627yER1bvfUkBMQxo0CXe -H6HX6Vw1DgalZJeEGDZSErlAY7lWkXkHdsejlMoYayQSZz2b/EfeRetwxh3Ek0hMDScOgDlsdfAn -AiZ4//n3IlypCi4ZMnLPs308FYunvp+R0Wd8Yqj8ctKhiYs6fCSHksDd+JKPe2FC1Zqw9GCGhi32 -DBNRTHfE3tX3rTRs1pT0qbrQmpPfeBYfX00astGa3Dq/XWVO62IlqM7nVjglIPdi0tCIx+5RVZrY -uvULMipA+131TMxTpcGjUFxNwzPdogdpNhtL8+erfhG26C6b8wIDAQABMA0GCSqGSIb3DQEBCwUA -A4IBAQCIOe/bW+mdE9PuarSz60HPGe9ROibyEOTyAWGxvSFfqoNFzaH3oOiEHMNG+ZkHHGtGEeWc -KYQ72V1OKO4aNqy2XaT3onOkd2oh4N8Q5pWrgMRkAB2HvBhBcQeO6yojVamTd43Kbtc+Hly3o+Or -XXOR9cgfxX/0Dbb+xwzTcwcMoJ1CPd3T4zxByKMHNflWrgrmZ9DmDOya4Aqs+xvrvPJB2VHaXoJ6 -r/N+xtG8zO8wNRuxQxNUvtcFKKX2sZAqQRASGi1z8Y1FhU6rWBdBRtaiASAIgkNwOmS603Mm08Yr -0Yq7x6h3XlG8HO0bAOto6pr6q85pLqqv7v7/x7mfdjV3 - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - - - - + + + + + + + unicon.net + + Unicon, Inc. + Login service for Unicon Employees + https://idp.unicon.net/logo_135_0.png + + + + + + + + MIIDIzCCAgugAwIBAgIUIEHTfbStY0ckKZzxIgqd5p1O2K0wDQYJKoZIhvcNAQEF + BQAwGTEXMBUGA1UEAxMOaWRwLnVuaWNvbi5uZXQwHhcNMTEwOTEzMDMyMzE2WhcN + MzEwOTEzMDMyMzE2WjAZMRcwFQYDVQQDEw5pZHAudW5pY29uLm5ldDCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBANtUsFXxlhvD3bWT5Y7TqKkf5rxa+dPA + z7vpbJ6bWhDPSMXb/9MiJe/ciY5ZKKrB1rdRC04s7blrzem3YtjGihfGd4ld+NRt + Pi0xoAT2YIp83CvEe5BHAKwqD7KTonN1unbN84mVo65itbme9d8lZKc0PfLM+BQp + fhXKUBfYeBCkYU4YWxmgL4Vs7XBaKjEjpTN4ncar4YSrarWTTPyO5RzmVPLAcv88 + 1OBqewTyN41+JRXt0Jopi4ZQ8JjKkm73vhoYDBPHr/VMqk1lFfrDcDwJa2ygyWCm + qTlq6zyLE9Fr6sYz6CbgA2lAqu/b1rYCqVCnRpoHZKahAQ9uGQSfHD8CAwEAAaNj + MGEwQAYDVR0RBDkwN4IOaWRwLnVuaWNvbi5uZXSGJWh0dHBzOi8vaWRwLnVuaWNv + bi5uZXQvaWRwL3NoaWJib2xldGgwHQYDVR0OBBYEFK6yUrpGjvY3B09ke0kVl4wA + CMAnMA0GCSqGSIb3DQEBBQUAA4IBAQDG/gMpr3N+nAMuo7RhtDBsckiJV2+BwT/r + JmpxlHAV1Zgc3eeuOdyxm5/jA78tspLldL0+6W/LzZWov/je36IqVT1wSGy1n0Sc + Pjw8DHgyEJLCij2vVScV+j/Y4Eg0bVy6pZTeQW+e3ygb6WgiVT/ARM8QBp6GjAUC + qIlJCads9Rcx3vAih72I4exUUD4qMuBMeLIdY5XReHy5YHqxbkPjQhDIEORAFlzJ + jLqO/Ldzn4waEa5snDZyeYjsl6pi+8CVGfXLSDVsDuk5s47B9OD+gOSJ1wEc7O/N + nU9d/WCcM1V4IGZGL8TXUdfJoVXYZUFF08jUGSL2mj30WS1orIWo + + + + + + + + + + + MIIDIzCCAgugAwIBAgIUIEHTfbStY0ckKZzxIgqd5p1O2K0wDQYJKoZIhvcNAQEF + BQAwGTEXMBUGA1UEAxMOaWRwLnVuaWNvbi5uZXQwHhcNMTEwOTEzMDMyMzE2WhcN + MzEwOTEzMDMyMzE2WjAZMRcwFQYDVQQDEw5pZHAudW5pY29uLm5ldDCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBANtUsFXxlhvD3bWT5Y7TqKkf5rxa+dPA + z7vpbJ6bWhDPSMXb/9MiJe/ciY5ZKKrB1rdRC04s7blrzem3YtjGihfGd4ld+NRt + Pi0xoAT2YIp83CvEe5BHAKwqD7KTonN1unbN84mVo65itbme9d8lZKc0PfLM+BQp + fhXKUBfYeBCkYU4YWxmgL4Vs7XBaKjEjpTN4ncar4YSrarWTTPyO5RzmVPLAcv88 + 1OBqewTyN41+JRXt0Jopi4ZQ8JjKkm73vhoYDBPHr/VMqk1lFfrDcDwJa2ygyWCm + qTlq6zyLE9Fr6sYz6CbgA2lAqu/b1rYCqVCnRpoHZKahAQ9uGQSfHD8CAwEAAaNj + MGEwQAYDVR0RBDkwN4IOaWRwLnVuaWNvbi5uZXSGJWh0dHBzOi8vaWRwLnVuaWNv + bi5uZXQvaWRwL3NoaWJib2xldGgwHQYDVR0OBBYEFK6yUrpGjvY3B09ke0kVl4wA + CMAnMA0GCSqGSIb3DQEBBQUAA4IBAQDG/gMpr3N+nAMuo7RhtDBsckiJV2+BwT/r + JmpxlHAV1Zgc3eeuOdyxm5/jA78tspLldL0+6W/LzZWov/je36IqVT1wSGy1n0Sc + Pjw8DHgyEJLCij2vVScV+j/Y4Eg0bVy6pZTeQW+e3ygb6WgiVT/ARM8QBp6GjAUC + qIlJCads9Rcx3vAih72I4exUUD4qMuBMeLIdY5XReHy5YHqxbkPjQhDIEORAFlzJ + jLqO/Ldzn4waEa5snDZyeYjsl6pi+8CVGfXLSDVsDuk5s47B9OD+gOSJ1wEc7O/N + nU9d/WCcM1V4IGZGL8TXUdfJoVXYZUFF08jUGSL2mj30WS1orIWo + + + + + + + urn:mace:shibboleth:1.0:nameIdentifier + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + From 9220aeb69fa3f3c66b54410be3caad3db8e969c4 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 9 Jan 2019 17:31:12 -0700 Subject: [PATCH 03/42] [SHIBUI-1030] First pass at email template handling and email sending. --- .../ui/configuration/EmailConfiguration.java | 91 +++++++++++++++++++ .../admin/ui/service/EmailService.java | 11 +++ .../admin/ui/service/EmailServiceImpl.java | 53 +++++++++++ .../src/main/resources/application.properties | 8 ++ .../src/main/resources/mail/html/test.html | 4 + backend/src/main/resources/mail/text/test.txt | 1 + 6 files changed, 168 insertions(+) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EmailConfiguration.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailService.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImpl.java create mode 100644 backend/src/main/resources/mail/html/test.html create mode 100644 backend/src/main/resources/mail/text/test.txt 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..0c6e76aa9 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EmailConfiguration.java @@ -0,0 +1,91 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration; + +import edu.internet2.tier.shibboleth.admin.ui.service.EmailService; +import edu.internet2.tier.shibboleth.admin.ui.service.EmailServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +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 +public class EmailConfiguration { + + private static final String EMAIL_TEMPLATE_ENCODING = "UTF-8"; + + @Value("${shibui.text.email.template.path.prefix:/mail/text/}") + private String textEmailTemplatePathPrefix; + + @Value("${shibui.html.email.template.path.prefix:/mail/html/}") + private String htmlEmailTemplatePathPrefix; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private JavaMailSender javaMailSender; + + @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()); + } +} 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..2b76360ae --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailService.java @@ -0,0 +1,11 @@ +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 recipient, String subject, Locale locale) throws MessagingException; +} 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..9bdc1fc15 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImpl.java @@ -0,0 +1,53 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +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.Locale; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class EmailServiceImpl implements EmailService { + private static final Logger logger = LoggerFactory.getLogger(EmailServiceImpl.class); + + private JavaMailSender emailSender; + private ResourceBundleMessageSource emailMessageSource; + private TemplateEngine textEmailTemplateEngine; + private TemplateEngine htmlEmailTemplateEngine; + + public EmailServiceImpl(JavaMailSender emailSender, + ResourceBundleMessageSource emailMessageSource, + TemplateEngine textEmailTemplateEngine, + TemplateEngine htmlEmailTemplateEngine) { + this.emailSender = emailSender; + this.emailMessageSource = emailMessageSource; + this.textEmailTemplateEngine = textEmailTemplateEngine; + this.htmlEmailTemplateEngine = htmlEmailTemplateEngine; + } + + public void sendMail(String emailTemplate, String fromAddress, String recipient, 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(recipient); + + String textContent = textEmailTemplateEngine.process(emailTemplate, context); + String htmlContent = htmlEmailTemplateEngine.process(emailTemplate, context); + message.setText(textContent, htmlContent); + + // TODO: Uncomment when we're ready to actually send emails + // emailSender.send(mimeMessage); + } +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 81a0d09dd..113cc8e92 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -65,3 +65,11 @@ 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 +spring.mail.host=localhost +spring.mail.port=25 +spring.mail.username=username +spring.mail.password=password +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true \ No newline at end of file diff --git a/backend/src/main/resources/mail/html/test.html b/backend/src/main/resources/mail/html/test.html new file mode 100644 index 000000000..db4472ad1 --- /dev/null +++ b/backend/src/main/resources/mail/html/test.html @@ -0,0 +1,4 @@ + + This is a test! + This is a test email template. Can't you tell? + \ No newline at end of file diff --git a/backend/src/main/resources/mail/text/test.txt b/backend/src/main/resources/mail/text/test.txt new file mode 100644 index 000000000..55781c52c --- /dev/null +++ b/backend/src/main/resources/mail/text/test.txt @@ -0,0 +1 @@ +This is a test email template. Can't you tell? \ No newline at end of file From feac2ef44bb8e11fb8bdf0542554eeda9c8c40a8 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 10 Jan 2019 09:10:42 -0700 Subject: [PATCH 04/42] [SHIBUI-1031] Added mail starter. --- backend/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/build.gradle b/backend/build.gradle index 3b6f64ddc..66ef8d383 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -91,7 +91,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 From 094cb14e36592d0c578149fbdeaedd871b742ea2 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 10 Jan 2019 09:45:49 -0700 Subject: [PATCH 05/42] [SHIBUI-1030] Replaced @Value with @Setter based on feedback from Dima. --- .../ui/configuration/EmailConfiguration.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 index 0c6e76aa9..cb3525f08 100644 --- 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 @@ -2,8 +2,9 @@ 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.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,15 +22,20 @@ * @author Bill Smith (wsmith@unicon.net) */ @Configuration +@ConfigurationProperties("shibui") public class EmailConfiguration { private static final String EMAIL_TEMPLATE_ENCODING = "UTF-8"; - @Value("${shibui.text.email.template.path.prefix:/mail/text/}") - private String textEmailTemplatePathPrefix; + //Configured via @ConfigurationProperties (using setter method) with 'shibui.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/"; - @Value("${shibui.html.email.template.path.prefix:/mail/html/}") - private String htmlEmailTemplatePathPrefix; + //Configured via @ConfigurationProperties (using setter method) with 'shibui.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/"; @Autowired private ApplicationContext applicationContext; From 8bec6188790cbb8db74ba609c49b4537b17ac0be Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 11 Jan 2019 09:03:23 -0700 Subject: [PATCH 06/42] SHIBUI-1029 Added static page for message to new user --- .../resources/i18n/messages_en.properties | 3 ++ ui/angular.json | 2 +- ui/package-lock.json | 6 ++++ ui/package.json | 6 +++- ui/src/static.html | 30 +++++++++++++++++++ ui/src/static.scss | 28 +++++++++++++++++ 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 ui/src/static.html create mode 100644 ui/src/static.scss diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 1d34c9e01..d00582b1d 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -429,6 +429,9 @@ message.required-for-regex=Required for Regex message.file-doesnt-exist=The requested file to be processed does not exist on the server. message.database-constraint=There was a database constraint problem processing the request. Check the request to ensure that fields that must be unique are truly unique. +message.user-request-received-title=User request received +message.user-request-received-body=Your request has been received and is being reviewed. You will be notified with access status. + tooltip.entity-id=Entity ID tooltip.service-provider-name=Service Provider Name (Dashboard Display Only) tooltip.force-authn=Disallows use (or reuse) of authentication results and login flows that don\u0027t provide a real-time proof of user presence in the login process diff --git a/ui/angular.json b/ui/angular.json index ebefdc05a..74517d5fc 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -35,7 +35,7 @@ "configurations": { "production": { "optimization": true, - "outputHashing": "all", + "outputHashing": "bundles", "sourceMap": false, "extractCss": true, "namedChunks": false, diff --git a/ui/package-lock.json b/ui/package-lock.json index fa183d636..19c42e7e3 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -10066,6 +10066,12 @@ } } }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "dev": true + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", diff --git a/ui/package.json b/ui/package.json index ed5f531dc..afe00523b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -9,7 +9,10 @@ "test": "ng test --code-coverage", "lint": "ng lint", "e2e": "ng e2e", - "buildProd": "ng build --prod", + "build:static": "node-sass src/static.scss ./dist/static.css", + "copy:static": "ncp ./src/static.html ./dist/static.html", + "copy": "npm run build:static && npm run copy:static", + "buildProd": "ng build --prod && npm run copy", "bundle-report": "webpack-bundle-analyzer dist/stats.json" }, "private": true, @@ -63,6 +66,7 @@ "karma-jasmine-html-reporter": "^0.2.2", "karma-phantomjs-launcher": "^1.0.4", "karma-spec-reporter": "0.0.31", + "ncp": "^2.0.0", "path": "^0.12.7", "protractor": "~5.1.2", "ts-node": "~3.2.0", diff --git a/ui/src/static.html b/ui/src/static.html new file mode 100644 index 000000000..5535dc452 --- /dev/null +++ b/ui/src/static.html @@ -0,0 +1,30 @@ + + + + + Shibboleth Metadata Management + + + + + + + + +
+
+
+
+ +
+
User request received
+

Your request has been received and is being reviewed. You will be notified with access status.

+
+
+
+
+
+ + diff --git a/ui/src/static.scss b/ui/src/static.scss new file mode 100644 index 000000000..67ebeacae --- /dev/null +++ b/ui/src/static.scss @@ -0,0 +1,28 @@ +@import '../node_modules/bootstrap/scss/bootstrap'; + +@import './theme/palette'; + +$font-size-xs: .75rem !default; +$fa-font-path: "."; + +@import '../node_modules/font-awesome/scss/font-awesome'; + +body { + background-color: map-get($theme-colors, light); + padding-top: 56px; +} + +.section { + .section-body { + background: $white; + } +} + +nav.fixed-top { + border-bottom: 3px solid map-get($theme-colors, primary); +} + +.card { + margin: 100px; + max-width: 500px; +} From d01192de8edddfb5b5836960ff58e1756ddd6a02 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 11 Jan 2019 10:13:06 -0700 Subject: [PATCH 07/42] [SHIBUI-1030] Added support for Mailhog. Created service method for sending new user mail to admins. --- backend/build.gradle | 12 +++++++++++ .../src/main/docker-files/docker-compose.yml | 9 +++++++++ .../ui/configuration/EmailConfiguration.java | 7 ++++++- .../admin/ui/service/EmailService.java | 3 ++- .../admin/ui/service/EmailServiceImpl.java | 20 +++++++++++++++---- .../src/main/resources/application.properties | 9 +++++---- .../main/resources/mail/html/new-user.html | 4 ++++ .../src/main/resources/mail/html/test.html | 4 ---- .../src/main/resources/mail/text/new-user.txt | 1 + backend/src/main/resources/mail/text/test.txt | 1 - 10 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 backend/src/main/docker-files/docker-compose.yml create mode 100644 backend/src/main/resources/mail/html/new-user.html delete mode 100644 backend/src/main/resources/mail/html/test.html create mode 100644 backend/src/main/resources/mail/text/new-user.txt delete mode 100644 backend/src/main/resources/mail/text/test.txt diff --git a/backend/build.gradle b/backend/build.gradle index 66ef8d383..1170bf695 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -6,6 +6,7 @@ plugins { id 'net.researchgate.release' version '2.6.0' id 'io.franzbecker.gradle-lombok' version '1.13' id 'com.palantir.docker' version '0.20.1' + id 'com.avast.gradle.docker-compose' version '0.8.0' } apply plugin: 'io.spring.dependency-management' @@ -248,4 +249,15 @@ docker { files tasks.bootJar.outputs files 'src/main/docker-files/loader.properties' buildArgs(['JAR_FILE': "shibui-${version}.jar"]) +} + +/* + * Docker Compose (gradle-docker-compose-plugin) settings. + * Used to start and stop docker containers before running automation tests. + */ +apply plugin: 'docker-compose' +dockerCompose { + useComposeFiles = ['./src/main/docker-files/docker-compose.yml'] + captureContainersOutput = true + waitForTcpPorts = false } \ No newline at end of file diff --git a/backend/src/main/docker-files/docker-compose.yml b/backend/src/main/docker-files/docker-compose.yml new file mode 100644 index 000000000..96af88dd4 --- /dev/null +++ b/backend/src/main/docker-files/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3" +services: + + redis: + image: mailhog/mailhog:latest + ports: + - 1025:1025 + - 8025:8025 + container_name: mailhog \ No newline at end of file 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 index cb3525f08..e84551685 100644 --- 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 @@ -37,6 +37,11 @@ public class EmailConfiguration { @Setter private String htmlEmailTemplatePathPrefix = "/mail/html/"; + //Configured via @ConfigurationProperties (using setter method) with 'shibui.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 ApplicationContext applicationContext; @@ -92,6 +97,6 @@ private ITemplateResolver htmlTemplateResolver() { @Bean public EmailService emailService() { - return new EmailServiceImpl(javaMailSender, emailMessageSource(), textEmailTemplateEngine(), htmlEmailTemplateEngine()); + return new EmailServiceImpl(javaMailSender, emailMessageSource(), textEmailTemplateEngine(), htmlEmailTemplateEngine(), systemEmailAddress); } } 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 index 2b76360ae..39b441e9a 100644 --- 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 @@ -7,5 +7,6 @@ * @author Bill Smith (wsmith@unicon.net) */ public interface EmailService { - void sendMail(String emailTemplate, String fromAddress, String recipient, String subject, Locale locale) throws MessagingException; + void sendMail(String emailTemplate, String fromAddress, String[] recipients, String subject, Locale locale) throws MessagingException; + void sendNewUserMail(String newUsername) throws MessagingException; } 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 index 9bdc1fc15..90fa8b33b 100644 --- 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 @@ -18,6 +18,7 @@ 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; @@ -26,14 +27,16 @@ public class EmailServiceImpl implements EmailService { public EmailServiceImpl(JavaMailSender emailSender, ResourceBundleMessageSource emailMessageSource, TemplateEngine textEmailTemplateEngine, - TemplateEngine htmlEmailTemplateEngine) { + TemplateEngine htmlEmailTemplateEngine, + String systemEmailAddress) { this.emailSender = emailSender; this.emailMessageSource = emailMessageSource; this.textEmailTemplateEngine = textEmailTemplateEngine; this.htmlEmailTemplateEngine = htmlEmailTemplateEngine; + this.systemEmailAddress = systemEmailAddress; } - public void sendMail(String emailTemplate, String fromAddress, String recipient, String subject, Locale locale) throws MessagingException { + 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 @@ -41,13 +44,22 @@ public void sendMail(String emailTemplate, String fromAddress, String recipient, MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true,"UTF-8"); message.setSubject(subject); message.setFrom(fromAddress); - message.setTo(recipient); + message.setTo(recipients); String textContent = textEmailTemplateEngine.process(emailTemplate, context); String htmlContent = htmlEmailTemplateEngine.process(emailTemplate, context); message.setText(textContent, htmlContent); // TODO: Uncomment when we're ready to actually send emails - // emailSender.send(mimeMessage); + emailSender.send(mimeMessage); + } + + public void sendNewUserMail(String newUsername) throws MessagingException { + String subject = String.format("User Access Request for %s", newUsername); + sendMail("new-user", systemEmailAddress, getSystemAdmins(), subject, Locale.getDefault()); + } + + private String[] getSystemAdmins() { + return new String[]{"admin1@shibui.org", "admin2@shibui.org"}; } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 113cc8e92..7eec3663d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -66,10 +66,11 @@ shibui.nameid-filter-ui-schema-location=classpath:nameid-filter.schema.json # shibui.metadataProviders.target=file:/opt/shibboleth-idp/conf/shibui-metadata-providers.xml # shibui.metadataProviders.taskRunRate=30000 -# Email configuration +# Email configuration (local mailhog) spring.mail.host=localhost -spring.mail.port=25 +spring.mail.port=1025 spring.mail.username=username spring.mail.password=password -spring.mail.properties.mail.smtp.auth=true -spring.mail.properties.mail.smtp.starttls.enable=true \ No newline at end of file +spring.mail.properties.mail.smtp.auth=false +spring.mail.properties.mail.smtp.starttls.enable=false +shibui.system.email.address=doNotReply@shibui.org \ No newline at end of file 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/html/test.html b/backend/src/main/resources/mail/html/test.html deleted file mode 100644 index db4472ad1..000000000 --- a/backend/src/main/resources/mail/html/test.html +++ /dev/null @@ -1,4 +0,0 @@ - - This is a test! - This is a test email template. Can't you tell? - \ 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/main/resources/mail/text/test.txt b/backend/src/main/resources/mail/text/test.txt deleted file mode 100644 index 55781c52c..000000000 --- a/backend/src/main/resources/mail/text/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is a test email template. Can't you tell? \ No newline at end of file From 56bb76518204fd0f1ae89bfa822ffae49010ec2e Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 11 Jan 2019 10:16:24 -0700 Subject: [PATCH 08/42] [SHIBUI-1030] Fixed a copy/paste issue. --- backend/src/main/docker-files/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/docker-files/docker-compose.yml b/backend/src/main/docker-files/docker-compose.yml index 96af88dd4..0bf34437a 100644 --- a/backend/src/main/docker-files/docker-compose.yml +++ b/backend/src/main/docker-files/docker-compose.yml @@ -1,7 +1,7 @@ version: "3" services: - redis: + mailhog: image: mailhog/mailhog:latest ports: - 1025:1025 From f475fb8b5f4c3629c2a66382e4205812dc7c4c54 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 11 Jan 2019 14:26:16 -0700 Subject: [PATCH 09/42] [SHIBUI-1030] Added query to get users by role name. Added simple email test. Updated gradle build to spin up docker compose before the test task and shut down after. In theory. --- backend/build.gradle | 5 +++ .../ui/configuration/EmailConfiguration.java | 13 +++++-- .../controller/ConfigurationController.java | 17 ++++++++ .../security/repository/UserRepository.java | 2 + .../admin/ui/service/EmailServiceImpl.java | 18 +++++++-- .../ui/configuration/TestConfiguration.groovy | 11 ++++++ .../ui/service/EmailServiceImplTests.groovy | 39 +++++++++++++++++++ 7 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy diff --git a/backend/build.gradle b/backend/build.gradle index d5dd5f394..ec6171fed 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -325,4 +325,9 @@ dockerCompose { useComposeFiles = ['./src/main/docker-files/docker-compose.yml'] captureContainersOutput = true waitForTcpPorts = false +} + +task test(overwrite: true) { + dependsOn 'composeUp' + finalizedBy 'composeDown' } \ No newline at end of file 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 index e84551685..8377acc7b 100644 --- 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 @@ -1,11 +1,11 @@ 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.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; @@ -43,10 +43,10 @@ public class EmailConfiguration { private String systemEmailAddress = "doNotReply@shibui.org"; @Autowired - private ApplicationContext applicationContext; + private JavaMailSender javaMailSender; @Autowired - private JavaMailSender javaMailSender; + private UserRepository userRepository; @Bean public ResourceBundleMessageSource emailMessageSource() { @@ -97,6 +97,11 @@ private ITemplateResolver htmlTemplateResolver() { @Bean public EmailService emailService() { - return new EmailServiceImpl(javaMailSender, emailMessageSource(), textEmailTemplateEngine(), htmlEmailTemplateEngine(), systemEmailAddress); + 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..313bee981 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,12 +3,15 @@ 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; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import javax.mail.MessagingException; import java.util.stream.Collectors; /** @@ -24,6 +27,14 @@ public class ConfigurationController { @Autowired RoleRepository roleRepository; + @Autowired + EmailService emailService; + + @ExceptionHandler(MessagingException.class) + public ResponseEntity handleMessagingExcepgtion() { + return ResponseEntity.badRequest().body(new ErrorResponse("12345", "Something exploded.")); + } + @GetMapping(value = "/customAttributes") public ResponseEntity getCustomAttributes() { return ResponseEntity.ok(customPropertiesConfiguration.getAttributes()); @@ -33,4 +44,10 @@ public ResponseEntity getCustomAttributes() { public ResponseEntity getSupportedRoles() { return ResponseEntity.ok(roleRepository.findAll().stream().map(Role::getName).collect(Collectors.toList())); } + + @GetMapping(value = "/foo") + public ResponseEntity getFoo() throws MessagingException { + emailService.sendNewUserMail("foobar"); + return ResponseEntity.ok().build(); + } } 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/EmailServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImpl.java index 90fa8b33b..601ad48a0 100644 --- 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 @@ -1,5 +1,7 @@ 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; @@ -10,7 +12,9 @@ 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) @@ -23,17 +27,20 @@ public class EmailServiceImpl implements EmailService { private ResourceBundleMessageSource emailMessageSource; private TemplateEngine textEmailTemplateEngine; private TemplateEngine htmlEmailTemplateEngine; + private UserRepository userRepository; public EmailServiceImpl(JavaMailSender emailSender, ResourceBundleMessageSource emailMessageSource, TemplateEngine textEmailTemplateEngine, TemplateEngine htmlEmailTemplateEngine, - String systemEmailAddress) { + 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 { @@ -50,7 +57,6 @@ public void sendMail(String emailTemplate, String fromAddress, String[] recipien String htmlContent = htmlEmailTemplateEngine.process(emailTemplate, context); message.setText(textContent, htmlContent); - // TODO: Uncomment when we're ready to actually send emails emailSender.send(mimeMessage); } @@ -60,6 +66,12 @@ public void sendNewUserMail(String newUsername) throws MessagingException { } private String[] getSystemAdmins() { - return new String[]{"admin1@shibui.org", "admin2@shibui.org"}; + 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/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..f40c9fa3c --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy @@ -0,0 +1,39 @@ +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 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.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +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 + + def "emailService can successfully send an email"() { + when: + emailService.sendNewUserMail("foobar") + + then: + noExceptionThrown() + } +} From b565bd73b542bbe17d978c77d69a6566dd91698d Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Mon, 14 Jan 2019 10:01:50 -0700 Subject: [PATCH 10/42] [SHIBUI-1030] Update build to hopefully use mailhog properly now. --- backend/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/build.gradle b/backend/build.gradle index ec6171fed..3127043b1 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -327,7 +327,7 @@ dockerCompose { waitForTcpPorts = false } -task test(overwrite: true) { +test { dependsOn 'composeUp' finalizedBy 'composeDown' -} \ No newline at end of file +} From e9b1d69c810df176ff1771385dbe0721f7c8f0df Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Mon, 14 Jan 2019 12:44:32 -0700 Subject: [PATCH 11/42] [SHIBUI-1030] Added explitic network configuration for SMTP. --- backend/src/main/docker-files/docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/main/docker-files/docker-compose.yml b/backend/src/main/docker-files/docker-compose.yml index 0bf34437a..07556a417 100644 --- a/backend/src/main/docker-files/docker-compose.yml +++ b/backend/src/main/docker-files/docker-compose.yml @@ -6,4 +6,6 @@ services: ports: - 1025:1025 - 8025:8025 - container_name: mailhog \ No newline at end of file + container_name: mailhog + environment: + - MH_SMTP_BIND_ADDR=0.0.0.0:1025 From b936f1fe0150c09a5498517b63a001e1a2354cee Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Mon, 14 Jan 2019 12:58:16 -0700 Subject: [PATCH 12/42] [SHIBUI-1030] Another attempt at fixing networking so the build passes on jenkins. --- backend/src/main/docker-files/docker-compose.yml | 7 +++++-- backend/src/main/resources/application.properties | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/src/main/docker-files/docker-compose.yml b/backend/src/main/docker-files/docker-compose.yml index 07556a417..d065da0d4 100644 --- a/backend/src/main/docker-files/docker-compose.yml +++ b/backend/src/main/docker-files/docker-compose.yml @@ -7,5 +7,8 @@ services: - 1025:1025 - 8025:8025 container_name: mailhog - environment: - - MH_SMTP_BIND_ADDR=0.0.0.0:1025 + networks: + - backend + +networks: + backend: diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 7eec3663d..a9beb4ac4 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -67,10 +67,10 @@ shibui.nameid-filter-ui-schema-location=classpath:nameid-filter.schema.json # shibui.metadataProviders.taskRunRate=30000 # Email configuration (local mailhog) -spring.mail.host=localhost +spring.mail.host=mailhog 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.system.email.address=doNotReply@shibui.org \ No newline at end of file +shibui.system.email.address=doNotReply@shibui.org From 253d4588b1ce2ec335fb8d3e45a748fa8182395d Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Mon, 14 Jan 2019 13:17:43 -0700 Subject: [PATCH 13/42] [SHIBUI-1030] Switching around the hostnames so that tests look for a host named 'mailhog'. --- backend/src/main/resources/application.properties | 2 +- .../shibboleth/admin/ui/configuration/TestConfiguration.groovy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index a9beb4ac4..7a11fdb1d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -67,7 +67,7 @@ shibui.nameid-filter-ui-schema-location=classpath:nameid-filter.schema.json # shibui.metadataProviders.taskRunRate=30000 # Email configuration (local mailhog) -spring.mail.host=mailhog +spring.mail.host=localhost spring.mail.port=1025 spring.mail.username=username spring.mail.password=password 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 a01645a09..2ee2a5638 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 @@ -40,7 +40,7 @@ class TestConfiguration { @Bean JavaMailSender javaMailSender() { return new JavaMailSenderImpl().with { - it.host = 'localhost' + it.host = 'mailhog' it.port = 1025 it } From 318a24d7d810e1761d62418b5f2cc09034fcfcea Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 15 Jan 2019 08:11:13 -0700 Subject: [PATCH 14/42] [SHIBUI-1030] Ignored a new unit test until we figure out how to get the unit tests to communicate with mailhog/docer compose on Jenkins. --- .../shibboleth/admin/ui/service/EmailServiceImplTests.groovy | 3 +++ 1 file changed, 3 insertions(+) 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 index f40c9fa3c..ce735d105 100644 --- 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 @@ -13,6 +13,7 @@ import org.springframework.boot.test.context.SpringBootTest 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 /** @@ -29,6 +30,8 @@ class EmailServiceImplTests extends Specification { @Autowired EmailService emailService + // Ignoring until we can figure out how to get this to pass on Jenkins + @Ignore def "emailService can successfully send an email"() { when: emailService.sendNewUserMail("foobar") From fbd67b9bac9e08aa3c371a6fd0420b3ef45d8d88 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 15 Jan 2019 09:14:16 -0700 Subject: [PATCH 15/42] [SHIBUI-1030] Backed out at test endpoint I didn't mean to commit. --- .../admin/ui/controller/ConfigurationController.java | 11 ----------- 1 file changed, 11 deletions(-) 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 313bee981..95fb120ec 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 @@ -30,11 +30,6 @@ public class ConfigurationController { @Autowired EmailService emailService; - @ExceptionHandler(MessagingException.class) - public ResponseEntity handleMessagingExcepgtion() { - return ResponseEntity.badRequest().body(new ErrorResponse("12345", "Something exploded.")); - } - @GetMapping(value = "/customAttributes") public ResponseEntity getCustomAttributes() { return ResponseEntity.ok(customPropertiesConfiguration.getAttributes()); @@ -44,10 +39,4 @@ public ResponseEntity getCustomAttributes() { public ResponseEntity getSupportedRoles() { return ResponseEntity.ok(roleRepository.findAll().stream().map(Role::getName).collect(Collectors.toList())); } - - @GetMapping(value = "/foo") - public ResponseEntity getFoo() throws MessagingException { - emailService.sendNewUserMail("foobar"); - return ResponseEntity.ok().build(); - } } From 0ad7ccb7abc9d9b144bd638809c7bdc838d89047 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 15 Jan 2019 09:14:44 -0700 Subject: [PATCH 16/42] [SHIBUI-1030] Removed unused imports. --- .../shibboleth/admin/ui/controller/ConfigurationController.java | 2 -- 1 file changed, 2 deletions(-) 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 95fb120ec..48dcb4237 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 @@ -7,11 +7,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import javax.mail.MessagingException; import java.util.stream.Collectors; /** From a6ed8ff6917ed1d03cbb2cfb87ade69acfc08f6d Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 15 Jan 2019 16:04:21 -0700 Subject: [PATCH 17/42] [SHIBUI-1029] First pass at adding a custom new user filter. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 45 +++++++++++++++++++ .../net/unicon/shibui/pac4j/WebSecurity.java | 13 ++++-- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java 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 new file mode 100644 index 000000000..646c1a3b0 --- /dev/null +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java @@ -0,0 +1,45 @@ +package net.unicon.shibui.pac4j; + +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.security.Principal; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class AddNewUserFilter implements Filter { + + private UserRepository userRepository; + + public AddNewUserFilter(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + Principal principal = ((HttpServletRequest) request).getUserPrincipal(); + String username = principal.getName(); + System.out.println("WOO! Principal: " + username); + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + + } +} 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 e3ff9d4b6..a20f59ebe 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,5 +1,7 @@ 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 org.pac4j.core.config.Config; import org.pac4j.springframework.security.web.CallbackFilter; import org.pac4j.springframework.security.web.SecurityFilter; @@ -17,8 +19,8 @@ @AutoConfigureOrder(-1) public class WebSecurity { @Bean("webSecurityConfig") - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config) { - return new Pac4jWebSecurityConfigurerAdapter(config); + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository); } @Configuration @@ -33,9 +35,11 @@ protected void configure(HttpSecurity http) throws Exception { @Order(1) public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { private final Config config; + private UserRepository userRepository; - public Pac4jWebSecurityConfigurerAdapter(final Config config) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository) { this.config = config; + this.userRepository = userRepository; } @Override @@ -48,6 +52,9 @@ protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().fullyAuthenticated(); http.addFilterBefore(securityFilter, BasicAuthenticationFilter.class); + + http.addFilterBefore(new AddNewUserFilter(userRepository), BasicAuthenticationFilter.class); + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); From b87325d368a5bf4284e0f058baf2bdb0a7cb226e Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 15 Jan 2019 17:23:52 -0700 Subject: [PATCH 18/42] [SHIBUI-1029] First pass at a functioning filter. Build exhibits dependency issues. --- .../net/unicon/shibui/pac4j/AddNewUserFilter.java | 12 ++++++++---- .../java/net/unicon/shibui/pac4j/WebSecurity.java | 1 - 2 files changed, 8 insertions(+), 5 deletions(-) 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 646c1a3b0..18bb1b9bc 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 @@ -1,7 +1,8 @@ package net.unicon.shibui.pac4j; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; -import org.springframework.security.core.userdetails.UserDetails; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -18,6 +19,8 @@ */ public class AddNewUserFilter implements Filter { + private static final Logger logger = LoggerFactory.getLogger(AddNewUserFilter.class); + private UserRepository userRepository; public AddNewUserFilter(UserRepository userRepository) { @@ -26,20 +29,21 @@ public AddNewUserFilter(UserRepository userRepository) { @Override public void init(FilterConfig filterConfig) throws ServletException { - + logger.info("WOO! INIT!"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + logger.info("WOO! Doing filter..."); Principal principal = ((HttpServletRequest) request).getUserPrincipal(); String username = principal.getName(); - System.out.println("WOO! Principal: " + username); + logger.info("WOO! Principal: " + username); chain.doFilter(request, response); } @Override public void destroy() { - + logger.info("WOO! DESTROY!"); } } 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 a20f59ebe..186b053d1 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,6 +1,5 @@ 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 org.pac4j.core.config.Config; import org.pac4j.springframework.security.web.CallbackFilter; From ba59bc4014887a0caf0171700ae4696ee0421a60 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 16 Jan 2019 14:36:27 -0700 Subject: [PATCH 19/42] [SHIBUI-1029] Updated new user filter. All that is missing is a call to the email service. --- backend/build.gradle | 4 ++ .../unicon/shibui/pac4j/AddNewUserFilter.java | 61 +++++++++++++++---- .../net/unicon/shibui/pac4j/WebSecurity.java | 11 ++-- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/backend/build.gradle b/backend/build.gradle index 63170b7fa..2b67f5d00 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -48,6 +48,10 @@ configurations { processResources.dependsOn(':ui:npm_run_buildProd') +jar { + enabled = true +} + //Integration of the frontend and backend into the build to have all of the UI resources available in the app's executable war bootWar.dependsOn(':ui:npm_run_buildProd') bootWar.baseName = 'shibui' 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 18bb1b9bc..0d9f6b723 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 @@ -1,8 +1,17 @@ package net.unicon.shibui.pac4j; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse; +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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.lang.RandomStringUtils; +import org.apache.http.entity.ContentType; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCrypt; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -10,40 +19,68 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.security.Principal; +import java.util.Optional; /** * @author Bill Smith (wsmith@unicon.net) */ public class AddNewUserFilter implements Filter { - private static final Logger logger = LoggerFactory.getLogger(AddNewUserFilter.class); + private static final String ROLE_NONE = "ROLE_NONE"; private UserRepository userRepository; + private RoleRepository roleRepository; - public AddNewUserFilter(UserRepository userRepository) { + public AddNewUserFilter(UserRepository userRepository, RoleRepository roleRepository) { this.userRepository = userRepository; + this.roleRepository = roleRepository; } @Override public void init(FilterConfig filterConfig) throws ServletException { - logger.info("WOO! INIT!"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - logger.info("WOO! Doing filter..."); - Principal principal = ((HttpServletRequest) request).getUserPrincipal(); - String username = principal.getName(); - logger.info("WOO! Principal: " + username); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + String username = authentication.getName(); + if (username != null) { + Optional persistedUser = userRepository.findByUsername(username); + User user; + if (!persistedUser.isPresent()) { + user = new User(); + user.setUsername(username); + user.setPassword(BCrypt.hashpw(RandomStringUtils.randomAlphanumeric(20), BCrypt.gensalt())); + Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE)); + user.getRoles().add(noRole); + userRepository.save(user); + //TODO: Add call to email service here + } else { + user = persistedUser.get(); + } + if (user.getRole().equals(ROLE_NONE)) { + response.setContentType(ContentType.APPLICATION_JSON.getMimeType()); + ((HttpServletResponse) response).setStatus(HttpStatus.FORBIDDEN.value()); + response.getOutputStream().write(getJsonResponseBytes( + new ErrorResponse(String.valueOf(HttpStatus.FORBIDDEN.value()), + "Your account is not yet authorized to access ShibUI."))); + return; + } // else, user is in the system already, carry on + } + } chain.doFilter(request, response); } @Override public void destroy() { - logger.info("WOO! DESTROY!"); + } + + private byte[] getJsonResponseBytes(ErrorResponse eErrorResponse) throws IOException { + String errorResponseJson = new ObjectMapper().writeValueAsString(eErrorResponse); + return errorResponseJson.getBytes(); } } 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 186b053d1..02106124e 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,5 +1,6 @@ package net.unicon.shibui.pac4j; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; import org.pac4j.core.config.Config; import org.pac4j.springframework.security.web.CallbackFilter; @@ -18,8 +19,8 @@ @AutoConfigureOrder(-1) public class WebSecurity { @Bean("webSecurityConfig") - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository) { - return new Pac4jWebSecurityConfigurerAdapter(config, userRepository); + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository); } @Configuration @@ -35,10 +36,12 @@ protected void configure(HttpSecurity http) throws Exception { public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { private final Config config; private UserRepository userRepository; + private RoleRepository roleRepository; - public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository) { this.config = config; this.userRepository = userRepository; + this.roleRepository = roleRepository; } @Override @@ -52,7 +55,7 @@ protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(securityFilter, BasicAuthenticationFilter.class); - http.addFilterBefore(new AddNewUserFilter(userRepository), BasicAuthenticationFilter.class); + http.addFilterAfter(new AddNewUserFilter(userRepository, roleRepository), BasicAuthenticationFilter.class); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); From 81a21ccd6e32fbb66d5bc7113f6719968aa13c89 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 16 Jan 2019 14:37:00 -0700 Subject: [PATCH 20/42] [SHIBUI-1029] Removed lombok as we're not using it. --- pac4j-module/build.gradle | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pac4j-module/build.gradle b/pac4j-module/build.gradle index 5133e4e48..8bcee9cd9 100644 --- a/pac4j-module/build.gradle +++ b/pac4j-module/build.gradle @@ -2,7 +2,6 @@ plugins { id 'java' id 'com.palantir.docker' version '0.20.1' id 'jacoco' - id 'io.franzbecker.gradle-lombok' version '1.13' id 'org.springframework.boot' version '2.0.0.RELEASE' apply false id 'io.spring.dependency-management' version '1.0.6.RELEASE' } @@ -18,11 +17,6 @@ repositories { } } -lombok { - version = "1.16.20" - sha256 = "c5178b18caaa1a15e17b99ba5e4023d2de2ebc18b58cde0f5a04ca4b31c10e6d" -} - dependencyManagement { imports { mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES From c1f47858cec5215bb8b26638442f0117df0eb66e Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 16 Jan 2019 14:54:27 -0700 Subject: [PATCH 21/42] [SHIBUI-1030] Relocated docker-compose-related files. --- backend/build.gradle | 2 +- backend/src/{main => test}/docker-files/docker-compose.yml | 0 backend/src/{main => test}/docker-files/loader.properties | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename backend/src/{main => test}/docker-files/docker-compose.yml (100%) rename backend/src/{main => test}/docker-files/loader.properties (100%) diff --git a/backend/build.gradle b/backend/build.gradle index 3127043b1..1ef670fa5 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -322,7 +322,7 @@ task runChecker << { */ apply plugin: 'docker-compose' dockerCompose { - useComposeFiles = ['./src/main/docker-files/docker-compose.yml'] + useComposeFiles = ['./src/test/docker-files/docker-compose.yml'] captureContainersOutput = true waitForTcpPorts = false } diff --git a/backend/src/main/docker-files/docker-compose.yml b/backend/src/test/docker-files/docker-compose.yml similarity index 100% rename from backend/src/main/docker-files/docker-compose.yml rename to backend/src/test/docker-files/docker-compose.yml diff --git a/backend/src/main/docker-files/loader.properties b/backend/src/test/docker-files/loader.properties similarity index 100% rename from backend/src/main/docker-files/loader.properties rename to backend/src/test/docker-files/loader.properties From 7c8aa938f9c9311bed7cdb4420c020002cd3a6c6 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 16 Jan 2019 15:12:52 -0700 Subject: [PATCH 22/42] [SHIBUI-1030] Updated email configuration to use "shibui.email" prefix. --- .../admin/ui/configuration/CoreShibUiConfiguration.java | 2 +- .../admin/ui/configuration/EmailConfiguration.java | 8 ++++---- backend/src/main/resources/application.properties | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index b9679866d..7e55c0ecf 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -49,7 +49,7 @@ import javax.servlet.http.HttpServletRequest; @Configuration -@EnableConfigurationProperties({CustomPropertiesConfiguration.class, ShibUIConfiguration.class}) +@EnableConfigurationProperties({CustomPropertiesConfiguration.class, ShibUIConfiguration.class, EmailConfiguration.class}) public class CoreShibUiConfiguration { private static final Logger logger = LoggerFactory.getLogger(CoreShibUiConfiguration.class); 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 index 8377acc7b..aa11a2076 100644 --- 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 @@ -22,22 +22,22 @@ * @author Bill Smith (wsmith@unicon.net) */ @Configuration -@ConfigurationProperties("shibui") +@ConfigurationProperties("shibui.mail") public class EmailConfiguration { private static final String EMAIL_TEMPLATE_ENCODING = "UTF-8"; - //Configured via @ConfigurationProperties (using setter method) with 'shibui.text.email.template.path.prefix' property and + //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.html.email.template.path.prefix' property and + //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.system.email.address' property and + //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"; diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 7a11fdb1d..6e999d002 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -73,4 +73,7 @@ spring.mail.username=username spring.mail.password=password spring.mail.properties.mail.smtp.auth=false spring.mail.properties.mail.smtp.starttls.enable=false -shibui.system.email.address=doNotReply@shibui.org + +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 From 2dc59edfb42a43621efcfc68b2279cdca581c433 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 16 Jan 2019 15:14:16 -0700 Subject: [PATCH 23/42] [SHIBUI-1030] Removed an unused (and autowired) service. --- .../admin/ui/controller/ConfigurationController.java | 3 --- 1 file changed, 3 deletions(-) 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 48dcb4237..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 @@ -25,9 +25,6 @@ public class ConfigurationController { @Autowired RoleRepository roleRepository; - @Autowired - EmailService emailService; - @GetMapping(value = "/customAttributes") public ResponseEntity getCustomAttributes() { return ResponseEntity.ok(customPropertiesConfiguration.getAttributes()); From e74abe9593ca6a538e17a58a15bd2fa921e270fc Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 16 Jan 2019 15:36:35 -0700 Subject: [PATCH 24/42] [SHIBUI-1030] Fixed recently broken tests. Unfortunate, considering this annotation change made IntelliJ happy. --- .../admin/ui/configuration/CoreShibUiConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index 7e55c0ecf..b9679866d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -49,7 +49,7 @@ import javax.servlet.http.HttpServletRequest; @Configuration -@EnableConfigurationProperties({CustomPropertiesConfiguration.class, ShibUIConfiguration.class, EmailConfiguration.class}) +@EnableConfigurationProperties({CustomPropertiesConfiguration.class, ShibUIConfiguration.class}) public class CoreShibUiConfiguration { private static final Logger logger = LoggerFactory.getLogger(CoreShibUiConfiguration.class); From d5d7ad7077f9dedaa1d30c934a96615aabec1144 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 16 Jan 2019 18:50:24 -0700 Subject: [PATCH 25/42] [SHIBUI-1030] Modified the User<->Role relationship, removing Cascade completely. Added eager fetching to roles w/in users, lazy to users w/in roles. Modified DevConfig to create roles first, then create users. Replaced existing email-sending unit test with one that checks the results from MailHog. It is a bit messy due to all of the junk that ends up in the email json that Mailhog returns that isn't the same each time. --- .../admin/ui/configuration/DevConfig.groovy | 35 ++++++++++++++++--- .../admin/ui/security/model/Role.java | 2 +- .../admin/ui/security/model/User.java | 4 +-- .../admin/ui/service/EmailService.java | 1 + .../admin/ui/service/EmailServiceImpl.java | 4 +-- .../ui/configuration/TestConfiguration.groovy | 2 +- .../ui/service/EmailServiceImplTests.groovy | 30 +++++++++++++++- 7 files changed, 67 insertions(+), 11 deletions(-) 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/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/service/EmailService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EmailService.java index 39b441e9a..a4ace393f 100644 --- 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 @@ -9,4 +9,5 @@ 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 index 601ad48a0..6e8d20f28 100644 --- 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 @@ -62,10 +62,10 @@ public void sendMail(String emailTemplate, String fromAddress, String[] recipien public void sendNewUserMail(String newUsername) throws MessagingException { String subject = String.format("User Access Request for %s", newUsername); - sendMail("new-user", systemEmailAddress, getSystemAdmins(), subject, Locale.getDefault()); + sendMail("new-user", systemEmailAddress, getSystemAdminEmailAddresses(), subject, Locale.getDefault()); } - private String[] getSystemAdmins() { + public String[] getSystemAdminEmailAddresses() { Set systemAdmins = userRepository.findByRoles_Name("ROLE_ADMIN"); if (systemAdmins == null || systemAdmins.size() == 0) { //TODO: Should this be an exception? 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 2ee2a5638..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 @@ -40,7 +40,7 @@ class TestConfiguration { @Bean JavaMailSender javaMailSender() { return new JavaMailSenderImpl().with { - it.host = 'mailhog' + it.host = 'localhost' it.port = 1025 it } 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 index ce735d105..8960f807e 100644 --- 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 @@ -6,10 +6,13 @@ 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 @@ -30,13 +33,38 @@ 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: - noExceptionThrown() + // 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) } } From f3b48a9b5fa1b5c61ee0989538610181727a13fd Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 16 Jan 2019 19:10:13 -0700 Subject: [PATCH 26/42] [SHIBUI-1030] Updated test to persist roles before users. --- .../tier/shibboleth/admin/ui/service/UserBootstrap.groovy | 1 + 1 file changed, 1 insertion(+) 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 From 1ecda3ff578ae1fbd9a3dc3fa261ffe1ee4ead4d Mon Sep 17 00:00:00 2001 From: Jj! Date: Thu, 17 Jan 2019 10:20:56 -0600 Subject: [PATCH 27/42] [nojira] move loader.properties back to where it should be --- backend/src/{test => main}/docker-files/loader.properties | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/src/{test => main}/docker-files/loader.properties (100%) diff --git a/backend/src/test/docker-files/loader.properties b/backend/src/main/docker-files/loader.properties similarity index 100% rename from backend/src/test/docker-files/loader.properties rename to backend/src/main/docker-files/loader.properties From d4f2026fb30f6a8f9d42adbffc816b304f70f7e6 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 17 Jan 2019 17:43:42 -0700 Subject: [PATCH 28/42] [SHIBUI-1029] Various attempts at getting from the AddNewUserFilter to our new /static.html page. No joy so far. --- .../shibui/pac4j/AccessDeniedHandler.java | 19 +++++++++++++ .../unicon/shibui/pac4j/AddNewUserFilter.java | 4 +++ .../ExceptionHandlerExceptionResolver.java | 8 ++++++ .../pac4j/RestAuthenticationEntryPoint.java | 20 ++++++++++++++ .../net/unicon/shibui/pac4j/WebSecurity.java | 27 ++++++++++++++++++- .../src/test/docker/conf/application.yml | 4 +-- 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/AccessDeniedHandler.java create mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/ExceptionHandlerExceptionResolver.java create mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/RestAuthenticationEntryPoint.java diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AccessDeniedHandler.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AccessDeniedHandler.java new file mode 100644 index 000000000..8fe233dc2 --- /dev/null +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AccessDeniedHandler.java @@ -0,0 +1,19 @@ +package net.unicon.shibui.pac4j; + +import org.springframework.security.access.AccessDeniedException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + System.out.println("WOO! In handle!"); + response.sendRedirect("/static.html"); + } +} 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 0d9f6b723..a6a75fac1 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 @@ -9,6 +9,7 @@ import org.apache.commons.lang.RandomStringUtils; import org.apache.http.entity.ContentType; import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCrypt; @@ -55,6 +56,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha user.setUsername(username); user.setPassword(BCrypt.hashpw(RandomStringUtils.randomAlphanumeric(20), BCrypt.gensalt())); Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE)); + roleRepository.save(noRole); user.getRoles().add(noRole); userRepository.save(user); //TODO: Add call to email service here @@ -62,11 +64,13 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha user = persistedUser.get(); } if (user.getRole().equals(ROLE_NONE)) { +// throw new AccessDeniedException("DENIED!"); response.setContentType(ContentType.APPLICATION_JSON.getMimeType()); ((HttpServletResponse) response).setStatus(HttpStatus.FORBIDDEN.value()); response.getOutputStream().write(getJsonResponseBytes( new ErrorResponse(String.valueOf(HttpStatus.FORBIDDEN.value()), "Your account is not yet authorized to access ShibUI."))); + ((HttpServletResponse) response).sendRedirect("/static.html"); return; } // else, user is in the system already, carry on } diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/ExceptionHandlerExceptionResolver.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/ExceptionHandlerExceptionResolver.java new file mode 100644 index 000000000..640fde822 --- /dev/null +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/ExceptionHandlerExceptionResolver.java @@ -0,0 +1,8 @@ +package net.unicon.shibui.pac4j; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class ExceptionHandlerExceptionResolver extends org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver { + +} diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/RestAuthenticationEntryPoint.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/RestAuthenticationEntryPoint.java new file mode 100644 index 000000000..825e8ae98 --- /dev/null +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/RestAuthenticationEntryPoint.java @@ -0,0 +1,20 @@ +package net.unicon.shibui.pac4j; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + System.out.println("WOO! In auth!"); + response.sendRedirect("/static.html"); + } +} 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 02106124e..30bdc22d6 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 @@ -12,6 +12,9 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.access.AccessDeniedHandlerImpl; +import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.firewall.StrictHttpFirewall; @@ -23,6 +26,19 @@ public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config co return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository); } + @Bean + public static AccessDeniedHandler accessDeniedHandler() { + return new net.unicon.shibui.pac4j.AccessDeniedHandler(); + } + + @Bean + public static ExceptionTranslationFilter exceptionTranslationFilter(AccessDeniedHandler accessDeniedHandler) { + ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(new RestAuthenticationEntryPoint()); + exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler); + exceptionTranslationFilter.afterPropertiesSet(); + return exceptionTranslationFilter; + } + @Configuration @Order(0) public static class FaviconSecurityConfiguration extends WebSecurityConfigurerAdapter { @@ -55,7 +71,16 @@ protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(securityFilter, BasicAuthenticationFilter.class); - http.addFilterAfter(new AddNewUserFilter(userRepository, roleRepository), BasicAuthenticationFilter.class); + http.addFilterAfter(new AddNewUserFilter(userRepository, roleRepository), SecurityFilter.class); +/* + .exceptionHandling().accessDeniedHandler(accessDeniedHandler()); + http.addFilterAfter(exceptionTranslationFilter(accessDeniedHandler()), ExceptionTranslationFilter.class); +*/ +/* + ExceptionTranslationFilter customExceptionTranslationFilter = new ExceptionTranslationFilter(new RestAuthenticationEntryPoint()); + customExceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler); + http.addFilterAfter(customExceptionTranslationFilter, AddNewUserFilter.class); +*/ http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); diff --git a/pac4j-module/src/test/docker/conf/application.yml b/pac4j-module/src/test/docker/conf/application.yml index 41832e741..e4083f2d3 100644 --- a/pac4j-module/src/test/docker/conf/application.yml +++ b/pac4j-module/src/test/docker/conf/application.yml @@ -10,7 +10,7 @@ shibui: keystorePath: "/conf/samlKeystore.jks" keystorePassword: "changeit" privateKeyPassword: "changeit" - serviceProviderEntityId: "https://unicon.net/dev/shibui" + serviceProviderEntityId: "https://unicon.net/test/shibui" serviceProviderMetadataPath: "/conf/sp-metadata.xml" identityProviderMetadataPath: "/conf/idp-metadata.xml" forceServiceProviderMetadataGeneration: true @@ -19,4 +19,4 @@ shibui: logging: level: org.pac4j: "TRACE" - org.opensaml: "INFO" \ No newline at end of file + org.opensaml: "INFO" From 11f12035ecaf956d712f1bf22054db800e31c266 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 18 Jan 2019 09:41:14 -0700 Subject: [PATCH 29/42] [SHIBUI-1029] More attempts at fixing the redirect looping / exception catching issue. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 12 ++++++----- .../net/unicon/shibui/pac4j/WebSecurity.java | 21 +++++++++++++++---- 2 files changed, 24 insertions(+), 9 deletions(-) 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 a6a75fac1..581cde47e 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 @@ -64,19 +64,21 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha user = persistedUser.get(); } if (user.getRole().equals(ROLE_NONE)) { -// throw new AccessDeniedException("DENIED!"); - response.setContentType(ContentType.APPLICATION_JSON.getMimeType()); + throw new AccessDeniedException("DENIED!"); +/* response.setContentType(ContentType.APPLICATION_JSON.getMimeType()); ((HttpServletResponse) response).setStatus(HttpStatus.FORBIDDEN.value()); response.getOutputStream().write(getJsonResponseBytes( new ErrorResponse(String.valueOf(HttpStatus.FORBIDDEN.value()), "Your account is not yet authorized to access ShibUI."))); ((HttpServletResponse) response).sendRedirect("/static.html"); - return; - } // else, user is in the system already, carry on +// return;*/ + } else { + chain.doFilter(request, response);// else, user is in the system already, carry on + } } } - chain.doFilter(request, response); +// chain.doFilter(request, response); } @Override 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 30bdc22d6..18fee29a7 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 @@ -3,6 +3,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; import org.pac4j.core.config.Config; +import org.pac4j.core.context.HttpConstants; import org.pac4j.springframework.security.web.CallbackFilter; import org.pac4j.springframework.security.web.SecurityFilter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; @@ -17,6 +18,7 @@ import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.firewall.StrictHttpFirewall; +import org.springframework.web.bind.annotation.ExceptionHandler; @Configuration @AutoConfigureOrder(-1) @@ -27,6 +29,7 @@ public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config co } @Bean + @ExceptionHandler public static AccessDeniedHandler accessDeniedHandler() { return new net.unicon.shibui.pac4j.AccessDeniedHandler(); } @@ -62,18 +65,26 @@ public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository use @Override protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().antMatchers("/static.html").permitAll(); + final SecurityFilter securityFilter = new SecurityFilter(this.config, "Saml2Client"); final CallbackFilter callbackFilter = new CallbackFilter(this.config); // http.regexMatcher("/callback").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class); - http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class); + http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) + .addFilterBefore(securityFilter, BasicAuthenticationFilter.class) + .addFilterAfter(new AddNewUserFilter(userRepository, roleRepository), SecurityFilter.class) + .addFilterAfter(exceptionTranslationFilter(accessDeniedHandler()), ExceptionTranslationFilter.class) + .exceptionHandling().accessDeniedHandler(accessDeniedHandler()); http.authorizeRequests().anyRequest().fullyAuthenticated(); - http.addFilterBefore(securityFilter, BasicAuthenticationFilter.class); +// http.addFilterBefore(securityFilter, BasicAuthenticationFilter.class); + +// http.addFilterAfter(new AddNewUserFilter(userRepository, roleRepository), SecurityFilter.class) +// .exceptionHandling().accessDeniedHandler(accessDeniedHandler()); + - http.addFilterAfter(new AddNewUserFilter(userRepository, roleRepository), SecurityFilter.class); /* - .exceptionHandling().accessDeniedHandler(accessDeniedHandler()); http.addFilterAfter(exceptionTranslationFilter(accessDeniedHandler()), ExceptionTranslationFilter.class); */ /* @@ -94,6 +105,8 @@ protected void configure(HttpSecurity http) throws Exception { public void configure(org.springframework.security.config.annotation.web.builders.WebSecurity web) throws Exception { super.configure(web); +// web.ignoring().antMatchers("/static.html"); + StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); web.httpFirewall(firewall); From 2b6065c1c0e35b6aef8642c53c6d433276510de5 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 18 Jan 2019 16:39:11 -0700 Subject: [PATCH 30/42] [SHIBUI-1062] Added /api/admin/users/role/{rolename} to get all users with a specific role. --- .../admin/ui/security/controller/UsersController.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java index 0953d528c..b339227f8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java @@ -60,6 +60,12 @@ public ResponseEntity getOne(@PathVariable String username) { return ResponseEntity.ok(findUserOrThrowHttp404(username)); } + @Transactional + @GetMapping("/role/{rolename}") + public ResponseEntity getUsersWithRole(@PathVariable String rolename) { + return ResponseEntity.ok(userRepository.findByRoles_Name(rolename)); + } + @Transactional @DeleteMapping("/{username}") public ResponseEntity deleteOne(@PathVariable String username) { From b0c50a5dc82f9b7e937e5a3f7c1d67c620fe3756 Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Mon, 21 Jan 2019 08:58:31 -0700 Subject: [PATCH 31/42] SHIBUI-1047: Adding regex for real number between 0-1; --- .../schema/provider/filebacked-http-reloading.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json b/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json index 6467d6c45..4de59a093 100644 --- a/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json +++ b/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json @@ -54,13 +54,13 @@ "type": "string", "widget": { "id": "number", - "step": "any" + "step": 0.01 }, "placeholder": "label.real-number", "minimum": 0, "maximum": 1, "default": null, - "pattern": "^(0(\\.\\d+)?|1(\\.0+)?)?$" + "pattern": "^([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)$" } } } From 418e7369beff9c33d1656b1dd27fa50d685986a5 Mon Sep 17 00:00:00 2001 From: Jj! Date: Mon, 21 Jan 2019 11:16:32 -0600 Subject: [PATCH 32/42] [SHIBUI-1029] WIP --- .../net/unicon/shibui/pac4j/WebSecurity.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) 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 18fee29a7..073e210ac 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 @@ -51,7 +51,25 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Configuration @Order(1) + public static class StaticSecurityConfiguration extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.antMatcher("/static.html").authorizeRequests().antMatchers("/static.html").permitAll(); + } + } + + @Configuration + @Order(2) + public static class ErrorSecurityConfiguration extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.antMatcher("/error").authorizeRequests().antMatchers("/error").permitAll(); + } + } + + @Order(100) public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { private final Config config; private UserRepository userRepository; @@ -65,8 +83,6 @@ public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository use @Override protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests().antMatchers("/static.html").permitAll(); - final SecurityFilter securityFilter = new SecurityFilter(this.config, "Saml2Client"); final CallbackFilter callbackFilter = new CallbackFilter(this.config); From bfbff5104a6d0bc9e2b604ae47e5e31b6dd45a31 Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Mon, 21 Jan 2019 14:49:20 -0700 Subject: [PATCH 33/42] SHIBUI-1080: Added regex pattern for Refresh Delay Factor (real number 0-1) to Local Dynamic, File System providers; --- .../resources/file-system-metadata-provider.schema.json | 7 ++++--- .../resources/local-dynamic-metadata-provider.schema.json | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/src/main/resources/file-system-metadata-provider.schema.json b/backend/src/main/resources/file-system-metadata-provider.schema.json index 95dfbf1f7..b4722fc1d 100644 --- a/backend/src/main/resources/file-system-metadata-provider.schema.json +++ b/backend/src/main/resources/file-system-metadata-provider.schema.json @@ -128,15 +128,16 @@ "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", - "type": "number", + "type": "string", "widget": { "id": "number", "step": 0.01 }, "placeholder": "label.real-number", "minimum": 0, - "maximum": 0.99, - "default": null + "maximum": 1, + "default": null, + "pattern": "^([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)$" } } } diff --git a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json index c76434258..b3c193dcf 100644 --- a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json +++ b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json @@ -61,15 +61,16 @@ "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", - "type": "number", + "type": "string", "widget": { "id": "number", "step": 0.01 }, "placeholder": "label.real-number", "minimum": 0, - "maximum": 0.99, - "default": null + "maximum": 1, + "default": null, + "pattern": "^([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)$" }, "minCacheDuration": { "title": "label.min-cache-duration", From ef8e4bdf0ca95f7081bb6e2095a3004df5b1bffc Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Mon, 21 Jan 2019 16:22:20 -0700 Subject: [PATCH 34/42] SHIBUI-937: Added regex pattern for Refresh Delay Factor (real number 0-1) to Dynamic HTTP Metadata Provider; --- .../resources/dynamic-http-metadata-provider.schema.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json index 1969c9ab4..167e79ab1 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -117,15 +117,16 @@ "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", - "type": "number", + "type": "string", "widget": { "id": "number", "step": 0.01 }, "placeholder": "label.real-number", - "minimum": 0.01, - "maximum": 0.99, - "default": null + "minimum": 0, + "maximum": 1, + "default": null, + "pattern": "^([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)$" }, "minCacheDuration": { "title": "label.min-cache-duration", From 1030510581dc0afa2699bda50aca9c62f121cf48 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Mon, 21 Jan 2019 16:45:49 -0700 Subject: [PATCH 35/42] [SHIBUI-1029] Removed the attempt at throwing an Access Denied exception and creating a handler for it as it never seemed to work. Added in the EmailService. --- .../shibui/pac4j/AccessDeniedHandler.java | 19 ------- .../unicon/shibui/pac4j/AddNewUserFilter.java | 25 +++++---- .../net/unicon/shibui/pac4j/WebSecurity.java | 53 +++---------------- 3 files changed, 24 insertions(+), 73 deletions(-) delete mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/AccessDeniedHandler.java diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AccessDeniedHandler.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AccessDeniedHandler.java deleted file mode 100644 index 8fe233dc2..000000000 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AccessDeniedHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.unicon.shibui.pac4j; - -import org.springframework.security.access.AccessDeniedException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * @author Bill Smith (wsmith@unicon.net) - */ -public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler { - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { - System.out.println("WOO! In handle!"); - response.sendRedirect("/static.html"); - } -} 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 581cde47e..6cf95d8f8 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 @@ -6,14 +6,17 @@ 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.ui.service.EmailService; import org.apache.commons.lang.RandomStringUtils; import org.apache.http.entity.ContentType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; -import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCrypt; +import javax.mail.MessagingException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -29,14 +32,18 @@ */ public class AddNewUserFilter implements Filter { + private static final Logger logger = LoggerFactory.getLogger(AddNewUserFilter.class); + private static final String ROLE_NONE = "ROLE_NONE"; private UserRepository userRepository; private RoleRepository roleRepository; + private EmailService emailService; - public AddNewUserFilter(UserRepository userRepository, RoleRepository roleRepository) { + public AddNewUserFilter(UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { this.userRepository = userRepository; this.roleRepository = roleRepository; + this.emailService = emailService; } @Override @@ -59,26 +66,26 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha roleRepository.save(noRole); user.getRoles().add(noRole); userRepository.save(user); - //TODO: Add call to email service here + try { + emailService.sendNewUserMail(username); + } catch (MessagingException e) { + 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)) { - throw new AccessDeniedException("DENIED!"); -/* response.setContentType(ContentType.APPLICATION_JSON.getMimeType()); + response.setContentType(ContentType.APPLICATION_JSON.getMimeType()); ((HttpServletResponse) response).setStatus(HttpStatus.FORBIDDEN.value()); response.getOutputStream().write(getJsonResponseBytes( new ErrorResponse(String.valueOf(HttpStatus.FORBIDDEN.value()), "Your account is not yet authorized to access ShibUI."))); ((HttpServletResponse) response).sendRedirect("/static.html"); -// return;*/ } else { - chain.doFilter(request, response);// else, user is in the system already, carry on + chain.doFilter(request, response); // else, user is in the system already, carry on } } } - -// chain.doFilter(request, response); } @Override 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 073e210ac..e0e156eec 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 @@ -2,8 +2,8 @@ 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.pac4j.core.config.Config; -import org.pac4j.core.context.HttpConstants; import org.pac4j.springframework.security.web.CallbackFilter; import org.pac4j.springframework.security.web.SecurityFilter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; @@ -13,33 +13,15 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.security.web.access.AccessDeniedHandlerImpl; -import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.firewall.StrictHttpFirewall; -import org.springframework.web.bind.annotation.ExceptionHandler; @Configuration @AutoConfigureOrder(-1) public class WebSecurity { @Bean("webSecurityConfig") - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository) { - return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository); - } - - @Bean - @ExceptionHandler - public static AccessDeniedHandler accessDeniedHandler() { - return new net.unicon.shibui.pac4j.AccessDeniedHandler(); - } - - @Bean - public static ExceptionTranslationFilter exceptionTranslationFilter(AccessDeniedHandler accessDeniedHandler) { - ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(new RestAuthenticationEntryPoint()); - exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler); - exceptionTranslationFilter.afterPropertiesSet(); - return exceptionTranslationFilter; + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService); } @Configuration @@ -74,11 +56,13 @@ public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigu private final Config config; private UserRepository userRepository; private RoleRepository roleRepository; + private EmailService emailService; - public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { this.config = config; this.userRepository = userRepository; this.roleRepository = roleRepository; + this.emailService = emailService; } @Override @@ -86,33 +70,14 @@ protected void configure(HttpSecurity http) throws Exception { final SecurityFilter securityFilter = new SecurityFilter(this.config, "Saml2Client"); final CallbackFilter callbackFilter = new CallbackFilter(this.config); - // http.regexMatcher("/callback").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class); http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) .addFilterBefore(securityFilter, BasicAuthenticationFilter.class) - .addFilterAfter(new AddNewUserFilter(userRepository, roleRepository), SecurityFilter.class) - .addFilterAfter(exceptionTranslationFilter(accessDeniedHandler()), ExceptionTranslationFilter.class) - .exceptionHandling().accessDeniedHandler(accessDeniedHandler()); - http.authorizeRequests().anyRequest().fullyAuthenticated(); - -// http.addFilterBefore(securityFilter, BasicAuthenticationFilter.class); + .addFilterAfter(new AddNewUserFilter(userRepository, roleRepository, emailService), SecurityFilter.class); -// http.addFilterAfter(new AddNewUserFilter(userRepository, roleRepository), SecurityFilter.class) -// .exceptionHandling().accessDeniedHandler(accessDeniedHandler()); - - -/* - http.addFilterAfter(exceptionTranslationFilter(accessDeniedHandler()), ExceptionTranslationFilter.class); -*/ -/* - ExceptionTranslationFilter customExceptionTranslationFilter = new ExceptionTranslationFilter(new RestAuthenticationEntryPoint()); - customExceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler); - http.addFilterAfter(customExceptionTranslationFilter, AddNewUserFilter.class); -*/ + http.authorizeRequests().anyRequest().fullyAuthenticated(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); - // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); - http.csrf().disable(); http.headers().frameOptions().disable(); } @@ -121,8 +86,6 @@ protected void configure(HttpSecurity http) throws Exception { public void configure(org.springframework.security.config.annotation.web.builders.WebSecurity web) throws Exception { super.configure(web); -// web.ignoring().antMatchers("/static.html"); - StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); web.httpFirewall(firewall); From b3eed9c9018ed5e852788242c504bb8f3dd0060d Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 22 Jan 2019 10:00:36 -0700 Subject: [PATCH 36/42] [SHIBUI-1029] Updated backend app props to point spring mail at "mailhog" for use in the pac4j docker build. Added dev config to pac4j docker-compose so there will be admins to send email to. Added mailhog to the pac4j docker-compose so that there is a place for the app to send new user emails to. --- backend/src/main/resources/application.properties | 2 +- pac4j-module/src/test/docker/docker-compose.yml | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 6e999d002..ab466cce6 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -67,7 +67,7 @@ shibui.nameid-filter-ui-schema-location=classpath:nameid-filter.schema.json # shibui.metadataProviders.taskRunRate=30000 # Email configuration (local mailhog) -spring.mail.host=localhost +spring.mail.host=mailhog spring.mail.port=1025 spring.mail.username=username spring.mail.password=password diff --git a/pac4j-module/src/test/docker/docker-compose.yml b/pac4j-module/src/test/docker/docker-compose.yml index e6b2f6e70..ac3a781c2 100644 --- a/pac4j-module/src/test/docker/docker-compose.yml +++ b/pac4j-module/src/test/docker/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.7" services: shibui: image: unicon/shibui-pac4j - entrypoint: ["/usr/bin/java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005", "-jar", "app.jar"] + entrypoint: ["/usr/bin/java", "-Dspring.profiles.active=dev", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005", "-jar", "app.jar"] ports: - 8080:8080 - 5005:5005 @@ -13,6 +13,16 @@ services: - ./conf/application.yml:/application.yml networks: - front + + mailhog: + image: mailhog/mailhog:latest + ports: + - 1025:1025 + - 8025:8025 + container_name: mailhog + networks: + - front + networks: front: - driver: bridge \ No newline at end of file + driver: bridge From db2e8e04504cb323fe3c211a28f3ebed08dcd5d3 Mon Sep 17 00:00:00 2001 From: Jj! Date: Tue, 22 Jan 2019 12:05:56 -0600 Subject: [PATCH 37/42] [nojira] update bootstrap for email --- .../tier/shibboleth/admin/ui/service/UserBootstrap.groovy | 3 ++- backend/src/test/resources/conf/1044.csv | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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 f9ab4ffb0..19b27dd1c 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 @@ -32,7 +32,7 @@ class UserBootstrap { if (shibUIConfiguration.userBootstrapResource) { log.info("configuring users from ${shibUIConfiguration.userBootstrapResource.URI}") new CSVReader(new InputStreamReader(shibUIConfiguration.userBootstrapResource.inputStream)).each { it -> - def (username, password, firstName, lastName, roleName) = it + def (username, password, firstName, lastName, roleName, email) = 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 { @@ -40,6 +40,7 @@ class UserBootstrap { it.firstName = firstName it.lastName = lastName it.roles.add(role) + it.emailAddress = email it } userRepository.saveAndFlush(user) diff --git a/backend/src/test/resources/conf/1044.csv b/backend/src/test/resources/conf/1044.csv index 666681a0b..d8dfbcd76 100644 --- a/backend/src/test/resources/conf/1044.csv +++ b/backend/src/test/resources/conf/1044.csv @@ -1,2 +1,2 @@ -"user1","password1","firstName1","lastName1","ROLE_ADMIN" -"user2","password2","firstName2","lastName2","ROLE_USER" \ No newline at end of file +"user1","password1","firstName1","lastName1","ROLE_ADMIN","user1@example.org" +"user2","password2","firstName2","lastName2","ROLE_USER","user2@example.org" \ No newline at end of file From 26abb4149b42bfbd182fc2567eb59568bb412349 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 22 Jan 2019 13:35:46 -0700 Subject: [PATCH 38/42] SHIBUI-1062 Updated path for new user request, added notifications --- ui/src/app/admin/admin.component.ts | 17 ++--------- .../container/action-required.component.ts | 4 ++- .../container/admin-management.component.ts | 4 ++- ui/src/app/admin/effect/collection.effect.ts | 30 ++++++++++++++++++- ui/src/app/admin/service/admin.service.ts | 8 +++++ .../container/dashboard.component.ts | 2 -- ui/src/app/notification/model/notification.ts | 2 +- 7 files changed, 47 insertions(+), 20 deletions(-) diff --git a/ui/src/app/admin/admin.component.ts b/ui/src/app/admin/admin.component.ts index 038d281b8..b3314e7ec 100644 --- a/ui/src/app/admin/admin.component.ts +++ b/ui/src/app/admin/admin.component.ts @@ -1,21 +1,10 @@ -import { Component, OnInit } from '@angular/core'; -import { LoadRoleRequest } from '../core/action/configuration.action'; - -import * as fromRoot from '../app.reducer'; -import { Store } from '@ngrx/store'; -import { LoadAdminRequest } from './action/collection.action'; +import { Component } from '@angular/core'; @Component({ selector: 'admin-page', templateUrl: './admin.component.html', styleUrls: [] }) -export class AdminComponent implements OnInit { - constructor( - private store: Store - ) { } - - ngOnInit(): void { - this.store.dispatch(new LoadAdminRequest()); - } +export class AdminComponent { + constructor() { } } diff --git a/ui/src/app/admin/container/action-required.component.ts b/ui/src/app/admin/container/action-required.component.ts index 8f7aab548..e920c81e1 100644 --- a/ui/src/app/admin/container/action-required.component.ts +++ b/ui/src/app/admin/container/action-required.component.ts @@ -15,5 +15,7 @@ export class ActionRequiredPageComponent { constructor( private store: Store - ) {} + ) { + this.store.dispatch(new LoadNewUsersRequest()); + } } diff --git a/ui/src/app/admin/container/admin-management.component.ts b/ui/src/app/admin/container/admin-management.component.ts index 2773eadc2..e9dd5ff68 100644 --- a/ui/src/app/admin/container/admin-management.component.ts +++ b/ui/src/app/admin/container/admin-management.component.ts @@ -15,5 +15,7 @@ export class AdminManagementPageComponent { constructor( private store: Store - ) {} + ) { + this.store.dispatch(new LoadAdminRequest()); + } } diff --git a/ui/src/app/admin/effect/collection.effect.ts b/ui/src/app/admin/effect/collection.effect.ts index 410166ed5..84d2aa3f2 100644 --- a/ui/src/app/admin/effect/collection.effect.ts +++ b/ui/src/app/admin/effect/collection.effect.ts @@ -15,6 +15,8 @@ import { LoadNewUsersRequest } from '../action/collection.action'; import { AdminService } from '../service/admin.service'; +import { AddNotification } from '../../notification/action/notification.action'; +import { Notification, NotificationType } from '../../notification/model/notification'; /* istanbul ignore next */ @@ -32,7 +34,7 @@ export class AdminCollectionEffects { @Effect() loadNewUsersRequest$ = this.actions$.pipe( ofType(AdminCollectionActionTypes.LOAD_NEW_USERS_REQUEST), - switchMap(() => this.adminService.query().pipe( + switchMap(() => this.adminService.queryByRole('ROLE_NONE').pipe( map(users => new LoadAdminSuccess(users)) )) ); @@ -49,6 +51,19 @@ export class AdminCollectionEffects { )) ); + @Effect() + updateAdminRoleSuccess$ = this.actions$.pipe( + ofType(AdminCollectionActionTypes.UPDATE_ADMIN_SUCCESS), + map(action => action.payload), + map(user => new AddNotification( + new Notification( + NotificationType.Success, + `User update successful for ${ user.changes.username }`, + 5000 + ) + )) + ); + @Effect() removeAdminRequest$ = this.actions$.pipe( ofType(AdminCollectionActionTypes.REMOVE_ADMIN_REQUEST), @@ -64,6 +79,19 @@ export class AdminCollectionEffects { map(action => new LoadAdminRequest()) ); + @Effect() + deleteAdminRoleSuccess$ = this.actions$.pipe( + ofType(AdminCollectionActionTypes.REMOVE_ADMIN_SUCCESS), + map(action => action.payload), + map(user => new AddNotification( + new Notification( + NotificationType.Success, + `User deleted.`, + 5000 + ) + )) + ); + constructor( private actions$: Actions, private adminService: AdminService, diff --git a/ui/src/app/admin/service/admin.service.ts b/ui/src/app/admin/service/admin.service.ts index 7226f3de2..452cc8f7a 100644 --- a/ui/src/app/admin/service/admin.service.ts +++ b/ui/src/app/admin/service/admin.service.ts @@ -22,6 +22,14 @@ export class AdminService { ); } + queryByRole(role: string): Observable { + return this.http.get( + `${this.base}${this.endpoint}/role/${role}`, {} + ).pipe( + map(users => users.map(u => new AdminEntity(u))) + ); + } + update(user: Admin): Observable { return this.http.patch( `${this.base}${this.endpoint}/${user.username}`, {...user} diff --git a/ui/src/app/dashboard/container/dashboard.component.ts b/ui/src/app/dashboard/container/dashboard.component.ts index 56a7013fe..9e30a7cf9 100644 --- a/ui/src/app/dashboard/container/dashboard.component.ts +++ b/ui/src/app/dashboard/container/dashboard.component.ts @@ -24,8 +24,6 @@ export class DashboardPageComponent { ) { this.actionsRequired$ = this.store.select(fromAdmin.getTotalActionsRequired); this.hasActions$ = this.actionsRequired$.pipe(map(a => a > 0)); - - this.store.dispatch(new LoadAdminRequest()); this.store.dispatch(new LoadRoleRequest()); } } diff --git a/ui/src/app/notification/model/notification.ts b/ui/src/app/notification/model/notification.ts index 7668d593d..4c7b237cd 100644 --- a/ui/src/app/notification/model/notification.ts +++ b/ui/src/app/notification/model/notification.ts @@ -9,7 +9,7 @@ export class Notification { } export enum NotificationType { - Success = 'alert-succes', + Success = 'alert-success', Info = 'alert-info', Warning = 'alert-warning', Danger = 'alert-danger' From 127a2305dbe0e4c78dddf8683d3161e430c2cf00 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 22 Jan 2019 13:52:33 -0700 Subject: [PATCH 39/42] Fixed test --- .../container/admin-management.component.spec.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ui/src/app/admin/container/admin-management.component.spec.ts b/ui/src/app/admin/container/admin-management.component.spec.ts index d12a61632..440fe4038 100644 --- a/ui/src/app/admin/container/admin-management.component.spec.ts +++ b/ui/src/app/admin/container/admin-management.component.spec.ts @@ -3,7 +3,17 @@ import { FormsModule } from '@angular/forms'; import { StoreModule, Store, combineReducers } from '@ngrx/store'; import * as fromAdmin from '../reducer'; import { AdminManagementPageComponent } from './admin-management.component'; -import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { MockI18nModule } from '../../../testing/i18n.stub'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'user-management', + template: '
' +}) +export class UserManagementComponent { + + constructor() {} +} describe('Admin Management Page Component', () => { let fixture: ComponentFixture; @@ -20,7 +30,8 @@ describe('Admin Management Page Component', () => { MockI18nModule ], declarations: [ - AdminManagementPageComponent + AdminManagementPageComponent, + UserManagementComponent ], }); From 778fccf1da17c8f55c6d25ad85168249655b84f4 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 22 Jan 2019 15:27:05 -0700 Subject: [PATCH 40/42] [SHIBUI-1029] Removed two unused classes. Removed unnecessary 403-related response code. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 5 ----- .../ExceptionHandlerExceptionResolver.java | 8 -------- .../pac4j/RestAuthenticationEntryPoint.java | 20 ------------------- 3 files changed, 33 deletions(-) delete mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/ExceptionHandlerExceptionResolver.java delete mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/RestAuthenticationEntryPoint.java 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 6cf95d8f8..5c5bf6e12 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 @@ -75,11 +75,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha user = persistedUser.get(); } if (user.getRole().equals(ROLE_NONE)) { - response.setContentType(ContentType.APPLICATION_JSON.getMimeType()); - ((HttpServletResponse) response).setStatus(HttpStatus.FORBIDDEN.value()); - response.getOutputStream().write(getJsonResponseBytes( - new ErrorResponse(String.valueOf(HttpStatus.FORBIDDEN.value()), - "Your account is not yet authorized to access ShibUI."))); ((HttpServletResponse) response).sendRedirect("/static.html"); } else { chain.doFilter(request, response); // else, user is in the system already, carry on diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/ExceptionHandlerExceptionResolver.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/ExceptionHandlerExceptionResolver.java deleted file mode 100644 index 640fde822..000000000 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/ExceptionHandlerExceptionResolver.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.unicon.shibui.pac4j; - -/** - * @author Bill Smith (wsmith@unicon.net) - */ -public class ExceptionHandlerExceptionResolver extends org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver { - -} diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/RestAuthenticationEntryPoint.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/RestAuthenticationEntryPoint.java deleted file mode 100644 index 825e8ae98..000000000 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/RestAuthenticationEntryPoint.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.unicon.shibui.pac4j; - -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * @author Bill Smith (wsmith@unicon.net) - */ -public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - System.out.println("WOO! In auth!"); - response.sendRedirect("/static.html"); - } -} From 850690f2dc56181772cb731a0c6227b93589536a Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 22 Jan 2019 16:23:03 -0700 Subject: [PATCH 41/42] [SHIBUI-1029] Added tests for AddNewUserFilter. --- pac4j-module/build.gradle | 7 +- .../shibui/pac4j/AddNewUserFilterTests.groovy | 75 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy diff --git a/pac4j-module/build.gradle b/pac4j-module/build.gradle index 8bcee9cd9..8bdb9c5d6 100644 --- a/pac4j-module/build.gradle +++ b/pac4j-module/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'java' + id 'groovy' id 'com.palantir.docker' version '0.20.1' id 'jacoco' id 'org.springframework.boot' version '2.0.0.RELEASE' apply false @@ -33,6 +33,11 @@ dependencies { exclude group: 'org.opensaml' } + testCompile project(':backend') + testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.spockframework:spock-core:1.1-groovy-2.4" + testCompile "org.spockframework:spock-spring:1.1-groovy-2.4" + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" docker project(':backend') diff --git a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy new file mode 100644 index 000000000..98c5e99f3 --- /dev/null +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -0,0 +1,75 @@ +package net.unicon.shibui.pac4j + +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.ui.service.EmailService +import org.springframework.security.core.Authentication +import org.springframework.security.core.context.SecurityContext +import org.springframework.security.core.context.SecurityContextHolder +import spock.lang.Specification +import spock.lang.Subject + +import javax.servlet.FilterChain +import javax.servlet.ServletRequest +import javax.servlet.http.HttpServletResponse + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +class AddNewUserFilterTests extends Specification { + + UserRepository userRepository = Mock() + RoleRepository roleRepository = Mock() + EmailService emailService = Mock() + + ServletRequest request = Mock() + HttpServletResponse response = Mock() + FilterChain chain = Mock() + + SecurityContext securityContext = Mock() + Authentication authentication = Mock() + + @Subject + AddNewUserFilter addNewUserFilter = new AddNewUserFilter(userRepository, roleRepository, emailService) + + def setup() { + SecurityContextHolder.setContext(securityContext) + securityContext.getAuthentication() >> authentication + } + + def "new users are redirected"() { + given: + authentication.getName() >> 'newUser' + userRepository.findByUsername('newUser') >> Optional.empty() + roleRepository.findByName('ROLE_NONE') >> Optional.of(new Role('ROLE_NONE')) + + when: + addNewUserFilter.doFilter(request, response, chain) + + then: + 1 * roleRepository.save(_) + 1 * userRepository.save(_) + 1 * emailService.sendNewUserMail('newUser') + 1 * response.sendRedirect("/static.html") + } + + def "existing users are not redirected"() { + given: + authentication.getName() >> 'existingUser' + userRepository.findByUsername('existingUser') >> Optional.of(new User().with { + it.username = 'existingUser' + it.roles = [new Role('ROLE_USER')] + it + }) + + when: + addNewUserFilter.doFilter(request, response, chain) + + then: + 0 * roleRepository.save(_) + 0 * userRepository.save(_) + 1 * chain.doFilter(_, _) + } +} From 7a1de10eb858d041c18c2d94bf7dd4ad2a42b819 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 23 Jan 2019 12:07:08 -0700 Subject: [PATCH 42/42] [SHIBUI-1062] Added test to check that we really can find users by role. Updated dev config to once again include anonymousUser as an admin. --- .../admin/ui/configuration/DevConfig.groovy | 8 ++++---- .../controller/UsersControllerIntegrationTests.groovy | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) 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 dcf255601..bcee9a94c 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 @@ -72,11 +72,11 @@ class DevConfig { roles.add(roleRepository.findByName('ROLE_USER').get()) it }, new User().with { - username = 'admin2' + username = 'anonymousUser' password = '{noop}anotheradmin' - firstName = 'Rand' - lastName = 'al\'Thor' - emailAddress = 'rand@institution.edu' + firstName = 'Anon' + lastName = 'Ymous' + emailAddress = 'anonymous@institution.edu' roles.add(roleRepository.findByName('ROLE_ADMIN').get()) it }] diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy index 04aa033e9..e92e99193 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy @@ -42,6 +42,17 @@ class UsersControllerIntegrationTests extends Specification { result.body[0].role == 'ROLE_ADMIN' } + def 'GET ALL users by role (when there are existing users)'() { + when: + def result = this.restTemplate.getForEntity(RESOURCE_URI + '/role/ROLE_ADMIN', Object) + + then: 'Request completed with HTTP 200 and returned a list of users' + result.statusCodeValue == 200 + ((Object[]) result.body).size() == 2 + result.body[0].role == 'ROLE_ADMIN' + result.body[1].role == 'ROLE_ADMIN' + } + def 'GET ONE existing user'() { when: 'GET request is made for one existing user' def result = this.restTemplate.getForEntity("$RESOURCE_URI/admin", Map)