diff --git a/backend/build.gradle b/backend/build.gradle index 1ef670fa5..a6797b52c 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -49,6 +49,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/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/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 1c207ee38..f7c238fb5 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -432,6 +432,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/pac4j-module/build.gradle b/pac4j-module/build.gradle index 5133e4e48..8bdb9c5d6 100644 --- a/pac4j-module/build.gradle +++ b/pac4j-module/build.gradle @@ -1,8 +1,7 @@ plugins { - id 'java' + id 'groovy' 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 @@ -39,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/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..5c5bf6e12 --- /dev/null +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java @@ -0,0 +1,94 @@ +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 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.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; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +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; + private EmailService emailService; + + public AddNewUserFilter(UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { + this.userRepository = userRepository; + this.roleRepository = roleRepository; + this.emailService = emailService; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @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(); + 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); + 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)) { + ((HttpServletResponse) response).sendRedirect("/static.html"); + } else { + chain.doFilter(request, response); // else, user is in the system already, carry on + } + } + } + } + + @Override + public void 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 e3ff9d4b6..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 @@ -1,5 +1,8 @@ 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 edu.internet2.tier.shibboleth.admin.ui.service.EmailService; import org.pac4j.core.config.Config; import org.pac4j.springframework.security.web.CallbackFilter; import org.pac4j.springframework.security.web.SecurityFilter; @@ -17,8 +20,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, RoleRepository roleRepository, EmailService emailService) { + return new Pac4jWebSecurityConfigurerAdapter(config, userRepository, roleRepository, emailService); } @Configuration @@ -30,12 +33,36 @@ 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; + private RoleRepository roleRepository; + private EmailService emailService; - public Pac4jWebSecurityConfigurerAdapter(final Config config) { + public Pac4jWebSecurityConfigurerAdapter(final Config config, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) { this.config = config; + this.userRepository = userRepository; + this.roleRepository = roleRepository; + this.emailService = emailService; } @Override @@ -43,15 +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); + http.antMatcher("/**").addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) + .addFilterBefore(securityFilter, BasicAuthenticationFilter.class) + .addFilterAfter(new AddNewUserFilter(userRepository, roleRepository, emailService), SecurityFilter.class); + http.authorizeRequests().anyRequest().fullyAuthenticated(); - http.addFilterBefore(securityFilter, BasicAuthenticationFilter.class); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); - // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); - http.csrf().disable(); http.headers().frameOptions().disable(); } diff --git a/pac4j-module/src/test/docker/conf/application.yml b/pac4j-module/src/test/docker/conf/application.yml index b850afb54..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/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" 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 + + + + + + + + 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 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(_, _) + } +} 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; +}