def test_SelfSourceExpression_match(self): """A 'self' source expression matches if the scheme, host and port of the self and other URI are the same (using default ports if absent).""" selfURI = URI("http", "seclab.nu", 80, "/other-path") srcExpr = SelfSourceExpression.SELF() assert srcExpr.matches(SourceExpressionTest.uri_urlFull, selfURI) assert srcExpr.matches(SourceExpressionTest.uri_urlFull_longer1, selfURI) assert not srcExpr.matches(SourceExpressionTest.uri_urlFull_secure, selfURI) assert not srcExpr.matches(SourceExpressionTest.uri_url1Sub, selfURI) assert srcExpr.matches(SourceExpressionTest.uri_schemedomain, selfURI) # using default port in URI assert not srcExpr.matches(URI.EMPTY(), selfURI) assert not srcExpr.matches(URI.INVALID(), selfURI) assert not srcExpr.matches(URI.INLINE(), selfURI) assert not srcExpr.matches(URI.EVAL(), selfURI) selfURIDefaultPort = URI("https", "seclab.nu", None, "/yet-another-path") assert not srcExpr.matches(SourceExpressionTest.uri_urlFull_secure, selfURIDefaultPort) assert srcExpr.matches( SourceExpressionTest.uri_urlFull_secure_defaultPort, selfURIDefaultPort) selfURINoPort = SourceExpressionTest.uri_chromeExtension assert not srcExpr.matches( SourceExpressionTest.uri_chromeExtension, selfURINoPort) # no valid port can be deduced from this scheme
def testReportParser_parse_unicode(self): """The JSON deserialiser returns strings as unicode objects. Check that they are correctly parsed in URIs.""" fullReport = """{"remote-addr": "XXX", "policy-type": "regular", "http-user-agent":""" \ + """ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:27.0) Gecko/20100101 Firefox/27.0",""" \ + """ "timestamp-utc": "2014-03-01 12:13:14.156789", "csp-report": {"violated-directive":""" \ + """ "img-src 'none'", "referrer": "http://handbook5.com/a/a-security-analysis-of-amazon%E2%80%99s-elastic-compute-cloud-service-w14847.html",""" \ + """ "blocked-uri": "http://www.iseclab.org/images/anr.png", "document-uri":""" \ + """ "http://www.iseclab.org/?p"}, "header-type": "standard"}""" expected = Report({ "violated-directive": Directive("img-src", ()), "referrer": URI( "http", "handbook5.com", None, u"/a/a-security-analysis-of-amazon’s-elastic-compute-cloud-service-w14847.html" ), "blocked-uri": URI("http", "www.iseclab.org", None, u"/images/anr.png"), "document-uri": URI("http", "www.iseclab.org", None, u"/", "p") }) parser = ReportParser(requiredKeys=[]) jsonReport = json.loads(fullReport) cspReport = parser.parseJsonDict(jsonReport["csp-report"]) assert cspReport == expected
def test_URISourceExpression_match_query(self): """The query component in an URI should not matter when matching.""" srcExpr = URISourceExpression("http", "seclab.nu", 80, "/path") selfURI = SourceExpressionTest.uri_urlFull_secure assert srcExpr.matches(URI("http", "seclab.nu", 80, "/path", None), selfURI) assert srcExpr.matches(URI("http", "seclab.nu", 80, "/path", "query"), selfURI)
def test_URISourceExpression_match_empty_path(self): """If the path component of the source expression or the URI is the empty string, it should be treated the same as being None.""" srcExprEmpty = URISourceExpression("http", "seclab.nu", 80, "") srcExprNone = URISourceExpression("http", "seclab.nu", 80, None) selfURI = SourceExpressionTest.uri_urlFull_secure assert srcExprEmpty.matches(SourceExpressionTest.uri_urlFull, selfURI) assert srcExprEmpty.matches(URI("http", "seclab.nu", 80, ""), selfURI) assert srcExprEmpty.matches(URI("http", "seclab.nu", 80, None), selfURI) assert srcExprNone.matches(SourceExpressionTest.uri_urlFull, selfURI) assert srcExprNone.matches(URI("http", "seclab.nu", 80, ""), selfURI) assert srcExprNone.matches(URI("http", "seclab.nu", 80, None), selfURI)
def test_str(self): """Checks the string serialisation of a few URIs.""" assert str(URI("data", "", None, None)) == "data:" assert str(URI("data", "image/png;base64,iVBORw0KGgoAAAA", None, None)) == "data:image/png;base64,iVBORw0KGgoAAAA" assert str(URI("about", "blank", None, None)) == "about:blank" assert str( URI("http", "www.seclab.org", 80, "/file", "parameter=value") ) == "http://www.seclab.org:80/file?parameter=value" assert str(URI(None, "www.seclab.nu", None, None)) == "www.seclab.nu" assert str(URI.EMPTY()) == "" assert str(URI.INVALID()) == "[invalid]" assert str(URI.INLINE()) == "[inline]" assert str(URI.EVAL()) == "[eval]"
def test_no_modification_partial_URI_scheme_host(self): """Simple test with no modification, splitting a partially specified URI.""" uriString = "chrome-extension://mkfokfffehpeedafpekjeddnmnjhmcmk" uriExpected = URI("chrome-extension", "mkfokfffehpeedafpekjeddnmnjhmcmk", None, None) assert URIParser(addScheme=False, addPort=False, decodeEscapedCharacters=False) \ .parse(uriString) == uriExpected
def test_addPort_schemePortMappingsNone_partial_URI_scheme_host(self): """Simple test with "adding" a None port, splitting a partially specified URI.""" uriString = "chrome-extension://mkfokfffehpeedafpekjeddnmnjhmcmk" uriExpected = URI("chrome-extension", "mkfokfffehpeedafpekjeddnmnjhmcmk", None, None) assert URIParser(addScheme=True, addPort=True, defaultPort=123, schemePortMappings={'chrome-extension': None}, decodeEscapedCharacters=False) \ .parse(uriString) == uriExpected
def test_str_unicode(self): """Test URI serialisation with unicode characters.""" url = u'http://handbook5.com/a/a-security-analysis-of-amazon%E2%80%99s-elastic-compute-cloud-service-w14847.html' unicodePath = u"/a/a-security-analysis-of-amazon’s-elastic-compute-cloud-service-w14847.html" parsed = URIParser(decodeEscapedCharacters=True).parse(url) assert parsed == URI("http", "handbook5.com", None, unicodePath) assert str(parsed) == url # must use quoted version
def test_addScheme_addPort_defaultScheme_schemePortMappings_partial_URI_host_path( self): """Test if scheme and port are added properly to partially specified URI.""" uriString = "www.seclab.nu/path/file" uriExpected = URI("test-scheme", "www.seclab.nu", 333, "/path/file") assert URIParser(addScheme=True, defaultScheme="test-scheme", addPort=True, schemePortMappings={"test-scheme": 333}, decodeEscapedCharacters=False) \ .parse(uriString) == uriExpected
def test_no_modification_partial_URI_data(self): """Data URIs use only the scheme and the host for the data. The data should not be converted to lowercase.""" uriString = "data:image/png;base64,iVBORw0KGgoAAAA" uriExpected = URI("data", "image/png;base64,iVBORw0KGgoAAAA", None, None) assert URIParser().parse(uriString) == uriExpected
def test_no_modification_partial_URI_host_path(self): """Simple test with no modification, splitting a partially specified URI.""" uriString = "www.seclab.nu/path/file" uriExpected = URI(None, "www.seclab.nu", None, "/path/file") actual = URIParser(addScheme=False, addPort=False, decodeEscapedCharacters=False) \ .parse(uriString) assert actual == uriExpected
def test_stripUserPassword_userOnly_URI_IPv4(self): """Check if user:password part properly removed (with IPv4 address).""" uriString = "http://[email protected]:80/index.php" uriExpected = URI("http", "123.123.123.123", 80, "/index.php") actual = URIParser(decodeEscapedCharacters=False) \ .parse(uriString) assert actual == uriExpected
def test_stripUserPassword_userOnly_URI(self): """Check if user:password part properly removed.""" uriString = "http://[email protected]:80/index.php" uriExpected = URI("http", "www.seclab.nu", 80, "/index.php") actual = URIParser(decodeEscapedCharacters=False) \ .parse(uriString) assert actual == uriExpected
def test_no_modification_partial_URI_scheme_host_path_query(self): """Simple test with no modification, splitting a partially specified URI.""" uriString = "https://www.seclab.nu/path/file?query=here" uriExpected = URI("https", "www.seclab.nu", None, "/path/file", "query=here") assert URIParser(addScheme=False, addPort=False, decodeEscapedCharacters=False) \ .parse(uriString) == uriExpected
def testPolicy_matches_matchingDirectiveType(self): """Policy contains directive of resource type that matches.""" pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert pol.matches( URI("https", "abc.seclab.nu", 443, "/path", "some-query"), "connect-src", selfURI)
def test_regularURI_singletons(self): """All the singleton URIs should return False for isRegularURI().""" assert URI.EMPTY().isRegularURI() == False assert URI.INVALID().isRegularURI() == False assert URI.INLINE().isRegularURI() == False assert URI.EVAL().isRegularURI() == False assert URI("http", "seclab.nu", None, None, None).isRegularURI() == True
def test_decodeEscapedCharacters_full_URI(self): """Check if escaped characters in the path are correctly decoded.""" uriString = "http://www.seclab.nu:80/a%20path/index.py?math=3%3D%281%2B1%29%2A1.5" uriExpected = URI("http", "www.seclab.nu", 80, "/a path/index.py", "math=3%3D%281%2B1%29%2A1.5") actual = URIParser(decodeEscapedCharacters=True) \ .parse(uriString) assert actual == uriExpected
def test_stripAnchor_full_URI(self): """Check if anchor part properly removed.""" uriString = "http://www.seclab.nu:80/index.php?parameter=value&more#anchor" uriExpected = URI("http", "www.seclab.nu", 80, "/index.php", "parameter=value&more") actual = URIParser(decodeEscapedCharacters=False) \ .parse(uriString) assert actual == uriExpected
class ReportDataReaderTest(unittest.TestCase): sampleURI1a = URI("http", "seclab.nu", None, None, None) sampleURI1b = URI("http", "seclab.nu", None, None, None) sampleURI2 = URI("http", "seclab.nu", None, "/blocked", "query") sampleDirective1a = Directive("default-src", ()) sampleDirective1b = Directive("default-src", ()) sampleDirective2a = Directive("script-src", (SourceExpression.UNSAFE_INLINE(), )) sampleDirective2b = Directive("script-src", (SourceExpression.UNSAFE_INLINE(), )) samplePolicy1a = Policy((sampleDirective1a, sampleDirective2a)) samplePolicy1b = Policy((sampleDirective1b, sampleDirective2b)) samplePolicy2 = Policy((sampleDirective1a, )) @pytest.fixture(autouse=True) def initdir(self, tmpdir): tmpdir.chdir() def setUp(self): self.fileIn = ReportDataReader(True) self.filename = "encodingdecoding.dat" self.fileOut = DataWriter(self.filename) def tearDown(self): pass def testReportCreation(self): """Writes a Report and loads it back as an object.""" report = Report({ "abc": True, "def": 1, "ghi": "http://seclab.nu/", "document-uri": ReportDataReaderTest.sampleURI1a, "violated-directive": ReportDataReaderTest.sampleDirective1a, "original-policy": ReportDataReaderTest.samplePolicy1a, "blocked-uri": ReportDataReaderTest.sampleURI2 }) self.fileOut.storeAll([report]) self.fileOut.close() dataOut = self.fileIn.loadAll(self.filename) assert len(dataOut) == 1 print report print dataOut[0] assert report in dataOut
def test_getters(self): """Checks the getters of URI.""" inputURI = URI("https", "seclab.nu", 443, "/index.html", "param=val") assert inputURI.getScheme() == "https" assert inputURI.getHost() == "seclab.nu" assert inputURI.getPort() == 443 assert inputURI.getPath() == "/index.html" assert inputURI.getQuery() == "param=val" assert inputURI.isRegularURI() == True
def test_eq(self): """Checks that the eq and hash methods are consistent for a few URIs.""" uri1a = URI("http", "www.seclab.org", 80, "/", "query") uri1b = URI("http", "www.seclab.org", 80, "/", "query") uri2 = URI("https", "www.seclab.org", 80, "/") uri3 = URI("http", "www.seclab.org", 80, None) assert uri1a == uri1b assert hash(uri1a) == hash(uri1b) assert uri1a != uri2 assert uri1a != uri3 assert uri2 != uri3 assert URI.EMPTY() == URI.EMPTY() assert URI.INVALID() == URI.INVALID() assert URI.EMPTY() not in (uri1a, uri1b, uri2, uri3, URI.INVALID(), URI.INLINE(), URI.EVAL()) assert URI.INVALID() not in (uri1a, uri1b, uri2, uri3, URI.EMPTY(), URI.INLINE(), URI.EVAL()) assert URI.INLINE() not in (uri1a, uri1b, uri2, uri3, URI.EMPTY(), URI.INVALID(), URI.EVAL()) assert URI.EVAL() not in (uri1a, uri1b, uri2, uri3, URI.EMPTY(), URI.INVALID(), URI.INLINE())
def test_no_modification_partial_URI_host(self): """Simple test with no modification and only host in URI.""" uriString = "www.seclab.nu" uriExpected = URI(None, "www.seclab.nu", None, None) assert URIParser(addScheme=False, addPort=False).parse(uriString) == uriExpected
class ReportTest(unittest.TestCase): sampleURI1a = URI("http", "seclab.nu", None, None, None) sampleURI1b = URI("http", "seclab.nu", None, None, None) sampleURI2 = URI("https", "seclab.ccs.neu.edu", None, None, "query") sampleDirective1a = Directive("default-src", (SelfSourceExpression.SELF(), )) sampleDirective1b = Directive("default-src", (SelfSourceExpression(), )) sampleDirective2a = Directive("script-src", (SourceExpression.UNSAFE_INLINE(), )) sampleDirective2b = Directive("script-src", (SourceExpression.UNSAFE_INLINE(), )) sampleDirective3 = Directive( "script-src", (URISourceExpression("http", "seclab.nu", None, None), )) samplePolicy1a = Policy((sampleDirective1a, sampleDirective2a)) samplePolicy1b = Policy((sampleDirective1b, sampleDirective2b)) def testReport_str_invalid(self): assert str(Report.INVALID()) == "[invalid]" def testReport_str_regular(self): """Extended object types should be serialised to strings when getting the report as a string.""" report = Report({ "violated-directive": ReportTest.sampleDirective2a, "original-policy": ReportTest.samplePolicy1a }) assert str(report) == """{"original-policy": "default-src 'self'; script-src 'unsafe-inline'",""" \ + """ "violated-directive": "script-src 'unsafe-inline'"}""" def testReport_str_primitives(self): """A Report with basic data types in it (instead of strings) should have them serialised to JSON-supported basic data types, not all strings.""" report = Report({"abc": True, "def": 1, "ghi": ReportTest.sampleURI1a}) expected = """{"abc": true, "def": 1, "ghi": "http://seclab.nu"}""" assert str(report) == expected def testReport_dict_iterateAndImmutable(self): report = Report({ "violated-directive": ReportTest.sampleDirective2a, "original-policy": ReportTest.samplePolicy1a }) assert len(report) == 2 assert "violated-directive" in report assert report["violated-directive"] == ReportTest.sampleDirective2a assert "original-policy" in report assert report["original-policy"] == ReportTest.samplePolicy1a for (key, value) in report.iteritems(): assert key in ("violated-directive", "original-policy") assert value in (ReportTest.sampleDirective2a, ReportTest.samplePolicy1a) with pytest.raises(TypeError): report["original-policy"] = None with pytest.raises(TypeError): report["something-else"] = 123 def testReport_eq(self): report1a = Report({ "violated-directive": ReportTest.sampleDirective2a, "original-policy": ReportTest.samplePolicy1a }) report1b = Report({ "violated-directive": ReportTest.sampleDirective2b, "original-policy": ReportTest.samplePolicy1b }) report2 = Report({ "violated-directive": ReportTest.sampleDirective1a, "original-policy": ReportTest.samplePolicy1a }) assert report1a == report1b assert hash(report1a) == hash(report1b) assert report1a != report2 assert report1a != Report.INVALID() assert report2 != Report.INVALID() def testReport_generatePolicy_regular(self): report = Report({ "blocked-uri": ReportTest.sampleURI1a, "violated-directive": ReportTest.sampleDirective2a, "document-uri": ReportTest.sampleURI2 }) assert report.generatePolicy("regular") == Policy( [ReportTest.sampleDirective3]) def testReport_generatePolicy_invalid(self): assert Report.INVALID().generatePolicy("regular") == Policy.INVALID() def testReport_generatePolicy_missingReportField(self): reportNoViolated = Report({ "blocked-uri": ReportTest.sampleURI1a, "document-uri": ReportTest.sampleURI2 }) reportNoBlocked = Report({ "violated-directive": ReportTest.sampleDirective2a, "document-uri": ReportTest.sampleURI2 }) assert reportNoViolated.generatePolicy("regular") == Policy.INVALID() assert reportNoBlocked.generatePolicy("regular") == Policy.INVALID() def testReport_generatePolicy_fromInvalidDirectiveResult(self): reportDefaultSrc = Report({ "blocked-uri": ReportTest.sampleURI1a, "violated-directive": ReportTest.sampleDirective1a, "document-uri": ReportTest.sampleURI2 }) assert reportDefaultSrc.generatePolicy("regular") == Policy.INVALID() def testReport_generatePolicy_wrongDocumentURI(self): reportEmptyDocument = Report({ "blocked-uri": ReportTest.sampleURI1a, "violated-directive": ReportTest.sampleDirective1a, "document-uri": URI.EMPTY() }) assert reportEmptyDocument.generatePolicy( "regular") == Policy.INVALID() def testReportParser_parse_typeFields(self): """Check that type indications for fields are properly parsed.""" report = """{"uri": "https://seclab.ccs.neu.edu?query", "directive": "script-src 'unsafe-inline'",""" \ + """ "policy": "script-src 'unsafe-inline'; default-src 'self'",""" \ + """ "nothing": "123"}""" cspReport = ReportParser(uriKeys=["uri", "url"], directiveKeys=["directive"], policyKeys=["policy"], requiredKeys=[], strict=True).parseString(report) expected = Report({ "uri": ReportTest.sampleURI2, "directive": ReportTest.sampleDirective2a, "policy": ReportTest.samplePolicy1a, "nothing": "123" }) assert cspReport == expected def testReportParser_parse_stringVsJSON(self): """Ensure the string parsing returns the same result as JSON parsing.""" reportJSON = {"something": 123, "uri": "http://seclab.nu"} reportString = """{"something": 123, "uri": "http://seclab.nu"}""" expected = Report({"something": 123, "uri": ReportTest.sampleURI1a}) parser = ReportParser(uriKeys=["uri"], requiredKeys=[], strict=True) parsedFromJSON = parser.parseJsonDict(reportJSON) parsedFromString = parser.parseString(reportString) print parsedFromJSON print parsedFromString assert parsedFromJSON == parsedFromString assert parsedFromJSON == expected def testReportParser_parse_fieldNameReplacements(self): """Checks that old field names are replaced correctly.""" report = """{"document-url": "http://seclab.nu", "original-policy": "default-src 'self'; script-src 'unsafe-inline'"}""" expected = Report({ "document-uri": ReportTest.sampleURI1a, "original-policy": ReportTest.samplePolicy1a }) parser = ReportParser( uriKeys=["document-uri"], policyKeys=["original-policy"], requiredKeys=["document-uri", "original-policy"], strict=True, keyNameReplacements={"document-url": "document-uri"}) cspReport = parser.parseString(report) assert cspReport == expected def testReportParser_parse_requiredFields(self): """Required fields must be present even if strict=False.""" report = """{"this-is": "a quite empty report"}""" expected = Report({"this-is": "a quite empty report"}) assert ReportParser(requiredKeys=[], strict=False).parseString(report) == expected assert ReportParser( requiredKeys=["does-not-exist"], strict=False).parseString(report) == Report.INVALID() def testReportParser_parse_emptyOrSelfURI(self): """This tests that the internal settings of the URI parser are chosen such that empty or self URIs are correctly handled.""" report = """{"empty-uri": "", "self-uri": "self", "document-uri": "http://seclab.nu"}""" expected = Report({ "empty-uri": URI.EMPTY(), "self-uri": ReportTest.sampleURI1a, "document-uri": ReportTest.sampleURI1a }) assert ReportParser(requiredKeys=[], strict=True, uriKeys=["empty-uri", "self-uri", "document-uri"]) \ .parseString(report) == expected def testReportParser_parse_failIfStrict(self): """The report must be declared invalid in strict mode when a child element is invalid.""" report = """{"invalid-policy": "awesomeness-src 'self'", "example": true}""" assert ReportParser(requiredKeys=[], strict=True, policyKeys=["invalid-policy"]) \ .parseString(report) == Report.INVALID() def testReportParser_parse_skipIfNotStrict(self): """Invalid portions of the report must be skipped in non-strict mode.""" report = """{"invalid-policy": "awesomeness-src 'self'", "example": true}""" expected = Report({"example": True}) assert ReportParser(requiredKeys=[], strict=False, policyKeys=["invalid-policy"]) \ .parseString(report) == expected def testReportParser_parse_inferSelfURI(self): """Tests if the self URI is correctly inferred from the "document-uri" field (even after renaming).""" report = """{"violated-directive": "default-src 'self'", "referrer": "",""" \ + """ "blocked-uri": "self", "document-URL":""" \ + """ "http://seclab.nu"}""" expected = Report({ "violated-directive": ReportTest.sampleDirective1a, "referrer": URI.EMPTY(), "blocked-uri": ReportTest.sampleURI1a, "document-uri": ReportTest.sampleURI1a }) parser = ReportParser( requiredKeys=["violated-directive", "document-uri", "blocked-uri"], strict=True, directiveKeys=["violated-directive"], uriKeys=["referrer", "blocked-uri", "document-uri"], keyNameReplacements={'document-url': 'document-uri'}) cspReport = parser.parseString(report) assert cspReport == expected def testReportParser_parse_selfURIFailStrict(self): """Tests that parsing fails if strict and no document-uri but another 'self' URI.""" report = """{"blocked-uri": "self", "other": "value"}""" parser = ReportParser(requiredKeys=[], strict=True, uriKeys=["blocked-uri", "document-uri"]) cspReport = parser.parseString(report) assert cspReport == Report.INVALID() def testReportParser_parse_selfURISkipStrict(self): """Tests that parsing fails if strict and no document-uri but another 'self' URI.""" report = """{"blocked-uri": "self", "other": "value"}""" expected = Report({"other": "value"}) parser = ReportParser(requiredKeys=[], strict=False, uriKeys=["blocked-uri", "document-uri"]) cspReport = parser.parseString(report) assert cspReport == expected def testReportParser_parse_unicode(self): """The JSON deserialiser returns strings as unicode objects. Check that they are correctly parsed in URIs.""" fullReport = """{"remote-addr": "XXX", "policy-type": "regular", "http-user-agent":""" \ + """ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:27.0) Gecko/20100101 Firefox/27.0",""" \ + """ "timestamp-utc": "2014-03-01 12:13:14.156789", "csp-report": {"violated-directive":""" \ + """ "img-src 'none'", "referrer": "http://handbook5.com/a/a-security-analysis-of-amazon%E2%80%99s-elastic-compute-cloud-service-w14847.html",""" \ + """ "blocked-uri": "http://www.iseclab.org/images/anr.png", "document-uri":""" \ + """ "http://www.iseclab.org/?p"}, "header-type": "standard"}""" expected = Report({ "violated-directive": Directive("img-src", ()), "referrer": URI( "http", "handbook5.com", None, u"/a/a-security-analysis-of-amazon’s-elastic-compute-cloud-service-w14847.html" ), "blocked-uri": URI("http", "www.iseclab.org", None, u"/images/anr.png"), "document-uri": URI("http", "www.iseclab.org", None, u"/", "p") }) parser = ReportParser(requiredKeys=[]) jsonReport = json.loads(fullReport) cspReport = parser.parseJsonDict(jsonReport["csp-report"]) assert cspReport == expected
class DirectiveTest(unittest.TestCase): sampleURI1 = URI("http", "seclab.nu", 80, None, None) sampleURI2 = URI("http", "seclab.ccs.neu.edu", 80, "/path", "query-parameters") sampleSrcExpr1a = URISourceExpression("http", "seclab.nu", "*", None) sampleSrcExpr1b = URISourceExpression("http", "seclab.nu", "*", None) sampleSrcExpr2 = URISourceExpression("https", "seclab.nu", 443, "/path1") sampleSrcExpr3 = URISourceExpression("https", "seclab.nu", 443, "/path2") def testDirective_str_none(self): assert str(Directive("script-src", [])) == "script-src 'none'" def testDirective_str_regular(self): srcExpr1 = URISourceExpression("http", "seclab.nu", "*", None) srcExpr2 = SourceExpression.UNSAFE_INLINE() srcExpr3 = SelfSourceExpression.SELF() assert str(Directive("style-src", [srcExpr1, srcExpr2, srcExpr3])) \ == "style-src 'self' 'unsafe-inline' http://seclab.nu:*" def testDirective_str_invalid(self): assert str(Directive.INVALID()) == "[invalid]" def testDirective_str_inlineStyleBaseRestriction(self): assert str(Directive.INLINE_STYLE_BASE_RESTRICTION() ) == "inline style base restriction" def testDirective_str_inlineScriptBaseRestriction(self): assert str(Directive.INLINE_SCRIPT_BASE_RESTRICTION() ) == "inline script base restriction" def testDirective_str_evalScriptBaseRestriction(self): assert str(Directive.EVAL_SCRIPT_BASE_RESTRICTION() ) == "eval script base restriction" def testDirective_getType(self): assert Directive("default-src", []).getType() == "default-src" assert Directive.INLINE_STYLE_BASE_RESTRICTION().getType( ) == "style-src" assert Directive.INLINE_SCRIPT_BASE_RESTRICTION().getType( ) == "script-src" assert Directive.EVAL_SCRIPT_BASE_RESTRICTION().getType( ) == "script-src" def testDirective_init_removeDoubleExpressions(self): srcExpr1 = URISourceExpression("http", "seclab.nu", "*", None) srcExpr2 = URISourceExpression("http", "seclab.nu", "*", None) directive = Directive("style-src", [srcExpr1, srcExpr2]) whitelisted = directive.getWhitelistedSourceExpressions() assert whitelisted == set( [srcExpr1]) # duplicate source expressions should be removed assert whitelisted == set([srcExpr2]) # sets should be equal assert directive == Directive("style-src", [srcExpr1]) assert directive == Directive("style-src", [srcExpr2]) def testDirective_init_removeDoubleSelfExpressions(self): srcExpr1 = URISourceExpression("http", "seclab.nu", "*", None) srcExpr2 = SelfSourceExpression.SELF() srcExpr3 = SelfSourceExpression() whitelisted = Directive( "img-src", [srcExpr1, srcExpr2, srcExpr3]).getWhitelistedSourceExpressions() assert len( whitelisted ) == 2 # one self expression should be removed (can have at most one) assert srcExpr1 in whitelisted and (srcExpr2 in whitelisted or srcExpr3 in whitelisted) def testDirective_init_removeInvalidSourceExpressions(self): assert Directive("img-src", [SourceExpression.INVALID()]) == Directive( "img-src", []) def testDirective_eq(self): srcExpr1 = URISourceExpression("http", "seclab.nu", "*", None) srcExpr2 = URISourceExpression("https", "seclab.nu", 443, "/") directive1a = Directive("object-src", [srcExpr1, srcExpr2]) directive1b = Directive("object-src", [srcExpr2, srcExpr1]) directive2 = Directive("frame-src", [srcExpr1, srcExpr2]) directive3 = Directive("object-src", [srcExpr2]) directive4a = Directive("script-src", (SourceExpression.UNSAFE_INLINE(), )) directive4b = Directive("script-src", (SourceExpression("unsafe-inline"), )) assert directive1a == directive1b assert hash(directive1a) == hash(directive1b) assert directive1a != directive2 assert directive1a != directive3 assert directive2 != directive3 assert directive4a == directive4b assert hash(directive4a) == hash(directive4b) assert Directive.INVALID() == Directive.INVALID() assert Directive.INVALID() not in (directive1a, directive1b, directive2, directive3) assert Directive.INLINE_STYLE_BASE_RESTRICTION() not in (directive1a, directive1b, directive2, directive3) def testDirective_matches_special(self): """An invalid/special directive matches nothing.""" selfURI = DirectiveTest.sampleURI2 assert not Directive.INVALID().matches(URI.EMPTY(), selfURI) assert not Directive.INVALID().matches(URI.INVALID(), selfURI) assert not Directive.INVALID().matches(URI.INLINE(), selfURI) assert not Directive.INVALID().matches(URI.EVAL(), selfURI) assert not Directive.INVALID().matches(DirectiveTest.sampleURI1, selfURI) assert not Directive.EVAL_SCRIPT_BASE_RESTRICTION().matches( URI.EMPTY(), selfURI) assert not Directive.EVAL_SCRIPT_BASE_RESTRICTION().matches( URI.INVALID(), selfURI) assert not Directive.EVAL_SCRIPT_BASE_RESTRICTION().matches( URI.INLINE(), selfURI) assert not Directive.EVAL_SCRIPT_BASE_RESTRICTION().matches( URI.EVAL(), selfURI) assert not Directive.EVAL_SCRIPT_BASE_RESTRICTION().matches( DirectiveTest.sampleURI1, selfURI) assert not Directive.INLINE_SCRIPT_BASE_RESTRICTION().matches( URI.EMPTY(), selfURI) assert not Directive.INLINE_SCRIPT_BASE_RESTRICTION().matches( URI.INVALID(), selfURI) assert not Directive.INLINE_SCRIPT_BASE_RESTRICTION().matches( URI.INLINE(), selfURI) assert not Directive.INLINE_SCRIPT_BASE_RESTRICTION().matches( URI.EVAL(), selfURI) assert not Directive.INLINE_SCRIPT_BASE_RESTRICTION().matches( DirectiveTest.sampleURI1, selfURI) assert not Directive.INLINE_STYLE_BASE_RESTRICTION().matches( URI.EMPTY(), selfURI) assert not Directive.INLINE_STYLE_BASE_RESTRICTION().matches( URI.INVALID(), selfURI) assert not Directive.INLINE_STYLE_BASE_RESTRICTION().matches( URI.INLINE(), selfURI) assert not Directive.INLINE_STYLE_BASE_RESTRICTION().matches( URI.EVAL(), selfURI) assert not Directive.INLINE_STYLE_BASE_RESTRICTION().matches( DirectiveTest.sampleURI1, selfURI) def testDirective_matches(self): """A few standard tests.""" directive1 = Directive( "object-src", [DirectiveTest.sampleSrcExpr1a, DirectiveTest.sampleSrcExpr2]) directive2 = Directive("frame-src", [DirectiveTest.sampleSrcExpr2]) directive3 = Directive("default-src", []) selfURI = DirectiveTest.sampleURI2 assert directive1.matches(DirectiveTest.sampleURI1, selfURI) assert not directive1.matches(DirectiveTest.sampleURI2, selfURI) assert not directive2.matches(DirectiveTest.sampleURI1, selfURI) assert not directive2.matches(DirectiveTest.sampleURI2, selfURI) assert not directive3.matches(DirectiveTest.sampleURI1, selfURI) assert not directive3.matches(DirectiveTest.sampleURI2, selfURI) def testDirective_generateDirective_regular(self): violated = Directive( "object-src", [DirectiveTest.sampleSrcExpr1a, DirectiveTest.sampleSrcExpr2]) generated = violated.generateDirective("regular", DirectiveTest.sampleURI2) assert generated == Directive( "object-src", [URISourceExpression("http", "seclab.ccs.neu.edu", 80, "/path")]) def testDirective_generateDirective_inline(self): violated = Directive("style-src", [DirectiveTest.sampleSrcExpr2]) generated = violated.generateDirective("inline", URI.EMPTY()) assert generated == Directive("style-src", [SourceExpression.UNSAFE_INLINE()]) def testDirective_generateDirective_inline_special_style(self): violated = Directive.INLINE_STYLE_BASE_RESTRICTION() generated = violated.generateDirective("inline", DirectiveTest.sampleURI1) assert generated == Directive("style-src", [SourceExpression.UNSAFE_INLINE()]) def testDirective_generateDirective_inline_special_script(self): violated = Directive.INLINE_SCRIPT_BASE_RESTRICTION() generated = violated.generateDirective("inline", DirectiveTest.sampleURI1) assert generated == Directive("script-src", [SourceExpression.UNSAFE_INLINE()]) def testDirective_generateDirective_eval(self): violated = Directive("script-src", [DirectiveTest.sampleSrcExpr1a]) generated = violated.generateDirective("eval", URI.EMPTY()) assert generated == Directive("script-src", [SourceExpression.UNSAFE_EVAL()]) def testDirective_generateDirective_eval_special(self): violated = Directive.EVAL_SCRIPT_BASE_RESTRICTION() generated = violated.generateDirective("eval", DirectiveTest.sampleURI2) assert generated == Directive("script-src", [SourceExpression.UNSAFE_EVAL()]) def testDirective_generateDirective_invalidType(self): violated = Directive("script-src", [DirectiveTest.sampleSrcExpr1a]) assert violated.generateDirective("evaluate", URI.EMPTY()) == Directive.INVALID() def testDirective_generateDirective_invalidDirective(self): assert Directive.INVALID().generateDirective( "eval", URI.EMPTY()) == Directive.INVALID() def testDirective_generateDirective_defaultSrcNotAllowed(self): violated = Directive("default-src", []) assert violated.generateDirective( "regular", DirectiveTest.sampleURI1) == Directive.INVALID() def testDirective_generateDirective_incompatibleType(self): violatedWrongInlineType = Directive("object-src", []) violatedWrongEvalType = Directive("style-src", []) assert violatedWrongInlineType.generateDirective( "inline", DirectiveTest.sampleURI1) == Directive.INVALID() assert violatedWrongEvalType.generateDirective( "eval", DirectiveTest.sampleURI1) == Directive.INVALID() def testDirective_generateDirective_incompatibleURI(self): violatedRegular = Directive("object-src", []) violatedInline = Directive("style-src", []) violatedEval = Directive("script-src", []) assert violatedRegular.generateDirective( "regular", URI.EMPTY()) == Directive.INVALID() assert violatedRegular.generateDirective( "regular", URI.INVALID()) == Directive.INVALID() #assert violatedInline.generateDirective("inline", DirectiveTest.sampleURI1) == Directive.INVALID() assert violatedInline.generateDirective( "inline", URI.INVALID()) == Directive.INVALID() #assert violatedEval.generateDirective("eval", DirectiveTest.sampleURI1) == Directive.INVALID() assert violatedEval.generateDirective( "eval", URI.INVALID()) == Directive.INVALID() def testDirective_isRegularDirective(self): assert Directive.INVALID().isRegularDirective() == False assert Directive.EVAL_SCRIPT_BASE_RESTRICTION().isRegularDirective( ) == False assert Directive.INLINE_SCRIPT_BASE_RESTRICTION().isRegularDirective( ) == False assert Directive.INLINE_STYLE_BASE_RESTRICTION().isRegularDirective( ) == False assert Directive("default-src", []).isRegularDirective() == True def testDirective_combine_regular(self): direct1 = Directive("default-src", [DirectiveTest.sampleSrcExpr1a]) direct2 = Directive("default-src", [DirectiveTest.sampleSrcExpr2]) expected = Directive( "default-src", [DirectiveTest.sampleSrcExpr1a, DirectiveTest.sampleSrcExpr2]) assert direct1.combinedDirective(direct2) == expected assert direct2.combinedDirective(direct1) == expected assert direct1.combinedDirective(direct1) == direct1 assert direct2.combinedDirective(direct2) == direct2 def testDirective_combine_differentType(self): direct1 = Directive("default-src", [DirectiveTest.sampleSrcExpr1a]) direct2 = Directive("script-src", [DirectiveTest.sampleSrcExpr2]) assert direct1.combinedDirective(direct2) == Directive.INVALID() assert direct2.combinedDirective(direct1) == Directive.INVALID() def testDirective_combine_notRegularURI(self): direct = Directive("style-src", [SelfSourceExpression.SELF()]) assert direct.combinedDirective( Directive.INVALID()) == Directive.INVALID() assert Directive.INVALID().combinedDirective( direct) == Directive.INVALID() assert direct.combinedDirective( Directive.EVAL_SCRIPT_BASE_RESTRICTION()) == Directive.INVALID() assert Directive.EVAL_SCRIPT_BASE_RESTRICTION().combinedDirective( direct) == Directive.INVALID() def testDirective_combine_removeDuplicates(self): direct1 = Directive( "img-src", [DirectiveTest.sampleSrcExpr1a, SelfSourceExpression.SELF()]) direct2 = Directive("img-src", [DirectiveTest.sampleSrcExpr1b]) assert direct1.combinedDirective(direct2) == direct1 assert direct2.combinedDirective(direct1) == direct1 def testDirective_withoutPaths(self): withPaths = Directive( "script-src", [DirectiveTest.sampleSrcExpr2, SelfSourceExpression.SELF()]) withoutPaths = Directive("script-src", [ DirectiveTest.sampleSrcExpr2.removePath(), SelfSourceExpression.SELF() ]) assert withPaths.withoutPaths() == withoutPaths assert withoutPaths.withoutPaths() == withoutPaths assert Directive.INVALID().withoutPaths() == Directive.INVALID() assert Directive.EVAL_SCRIPT_BASE_RESTRICTION().withoutPaths( ) == Directive.EVAL_SCRIPT_BASE_RESTRICTION() assert Directive.INLINE_SCRIPT_BASE_RESTRICTION().withoutPaths( ) == Directive.INLINE_SCRIPT_BASE_RESTRICTION() assert Directive.INLINE_STYLE_BASE_RESTRICTION().withoutPaths( ) == Directive.INLINE_STYLE_BASE_RESTRICTION() def testDirective_withoutPaths_schemeOnly(self): chromeExt = Directive("img-src", [ URISourceExpression("chrome-extension", "mkfokfffehpeedafpekjeddnmnjhmcmk", None, None) ]) assert chromeExt.withoutPaths(["chrome-extension"]) == Directive( "img-src", [URISourceExpression("chrome-extension", None, None, None)]) def testDirective_withoutPaths_removeDuplicates(self): withPaths = Directive( "script-src", [DirectiveTest.sampleSrcExpr2, DirectiveTest.sampleSrcExpr3]) withoutPaths = Directive("script-src", [DirectiveTest.sampleSrcExpr2.removePath()]) assert withPaths.withoutPaths() == withoutPaths def testDirective_asBasicDirectives_single(self): assert Directive.INVALID().asBasicDirectives() == set([]) assert Directive.EVAL_SCRIPT_BASE_RESTRICTION().asBasicDirectives( ) == set([]) assert Directive.INLINE_SCRIPT_BASE_RESTRICTION().asBasicDirectives( ) == set([]) assert Directive.INLINE_STYLE_BASE_RESTRICTION().asBasicDirectives( ) == set([]) sampleDirective = Directive("img-src", [DirectiveTest.sampleSrcExpr1b]) assert sampleDirective.asBasicDirectives() == set([sampleDirective]) def testDirective_asBasicDirectives_multiple(self): sampleDirective = Directive("script-src", [ SelfSourceExpression.SELF(), DirectiveTest.sampleSrcExpr1a, DirectiveTest.sampleSrcExpr2, DirectiveTest.sampleSrcExpr3 ]) assert sampleDirective.asBasicDirectives() == set([ Directive("script-src", [SelfSourceExpression.SELF()]), Directive("script-src", [DirectiveTest.sampleSrcExpr1a]), Directive("script-src", [DirectiveTest.sampleSrcExpr2]), Directive("script-src", [DirectiveTest.sampleSrcExpr3]) ]) def testDirective_isBasicDirective(self): assert Directive.INVALID().isBasicDirective() == False assert Directive.EVAL_SCRIPT_BASE_RESTRICTION().isBasicDirective( ) == False assert Directive("default-src", ()).isBasicDirective() == True assert Directive( "script-src", [DirectiveTest.sampleSrcExpr2]).isBasicDirective() == True assert Directive( "object-src", [DirectiveTest.sampleSrcExpr2, DirectiveTest.sampleSrcExpr3 ]).isBasicDirective() == False def testDirectiveParser_parse_empty(self): assert DirectiveParser(strict=True).parse(" ") == Directive.INVALID() assert DirectiveParser(strict=False).parse(" ") == Directive.INVALID() assert DirectiveParser( strict=True).parse("img-src ") == Directive.INVALID() assert DirectiveParser( strict=False).parse("img-src ") == Directive.INVALID() def testDirectiveParser_parse_standard(self): assert DirectiveParser(strict=True).parse("default-src https: 'unsafe-inline' 'unsafe-eval'") \ == Directive("default-src", [URISourceExpression("https", None, None, None), SourceExpression.UNSAFE_INLINE(), SourceExpression.UNSAFE_EVAL()]) assert DirectiveParser(strict=True).parse("default-src 'self'") \ == Directive("default-src", [SelfSourceExpression.SELF()]) assert DirectiveParser(strict=True).parse("img-src *") \ == Directive("img-src", [URISourceExpression(None, "*", None, None)]) assert DirectiveParser(strict=True).parse("object-src media1.example.com media2.example.com *.cdn.example.com") \ == Directive("object-src", [URISourceExpression(None, "media1.example.com", None, None), URISourceExpression(None, "media2.example.com", None, None), URISourceExpression(None, "*.cdn.example.com", None, None)]) def testDirectiveParser_parse_whitespaceRemoval(self): """Whitespace is properly removed.""" directive = "img-src 'self' https://abc.cloudfront.net/the-path chrome-extension: data: https://def.cloudfront.net/another-path " directiveClean = "img-src 'self' chrome-extension: data: https://abc.cloudfront.net/the-path https://def.cloudfront.net/another-path" cspDirective = DirectiveParser().parse(directive) assert cspDirective.getType() == "img-src" assert cspDirective.getWhitelistedSourceExpressions() == set([ SelfSourceExpression.SELF(), URISourceExpression("chrome-extension", None, None, None), URISourceExpression("data", None, None, None), URISourceExpression("https", "abc.cloudfront.net", None, "/the-path"), URISourceExpression("https", "def.cloudfront.net", None, "/another-path") ]) assert str(cspDirective) == directiveClean cspDirectiveClean = DirectiveParser().parse(directiveClean) assert cspDirective == cspDirectiveClean def testDirectiveParser_parse_invalid(self): invalidDirective = "blah" assert DirectiveParser().parse(invalidDirective) is Directive.INVALID() def testDirectiveParser_parse_translate(self): """The old directive type 'xhr-src' is correctly rewritten to 'connect-src'.""" translateDirective = "xhr-src http://localhost" cspTranslateDirective = DirectiveParser().parse(translateDirective) assert cspTranslateDirective == Directive( "connect-src", (URISourceExpression("http", "localhost", None, None), )) def testDirectiveParser_parse_ignore(self): """Report URIs are not supported in CSP Directives.""" ignoredDirective = "report-uri http://localhost/saveme.exe" assert DirectiveParser().parse(ignoredDirective) is Directive.INVALID() def testDirectiveParser_parse_none(self): """Parse a 'none' value.""" noneDirective = """default-src 'none' """ cspNoneDirective = DirectiveParser().parse(noneDirective) assert cspNoneDirective == Directive("default-src", []) def testDirectiveParser_parse_none_syntaxerror_strict(self): """If 'none' appears in a directive parsed strictly, no other values are permitted.""" noneDirectiveInvalid = """default-src http://one 'None' http://two""" assert DirectiveParser(strict=True).parse(noneDirectiveInvalid) \ == Directive.INVALID() def testDirectiveParser_parse_none_syntaxerror_notstrict(self): """If 'none' appears in a directive not parsed strictly and other expressions occur, 'none' is ignored.""" noneDirectiveInvalid = """default-src http://one 'None' http://two""" cspDirective = DirectiveParser( strict=False).parse(noneDirectiveInvalid) assert cspDirective == Directive("default-src", [ URISourceExpression("http", "one", None, None), URISourceExpression("http", "two", None, None) ]) def testDirectiveParser_parse_invalidSourceExpression_strict(self): """In strict mode, only valid source expressions may be used.""" invalidDirective = """img-src http://url 'blah'""" assert DirectiveParser(strict=True).parse(invalidDirective) \ == Directive.INVALID() def testDirectiveParser_parse_invalidSourceExpression_notstrict(self): """In non-strict mode, invalid source expressions are ignored.""" invalidDirective = """img-src http://url 'blah'""" cspDirective = DirectiveParser(strict=False).parse(invalidDirective) assert cspDirective == Directive( "img-src", [URISourceExpression("http", "url", None, None)]) def testDirectiveParser_parse_inline(self): """'unsafe-inline' keyword allowed only in 'script-src' and 'style-src' and 'default-src'""" scriptSrcWithInline = """script-src 'self' 'unsafe-inline' http://me.com/""" styleSrcWithInline = """style-src 'unsafe-inline' http://other""" styleSrcWithInlineOnly = """style-src 'unsafe-inline'""" defaultSrcWithInlineOnly = """default-src 'unsafe-inline'""" invalidObjectSrcWithInline = """object-src 'self' 'unsafe-inline'""" invalidObjectSrcWithInlineOnly = """object-src 'unsafe-inline'""" assert str(DirectiveParser().parse( scriptSrcWithInline)) == scriptSrcWithInline assert str( DirectiveParser().parse(styleSrcWithInline)) == styleSrcWithInline assert str(DirectiveParser().parse( styleSrcWithInlineOnly)) == styleSrcWithInlineOnly assert str(DirectiveParser().parse( defaultSrcWithInlineOnly)) == defaultSrcWithInlineOnly assert DirectiveParser(strict=True).parse(invalidObjectSrcWithInline) \ == Directive.INVALID() assert str(DirectiveParser(strict=False).parse(invalidObjectSrcWithInline)) \ == "object-src 'self'" assert DirectiveParser(strict=True).parse(invalidObjectSrcWithInlineOnly) \ == Directive.INVALID() assert str(DirectiveParser(strict=False).parse(invalidObjectSrcWithInlineOnly)) \ == "object-src 'none'" def testDirectiveParser_parse_eval(self): """'unsafe-eval' keyword allowed only in 'script-src' and 'default-src'""" scriptSrcWithEval = """script-src 'self' 'unsafe-eval' 'unsafe-inline'""" scriptSrcWithEvalOnly = """script-src 'unsafe-eval'""" defaultSrcWithInlineAndEvalOnly = """default-src 'unsafe-eval' 'unsafe-inline'""" invalidImgSrcWithEval = """img-src http://example/path 'unsafe-eval'""" assert str( DirectiveParser().parse(scriptSrcWithEval)) == scriptSrcWithEval assert str(DirectiveParser().parse( scriptSrcWithEvalOnly)) == scriptSrcWithEvalOnly assert str(DirectiveParser().parse(defaultSrcWithInlineAndEvalOnly)) \ == defaultSrcWithInlineAndEvalOnly assert DirectiveParser(strict=True).parse(invalidImgSrcWithEval) \ == Directive.INVALID() assert str(DirectiveParser(strict=False).parse(invalidImgSrcWithEval)) \ == "img-src http://example/path" def testDirectiveParser_parse_inlineStyleBaseRestriction(self): """The Firefox value 'inline style base restriction' for the 'violated-directive' field is parsed correctly.""" firefoxViolatedDirective = "inline style base restriction" assert DirectiveParser().parse(firefoxViolatedDirective) \ == Directive.INLINE_STYLE_BASE_RESTRICTION() def testDirectiveParser_parse_inlineScriptBaseRestriction(self): """The Firefox value 'inline script base restriction' for the 'violated-directive' field is parsed correctly.""" firefoxViolatedDirective = "inline script base restriction" assert DirectiveParser().parse(firefoxViolatedDirective) \ == Directive.INLINE_SCRIPT_BASE_RESTRICTION() def testDirectiveParser_parse_evalScriptBaseRestriction(self): """The Firefox value 'eval script base restriction' for the 'violated-directive' field is parsed correctly.""" firefoxViolatedDirective = "eval script base restriction" assert DirectiveParser().parse(firefoxViolatedDirective) \ == Directive.EVAL_SCRIPT_BASE_RESTRICTION()
class LogEntryTest(unittest.TestCase): starSourceExpr = URISourceExpression(None, "*", None, None) strLogEntry = """{"csp-report": {"blocked-uri": "", "document-uri": "http://seclab.nu/csp-test.html", """ \ + """"original-policy": "default-src *; script-src 'unsafe-eval' *; style-src *", """ \ + """"referrer": "", "status-code": 200, "violated-directive": "style-src *"}, """ \ + """"header-type": "webkit", "http-user-agent": """ \ + """"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, """ \ + """like Gecko) Chrome/31.0.1650.63 Safari/537.36", "policy-type": "inline", """ \ + """"remote-addr": "1.2.3.4", "timestamp-utc": "2013-12-14 01:02:03.456789"}""" logEntryData = {u"csp-report": Report({u"document-uri": URI("http", "seclab.nu", None, u"/csp-test.html"), u"referrer": URI.EMPTY(), u"violated-directive": Directive("style-src", (starSourceExpr,)), u"original-policy": Policy((Directive("default-src", (starSourceExpr,)), Directive("script-src", (starSourceExpr, SourceExpression.UNSAFE_EVAL())), Directive("style-src", (starSourceExpr,)))), u"blocked-uri": URI.EMPTY(), u"status-code": 200 }), u"remote-addr": u"1.2.3.4", u"http-user-agent": u"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) " \ + u"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36", u"timestamp-utc": u"2013-12-14 01:02:03.456789", u"policy-type": u"inline", u"header-type": u"webkit" } cspLogEntry = LogEntry(logEntryData) def testLogEntry_str_invalid(self): assert str(LogEntry.INVALID()) == "[invalid]" def testLogEntry_str_regular(self): assert str(LogEntryTest.cspLogEntry) == LogEntryTest.strLogEntry def testLogEntry_dict_iterateAndImmutable(self): assert len(LogEntryTest.cspLogEntry) == 6 assert "http-user-agent" in LogEntryTest.cspLogEntry assert LogEntryTest.cspLogEntry["remote-addr"] == "1.2.3.4" for key in LogEntryTest.cspLogEntry.keys(): assert key in ("csp-report", "remote-addr", "http-user-agent", "timestamp-utc", "policy-type", "header-type") with pytest.raises(TypeError): LogEntryTest.cspLogEntry["csp-report"] = None with pytest.raises(TypeError): LogEntryTest.cspLogEntry["something-else"] = 123 def testLogEntry_eq(self): assert LogEntryTest.cspLogEntry != LogEntry.INVALID() def testLogEntry_generatePolicy_standard(self): assert LogEntryTest.cspLogEntry.generatePolicy() == Policy( [Directive("style-src", [SourceExpression.UNSAFE_INLINE()])]) def testLogEntry_generatePolicy_invalid(self): assert LogEntry.INVALID().generatePolicy() == Policy.INVALID() def testLogEntry_generatePolicy_incomplete(self): logEntryNoReport = LogEntryTest.logEntryData.copy() del logEntryNoReport['csp-report'] logEntryNoPolicyType = LogEntryTest.logEntryData.copy() del logEntryNoPolicyType['policy-type'] assert LogEntry(logEntryNoReport).generatePolicy() == Policy.INVALID() assert LogEntry( logEntryNoPolicyType).generatePolicy() == Policy.INVALID() def testLogEntryParser_parse(self): parser = LogEntryParser(strict=True) parsed = parser.parseString(LogEntryTest.strLogEntry) print parsed._entryData print LogEntryTest.cspLogEntry._entryData print parsed._entryData[ 'csp-report'] == LogEntryTest.cspLogEntry._entryData['csp-report'] assert parsed == LogEntryTest.cspLogEntry
def test_no_modification_partial_URI_data_scheme_colon_slash_slash(self): """Ensure that an URI with only 'data' is parsed as the scheme with empty host name, not as host name.""" uriString = "data://" uriExpected = URI("data", "", None, None) assert URIParser().parse(uriString) == uriExpected
def test_no_modification_partial_URI_data_scheme(self): """Ensure that an URI with only 'data' is parsed as the scheme with empty host name, not as host name.""" uriString = "data" uriExpected = URI("data", "", None, None) assert URIParser(addScheme=False, addPort=False).parse(uriString) == uriExpected
def test_no_modification_partial_URI_host_port_query(self): """Simple test with no modification and only host and port and query in URI.""" uriString = "www.seclab.nu:80?query" uriExpected = URI(None, "www.seclab.nu", 80, None, "query") assert URIParser(addScheme=False, addPort=False).parse(uriString) == uriExpected
class PolicyTest(unittest.TestCase): sampleURI1a = URI("http", "seclab.nu", None, None, None) sampleURI1b = URI("http", "seclab.nu", None, None, None) sampleURI2 = URI("http", "seclab.ccs.neu.edu", None, None, None) sampleSourceExpression1 = URISourceExpression("http", "seclab.nu", None, None) sampleSourceExpression2 = SelfSourceExpression.SELF() sampleDirective1a = Directive( "default-src", (URISourceExpression("http", "seclab.nu", None, None), )) sampleDirective1b = Directive( "default-src", (URISourceExpression("http", "seclab.nu", None, None), )) sampleDirective2 = Directive("script-src", (SourceExpression.UNSAFE_INLINE(), )) sampleDirective3 = Directive( "img-src", (URISourceExpression(None, "*", None, None), )) sampleDirective4 = Directive("img-src", ()) sampleDirective5 = Directive( "connect-src", (SelfSourceExpression.SELF(), URISourceExpression("chrome-extension", None, None, None), URISourceExpression("https", "abc.seclab.nu", None, "/path"))) sampleDirective6 = Directive( "style-src", (SourceExpression.UNSAFE_INLINE(), SelfSourceExpression.SELF())) sampleDirective7 = Directive("script-src", (SourceExpression.UNSAFE_EVAL(), )) sampleDirective8 = Directive("style-src", (SelfSourceExpression.SELF(), )) sampleDirective9 = Directive("default-src", (SourceExpression.UNSAFE_INLINE(), )) def testPolicy_str_invalid(self): assert str(Policy.INVALID()) == "[invalid]" def testPolicy_str_empty(self): assert str(Policy(())) == "" def testPolicy_str_normal(self): pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3)) assert str( pol ) == "default-src http://seclab.nu; img-src *; script-src 'unsafe-inline'" def testPolicy_eq(self): pol1a = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3)) pol1b = Policy( (PolicyTest.sampleDirective1b, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3)) pol2 = Policy( (PolicyTest.sampleDirective2, PolicyTest.sampleDirective3)) assert Policy.INVALID() == Policy.INVALID() assert pol1a == pol1b assert hash(pol1a) == hash(pol1b) assert pol1a != pol2 def testPolicy_init_duplicateType(self): pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective3, PolicyTest.sampleDirective4)) directives = pol.getDirectives() assert PolicyTest.sampleDirective1a in directives and (PolicyTest.sampleDirective3 in directives \ or PolicyTest.sampleDirective4 in directives) def testPolicy_init_duplicateDirective(self): pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective1b, PolicyTest.sampleDirective2)) assert pol == Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2)) assert pol == Policy( (PolicyTest.sampleDirective1b, PolicyTest.sampleDirective2)) def testPolicy_init_noDuplicatesHere(self): directives = set([ PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective5 ]) pol = Policy(directives) assert pol.getDirectives() == directives def testPolicy_init_removeNotRegularDirective(self): pol = Policy([ PolicyTest.sampleDirective1a, Directive.INVALID(), Directive.EVAL_SCRIPT_BASE_RESTRICTION() ]) expected = Policy([PolicyTest.sampleDirective1a]) assert pol == expected def testPolicy_matches_invalid(self): """An invalid policy matches nothing.""" selfURI = PolicyTest.sampleURI2 assert not Policy.INVALID().matches(PolicyTest.sampleURI1a, "script-src", selfURI) assert not Policy.INVALID().matches(URI.INVALID(), "script-src", selfURI) assert not Policy.INVALID().matches(URI.EMPTY(), "script-src", selfURI) assert not Policy.INVALID().matches(URI.INLINE(), "script-src", selfURI) assert not Policy.INVALID().matches(URI.EVAL(), "script-src", selfURI) def testPolicy_matches_matchingDirectiveType(self): """Policy contains directive of resource type that matches.""" pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert pol.matches( URI("https", "abc.seclab.nu", 443, "/path", "some-query"), "connect-src", selfURI) def testPolicy_matches_nonMatchingDirectiveTypeButDefaultMatches(self): """Policy contains directive of resource type that does not match and default directive that does match, but it should not be applied.""" pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert not pol.matches(PolicyTest.sampleURI1a, "connect-src", selfURI) def testPolicy_matches_defaultSrcMatches(self): """Policy contains no directive of resource type, but a default directive that matches.""" pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert pol.matches(PolicyTest.sampleURI1a, "script-src", selfURI) def testPolicy_matches_defaultSrcNoMatch(self): """Policy contains no directive of resource type, but a default directive. Default-src does not match.""" pol = Policy((PolicyTest.sampleDirective1a, )) selfURI = PolicyTest.sampleURI1a assert not pol.matches(PolicyTest.sampleURI2, "img-src", selfURI) def testPolicy_matches_defaultSrcNotUsable(self): """Policy contains no directive of resource type, but a default directive. Default-src cannot be used in this case because not allowed for resource type.""" pol = Policy((PolicyTest.sampleDirective1a, )) selfURI = PolicyTest.sampleURI2 assert not pol.matches(PolicyTest.sampleURI1a, "form-action", selfURI) def testPolicy_matches_defaultSrcNotSpecified_match(self): """Policy contains no directive of resource type, and no default directive either. Should assume 'default-src *' (match for regular resources).""" pol = Policy((PolicyTest.sampleDirective5, )) selfURI = PolicyTest.sampleURI1a assert pol.matches(PolicyTest.sampleURI2, "script-src", selfURI) def testPolicy_matches_defaultSrcNotSpecified_noMatch(self): """Policy contains no directive of resource type, and no default directive either. Should assume 'default-src *' (no match for inline/eval resources).""" pol = Policy((PolicyTest.sampleDirective5, )) selfURI = PolicyTest.sampleURI1a assert not pol.matches(URI.INLINE(), "script-src", selfURI) assert not pol.matches(URI.EVAL(), "script-src", selfURI) def testPolicyParser_parse_normal(self): simplePolicy = """connect-src 'self' https://abc.seclab.nu/path chrome-extension:; img-src 'none'""" cspSimplePolicy = PolicyParser().parse(simplePolicy) print cspSimplePolicy assert cspSimplePolicy == Policy( [PolicyTest.sampleDirective4, PolicyTest.sampleDirective5]) def testPolicyParser_parse_strict_onlyValidDirectives(self): """Ensures that a CSP policy does not parse in strict mode if it contains an invalid directive.""" policy = """img-src 'none'; script-src""" cspPolicy = PolicyParser(strict=True).parse(policy) assert cspPolicy == Policy.INVALID() def testPolicyParser_parse_nonstrict_onlyValidDirectives(self): """Ensures that a CSP policy does ignore invalid portions in non-strict mode if it contains an invalid directive.""" policy = """img-src 'none'; script-src""" cspPolicy = PolicyParser(strict=False).parse(policy) assert cspPolicy == Policy([PolicyTest.sampleDirective4]) def testPolicyParser_parse_ignoredDirective(self): """Ensure that unsupported directives ('report-uri' etc.) are skipped without causing an error.""" policy = """img-src *; report-uri /csp.cgi""" cspPolicy = PolicyParser(strict=True, ignoredTypes=("report-uri", )).parse(policy) assert cspPolicy == Policy([PolicyTest.sampleDirective3]) def testPolicyParser_parse_defaultSrcRewriting(self): """The default directive is used for each type that is not specifically defined (if the flag is enabled).""" policy = """default-src 'self' http://seclab.nu; connect-src 'self' https://abc.seclab.nu/path chrome-extension:""" cspPolicy = PolicyParser(expandDefaultSrc=True, defaultSrcTypes=("img-src", "connect-src")).parse(policy) assert cspPolicy == Policy([ PolicyTest.sampleDirective5, Directive("img-src", [ PolicyTest.sampleSourceExpression1, PolicyTest.sampleSourceExpression2 ]) ]) def testPolicyParser_parse_noDefaultSrcRewriting(self): policy = """default-src 'self' http://seclab.nu""" cspPolicy = PolicyParser(expandDefaultSrc=False, defaultSrcTypes=("img-src", "connect-src")).parse(policy) assert cspPolicy == Policy([ Directive("default-src", [ PolicyTest.sampleSourceExpression1, PolicyTest.sampleSourceExpression2 ]) ]) def testPolicyParser_parse_duplicates(self): """The CSP standard mandates that only the first directive of each type should be used.""" duplicatePolicy = """connect-src 'self' chrome-extension: https://abc.seclab.nu/path; """ \ + """font-src 'self' http://seclab.nu; """ \ + """connect-src 'self' https://example.com""" cspPolicy = PolicyParser().parse(duplicatePolicy) assert cspPolicy == Policy([ PolicyTest.sampleDirective5, Directive("font-src", [ PolicyTest.sampleSourceExpression1, PolicyTest.sampleSourceExpression2 ]) ]) def testPolicy_combinedPolicy_normal(self): pol1 = Policy([ PolicyTest.sampleDirective6, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3 ]) pol2 = Policy( [PolicyTest.sampleDirective4, PolicyTest.sampleDirective7]) expected = Policy([ PolicyTest.sampleDirective6, PolicyTest.sampleDirective3, Directive("script-src", [ SourceExpression.UNSAFE_EVAL(), SourceExpression.UNSAFE_INLINE() ]) ]) assert pol1.combinedPolicy(pol1) == pol1 assert pol2.combinedPolicy(pol2) == pol2 assert pol1.combinedPolicy(pol2) == expected assert pol2.combinedPolicy(pol1) == expected def testPolicy_combinedPolicy_invalidPolicy(self): pol = Policy([ PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3 ]) assert pol.combinedPolicy(Policy.INVALID()) == Policy.INVALID() assert Policy.INVALID().combinedPolicy(pol) == Policy.INVALID() def testPolicy_combinedPolicy_invalidDefaultSrcAndOtherDirective(self): pol1 = Policy( [PolicyTest.sampleDirective1a, PolicyTest.sampleDirective3]) pol2 = Policy([PolicyTest.sampleDirective9]) assert pol1.combinedPolicy(pol2) == Policy.INVALID() assert pol2.combinedPolicy(pol1) == Policy.INVALID() def testPolicy_combinedPolicy_invalidDefaultSrcInOnePolicyOnly(self): pol1 = Policy([PolicyTest.sampleDirective3]) pol2 = Policy([PolicyTest.sampleDirective9]) assert pol1.combinedPolicy(pol2) == Policy.INVALID() assert pol2.combinedPolicy(pol1) == Policy.INVALID() def testPolicy_combinedPolicy_validDefaultSrcOnly(self): """Combination of two policies with default directive is possible only if both policies contain only a default directive.""" pol1 = Policy([PolicyTest.sampleDirective1a]) pol2 = Policy([PolicyTest.sampleDirective9]) expected = Policy([ Directive("default-src", [ PolicyTest.sampleSourceExpression1, SourceExpression.UNSAFE_INLINE() ]) ]) assert pol1.combinedPolicy(pol2) == expected assert pol2.combinedPolicy(pol1) == expected def testPolicy_withoutPaths(self): withPaths = Policy([ PolicyTest.sampleDirective3, PolicyTest.sampleDirective5, PolicyTest.sampleDirective7 ]) withoutPaths = Policy([ PolicyTest.sampleDirective3, PolicyTest.sampleDirective5.withoutPaths(), PolicyTest.sampleDirective7 ]) assert withPaths.withoutPaths() == withoutPaths assert withoutPaths.withoutPaths() == withoutPaths assert Policy.INVALID().withoutPaths() == Policy.INVALID() def testPolicy_withoutPaths_schemeOnly(self): withPaths = Policy( [PolicyTest.sampleDirective3, PolicyTest.sampleDirective5]) withoutPaths = Policy([ PolicyTest.sampleDirective3, PolicyTest.sampleDirective5.withoutPaths(["http"]) ]) assert withPaths.withoutPaths(["http"]) == withoutPaths def testPolicy_asBasicPolicies_single(self): assert Policy.INVALID().asBasicPolicies() == set([]) assert Policy([PolicyTest.sampleDirective1a]).asBasicPolicies() == set( [Policy([PolicyTest.sampleDirective1a])]) def testPolicy_asBasicPolicies_multiple(self): assert Policy( [PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2]).asBasicPolicies() == set([ Policy([PolicyTest.sampleDirective1a]), Policy([PolicyTest.sampleDirective2]) ]) def testPolicy_asBasicPolicies_recursive(self): expected = set( map(lambda direct: Policy((direct, )), PolicyTest.sampleDirective5.asBasicDirectives())) actual = Policy([PolicyTest.sampleDirective5]).asBasicPolicies() assert actual == expected def testPolicy_isBasicPolicy(self): assert Policy.INVALID().isBasicPolicy() == False assert Policy([PolicyTest.sampleDirective6]).isBasicPolicy() == False assert Policy([PolicyTest.sampleDirective1a]).isBasicPolicy() == True assert Policy( [PolicyTest.sampleDirective2, PolicyTest.sampleDirective4]).isBasicPolicy() == False def testPolicy_isBasicNonePolicy(self): assert Policy.INVALID().isBasicNonePolicy() == False assert Policy([PolicyTest.sampleDirective6 ]).isBasicNonePolicy() == False assert Policy([PolicyTest.sampleDirective1a ]).isBasicNonePolicy() == False assert Policy( [PolicyTest.sampleDirective2, PolicyTest.sampleDirective4]).isBasicNonePolicy() == False assert Policy([PolicyTest.sampleDirective4 ]).isBasicNonePolicy() == True def testPolicy_hasDefaultDirective(self): assert Policy.INVALID().hasDefaultDirective() == False assert Policy( [PolicyTest.sampleDirective2, PolicyTest.sampleDirective9]).hasDefaultDirective() == True assert Policy( [PolicyTest.sampleDirective5, PolicyTest.sampleDirective6]).hasDefaultDirective() == False def testPolicy_compareTo_invalid(self): assert Policy.INVALID().compareTo(Policy.INVALID()) == (set([]), set([]), set([])) pol = Policy([PolicyTest.sampleDirective9]) assert pol.compareTo(Policy.INVALID()) == (set([]), set([]), set([])) assert Policy.INVALID().compareTo(pol) == (set([]), set([]), set([])) def testPolicy_compareTo_regular(self): pol1 = Policy( [PolicyTest.sampleDirective7, PolicyTest.sampleDirective8]) pol2 = Policy( [PolicyTest.sampleDirective7, PolicyTest.sampleDirective9]) assert pol1.compareTo(pol2) == (set([ Policy([PolicyTest.sampleDirective7]) ]), set([Policy([PolicyTest.sampleDirective8]) ]), set([Policy([PolicyTest.sampleDirective9])])) assert pol2.compareTo(pol1) == (set([ Policy([PolicyTest.sampleDirective7]) ]), set([Policy([PolicyTest.sampleDirective9]) ]), set([Policy([PolicyTest.sampleDirective8])])) assert pol1.compareTo(pol1) == (set([ Policy([PolicyTest.sampleDirective7]), Policy([PolicyTest.sampleDirective8]) ]), set([]), set([])) def testPolicy_compareTo_recursive(self): pol1 = Policy([PolicyTest.sampleDirective6]) pol2 = Policy( [Directive("style-src", [SourceExpression.UNSAFE_INLINE()])]) assert pol1.compareTo(pol2) == ( set([pol2]), set([ Policy([Directive("style-src", [SelfSourceExpression.SELF()])]) ]), set([]))
def test_no_modification_partial_URI_viewSource(self): """view-source: URIs are similar to data URIs.""" uriString = "view-source:http://www.seclab.nu/page/" uriExpected = URI("view-source", "http://www.seclab.nu/page/", None, None) assert URIParser().parse(uriString) == uriExpected