From 7356d8c9a97f36f2c53f2477f366fbf254c0a231 Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Fri, 14 Dec 2018 15:24:13 -0700 Subject: [PATCH 01/31] 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/31] [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 8bec6188790cbb8db74ba609c49b4537b17ac0be Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 11 Jan 2019 09:03:23 -0700 Subject: [PATCH 03/31] 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 a6ed8ff6917ed1d03cbb2cfb87ade69acfc08f6d Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 15 Jan 2019 16:04:21 -0700 Subject: [PATCH 04/31] [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 05/31] [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 06/31] [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 07/31] [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 d4f2026fb30f6a8f9d42adbffc816b304f70f7e6 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 17 Jan 2019 17:43:42 -0700 Subject: [PATCH 08/31] [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 09/31] [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 b0c50a5dc82f9b7e937e5a3f7c1d67c620fe3756 Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Mon, 21 Jan 2019 08:58:31 -0700 Subject: [PATCH 10/31] 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 11/31] [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 12/31] 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 13/31] 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 14/31] [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 6c403585cde2c398a473eedeb27340a491dbe41c Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 22 Jan 2019 08:08:10 -0700 Subject: [PATCH 15/31] Fixes missing required message --- .../resources/i18n/messages_en.properties | 1 + .../model/wizards/metadata-source-base.ts | 22 ++++++++++++++++ .../widget/string/string.component.ts | 25 ++++++++++++++++--- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 1c207ee38..ec030c6e9 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -401,6 +401,7 @@ message.org-incomplete=These three fields must all be entered if any single fiel message.type-required=Missing required property: Type message.match-required=Missing required property: Match message.value-required=Missing required property: Value +message.required=Field is required. message.conflict=Conflict message.data-version-contention=Data Version Contention diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts index ebc8921d8..673f176c1 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts @@ -60,6 +60,28 @@ export class MetadataSourceBase implements Wizard { } getValidators(entityIdList: string[]): { [key: string]: any } { + const checkRequiredChild = (value, property, form) => { + if (!value) { + return { + code: 'REQUIRED', + path: `#${property.path}`, + message: `message.required`, + params: [value] + }; + } + return null; + }; + const checkRequiredChildren = (value, property, form) => { + let errors; + Object.keys(value).forEach((item, index, all) => { + const error = checkRequiredChild(item, { path: `${index}` }, form); + if (error) { + errors = errors || []; + errors.push(error); + } + }); + return errors; + }; const checkOrg = (value, property, form) => { const org = property.parent; const orgValue = org.value || {}; diff --git a/ui/src/app/schema-form/widget/string/string.component.ts b/ui/src/app/schema-form/widget/string/string.component.ts index 2de93e371..95d19c2bc 100644 --- a/ui/src/app/schema-form/widget/string/string.component.ts +++ b/ui/src/app/schema-form/widget/string/string.component.ts @@ -1,14 +1,18 @@ -import { Component } from '@angular/core'; +import { Component, AfterViewInit, OnDestroy } from '@angular/core'; import { StringWidget } from 'ngx-schema-form'; import { Validators } from '@angular/forms'; import { SchemaService } from '../../service/schema.service'; +import { startWith } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; @Component({ selector: 'custom-string', templateUrl: `./string.component.html`, styleUrls: ['../widget.component.scss'] }) -export class CustomStringComponent extends StringWidget { +export class CustomStringComponent extends StringWidget implements AfterViewInit, OnDestroy { + + errorSub: Subscription; constructor( private widgetService: SchemaService @@ -16,7 +20,22 @@ export class CustomStringComponent extends StringWidget { super(); } + ngAfterViewInit(): void { + super.ngAfterViewInit(); + this.errorSub = this.control.valueChanges.pipe(startWith(this.control.value)).subscribe(v => { + if (!v && this.required && this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { + this.errorMessages.push('message.required'); + } + }); + } + + ngOnDestroy(): void { + this.errorSub.unsubscribe(); + } + get required(): boolean { - return this.widgetService.isRequired(this.formProperty); + const req = this.widgetService.isRequired(this.formProperty); + + return req; } } From b3eed9c9018ed5e852788242c504bb8f3dd0060d Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 22 Jan 2019 10:00:36 -0700 Subject: [PATCH 16/31] [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 17/31] [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 17c0cfe30b44a9714a5e9d3b9318d9285c9b2d3a Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 22 Jan 2019 15:03:18 -0700 Subject: [PATCH 18/31] Adding messaging for required properties --- .../resources/i18n/messages_en.properties | 2 +- .../widget/select/select.component.ts | 19 +++++++++++++--- .../widget/string/string.component.ts | 2 +- .../widget/textarea/textarea.component.html | 6 +++++ .../widget/textarea/textarea.component.ts | 22 +++++++++++++++++-- 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index ec030c6e9..13fc999b2 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -401,7 +401,7 @@ message.org-incomplete=These three fields must all be entered if any single fiel message.type-required=Missing required property: Type message.match-required=Missing required property: Match message.value-required=Missing required property: Value -message.required=Field is required. +message.required=Missing required property. message.conflict=Conflict message.data-version-contention=Data Version Contention diff --git a/ui/src/app/schema-form/widget/select/select.component.ts b/ui/src/app/schema-form/widget/select/select.component.ts index 5c6a2b65d..d504f0384 100644 --- a/ui/src/app/schema-form/widget/select/select.component.ts +++ b/ui/src/app/schema-form/widget/select/select.component.ts @@ -1,17 +1,20 @@ -import { Component, AfterViewInit } from '@angular/core'; +import { Component, AfterViewInit, OnDestroy } from '@angular/core'; import { SelectWidget } from 'ngx-schema-form'; import { SchemaService } from '../../service/schema.service'; -import { map, shareReplay } from 'rxjs/operators'; +import { map, shareReplay, startWith } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; @Component({ selector: 'select-component', templateUrl: `./select.component.html` }) -export class CustomSelectComponent extends SelectWidget implements AfterViewInit { +export class CustomSelectComponent extends SelectWidget implements AfterViewInit, OnDestroy { options$: any; + errorSub: Subscription; + constructor( private widgetService: SchemaService ) { @@ -38,6 +41,16 @@ export class CustomSelectComponent extends SelectWidget implements AfterViewInit ) ); } + + this.errorSub = this.control.valueChanges.pipe(startWith(this.control.value)).subscribe(v => { + if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { + this.errorMessages.push('message.required'); + } + }); + } + + ngOnDestroy(): void { + this.errorSub.unsubscribe(); } get required(): boolean { diff --git a/ui/src/app/schema-form/widget/string/string.component.ts b/ui/src/app/schema-form/widget/string/string.component.ts index 95d19c2bc..6b6d16ae3 100644 --- a/ui/src/app/schema-form/widget/string/string.component.ts +++ b/ui/src/app/schema-form/widget/string/string.component.ts @@ -23,7 +23,7 @@ export class CustomStringComponent extends StringWidget implements AfterViewInit ngAfterViewInit(): void { super.ngAfterViewInit(); this.errorSub = this.control.valueChanges.pipe(startWith(this.control.value)).subscribe(v => { - if (!v && this.required && this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { + if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { this.errorMessages.push('message.required'); } }); diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.html b/ui/src/app/schema-form/widget/textarea/textarea.component.html index fb8164600..3705c4009 100644 --- a/ui/src/app/schema-form/widget/textarea/textarea.component.html +++ b/ui/src/app/schema-form/widget/textarea/textarea.component.html @@ -18,4 +18,10 @@ [rows]="schema.widget.rows || 5" [formControl]="control" [attr.aria-label]="schema.title"> + + + , + error + + diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.ts b/ui/src/app/schema-form/widget/textarea/textarea.component.ts index 0f68e524f..81d940bde 100644 --- a/ui/src/app/schema-form/widget/textarea/textarea.component.ts +++ b/ui/src/app/schema-form/widget/textarea/textarea.component.ts @@ -1,19 +1,37 @@ -import { Component } from '@angular/core'; +import { Component, AfterViewInit, OnDestroy } from '@angular/core'; import { TextAreaWidget } from 'ngx-schema-form'; import { SchemaService } from '../../service/schema.service'; +import { Subscription } from 'rxjs'; +import { startWith } from 'rxjs/operators'; @Component({ selector: 'textarea-component', templateUrl: `./textarea.component.html` }) -export class CustomTextAreaComponent extends TextAreaWidget { +export class CustomTextAreaComponent extends TextAreaWidget implements AfterViewInit, OnDestroy { + + errorSub: Subscription; + constructor( private widgetService: SchemaService ) { super(); } + ngAfterViewInit(): void { + super.ngAfterViewInit(); + this.errorSub = this.control.valueChanges.pipe(startWith(this.control.value)).subscribe(v => { + if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { + this.errorMessages.push('message.required'); + } + }); + } + + ngOnDestroy(): void { + this.errorSub.unsubscribe(); + } + get required(): boolean { return this.widgetService.isRequired(this.formProperty); } From 778fccf1da17c8f55c6d25ad85168249655b84f4 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 22 Jan 2019 15:27:05 -0700 Subject: [PATCH 19/31] [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 20/31] [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 69fd2224fd4bbdf987ef6785fdc254946ebecbf8 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 23 Jan 2019 09:42:52 -0700 Subject: [PATCH 21/31] SHIBUI-907 Fixed issue with validation messages --- ...dynamic-http-metadata-provider.schema.json | 3 +-- ui/src/app/metadata/metadata.module.ts | 9 +++++++-- ui/src/app/schema-form/model/messages.ts | 3 +++ ui/src/app/schema-form/schema-form.module.ts | 3 ++- .../schema-form/service/schema-validator.ts | 19 +++++++++++++++++++ .../app/schema-form/service/schema.service.ts | 3 ++- .../widget/select/select.component.html | 2 +- .../widget/select/select.component.ts | 7 ++++++- .../widget/string/string.component.html | 3 ++- .../widget/string/string.component.ts | 19 ++++++++++++++++--- .../widget/textarea/textarea.component.html | 2 +- .../widget/textarea/textarea.component.ts | 7 ++++++- .../filebacked-http-filters.schema.json | 7 +++++-- 13 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 ui/src/app/schema-form/model/messages.ts create mode 100644 ui/src/app/schema-form/service/schema-validator.ts 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..8e1989de0 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -598,8 +598,7 @@ "certificateFile": { "title": "label.certificate-file", "description": "tooltip.certificate-file", - "type": "string", - "default": "" + "type": "string" } }, "anyOf": [ diff --git a/ui/src/app/metadata/metadata.module.ts b/ui/src/app/metadata/metadata.module.ts index 946c89423..fd00a2274 100644 --- a/ui/src/app/metadata/metadata.module.ts +++ b/ui/src/app/metadata/metadata.module.ts @@ -9,7 +9,8 @@ import { MetadataRoutingModule } from './metadata.routing'; import { ProviderModule } from './provider/provider.module'; import { I18nModule } from '../i18n/i18n.module'; import { CustomWidgetRegistry } from '../schema-form/registry'; -import { WidgetRegistry } from 'ngx-schema-form'; +import { WidgetRegistry, SchemaValidatorFactory } from 'ngx-schema-form'; +import { CustomSchemaValidatorFactory } from '../schema-form/service/schema-validator'; @NgModule({ @@ -23,7 +24,11 @@ import { WidgetRegistry } from 'ngx-schema-form'; I18nModule ], providers: [ - { provide: WidgetRegistry, useClass: CustomWidgetRegistry } + { provide: WidgetRegistry, useClass: CustomWidgetRegistry }, + { + provide: SchemaValidatorFactory, + useClass: CustomSchemaValidatorFactory + } ], declarations: [ MetadataPageComponent diff --git a/ui/src/app/schema-form/model/messages.ts b/ui/src/app/schema-form/model/messages.ts new file mode 100644 index 000000000..6d6fa61fd --- /dev/null +++ b/ui/src/app/schema-form/model/messages.ts @@ -0,0 +1,3 @@ +export const HARD_CODED_REQUIRED_MSG = RegExp('Missing required property'); + +export const REQUIRED_MSG_OVERRIDE = 'message.required'; \ No newline at end of file diff --git a/ui/src/app/schema-form/schema-form.module.ts b/ui/src/app/schema-form/schema-form.module.ts index 842e903dd..13aba1369 100644 --- a/ui/src/app/schema-form/schema-form.module.ts +++ b/ui/src/app/schema-form/schema-form.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core'; -import { SchemaFormModule } from 'ngx-schema-form'; +import { SchemaFormModule, SchemaValidatorFactory } from 'ngx-schema-form'; import { CommonModule } from '@angular/common'; import { ReactiveFormsModule } from '@angular/forms'; @@ -23,6 +23,7 @@ import { CustomObjectWidget } from './widget/object/object.component'; import { CustomRadioComponent } from './widget/radio/radio.component'; import { InlineObjectListComponent } from './widget/array/inline-obj-list.component'; import { InlineObjectComponent } from './widget/object/inline-obj.component'; +import { CustomSchemaValidatorFactory } from './service/schema-validator'; export const COMPONENTS = [ BooleanRadioComponent, diff --git a/ui/src/app/schema-form/service/schema-validator.ts b/ui/src/app/schema-form/service/schema-validator.ts new file mode 100644 index 000000000..075a78ec9 --- /dev/null +++ b/ui/src/app/schema-form/service/schema-validator.ts @@ -0,0 +1,19 @@ +import * as ZSchema from 'z-schema'; +import { ZSchemaValidatorFactory } from 'ngx-schema-form'; + +export class CustomSchemaValidatorFactory extends ZSchemaValidatorFactory { + + protected zschema; + + constructor() { + super(); + this.createSchemaValidator(); + } + + private createSchemaValidator() { + this.zschema = new ZSchema({ + breakOnFirstError: false + }); + } +} + diff --git a/ui/src/app/schema-form/service/schema.service.ts b/ui/src/app/schema-form/service/schema.service.ts index 9cd7346de..617769de6 100644 --- a/ui/src/app/schema-form/service/schema.service.ts +++ b/ui/src/app/schema-form/service/schema.service.ts @@ -31,7 +31,8 @@ export class SchemaService { Object .keys(condition.properties) .some( - key => values.hasOwnProperty(key) ? condition.properties[key].enum[0] === values[key] : false + key => values.hasOwnProperty(key) && condition.properties[key].enum ? + condition.properties[key].enum[0] === values[key] : false ) ); currentConditions.forEach(el => { diff --git a/ui/src/app/schema-form/widget/select/select.component.html b/ui/src/app/schema-form/widget/select/select.component.html index e66a83350..a7d1dd2ff 100644 --- a/ui/src/app/schema-form/widget/select/select.component.html +++ b/ui/src/app/schema-form/widget/select/select.component.html @@ -42,7 +42,7 @@ , - error + error diff --git a/ui/src/app/schema-form/widget/select/select.component.ts b/ui/src/app/schema-form/widget/select/select.component.ts index d504f0384..ecd50a75b 100644 --- a/ui/src/app/schema-form/widget/select/select.component.ts +++ b/ui/src/app/schema-form/widget/select/select.component.ts @@ -4,6 +4,7 @@ import { SelectWidget } from 'ngx-schema-form'; import { SchemaService } from '../../service/schema.service'; import { map, shareReplay, startWith } from 'rxjs/operators'; import { Subscription } from 'rxjs'; +import { HARD_CODED_REQUIRED_MSG } from '../../model/messages'; @Component({ selector: 'select-component', @@ -43,7 +44,7 @@ export class CustomSelectComponent extends SelectWidget implements AfterViewInit } this.errorSub = this.control.valueChanges.pipe(startWith(this.control.value)).subscribe(v => { - if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { + if (!v && this.required && !this.errorMessages.some(msg => HARD_CODED_REQUIRED_MSG.test(msg))) { this.errorMessages.push('message.required'); } }); @@ -56,4 +57,8 @@ export class CustomSelectComponent extends SelectWidget implements AfterViewInit get required(): boolean { return this.widgetService.isRequired(this.formProperty); } + + getError(error: string): string { + return HARD_CODED_REQUIRED_MSG.test(error) ? 'message.required' : error; + } } diff --git a/ui/src/app/schema-form/widget/string/string.component.html b/ui/src/app/schema-form/widget/string/string.component.html index 26964a59f..4986846ff 100644 --- a/ui/src/app/schema-form/widget/string/string.component.html +++ b/ui/src/app/schema-form/widget/string/string.component.html @@ -13,6 +13,7 @@ validate="true" [attr.readonly]="schema.readOnly?true:null" class="text-widget.id textline-widget form-control" + [class.is-invalid]="control.touched && !control.value && errorMessages.length" [attr.type]="this.getInputType()" [attr.id]="id" [formControl]="control" @@ -25,7 +26,7 @@ , - error + error { - if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { - this.errorMessages.push('message.required'); + let listener = this.formProperty.parent ? this.formProperty.parent : this.control; + this.errorSub = listener.valueChanges.pipe(startWith(listener.value)).subscribe(v => { + if (!this.control.value + && this.required + && !this.errorMessages.some(msg => HARD_CODED_REQUIRED_MSG.test(msg)) + && this.errorMessages.indexOf(REQUIRED_MSG_OVERRIDE) < 0) { + this.errorMessages.push(REQUIRED_MSG_OVERRIDE); + } + if (!this.required) { + this.errorMessages = this.errorMessages.filter(e => e !== REQUIRED_MSG_OVERRIDE); } }); } @@ -38,4 +47,8 @@ export class CustomStringComponent extends StringWidget implements AfterViewInit return req; } + + getError(error: string): string { + return HARD_CODED_REQUIRED_MSG.test(error) ? REQUIRED_MSG_OVERRIDE : error; + } } diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.html b/ui/src/app/schema-form/widget/textarea/textarea.component.html index 3705c4009..d47d6007d 100644 --- a/ui/src/app/schema-form/widget/textarea/textarea.component.html +++ b/ui/src/app/schema-form/widget/textarea/textarea.component.html @@ -21,7 +21,7 @@ , - error + error diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.ts b/ui/src/app/schema-form/widget/textarea/textarea.component.ts index 81d940bde..fdbce4b35 100644 --- a/ui/src/app/schema-form/widget/textarea/textarea.component.ts +++ b/ui/src/app/schema-form/widget/textarea/textarea.component.ts @@ -4,6 +4,7 @@ import { TextAreaWidget } from 'ngx-schema-form'; import { SchemaService } from '../../service/schema.service'; import { Subscription } from 'rxjs'; import { startWith } from 'rxjs/operators'; +import { HARD_CODED_REQUIRED_MSG } from '../../model/messages'; @Component({ selector: 'textarea-component', @@ -22,7 +23,7 @@ export class CustomTextAreaComponent extends TextAreaWidget implements AfterView ngAfterViewInit(): void { super.ngAfterViewInit(); this.errorSub = this.control.valueChanges.pipe(startWith(this.control.value)).subscribe(v => { - if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { + if (!v && this.required && !this.errorMessages.some(msg => HARD_CODED_REQUIRED_MSG.test(msg))) { this.errorMessages.push('message.required'); } }); @@ -35,4 +36,8 @@ export class CustomTextAreaComponent extends TextAreaWidget implements AfterView get required(): boolean { return this.widgetService.isRequired(this.formProperty); } + + getError(error: string): string { + return error.match('required').length ? 'message.required' : error; + } } diff --git a/ui/src/assets/schema/provider/filebacked-http-filters.schema.json b/ui/src/assets/schema/provider/filebacked-http-filters.schema.json index 4bb2f399c..820063bfa 100644 --- a/ui/src/assets/schema/provider/filebacked-http-filters.schema.json +++ b/ui/src/assets/schema/provider/filebacked-http-filters.schema.json @@ -50,8 +50,7 @@ "title": "label.certificate-file", "description": "tooltip.certificate-file", "type": "string", - "widget": "textline", - "default": "" + "widget": "textline" } }, "anyOf": [ @@ -59,6 +58,10 @@ "properties": { "requireSignedRoot": { "enum": [ true ] + }, + "certificateFile": { + "minLength": 1, + "type": "string" } }, "required": [ From 1a8aa5861292e12eb299eead4fb4a6fc69dad52a Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 23 Jan 2019 10:46:02 -0700 Subject: [PATCH 22/31] Created new build script for error page --- ui/build.js | 20 ++++++++++++++++++++ ui/package-lock.json | 26 ++++++++++++++++++++++++++ ui/package.json | 7 ++++--- ui/src/{static.html => error.html} | 0 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 ui/build.js rename ui/src/{static.html => error.html} (100%) diff --git a/ui/build.js b/ui/build.js new file mode 100644 index 000000000..e68d3d848 --- /dev/null +++ b/ui/build.js @@ -0,0 +1,20 @@ +const fs = require('fs-extra'); + +fs.ensureDir('./dist/unsecured').then(function () { + try { + fs.copySync('./src/error.html', './dist/unsecured/error.html') + console.log('copy error page success!') + } catch (err) { + console.error(err) + } + + try { + fs.copySync('./node_modules/font-awesome/fonts', './dist/unsecured'); + console.log('copy fonts success!') + } catch (err) { + console.log(err); + } +}); + + + diff --git a/ui/package-lock.json b/ui/package-lock.json index 19c42e7e3..af48d3084 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -6747,6 +6747,17 @@ "null-check": "1.0.0" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.2" + } + }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -9072,6 +9083,15 @@ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -13370,6 +13390,12 @@ "imurmurhash": "0.1.4" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/ui/package.json b/ui/package.json index afe00523b..6f18e59dd 100644 --- a/ui/package.json +++ b/ui/package.json @@ -9,9 +9,9 @@ "test": "ng test --code-coverage", "lint": "ng lint", "e2e": "ng e2e", - "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", + "build:static": "node-sass src/static.scss ./dist/unsecured/static.css", + "copy:static": "node ./build", + "copy": "npm run copy:static && npm run build:static", "buildProd": "ng build --prod && npm run copy", "bundle-report": "webpack-bundle-analyzer dist/stats.json" }, @@ -55,6 +55,7 @@ "@types/jasminewd2": "~2.0.2", "@types/node": "~6.0.60", "codelyzer": "~4.2.1", + "fs-extra": "^7.0.1", "jasmine-core": "~2.99.0", "jasmine-marbles": "^0.3.1", "jasmine-spec-reporter": "~4.1.0", diff --git a/ui/src/static.html b/ui/src/error.html similarity index 100% rename from ui/src/static.html rename to ui/src/error.html From 983780d9619fb630ab7c7b77f0033af72a7a48cb Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 23 Jan 2019 11:13:29 -0700 Subject: [PATCH 23/31] [SHIBUI-1179] Replaced static.html web security entry with a new /unsecured/**/* entry. Updated new user redirect to redirect to /unsecured/error.html. Updated tests. --- .../main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java | 2 +- .../src/main/java/net/unicon/shibui/pac4j/WebSecurity.java | 4 ++-- .../net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy | 2 +- 3 files changed, 4 insertions(+), 4 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 5c5bf6e12..9ff528d30 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,7 +75,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha user = persistedUser.get(); } if (user.getRole().equals(ROLE_NONE)) { - ((HttpServletResponse) response).sendRedirect("/static.html"); + ((HttpServletResponse) response).sendRedirect("/unsecured/error.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/WebSecurity.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java index e0e156eec..4b1c549f7 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 @@ -35,10 +35,10 @@ protected void configure(HttpSecurity http) throws Exception { @Configuration @Order(1) - public static class StaticSecurityConfiguration extends WebSecurityConfigurerAdapter { + public static class UnsecuredSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { - http.antMatcher("/static.html").authorizeRequests().antMatchers("/static.html").permitAll(); + http.antMatcher("/unsecured/**/*").authorizeRequests().antMatchers("/unsecured/**/*").permitAll(); } } 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 index 98c5e99f3..16c5fa42c 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -52,7 +52,7 @@ class AddNewUserFilterTests extends Specification { 1 * roleRepository.save(_) 1 * userRepository.save(_) 1 * emailService.sendNewUserMail('newUser') - 1 * response.sendRedirect("/static.html") + 1 * response.sendRedirect("/unsecured/error.html") } def "existing users are not redirected"() { From ce1f0b9686638282cb2468301a343f5e725b4e93 Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Wed, 23 Jan 2019 15:18:50 -0700 Subject: [PATCH 24/31] SHIBUI-1171: Updated Refresh Dely form field to string with option regex match; --- .../dynamic-http-metadata-provider.schema.json | 10 ++++------ .../file-system-metadata-provider.schema.json | 9 +++------ backend/src/main/resources/i18n/messages_en.properties | 1 + .../local-dynamic-metadata-provider.schema.json | 10 ++++------ 4 files changed, 12 insertions(+), 18 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 167e79ab1..d346c5393 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -119,14 +119,12 @@ "description": "tooltip.refresh-delay-factor", "type": "string", "widget": { - "id": "number", - "step": 0.01 + "id": "string", + "help": "message.real-number" }, "placeholder": "label.real-number", - "minimum": 0, - "maximum": 1, - "default": null, - "pattern": "^([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)$" + "default": "", + "pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$" }, "minCacheDuration": { "title": "label.min-cache-duration", 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 b4722fc1d..834c75a3c 100644 --- a/backend/src/main/resources/file-system-metadata-provider.schema.json +++ b/backend/src/main/resources/file-system-metadata-provider.schema.json @@ -130,14 +130,11 @@ "description": "tooltip.refresh-delay-factor", "type": "string", "widget": { - "id": "number", - "step": 0.01 + "id": "string", + "help": "message.real-number" }, "placeholder": "label.real-number", - "minimum": 0, - "maximum": 1, - "default": null, - "pattern": "^([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)$" + "pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$" } } } diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index f7c238fb5..2d90ae43d 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -392,6 +392,7 @@ message.name-must-be-unique=Name must be unique. message.uri-valid-format=URI must be valid format. message.id-unique=ID must be unique. message.array-items-must-be-unique=Items in list must be unique. +message.real-number=Optional. If using a value, must be a real number between 0-1. message.org-name-required=Organization Name is required. message.org-displayName-required=Organization Name is required. 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 b3c193dcf..b3414be39 100644 --- a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json +++ b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json @@ -63,14 +63,12 @@ "description": "tooltip.refresh-delay-factor", "type": "string", "widget": { - "id": "number", - "step": 0.01 + "id": "string", + "help": "message.real-number" }, "placeholder": "label.real-number", - "minimum": 0, - "maximum": 1, - "default": null, - "pattern": "^([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)$" + "default": "", + "pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$" }, "minCacheDuration": { "title": "label.min-cache-duration", From f4e186af02adb53b2eaedafe580b0c9baf54c483 Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Wed, 23 Jan 2019 15:27:09 -0700 Subject: [PATCH 25/31] SHIBUI-1171: Added default value to filesystem metadata provider refresh delay factor form field json schema; --- .../src/main/resources/file-system-metadata-provider.schema.json | 1 + 1 file changed, 1 insertion(+) 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 834c75a3c..cbfec6b8c 100644 --- a/backend/src/main/resources/file-system-metadata-provider.schema.json +++ b/backend/src/main/resources/file-system-metadata-provider.schema.json @@ -134,6 +134,7 @@ "help": "message.real-number" }, "placeholder": "label.real-number", + "default": "", "pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$" } } From d6ff6ddbbad6dcdc9e8856a630dc896f9e441322 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 24 Jan 2019 14:57:56 -0700 Subject: [PATCH 26/31] [SHIBUI-1029] Added a filter to remove admins that have no email addressees from the new user email sending. --- .../tier/shibboleth/admin/ui/service/EmailServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 6e8d20f28..e21051abd 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 @@ -2,6 +2,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.User; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.support.ResourceBundleMessageSource; @@ -72,6 +73,6 @@ public String[] getSystemAdminEmailAddresses() { 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); + return systemAdmins.stream().filter(user -> StringUtils.isNotBlank(user.getEmailAddress())).map(User::getEmailAddress).distinct().toArray(String[]::new); } } From 29d948d8a0a5f65d52d199178c913a30da3794ba Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 24 Jan 2019 17:52:24 -0700 Subject: [PATCH 27/31] [SHIBUI-1029] Custom user attributes mapping WIP. Need to deal with some casting issues. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 41 ++++++++++++------- .../pac4j/CustomPropertiesConfiguration.java | 26 ++++++++++++ .../net/unicon/shibui/pac4j/WebSecurity.java | 10 +++-- .../main/resources/META-INF/spring.factories | 3 +- .../src/main/resources/application.yml | 6 +++ 5 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java create mode 100644 pac4j-module/src/main/resources/application.yml 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 5c5bf6e12..d30acf5bd 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 @@ -7,14 +7,11 @@ 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.pac4j.saml.profile.SAML2Profile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.mail.MessagingException; import javax.servlet.Filter; @@ -25,6 +22,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Map; import java.util.Optional; /** @@ -40,32 +38,47 @@ public class AddNewUserFilter implements Filter { private RoleRepository roleRepository; private EmailService emailService; - public AddNewUserFilter(UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { + private CustomPropertiesConfiguration customPropertiesConfiguration; + + private Map saml2ProfileMapping; + + public AddNewUserFilter(CustomPropertiesConfiguration customPropertiesConfiguration, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { this.userRepository = userRepository; this.roleRepository = roleRepository; this.emailService = emailService; + this.customPropertiesConfiguration = customPropertiesConfiguration; + saml2ProfileMapping = this.customPropertiesConfiguration.getSaml2ProfileMapping(); } @Override public void init(FilterConfig filterConfig) throws ServletException { } + private User buildAndPersistNewUserFromProfile(Map attributes) { + Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE)); + roleRepository.save(noRole); + + User user = new User(); + user.getRoles().add(noRole); + user.setUsername((String) attributes.get(saml2ProfileMapping.get("username"))); + user.setFirstName((String) attributes.get(saml2ProfileMapping.get("firstName"))); + user.setLastName((String) attributes.get(saml2ProfileMapping.get("lastName"))); + user.setEmailAddress((String) attributes.get(saml2ProfileMapping.get("email"))); + userRepository.save(user); + return user; + } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - String username = authentication.getName(); + SAML2Profile profile = (SAML2Profile) authentication.getPrincipal(); + if (profile != null) { + String username = (String) profile.getAttribute(saml2ProfileMapping.get("username")); 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)); - roleRepository.save(noRole); - user.getRoles().add(noRole); - userRepository.save(user); + user = buildAndPersistNewUserFromProfile(profile.getAttributes()); try { emailService.sendNewUserMail(username); } catch (MessagingException e) { diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java new file mode 100644 index 000000000..9d0f745b6 --- /dev/null +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java @@ -0,0 +1,26 @@ +package net.unicon.shibui.pac4j; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Component +@ConfigurationProperties(prefix="custom") +public class CustomPropertiesConfiguration { + + private Map saml2ProfileMapping; + + public Map getSaml2ProfileMapping() { + return saml2ProfileMapping; + } + + public void setSaml2ProfileMapping(Map saml2ProfileMapping) { + this.saml2ProfileMapping = saml2ProfileMapping; + } +} 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 e0e156eec..421dffe63 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 @@ -20,8 +20,8 @@ @AutoConfigureOrder(-1) public class WebSecurity { @Bean("webSecurityConfig") - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { - return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService); + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, CustomPropertiesConfiguration customPropertiesConfiguration) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, customPropertiesConfiguration); } @Configuration @@ -57,12 +57,14 @@ public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigu private UserRepository userRepository; private RoleRepository roleRepository; private EmailService emailService; + private CustomPropertiesConfiguration customPropertiesConfiguration; - public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, CustomPropertiesConfiguration customPropertiesConfiguration) { this.config = config; this.userRepository = userRepository; this.roleRepository = roleRepository; this.emailService = emailService; + this.customPropertiesConfiguration = customPropertiesConfiguration; } @Override @@ -72,7 +74,7 @@ protected void configure(HttpSecurity http) throws Exception { final CallbackFilter callbackFilter = new CallbackFilter(this.config); http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) .addFilterBefore(securityFilter, BasicAuthenticationFilter.class) - .addFilterAfter(new AddNewUserFilter(userRepository, roleRepository, emailService), SecurityFilter.class); + .addFilterAfter(new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService), SecurityFilter.class); http.authorizeRequests().anyRequest().fullyAuthenticated(); diff --git a/pac4j-module/src/main/resources/META-INF/spring.factories b/pac4j-module/src/main/resources/META-INF/spring.factories index 90f792d84..f46ede845 100644 --- a/pac4j-module/src/main/resources/META-INF/spring.factories +++ b/pac4j-module/src/main/resources/META-INF/spring.factories @@ -1,4 +1,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ net.unicon.shibui.pac4j.Pac4jConfiguration,\ net.unicon.shibui.pac4j.WebSecurity,\ - net.unicon.shibui.pac4j.Pac4jConfigurationProperties \ No newline at end of file + net.unicon.shibui.pac4j.Pac4jConfigurationProperties,\ + net.unicon.shibui.pac4j.CustomPropertiesConfiguration \ No newline at end of file diff --git a/pac4j-module/src/main/resources/application.yml b/pac4j-module/src/main/resources/application.yml new file mode 100644 index 000000000..7ffeb8bf9 --- /dev/null +++ b/pac4j-module/src/main/resources/application.yml @@ -0,0 +1,6 @@ +custom: + saml2ProfileMapping: + username: urn:oid:0.9.2342.19200300.100.1.3 + firstname: givenName + lastname: sn + email: mail \ No newline at end of file From 0d35c923a5b75a47e136dacc5a3b147b382cef0c Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 25 Jan 2019 11:07:27 -0700 Subject: [PATCH 28/31] [SHIBUI-1029] More WIP, trying to get test properties to populate. --- .../unicon/shibui/pac4j/AddNewUserFilterTests.groovy | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 index 98c5e99f3..526785d72 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -5,9 +5,13 @@ 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.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.annotation.ComponentScan import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContext import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.test.context.ContextConfiguration import spock.lang.Specification import spock.lang.Subject @@ -18,6 +22,9 @@ import javax.servlet.http.HttpServletResponse /** * @author Bill Smith (wsmith@unicon.net) */ +@SpringBootTest +@ComponentScan("net.unicon.shibui.pac4j") +@ContextConfiguration(classes=[CustomPropertiesConfiguration]) class AddNewUserFilterTests extends Specification { UserRepository userRepository = Mock() @@ -31,8 +38,10 @@ class AddNewUserFilterTests extends Specification { SecurityContext securityContext = Mock() Authentication authentication = Mock() + CustomPropertiesConfiguration customPropertiesConfiguration + @Subject - AddNewUserFilter addNewUserFilter = new AddNewUserFilter(userRepository, roleRepository, emailService) + AddNewUserFilter addNewUserFilter = new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService) def setup() { SecurityContextHolder.setContext(securityContext) From cb0b202056ec5dda302bc1b6a12b44518d179bc1 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 25 Jan 2019 12:08:30 -0700 Subject: [PATCH 29/31] Added validation to datalists --- .../widget/datalist/datalist.component.html | 6 ++++++ .../widget/datalist/datalist.component.ts | 5 +++++ .../shared/autocomplete/autocomplete.component.scss | 13 +++++++++++++ 3 files changed, 24 insertions(+) diff --git a/ui/src/app/schema-form/widget/datalist/datalist.component.html b/ui/src/app/schema-form/widget/datalist/datalist.component.html index 6ec585342..ece3d7878 100644 --- a/ui/src/app/schema-form/widget/datalist/datalist.component.html +++ b/ui/src/app/schema-form/widget/datalist/datalist.component.html @@ -21,4 +21,10 @@ role="textbox" [attr.aria-label]="schema.title | translate"> + + + , + error + + diff --git a/ui/src/app/schema-form/widget/datalist/datalist.component.ts b/ui/src/app/schema-form/widget/datalist/datalist.component.ts index 3393bd2c5..aecbe2fc3 100644 --- a/ui/src/app/schema-form/widget/datalist/datalist.component.ts +++ b/ui/src/app/schema-form/widget/datalist/datalist.component.ts @@ -2,6 +2,7 @@ import { Component, AfterViewInit } from '@angular/core'; import { ControlWidget } from 'ngx-schema-form'; import { SchemaService } from '../../service/schema.service'; +import { HARD_CODED_REQUIRED_MSG } from '../../model/messages'; @Component({ selector: 'datalist-component', @@ -26,4 +27,8 @@ export class DatalistComponent extends ControlWidget implements AfterViewInit { get required(): boolean { return this.widgetService.isRequired(this.formProperty); } + + getError(error: string): string { + return HARD_CODED_REQUIRED_MSG.test(error) ? 'message.required' : error; + } } diff --git a/ui/src/app/shared/autocomplete/autocomplete.component.scss b/ui/src/app/shared/autocomplete/autocomplete.component.scss index b721d9ea2..f794dce0c 100644 --- a/ui/src/app/shared/autocomplete/autocomplete.component.scss +++ b/ui/src/app/shared/autocomplete/autocomplete.component.scss @@ -1,5 +1,6 @@ @import "~bootstrap/scss/functions"; @import "~bootstrap/scss/variables"; +@import "../../../theme/palette"; .dropdown.form-group { margin-bottom: 0px; @@ -14,4 +15,16 @@ .btn-outline-secondary { border-color: $input-border-color; } + + &.is-invalid { + input.form-control { + border-color: $brand-danger; + } + } + + &.is-valid { + input.form-control { + border-color: $brand-success; + } + } } \ No newline at end of file From 127bfcf405808fbb07dd37960dc3660e253f9435 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 25 Jan 2019 12:28:26 -0700 Subject: [PATCH 30/31] [SHIBUI-1029] Updated tests with support for new custom user mappings. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 37 ++++++++++++++----- .../pac4j/CustomPropertiesConfiguration.java | 4 +- .../src/main/resources/application.yml | 4 +- .../shibui/pac4j/AddNewUserFilterTests.groovy | 28 +++++++++----- 4 files changed, 51 insertions(+), 22 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 d30acf5bd..af369937f 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 @@ -7,11 +7,13 @@ 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.lang3.RandomStringUtils; import org.pac4j.saml.profile.SAML2Profile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; @@ -22,6 +24,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -54,18 +57,22 @@ public AddNewUserFilter(CustomPropertiesConfiguration customPropertiesConfigurat public void init(FilterConfig filterConfig) throws ServletException { } - private User buildAndPersistNewUserFromProfile(Map attributes) { + private User buildAndPersistNewUserFromProfile(SAML2Profile profile) { Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE)); roleRepository.save(noRole); User user = new User(); user.getRoles().add(noRole); - user.setUsername((String) attributes.get(saml2ProfileMapping.get("username"))); - user.setFirstName((String) attributes.get(saml2ProfileMapping.get("firstName"))); - user.setLastName((String) attributes.get(saml2ProfileMapping.get("lastName"))); - user.setEmailAddress((String) attributes.get(saml2ProfileMapping.get("email"))); - userRepository.save(user); - return user; + user.setUsername(getAttributeFromProfile(profile, "username")); + user.setPassword(BCrypt.hashpw(RandomStringUtils.randomAlphanumeric(20), BCrypt.gensalt())); + user.setFirstName(getAttributeFromProfile(profile, "firstName")); + user.setLastName(getAttributeFromProfile(profile, "lastName")); + user.setEmailAddress(getAttributeFromProfile(profile, "email")); + User persistedUser = userRepository.save(user); + if (logger.isDebugEnabled()) { + logger.debug("Persisted new user:\n" + user); + } + return persistedUser; } @Override @@ -73,12 +80,12 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); SAML2Profile profile = (SAML2Profile) authentication.getPrincipal(); if (profile != null) { - String username = (String) profile.getAttribute(saml2ProfileMapping.get("username")); + String username = getAttributeFromProfile(profile, "username"); if (username != null) { Optional persistedUser = userRepository.findByUsername(username); User user; if (!persistedUser.isPresent()) { - user = buildAndPersistNewUserFromProfile(profile.getAttributes()); + user = buildAndPersistNewUserFromProfile(profile); try { emailService.sendNewUserMail(username); } catch (MessagingException e) { @@ -100,6 +107,18 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha public void destroy() { } + private String getAttributeFromProfile(SAML2Profile profile, String stringKey) { + String mappingKey = saml2ProfileMapping.get(stringKey); + List attributeList = (List) profile.getAttribute(mappingKey); + String attribute = null; + if (attributeList.size() > 0) { + if (attributeList.size() != 1) { + logger.warn(String.format("More than one attribute was found for key [%s]", stringKey)); + } + attribute = attributeList.get(0); + } + return attribute; + } 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/CustomPropertiesConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java index 9d0f745b6..793b43793 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java @@ -1,10 +1,9 @@ package net.unicon.shibui.pac4j; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; -import java.util.HashMap; import java.util.Map; /** @@ -12,6 +11,7 @@ */ @Component @ConfigurationProperties(prefix="custom") +@EnableConfigurationProperties public class CustomPropertiesConfiguration { private Map saml2ProfileMapping; diff --git a/pac4j-module/src/main/resources/application.yml b/pac4j-module/src/main/resources/application.yml index 7ffeb8bf9..ae30333c7 100644 --- a/pac4j-module/src/main/resources/application.yml +++ b/pac4j-module/src/main/resources/application.yml @@ -1,6 +1,6 @@ custom: saml2ProfileMapping: username: urn:oid:0.9.2342.19200300.100.1.3 - firstname: givenName - lastname: sn + firstName: givenName + lastName: sn email: mail \ No newline at end of file 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 index 526785d72..652b38783 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -5,13 +5,13 @@ 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.pac4j.saml.profile.SAML2Profile import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.test.context.SpringBootTest -import org.springframework.context.annotation.ComponentScan import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContext import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.test.context.ContextConfiguration import spock.lang.Specification import spock.lang.Subject @@ -22,9 +22,8 @@ import javax.servlet.http.HttpServletResponse /** * @author Bill Smith (wsmith@unicon.net) */ -@SpringBootTest -@ComponentScan("net.unicon.shibui.pac4j") -@ContextConfiguration(classes=[CustomPropertiesConfiguration]) +@SpringBootTest(classes = [CustomPropertiesConfiguration]) +@EnableConfigurationProperties([CustomPropertiesConfiguration]) class AddNewUserFilterTests extends Specification { UserRepository userRepository = Mock() @@ -37,20 +36,31 @@ class AddNewUserFilterTests extends Specification { SecurityContext securityContext = Mock() Authentication authentication = Mock() + SAML2Profile saml2Profile = Mock() + @Autowired CustomPropertiesConfiguration customPropertiesConfiguration + Map userAttributeMapping + @Subject - AddNewUserFilter addNewUserFilter = new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService) + AddNewUserFilter addNewUserFilter def setup() { SecurityContextHolder.setContext(securityContext) securityContext.getAuthentication() >> authentication + authentication.getPrincipal() >> saml2Profile + + addNewUserFilter = new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService) + userAttributeMapping = customPropertiesConfiguration.saml2ProfileMapping } def "new users are redirected"() { given: - authentication.getName() >> 'newUser' + saml2Profile.getAttribute(userAttributeMapping.get('username')) >> ['newUser'] + saml2Profile.getAttribute(userAttributeMapping.get('firstName')) >> ['New'] + saml2Profile.getAttribute(userAttributeMapping.get('lastName')) >> ['User'] + saml2Profile.getAttribute(userAttributeMapping.get('email')) >> ['newuser@institution.edu'] userRepository.findByUsername('newUser') >> Optional.empty() roleRepository.findByName('ROLE_NONE') >> Optional.of(new Role('ROLE_NONE')) @@ -59,14 +69,14 @@ class AddNewUserFilterTests extends Specification { then: 1 * roleRepository.save(_) - 1 * userRepository.save(_) + 1 * userRepository.save(_ as User) >> { User user -> user } 1 * emailService.sendNewUserMail('newUser') 1 * response.sendRedirect("/static.html") } def "existing users are not redirected"() { given: - authentication.getName() >> 'existingUser' + saml2Profile.getAttribute(userAttributeMapping.get('username')) >> ['existingUser'] userRepository.findByUsername('existingUser') >> Optional.of(new User().with { it.username = 'existingUser' it.roles = [new Role('ROLE_USER')] From 7856be12667b1c2967d2324d6d06db595c63bdce Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 25 Jan 2019 15:35:14 -0700 Subject: [PATCH 31/31] [SHIBUI-1029] Consolidated properties into Pac4jConfigurationProperties. Refactored tests and filter to use new properties. --- .../unicon/shibui/pac4j/AddNewUserFilter.java | 36 ++++++++----- .../pac4j/CustomPropertiesConfiguration.java | 26 ---------- .../shibui/pac4j/Pac4jConfiguration.java | 1 + .../pac4j/Pac4jConfigurationProperties.java | 50 +++++++++++++++++++ .../net/unicon/shibui/pac4j/WebSecurity.java | 12 ++--- .../src/main/resources/application.yml | 13 ++--- .../shibui/pac4j/AddNewUserFilterTests.groovy | 24 +++++---- 7 files changed, 100 insertions(+), 62 deletions(-) delete mode 100644 pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.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 af369937f..27275870d 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 @@ -41,16 +41,16 @@ public class AddNewUserFilter implements Filter { private RoleRepository roleRepository; private EmailService emailService; - private CustomPropertiesConfiguration customPropertiesConfiguration; + private Pac4jConfigurationProperties pac4jConfigurationProperties; - private Map saml2ProfileMapping; + private Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping; - public AddNewUserFilter(CustomPropertiesConfiguration customPropertiesConfiguration, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { + public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { this.userRepository = userRepository; this.roleRepository = roleRepository; this.emailService = emailService; - this.customPropertiesConfiguration = customPropertiesConfiguration; - saml2ProfileMapping = this.customPropertiesConfiguration.getSaml2ProfileMapping(); + this.pac4jConfigurationProperties = pac4jConfigurationProperties; + saml2ProfileMapping = this.pac4jConfigurationProperties.getSaml2ProfileMapping(); } @Override @@ -108,17 +108,27 @@ public void destroy() { } private String getAttributeFromProfile(SAML2Profile profile, String stringKey) { - String mappingKey = saml2ProfileMapping.get(stringKey); - List attributeList = (List) profile.getAttribute(mappingKey); String attribute = null; - if (attributeList.size() > 0) { - if (attributeList.size() != 1) { - logger.warn(String.format("More than one attribute was found for key [%s]", stringKey)); - } - attribute = attributeList.get(0); + switch (stringKey) { + case "username": + attribute = saml2ProfileMapping.getUsername(); + break; + case "firstName": + attribute = saml2ProfileMapping.getFirstName(); + break; + case "lastName": + attribute = saml2ProfileMapping.getLastName(); + break; + case "email": + attribute = saml2ProfileMapping.getEmail(); + break; + default: + // do we care? Not yet. } - return attribute; + List attributeList = (List) profile.getAttribute(attribute); + return attributeList.size() < 1 ? null : attributeList.get(0); } + 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/CustomPropertiesConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java deleted file mode 100644 index 793b43793..000000000 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/CustomPropertiesConfiguration.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.unicon.shibui.pac4j; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Bill Smith (wsmith@unicon.net) - */ -@Component -@ConfigurationProperties(prefix="custom") -@EnableConfigurationProperties -public class CustomPropertiesConfiguration { - - private Map saml2ProfileMapping; - - public Map getSaml2ProfileMapping() { - return saml2ProfileMapping; - } - - public void setSaml2ProfileMapping(Map saml2ProfileMapping) { - this.saml2ProfileMapping = saml2ProfileMapping; - } -} diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java index 1c28b1ee1..5ee638178 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java @@ -22,6 +22,7 @@ public Config config(final Pac4jConfigurationProperties pac4jConfigurationProper saml2ClientConfiguration.setServiceProviderMetadataPath(pac4jConfigurationProperties.getServiceProviderMetadataPath()); saml2ClientConfiguration.setForceServiceProviderMetadataGeneration(pac4jConfigurationProperties.isForceServiceProviderMetadataGeneration()); saml2ClientConfiguration.setWantsAssertionsSigned(pac4jConfigurationProperties.isWantAssertionsSigned()); + saml2ClientConfiguration.setAttributeAsId(pac4jConfigurationProperties.getSaml2ProfileMapping().getUsername()); final SAML2Client saml2Client = new SAML2Client(saml2ClientConfiguration); saml2Client.setName("Saml2Client"); diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java index afb1369a1..cf36d8e65 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java @@ -1,10 +1,12 @@ package net.unicon.shibui.pac4j; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "shibui.pac4j") +@EnableConfigurationProperties public class Pac4jConfigurationProperties { private String keystorePath = "/tmp/samlKeystore.jks"; private String keystorePassword = "changeit"; @@ -16,6 +18,46 @@ public class Pac4jConfigurationProperties { private boolean forceServiceProviderMetadataGeneration = false; private String callbackUrl; private boolean wantAssertionsSigned = true; + private SAML2ProfileMapping saml2ProfileMapping; + + public static class SAML2ProfileMapping { + private String username; + private String email; + private String firstName; + private String lastName; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + } public String getKeystorePath() { return keystorePath; @@ -96,4 +138,12 @@ public boolean isWantAssertionsSigned() { public void setWantAssertionsSigned(boolean wantAssertionsSigned) { this.wantAssertionsSigned = wantAssertionsSigned; } + + public SAML2ProfileMapping getSaml2ProfileMapping() { + return saml2ProfileMapping; + } + + public void setSaml2ProfileMapping(SAML2ProfileMapping saml2ProfileMapping) { + this.saml2ProfileMapping = saml2ProfileMapping; + } } 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 421dffe63..f0c183175 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 @@ -20,8 +20,8 @@ @AutoConfigureOrder(-1) public class WebSecurity { @Bean("webSecurityConfig") - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, CustomPropertiesConfiguration customPropertiesConfiguration) { - return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, customPropertiesConfiguration); + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, Pac4jConfigurationProperties pac4jConfigurationProperties) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService, pac4jConfigurationProperties); } @Configuration @@ -57,14 +57,14 @@ public static class Pac4jWebSecurityConfigurerAdapter extends WebSecurityConfigu private UserRepository userRepository; private RoleRepository roleRepository; private EmailService emailService; - private CustomPropertiesConfiguration customPropertiesConfiguration; + private Pac4jConfigurationProperties pac4jConfigurationProperties; - public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, CustomPropertiesConfiguration customPropertiesConfiguration) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService, Pac4jConfigurationProperties pac4jConfigurationProperties) { this.config = config; this.userRepository = userRepository; this.roleRepository = roleRepository; this.emailService = emailService; - this.customPropertiesConfiguration = customPropertiesConfiguration; + this.pac4jConfigurationProperties = pac4jConfigurationProperties; } @Override @@ -74,7 +74,7 @@ protected void configure(HttpSecurity http) throws Exception { final CallbackFilter callbackFilter = new CallbackFilter(this.config); http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) .addFilterBefore(securityFilter, BasicAuthenticationFilter.class) - .addFilterAfter(new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService), SecurityFilter.class); + .addFilterAfter(new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, emailService), SecurityFilter.class); http.authorizeRequests().anyRequest().fullyAuthenticated(); diff --git a/pac4j-module/src/main/resources/application.yml b/pac4j-module/src/main/resources/application.yml index ae30333c7..4042fd939 100644 --- a/pac4j-module/src/main/resources/application.yml +++ b/pac4j-module/src/main/resources/application.yml @@ -1,6 +1,7 @@ -custom: - saml2ProfileMapping: - username: urn:oid:0.9.2342.19200300.100.1.3 - firstName: givenName - lastName: sn - email: mail \ No newline at end of file +shibui: + pac4j: + saml2ProfileMapping: + username: urn:oid:0.9.2342.19200300.100.1.3 + firstName: givenName + lastName: sn + email: mail \ No newline at end of file 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 index 652b38783..ac470ea25 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -22,8 +22,8 @@ import javax.servlet.http.HttpServletResponse /** * @author Bill Smith (wsmith@unicon.net) */ -@SpringBootTest(classes = [CustomPropertiesConfiguration]) -@EnableConfigurationProperties([CustomPropertiesConfiguration]) +@SpringBootTest(classes = [Pac4jConfigurationProperties]) +@EnableConfigurationProperties([Pac4jConfigurationProperties]) class AddNewUserFilterTests extends Specification { UserRepository userRepository = Mock() @@ -39,9 +39,9 @@ class AddNewUserFilterTests extends Specification { SAML2Profile saml2Profile = Mock() @Autowired - CustomPropertiesConfiguration customPropertiesConfiguration + Pac4jConfigurationProperties pac4jConfigurationProperties - Map userAttributeMapping + Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping @Subject AddNewUserFilter addNewUserFilter @@ -51,16 +51,18 @@ class AddNewUserFilterTests extends Specification { securityContext.getAuthentication() >> authentication authentication.getPrincipal() >> saml2Profile - addNewUserFilter = new AddNewUserFilter(customPropertiesConfiguration, userRepository, roleRepository, emailService) - userAttributeMapping = customPropertiesConfiguration.saml2ProfileMapping + addNewUserFilter = new AddNewUserFilter(pac4jConfigurationProperties, userRepository, roleRepository, emailService) + saml2ProfileMapping = pac4jConfigurationProperties.saml2ProfileMapping } def "new users are redirected"() { given: - saml2Profile.getAttribute(userAttributeMapping.get('username')) >> ['newUser'] - saml2Profile.getAttribute(userAttributeMapping.get('firstName')) >> ['New'] - saml2Profile.getAttribute(userAttributeMapping.get('lastName')) >> ['User'] - saml2Profile.getAttribute(userAttributeMapping.get('email')) >> ['newuser@institution.edu'] + ['Username': 'newUser', + 'FirstName': 'New', + 'LastName': 'User', + 'Email': 'newuser@institution.edu'].each { key, value -> + saml2Profile.getAttribute(saml2ProfileMapping."get${key}"()) >> [value] + } userRepository.findByUsername('newUser') >> Optional.empty() roleRepository.findByName('ROLE_NONE') >> Optional.of(new Role('ROLE_NONE')) @@ -76,7 +78,7 @@ class AddNewUserFilterTests extends Specification { def "existing users are not redirected"() { given: - saml2Profile.getAttribute(userAttributeMapping.get('username')) >> ['existingUser'] + saml2Profile.getAttribute(saml2ProfileMapping.getUsername()) >> ['existingUser'] userRepository.findByUsername('existingUser') >> Optional.of(new User().with { it.username = 'existingUser' it.roles = [new Role('ROLE_USER')]