Suppress CSP errors when the app is of type 'web' (bug 841840)

This commit is contained in:
Matt Basta 2013-02-15 15:17:56 -08:00
Родитель 17628785cc
Коммит 16d2cab698
8 изменённых файлов: 144 добавлений и 77 удалений

Просмотреть файл

@ -2,20 +2,34 @@
CSP_INFO = "https://developer.mozilla.org/en-US/docs/Security/CSP"
MESSAGE_TITLE = "CSP Violation Detected"
MESSAGE_DESCRIPTION = ["It appears that your code may be performing an action "
"which violates the CSP (content security policy) for "
"privileged apps.",
"You can find more information about what is and is "
"not allowed by the CSP on the Mozilla Developers "
"website. %s" % CSP_INFO]
MESSAGE_GENERAL_DESC = ("You can find more information about what is and is "
"not allowed by the CSP on the Mozilla Developers "
"website. %s" % CSP_INFO)
MESSAGE_DESC = ["It appears that your code may be performing an action which "
"violates the CSP (content security policy) for privileged "
"apps.", MESSAGE_GENERAL_DESC]
MESSAGE_DESC_WEB = ["An action that you're performing violates the CSP "
"(content security policy). While this does not affect "
"your app, if you decide to add permissions to your app "
"in the future, you will be unable to do so until this "
"problem is corrected. It is highly recommended that "
"you remedy this.", MESSAGE_GENERAL_DESC]
def warn(err, filename, line, column, context, violation_type="default",
severity="warning"):
app_type = err.get_resource("app_type")
if app_type == "web":
severity = "warning"
params = dict(err_id=("csp", violation_type),
description=MESSAGE_DESCRIPTION,
description=MESSAGE_DESC_WEB if app_type == "web" else
MESSAGE_DESC,
filename=filename,
line=line,
column=column,
context=context)
params[severity] = MESSAGE_TITLE
getattr(err, severity)(**params)

Просмотреть файл

@ -7,7 +7,7 @@ from . import csstester
from appvalidator.contextgenerator import ContextGenerator
from appvalidator.constants import *
from appvalidator.csp import warn as message_csp
from patchedhtmlparser import PatchedHTMLParser
from patchedhtmlparser import htmlparser, PatchedHTMLParser
DEBUG = False
@ -18,11 +18,11 @@ SELF_CLOSING_TAGS = ("area", "base", "basefont", "br", "col", "frame", "hr",
TAG_NOT_OPENED = "Tag (%s) being closed before it is opened."
REMOTE_URL_PATTERN = re.compile("((ht|f)tps?:)?//")
DOM_MUTATION_HANDLERS = (
DOM_MUTATION_HANDLERS = set([
"ondomattrmodified", "ondomattributenamechanged",
"ondomcharacterdatamodified", "ondomelementnamechanged",
"ondomnodeinserted", "ondomnodeinsertedintodocument", "ondomnoderemoved",
"ondomnoderemovedfromdocument", "ondomsubtreemodified", )
"ondomnoderemovedfromdocument", "ondomsubtreemodified", ])
class MarkupParser(PatchedHTMLParser):
@ -44,7 +44,7 @@ class MarkupParser(PatchedHTMLParser):
self.reported = set()
def process(self, filename, data, extension="xul"):
def process(self, filename, data, extension="html"):
"""Processes data by splitting it into individual lines, then
incrementally feeding each line into the parser, increasing the
value of the line number with each line."""
@ -107,7 +107,7 @@ class MarkupParser(PatchedHTMLParser):
# There's no recovering from a unicode error here. We've got the
# unicodehelper; if that doesn't help us, nothing will.
return
except Exception as inst:
except htmlparser.HTMLParseError as inst:
if DEBUG: # pragma: no cover
print self.xml_state, inst
@ -153,7 +153,6 @@ class MarkupParser(PatchedHTMLParser):
# Normalize!
tag = tag.lower()
orig_tag = tag
# Be extra sure it's not a self-closing tag.
if not self_closing:
@ -162,49 +161,31 @@ class MarkupParser(PatchedHTMLParser):
if DEBUG: # pragma: no cover
print "S: ", self.xml_state, tag, self_closing
# Find CSS and JS attributes and handle their values like they
# would otherwise be handled by the standard parser flow.
for attr in attrs:
attr_name, attr_value = attr[0].lower(), attr[1]
attr_dict = dict([(a[0].lower(), a[1]) for a in attrs if a[1]])
# We don't care about valueless attributes.
if attr_value is None:
continue
if "style" in attr_dict:
csstester.test_css_snippet(
self.err, self.filename, attr_dict["style"], self.line)
if attr_name == "style":
csstester.test_css_snippet(self.err,
self.filename,
attr_value,
self.line)
elif attr_name.startswith("on"): # JS attribute
# Warn about DOM mutation event handlers.
if attr_name in DOM_MUTATION_HANDLERS:
self.err.error(
err_id=("testcases_markup_markuptester",
"handle_starttag",
"dom_manipulation_handler"),
error="DOM Mutation Events Prohibited",
description="DOM mutation events are flagged because "
"of their deprecated status, as well as "
"their extreme inefficiency. Consider "
"using a different event.",
filename=self.filename,
line=self.line,
context=self.context)
script_attributes = dict(
(k, v) for k, v in attr_dict.iteritems() if k.startswith("on"))
if script_attributes:
if any(k in DOM_MUTATION_HANDLERS for k in script_attributes):
self.err.error(
err_id=("testcases_markup_markuptester",
"handle_starttag", "dom_manipulation_handler"),
error="DOM Mutation Events Prohibited",
description="DOM mutation events are flagged because of "
"their deprecated status, as well as thier "
"extreme inefficiency. Consider using a "
"different event.",
filename=self.filename,
line=self.line,
context=self.context)
message_csp(err=self.err, filename=self.filename,
line=self.line, column=None,
context=self.context,
violation_type="script_attribute",
severity="error")
elif attr_name == "src" and tag == "script":
if not self._is_url_local(attr_value):
message_csp(err=self.err, filename=self.filename,
line=self.line, column=None,
context=self.context,
violation_type="remote_script",
severity="error")
message_csp(err=self.err, filename=self.filename,
line=self.line, column=None, context=self.context,
violation_type="script_attribute", severity="error")
# When the dev forgets their <!-- --> on a script tag, bad
# things happen.
@ -212,9 +193,30 @@ class MarkupParser(PatchedHTMLParser):
self._save_to_buffer("<" + tag + self._format_args(attrs) + ">")
return
self.xml_state.append(orig_tag)
elif (tag == "script" and
("type" not in attr_dict or
any(a[0] == "type" and "javascript" in a[1].lower() for
a in attrs))):
# Inspect scripts which either have no type or have a type which
# is JS.
if "src" not in attr_dict:
# CSP warnings for inline scripts
message_csp(err=self.err, filename=self.filename,
line=self.line, column=None,
context=self.context,
violation_type="inline_script",
severity="error")
elif not self._is_url_local(attr_dict.get("src", "")):
# If there's a remote SRC, then that's a CSP violation.
message_csp(err=self.err, filename=self.filename,
line=self.line, column=None, context=self.context,
violation_type="remote_script", severity="error")
self.xml_state.append(tag)
self.xml_line_stack.append(self.line)
self.xml_buffer.append(unicode(""))
self.xml_buffer.append(u"")
def handle_endtag(self, tag):
@ -303,16 +305,9 @@ class MarkupParser(PatchedHTMLParser):
data_buffer = data_buffer.strip()
# Perform analysis on collected data.
if data_buffer:
if tag == "script":
message_csp(err=self.err, filename=self.filename,
line=self.line, column=None,
context=self.context,
violation_type="inline_script",
severity="error")
elif tag == "style":
csstester.test_css_file(self.err, self.filename, data_buffer,
old_line)
if data_buffer and tag == "style":
csstester.test_css_file(self.err, self.filename, data_buffer,
old_line)
def handle_data(self, data):
self._save_to_buffer(data)

Просмотреть файл

@ -31,6 +31,8 @@ def test_app_manifest(err, package):
webapp = detect_webapp_string(err, package.read("manifest.webapp"))
err.save_resource("manifest", webapp)
if webapp:
err.save_resource("app_type", str(webapp.get("type", "web")).lower())
@register_test(tier=2)
@ -40,7 +42,7 @@ def test_permissions(err, package):
not err.get_resource("manifest")):
return
app_type = err.get_resource("manifest").get("type", "web")
app_type = err.get_resource("app_type")
def error(permission):
err.error(
@ -258,7 +260,7 @@ def test_icon(err, data, url, size):
"may contain invalid or corrupt data. Icons may "
"be only JPG or PNG images.",
"%dpx icon (%s)" % (size, url)])
else:
finally:
if gzf:
gzf.close()
else:

Просмотреть файл

@ -1,9 +1,58 @@
from nose.tools import eq_
from js_helper import TestCase
import appvalidator.testcases.markup.markuptester as markuptester
from ..helper import TestCase
from js_helper import TestCase as JSTestCase
class TestCSP(TestCase):
class TestCSPTags(TestCase):
def analyze(self, snippet, app_type="web"):
self.setup_err()
self.err.save_resource("app_type", app_type)
markuptester.MarkupParser(self.err, debug=True).process("", snippet)
def test_script_not_js(self):
markup = """
<script type="text/x-jquery-tmpl">foo</script>
"""
self.analyze(markup)
self.assert_silent()
self.analyze(markup, "privileged")
self.assert_silent()
def test_script(self):
markup = """<script>foo</script>"""
self.analyze(markup)
self.assert_failed(with_warnings=True)
self.analyze(markup, "privileged")
self.assert_failed(with_errors=True)
def test_script_attrs(self):
markup = """<button onclick="foo();"></button>"""
self.analyze(markup)
self.assert_failed(with_warnings=True)
self.analyze(markup, "privileged")
self.assert_failed(with_errors=True)
def test_script_remote(self):
markup = """<script src="http://foo.bar/zip.js"></script>"""
self.analyze(markup)
self.assert_failed(with_warnings=True)
self.analyze(markup, "privileged")
self.assert_failed(with_errors=True)
class TestCSP(JSTestCase):
def test_function(self):
self.run_script("var x = Function('foo');")
@ -34,7 +83,7 @@ class TestCSP(TestCase):
self.assert_silent()
class TestCreateElement(TestCase):
class TestCreateElement(JSTestCase):
def test_pass(self):
"Tests that createElement and createElementNS throw errors."

Просмотреть файл

@ -44,4 +44,3 @@ def test_remote_urls():
assert not t("UrL(/abc.def)")
assert t("url(HTTP://foo.bar/)")

Просмотреть файл

@ -22,6 +22,7 @@ def _test_xul_raw(data, path, should_fail=False, should_fail_csp=None,
extension = filename.split(".")[-1]
err = ErrorBundle()
err.save_resource("app_type", "certified")
if type_:
err.set_type(type_)
@ -186,7 +187,7 @@ def test_script_attrs():
"""Test that script attributes are warned against."""
_test_xul_raw("""
<foo><bar onzap="" /></foo>
<foo><bar onzap="asdf" /></foo>
""", "foo.xul", should_fail_csp=True)
@ -194,7 +195,7 @@ def test_dom_mutation():
"""Test that DOM mutation events are warned against. This should fail both
the standard tests as well as the CSP tests."""
_test_xul_raw("""
<foo><bar ondomattrmodified="" /></foo>
<foo><bar ondomattrmodified="asdf" /></foo>
""", "foo.xul", should_fail=True, should_fail_csp=True)
@ -203,7 +204,7 @@ def test_proper_line_numbers():
"""Test that the proper line numbers are passed to test_js_snippet."""
err = _test_xul_raw("""<foo>
<script>
<script> // This is line 2
eval("OWOWOWOWOWOWOWOW");
</script>
</foo>""", "foo.xul", should_fail_csp=True)
@ -211,8 +212,7 @@ def test_proper_line_numbers():
assert err.errors
error = err.errors[0]
eq_(error["file"], "foo.xul")
# 4 because it detects the script when it gets closed.
eq_(error["line"], 4)
eq_(error["line"], 2)
def test_script_scraping():

Просмотреть файл

@ -97,7 +97,7 @@ class TestWebapps(TestCase):
"type": "web",
}
self.resources = []
self.resources = [("app_type", "web")]
def analyze(self):
"""Run the webapp tests on the file."""

Просмотреть файл

@ -1,3 +1,5 @@
from nose.tools import eq_
from helper import TestCase
from appvalidator.errorbundle import ErrorBundle
import appvalidator.testcases.webappbase as appbase
@ -24,6 +26,8 @@ class TestWebappPermissions(TestCase):
def analyze(self):
self.err.save_resource(
"permissions", self.manifest.get("permissions", {}).keys())
self.err.save_resource(
"app_type", self.manifest.get("type", "web"))
appbase.test_permissions(self.err, None)
def test_no_perms(self):
@ -35,6 +39,7 @@ class TestWebappPermissions(TestCase):
self.manifest["type"] = "certified"
self.analyze()
self.assert_silent()
eq_(self.err.get_resource("app_type"), "certified")
def test_certified_perms_priv(self):
self.manifest["permissions"][CERT_PERM] = True
@ -58,6 +63,7 @@ class TestWebappPermissions(TestCase):
self.manifest["type"] = "privileged"
self.analyze()
self.assert_silent()
eq_(self.err.get_resource("app_type"), "privileged")
def test_privileged_perms_cert(self):
self.manifest["permissions"][PRIV_PERM] = True
@ -93,8 +99,10 @@ class TestWebappPermissions(TestCase):
self.manifest["type"] = "web"
self.analyze()
self.assert_silent()
eq_(self.err.get_resource("app_type"), "web")
def test_web_perms_web_implicit(self):
self.manifest["permissions"][WEB_PERM] = True
self.analyze()
self.assert_silent()
eq_(self.err.get_resource("app_type"), "web")