/** * Copyright (C) 2009-2014 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.http; import org.junit.Test; import java.net.URI; import java.util.List; import java.util.UUID; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class CsrfProtectionRefererFilterTest { private void assertUri(String scheme, String host, int port, URI actualUri) { assertEquals("scheme", scheme, actualUri.getScheme()); assertEquals("host", host, actualUri.getHost()); assertEquals("port", port, actualUri.getPort()); } private void assertSingleUri(String referer, String scheme, String host, int port) { List<URI> uris = CsrfProtectionRefererFilter.parseAllowedReferers(referer); assertEquals(1, uris.size()); assertUri(scheme, host, port, uris.get(0)); } private Exception assertIllegalAllowedReferers(String allowedReferers) { try { List<URI> uris = CsrfProtectionRefererFilter.parseAllowedReferers(allowedReferers); fail("Expected an exception to be thrown; " + uris); } catch (IllegalArgumentException e) { return e; } throw new RuntimeException("it can't actually get here, but the compiler's not smart enough to figure that out."); } @Test public void testParseNullAllowedReferers() { assertIllegalAllowedReferers(null); } @Test public void testParseEmptyAllowedReferers() { assertIllegalAllowedReferers(""); } @Test public void testParseEmptyListOfAllowedReferers() { assertIllegalAllowedReferers(",,,"); } @Test public void testParseOneAllowedReferers() { assertSingleUri("http://my-site.com:45", "http", "my-site.com", 45); } @Test public void testParseThreeAllowedReferers() { List<URI> uris = CsrfProtectionRefererFilter.parseAllowedReferers("http://my-site.com:45,https://other.site.com,http://wherever.edu"); assertEquals(3, uris.size()); assertUri("http", "my-site.com", 45, uris.get(0)); assertUri("https", "other.site.com", -1, uris.get(1)); assertUri("http", "wherever.edu", -1, uris.get(2)); } @Test public void testParseAllowedReferersWithLeadingComma() { List<URI> uris = CsrfProtectionRefererFilter.parseAllowedReferers(",https://other.site.com,http://wherever.edu"); assertEquals(2, uris.size()); assertUri("https", "other.site.com", -1, uris.get(0)); assertUri("http", "wherever.edu", -1, uris.get(1)); } @Test public void testParseBlankAllowedRefererInTheMiddle() { List<URI> uris = CsrfProtectionRefererFilter.parseAllowedReferers("http://my-site.com:45,,http://wherever.edu"); assertEquals(2, uris.size()); assertUri("http", "my-site.com", 45, uris.get(0)); assertUri("http", "wherever.edu", -1, uris.get(1)); } @Test public void testParseThreeAllowedReferersWithTrailingComma() { List<URI> uris = CsrfProtectionRefererFilter.parseAllowedReferers("http://my-site.com:45,https://other.site.com,http://wherever.edu,"); assertEquals(3, uris.size()); assertUri("http", "my-site.com", 45, uris.get(0)); assertUri("https", "other.site.com", -1, uris.get(1)); assertUri("http", "wherever.edu", -1, uris.get(2)); } @Test public void testLooksLikeARegex() { assertIllegalAllowedReferers("http://my*site.com"); } @Test public void testErrorContainsBadURI() { Exception exception = assertIllegalAllowedReferers("http://site1.com,not-http.com,https://site2.com"); assertThat(exception.getMessage(), containsString("not-http.com")); } @Test public void testLooksLikeARegex2() { assertIllegalAllowedReferers("http://my.*site.com"); } @Test public void testNoPathsAllowed() { // We don't check the paths on the referer, so don't let it be configured as such assertIllegalAllowedReferers("http://my-site.subdomain.com/wherever?boo=3"); } @Test public void testBlankPathOk() { assertSingleUri("http://my-site.com/", "http", "my-site.com", -1); } @Test public void testFragmentProhibited() { // We don't check the fragments on the referer, so don't let it be configured as such assertIllegalAllowedReferers("http://my-site.subdomain.com#earth"); } @Test public void testNoUserAllowed() { // I don't know what they would be thinking // Also, according to the spec, the referer is not supposed to include user info or fragment components assertIllegalAllowedReferers("http://user@my-site.com"); } @Test public void testNoAuthAllowed() { // I don't know what they would be thinking // Also, according to the spec, the referer is not supposed to include user info or fragment components assertIllegalAllowedReferers("http://user:passw@my-site.com"); } @Test public void testAboutBlankProhibited() { // From the spec: // If the target URI was obtained from a source that does not have its // own URI (e.g., input from the user keyboard, or an entry within the // user's bookmarks/favorites), the user agent MUST either exclude the // Referer field or send it with a value of "about:blank". assertIllegalAllowedReferers("about:blank"); } @Test public void testMustBeHttpOrHttpsFile() { assertIllegalAllowedReferers("file:///~/calendar"); } @Test public void testMustBeHttpOrHttpsRandom() { assertIllegalAllowedReferers("mynewscheme://somewhere.com"); } @Test public void testCanBeHttp() { assertSingleUri("http://my-site.com:45", "http", "my-site.com", 45); } @Test public void testCanBeHttps() { assertSingleUri("https://my-site.com:45", "https", "my-site.com", 45); } @Test public void testPortOptionalThere() { assertSingleUri("https://my-site.com:445", "https", "my-site.com", 445); } @Test public void testPortOptionalNotThere() { assertSingleUri("https://my-site.com", "https", "my-site.com", -1); } @Test public void testMustBeHierarchical() { assertIllegalAllowedReferers("http:my-site.com"); } @Test public void testHostnameRequired() { // I don't know what they would be thinking assertIllegalAllowedReferers("/boo"); } @Test public void testHostnameRequiredHttp() { // I don't know what they would be thinking assertIllegalAllowedReferers("http://"); } @Test public void testHostnameRequiredHttps() { // I don't know what they would be thinking assertIllegalAllowedReferers("http://"); } @Test public void testHostnameRequired2() { assertIllegalAllowedReferers("."); } @Test public void testHostnameRequired3() { assertIllegalAllowedReferers("boo"); } @Test public void testSchemeRequired() { assertIllegalAllowedReferers("foundationdb.com"); } @Test public void testEncodedCharacters() { // My understanding of the URI spec allows % encoded characters in the host name // but URI refuses to parse them. For now work under the assumption that host's // do not have weird characters. assertIllegalAllowedReferers("https://here%20is%20my%2Asweet%20uri.com"); } @Test public void testUuidUri() { String uuid = UUID.randomUUID().toString(); assertSingleUri("https://" + uuid + ".com", "https", uuid + ".com", -1); } @Test public void testLocalhost() { assertSingleUri("https://localhost", "https", "localhost", -1); } @Test public void testLocalhostWithPort() { assertSingleUri("http://localhost:4567", "http", "localhost", 4567); } @Test public void testIPLocalhost() { assertSingleUri("https://127.0.0.1", "https", "127.0.0.1", -1); } @Test public void testIPLocal192() { // RFC-1918: Private Address Space: 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) assertSingleUri("http://192.168.1.100:9342", "http", "192.168.1.100", 9342); } @Test public void testIPLocal10() { // RFC-1918: Private Address Space: 10.0.0.0 - 10.255.255.255 (10/8 prefix) assertSingleUri("http://10.3.174.28:80", "http", "10.3.174.28", 80); } @Test public void testIPLocal172() { // RFC-1918: Private Address Space: 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) assertSingleUri("http://172.16.38.254", "http", "172.16.38.254", -1); } @Test public void testIPGlobal() { assertSingleUri("https://54.221.210.62", "https", "54.221.210.62", -1); } @Test public void testIPV6Localhost() { assertSingleUri("https://[::1]", "https", "[::1]", -1); } @Test public void testIPV6Global() { assertSingleUri("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", "https", "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", -1); } @Test public void testIPV6GlobalWithPort() { assertSingleUri("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:4322", "https", "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", 4322); } }