[java-idp-integration-tests] branch master updated: IDP-1549 - Negative test for anti-csrf token
Phil Smart
philip.smart at jisc.ac.uk
Wed Feb 19 08:57:57 EST 2020
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch master
in repository java-idp-integration-tests.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-integration-tests.git;a=commit;h=10cec88da6c77f2ced0f3b932ff412c39591e03f
The following commit(s) were added to refs/heads/master by this push:
new 10cec88 IDP-1549 - Negative test for anti-csrf token
10cec88 is described below
commit 10cec88da6c77f2ced0f3b932ff412c39591e03f
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Feb 19 13:46:32 2020 +0000
IDP-1549 - Negative test for anti-csrf token
Add a negative integration test for the anti-csrf token.
https://issues.shibboleth.net/jira/browse/IDP-1549
---
.DS_Store | Bin 0 -> 6148 bytes
.../shibboleth/idp/test/BaseIntegrationTest.java | 37 +++++
.../idp/test/ui/csrf/CSRFMitigationTest.java | 170 +++++++++++++++++++++
.../shibboleth/idp/test/ui/csrf/package-info.java | 21 +++
4 files changed, 228 insertions(+)
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/.DS_Store differ
diff --git a/src/test/java/net/shibboleth/idp/test/BaseIntegrationTest.java b/src/test/java/net/shibboleth/idp/test/BaseIntegrationTest.java
index 0529ac5..acda6ed 100644
--- a/src/test/java/net/shibboleth/idp/test/BaseIntegrationTest.java
+++ b/src/test/java/net/shibboleth/idp/test/BaseIntegrationTest.java
@@ -33,6 +33,7 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Properties;
import java.util.SortedSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -275,6 +276,9 @@ public abstract class BaseIntegrationTest
/** Path to conf/ldap.properties. */
@NonnullAfterInit protected Path pathToLDAPProperties;
+
+ /** Path to system/messages/messages.properties.*/
+ @NonnullAfterInit protected Path pathToMessagesProperties;
/** Path to jetty.base. */
@Nullable protected Path pathToJettyBase;
@@ -408,6 +412,11 @@ public abstract class BaseIntegrationTest
// Path to conf/ldap.properties
pathToLDAPProperties = Paths.get(pathToIdPHome.toAbsolutePath().toString(), "conf", "ldap.properties");
Assert.assertTrue(pathToLDAPProperties.toFile().exists(), "Path to conf/ldap.properties not found");
+
+ //Path to system/messages/message.properties
+ pathToMessagesProperties = pathToIdPHome.resolve(Paths.get("system", "messages","messages.properties"));
+ Assert.assertTrue(pathToMessagesProperties.toFile().exists(), "Path to message properties not found");
+ log.debug("Path to message properties '{}'", pathToMessagesProperties);
}
/**
@@ -910,6 +919,34 @@ public abstract class BaseIntegrationTest
server.stop();
}
}
+
+ /**
+ * Get a message value from the default <code>system/messages/messages.properties</code> file relating to the <code>key</code> argument.
+ * <p> Can NOT be used to get messages for different languages, only the default system message bundle.<p>
+ *
+ * @param key the key used to lookup the value.
+ * @return the value to which the specified key is mapped, or {@literal null} if the key does not exist or the
+ * value is not a {@link String}.
+ * @throws IOException if there is an error loading the messages.properties file.
+ */
+ @Nullable
+ protected String getMessage(@Nonnull @NotEmpty final String key) throws IOException {
+ Constraint.isNotNull(StringSupport.trimOrNull(key), "Replacement property key cannot be null nor empty");
+
+ log.debug("Finding message property '{}' in file '{}'", key, pathToMessagesProperties);
+
+ final FileSystemResource propertyResource =
+ new FileSystemResource(pathToMessagesProperties.toAbsolutePath().toString());
+
+ final Properties props = new Properties();
+ props.load(propertyResource.getInputStream());
+ Object propValueObject = props.get(key);
+ if (propValueObject instanceof String) {
+ return (String)propValueObject;
+ }
+ return null;
+
+ }
/**
* Replace a property in conf/idp.properties.
diff --git a/src/test/java/net/shibboleth/idp/test/ui/csrf/CSRFMitigationTest.java b/src/test/java/net/shibboleth/idp/test/ui/csrf/CSRFMitigationTest.java
new file mode 100644
index 0000000..11e1a9a
--- /dev/null
+++ b/src/test/java/net/shibboleth/idp/test/ui/csrf/CSRFMitigationTest.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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
+ *
+ * http://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 net.shibboleth.idp.test.ui.csrf;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.test.BaseIntegrationTest;
+import net.shibboleth.idp.test.BrowserData;
+
+/**
+ * Test the anti-csrf token is required when submitting the username and password form.
+ */
+public class CSRFMitigationTest extends BaseIntegrationTest {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(CSRFMitigationTest.class);
+
+ /** Title of error page where we would expect a login without CSRF token to end up. */
+ public final static String ERROR_PAGE_TITLE_PROPERTY = "idp.title";
+
+ /** The title used when displaying the invalid CSRF token page.*/
+ public final static String CSRF_ERROR_PAGE_TITLE_PROPERTY = "invalid-csrf-token.title";
+
+ /** The message show when the invalid CSRF token page is displayed.*/
+ public final static String CSRF_ERROR_PAGE_MESSAGE_PROPERTY = "invalid-csrf-token.message";
+
+ /** The name of the input element holding the anti-csrf token value.*/
+ public final static String CSRF_INPUT_ELEMENT_NAME = "csrf_token";
+
+ /** The path to the messages.properties file.*/
+ public Path pathToMessagesProperties;
+
+ @BeforeClass
+ public void setUp() throws Exception {
+
+ startFlowURLPath = "/sp/SAML2/InitSSO/Redirect";
+
+ loginPageURLPath = "/idp/profile/SAML2/Redirect/SSO";
+
+ }
+
+ /**
+ *
+ * Check that a username/password login form submitted without an anti-csrf token renders the invalid CSRF token page.
+ *
+ * @param browserData the browser data
+ * @throws Exception on exception
+ */
+ @Test(dataProvider = "sauceOnDemandBrowserDataProvider")
+ public void testCSRFTokenRemovedFromLoginPage(@Nullable final BrowserData browserData) throws Exception {
+
+ startSeleniumClient(browserData);
+
+ //make sure CSRF protection is enabled
+ replaceIdPProperty("idp.csrf.enabled", "true");
+ //make sure we are using the password flow
+ replaceIdPProperty("idp.authn.flows", "Password");
+
+ startServer();
+ startFlow();
+ waitForLoginPage();
+ removeCSRFTokenAndlogin("jdoe","changeit");
+ checkCSRFErrorPage();
+ }
+
+ /**
+ * <p>Check the CSRF error page is displayed.</p>
+ *
+ * <p> More specifically, checks the <code>div</code> element with class <code>content</code>
+ * contains text content which matches with the {@value #CSRF_ERROR_PAGE_MESSAGE_PROPERTY}
+ * system messages property.</p>
+ *
+ * @throws IOException if the messages properties file can not be loaded,
+ */
+ private void checkCSRFErrorPage() throws IOException {
+
+ final String csrfErrorMessage = getMessage(CSRF_ERROR_PAGE_MESSAGE_PROPERTY);
+ Assert.assertNotNull(csrfErrorMessage);
+ final String errorPageTitle = getMessage(ERROR_PAGE_TITLE_PROPERTY);
+ Assert.assertNotNull(errorPageTitle);
+ final String errorPageSubtitle = getMessage(CSRF_ERROR_PAGE_TITLE_PROPERTY);
+ Assert.assertNotNull(errorPageSubtitle);
+
+
+ (new WebDriverWait(driver, 5)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ return d.getTitle().equals(errorPageTitle+" - "+errorPageSubtitle);
+ }
+ });
+ Assert.assertTrue(driver.getPageSource()!=null);
+ WebElement contentElement = driver.findElement(By.xpath("//div[contains(@class,'content')]"));
+ String contentText = contentElement.getText();
+ Assert.assertTrue(csrfErrorMessage.equals(contentText));
+ }
+
+ /**
+ * Wait for the username and password login page to display (max wait is 3 seconds). Find the anti-csrf token
+ * hidden input element and unhide it using Javascript. Clear the token from the input element,
+ * fill in the form with proper credentials and submit the form.
+ *
+ * @param user the user to authenticate
+ * @param password the password of the user to authenticate
+ */
+ private void removeCSRFTokenAndlogin(final @Nonnull String user, final @Nonnull String password) {
+
+
+ //wait for page to load for max 3 seconds.
+ WebDriverWait wait = new WebDriverWait(driver, 3);
+ wait.until(x -> x.findElement(By.name("j_username")));
+
+ //check the token input exists - fail if not.
+ final List<WebElement> csrfTokenInput = driver.findElements(By.name(CSRF_INPUT_ELEMENT_NAME));
+ Assert.assertNotNull(csrfTokenInput);
+ Assert.assertEquals(csrfTokenInput.size(),1,"Could not find csrf token input element, is csrf protection enabled?");
+
+ //unhide the csrftoken hidden input using JavaScript.
+ JavascriptExecutor jse = (JavascriptExecutor) driver;
+ jse.executeScript(
+ "document.getElementsByName('" + CSRF_INPUT_ELEMENT_NAME + "')[0].setAttribute('type', 'text');");
+
+ //now clear the CSRF token.
+ final WebElement csrfToken = driver.findElement(By.name(CSRF_INPUT_ELEMENT_NAME));
+ csrfToken.clear();
+
+ //finally add username and password.
+ final WebElement usernameElement = driver.findElement(By.name("j_username"));
+ final WebElement passwordElement = driver.findElement(By.name("j_password"));
+ usernameElement.sendKeys(user);
+ passwordElement.sendKeys(password);
+
+ //submit the form.
+ submitForm();
+
+ }
+
+
+
+}
diff --git a/src/test/java/net/shibboleth/idp/test/ui/csrf/package-info.java b/src/test/java/net/shibboleth/idp/test/ui/csrf/package-info.java
new file mode 100644
index 0000000..60811b3
--- /dev/null
+++ b/src/test/java/net/shibboleth/idp/test/ui/csrf/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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
+ *
+ * http://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.
+ */
+/**
+ * Test integration flows for CSRF mitigation.
+ */
+
+package net.shibboleth.idp.test.ui.csrf;
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list