diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle b/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle index a11b4772a..4a4572f39 100644 --- a/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle +++ b/servlet/spring-boot/java/saml2/refreshable-metadata/build.gradle @@ -13,6 +13,8 @@ repositories { maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" } } +sourceSets.main.java.srcDirs += "$projectDir/../identity-provider/src/main/java" +sourceSets.main.resources.srcDirs += "$projectDir/../identity-provider/src/main/resources" dependencies { constraints { @@ -28,6 +30,7 @@ dependencies { testImplementation 'org.htmlunit:htmlunit' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + runtimeOnly "org.springframework.boot:spring-boot-docker-compose" } tasks.withType(Test).configureEach { diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/PreDockerComposeServerPortInitializer.java b/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/PreDockerComposeServerPortInitializer.java new file mode 100644 index 000000000..96e373dba --- /dev/null +++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/PreDockerComposeServerPortInitializer.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example; + +import java.io.IOException; +import java.net.ServerSocket; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; + +/** + * Spring Boot doesn't determine the port before the docker containers are loaded, so + * we'll decide the test port here and override the associated properties. + * + * @author Josh Cummings + */ +public class PreDockerComposeServerPortInitializer implements EnvironmentPostProcessor { + + private static final Integer port = getPort(); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + environment.getPropertySources().addFirst(new ServerPortPropertySource(port)); + } + + private static Integer getPort() { + try (ServerSocket serverSocket = new ServerSocket(0)) { + return serverSocket.getLocalPort(); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static class ServerPortPropertySource extends PropertySource { + + ServerPortPropertySource(Integer port) { + super("server.port.override", port); + } + + @Override + public Object getProperty(String name) { + if ("server.port".equals(name)) { + return getSource(); + } + if ("SERVER_PORT".equals(name)) { + return getSource(); + } + return null; + } + + } + +} diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/Saml2LoginApplicationITests.java b/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/Saml2LoginApplicationITests.java index b53c389cc..49020477a 100644 --- a/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/Saml2LoginApplicationITests.java +++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/java/example/Saml2LoginApplicationITests.java @@ -16,29 +16,34 @@ package example; +import java.util.ArrayList; +import java.util.List; + import org.htmlunit.ElementNotFoundException; import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlButton; +import org.htmlunit.html.HtmlElement; import org.htmlunit.html.HtmlForm; import org.htmlunit.html.HtmlInput; import org.htmlunit.html.HtmlPage; import org.htmlunit.html.HtmlPasswordInput; -import org.htmlunit.html.HtmlSubmitInput; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.boot.test.web.server.LocalServerPort; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @AutoConfigureMockMvc public class Saml2LoginApplicationITests { - @Autowired - MockMvc mvc; + + @LocalServerPort + int port; @Autowired WebClient webClient; @@ -52,18 +57,40 @@ void setup() { void authenticationAttemptWhenValidThenShowsUserEmailAddress() throws Exception { performLogin(); HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage(); - assertThat(home.asNormalizedText()).contains("You're email address is testuser2@spring.security.saml"); + assertThat(home.asNormalizedText()).contains("You're email address is user1@example.org"); + } + + @Test + void logoutWhenRelyingPartyInitiatedLogoutThenLoginPageWithLogoutParam() throws Exception { + performLogin(); + HtmlPage home = (HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage(); + HtmlElement rpLogoutButton = home.getHtmlElementById("rp_logout_button"); + HtmlPage loginPage = rpLogoutButton.click(); + this.webClient.waitForBackgroundJavaScript(10000); + List urls = new ArrayList<>(); + urls.add(loginPage.getUrl().getFile()); + urls.add(((HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage()).getUrl().getFile()); + assertThat(urls).withFailMessage(() -> { + // @formatter:off + String builder = loginPage.asXml() + + "\n\n\n" + + "Enclosing Page" + + "\n\n\n" + + ((HtmlPage) this.webClient.getCurrentWindow().getEnclosedPage()).asXml(); + // @formatter:on + return builder; + }).contains("/login?logout"); } private void performLogin() throws Exception { - HtmlPage login = this.webClient.getPage("/"); + HtmlPage login = this.webClient.getPage("http://localhost:" + this.port + "/saml2/authenticate/one"); this.webClient.waitForBackgroundJavaScript(10000); HtmlForm form = findForm(login); HtmlInput username = form.getInputByName("username"); HtmlPasswordInput password = form.getInputByName("password"); - HtmlSubmitInput submit = login.getHtmlElementById("okta-signin-submit"); - username.type("testuser2@spring.security.saml"); - password.type("12345678"); + HtmlButton submit = (HtmlButton) form.getElementsByTagName("button").iterator().next(); + username.type("user1"); + password.type("user1pass"); submit.click(); this.webClient.waitForBackgroundJavaScript(10000); } @@ -71,7 +98,7 @@ private void performLogin() throws Exception { private HtmlForm findForm(HtmlPage login) { for (HtmlForm form : login.getForms()) { try { - if (form.getId().equals("form19")) { + if (form.getNameAttribute().equals("f")) { return form; } } diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/resources/META-INF/spring.factories b/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/resources/META-INF/spring.factories new file mode 100644 index 000000000..f4d2ae40c --- /dev/null +++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/integTest/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.env.EnvironmentPostProcessor=example.PreDockerComposeServerPortInitializer \ No newline at end of file diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/java/example/RefreshableRelyingPartyRegistrationRepository.java b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/java/example/RefreshableRelyingPartyRegistrationRepository.java index dd3eda515..e54c3100e 100644 --- a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/java/example/RefreshableRelyingPartyRegistrationRepository.java +++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/java/example/RefreshableRelyingPartyRegistrationRepository.java @@ -70,6 +70,10 @@ public void refreshMetadata() { private void fetchMetadata(String registrationId, Saml2RelyingPartyProperties.Registration registration) { RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations .fromMetadataLocation(registration.getAssertingparty().getMetadataUri()) + .entityId(registration.getEntityId()) + .assertionConsumerServiceLocation(registration.getAcs().getLocation()) + .singleLogoutServiceLocation(registration.getSinglelogout().getUrl()) + .singleLogoutServiceBinding(registration.getSinglelogout().getBinding()) .signingX509Credentials((credentials) -> registration.getSigning() .getCredentials() .stream() diff --git a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/application.yml b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/application.yml index e4d47c537..f6cedd31f 100644 --- a/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/application.yml +++ b/servlet/spring-boot/java/saml2/refreshable-metadata/src/main/resources/application.yml @@ -1,14 +1,26 @@ spring: + docker: + compose: + file: docker:docker/compose.yml + readiness: + wait: never + skip: + in-tests: false security: saml2: relyingparty: registration: one: + entity-id: "{baseUrl}/saml2/metadata" + acs.location: "{baseUrl}/login/saml2/sso" signing.credentials: - private-key-location: classpath:credentials/rp-private.key certificate-location: classpath:credentials/rp-certificate.crt + singlelogout: + binding: REDIRECT + url: "{baseUrl}/logout/saml2/slo" assertingparty: - metadata-uri: https://dev-05937739.okta.com/app/exk46xofd8NZvFCpS5d7/sso/saml/metadata + metadata-uri: http://idp-one.7f000001.nip.io/simplesaml/saml2/idp/metadata.php logging.level: org.springframework.security: TRACE