Skip to content

Commit

Permalink
Support SRI (#142)
Browse files Browse the repository at this point in the history
* Support SRI

Co-authored-by: Grey Li <[email protected]>
  • Loading branch information
yuxiaoy1 and greyli authored Sep 4, 2021
1 parent e427ddd commit 0eff73c
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 23 deletions.
87 changes: 68 additions & 19 deletions flask_bootstrap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ def is_hidden_field_filter(field):
def is_hidden_field_filter(field):
return isinstance(field, HiddenField)

# central definition of used versions
# central definition of used versions and SRI hashes
VERSION_BOOTSTRAP = '4.3.1'
VERSION_JQUERY = '3.4.1'
VERSION_POPPER = '1.14.0'
BOOTSTRAP_CSS_INTEGRITY = 'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T'
BOOTSTRAP_JS_INTEGRITY = 'sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM'
JQUERY_INTEGRITY = 'sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh'
POPPER_INTEGRITY = 'sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ'


def raise_helper(message): # pragma: no cover
Expand Down Expand Up @@ -78,7 +82,7 @@ def init_app(self, app):
app.config.setdefault('BOOTSTRAP_TABLE_NEW_TITLE', 'New')

@staticmethod
def load_css(version=VERSION_BOOTSTRAP):
def load_css(version=VERSION_BOOTSTRAP, bootstrap_sri=None):
"""Load Bootstrap's css resources with given version.
.. versionadded:: 0.1.0
Expand All @@ -89,26 +93,38 @@ def load_css(version=VERSION_BOOTSTRAP):
serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']
bootswatch_theme = current_app.config['BOOTSTRAP_BOOTSWATCH_THEME']

if version == VERSION_BOOTSTRAP and serve_local is False and bootstrap_sri is None:
bootstrap_sri = BOOTSTRAP_CSS_INTEGRITY

if not bootswatch_theme:
base_path = 'css/'
else:
base_path = 'css/swatch/%s/' % bootswatch_theme.lower()

if serve_local:
css = '<link rel="stylesheet" href="%s" type="text/css">' % \
url_for('bootstrap.static', filename=base_path + css_filename)
if bootstrap_sri:
css = ('<link rel="stylesheet" href="%s" integrity="%s" crossorigin="anonymous">' %
(url_for('bootstrap.static', filename=base_path + css_filename), bootstrap_sri))
else:
css = ('<link rel="stylesheet" href="%s">' %
url_for('bootstrap.static', filename=base_path + css_filename))
else:
if not bootswatch_theme:
css = '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/css/%s"' \
' type="text/css">' % (version, css_filename)
if bootstrap_sri:
css = ('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/css/%s"'
' integrity="%s" crossorigin="anonymous">' % (version, css_filename, bootstrap_sri))
else:
css = ('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/css/%s">' %
(version, css_filename))
else:
css = '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@%s/dist/%s/%s"' \
' type="text/css">' % (version, bootswatch_theme.lower(), css_filename)
css = ('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@%s/dist/%s/%s">' %
(version, bootswatch_theme.lower(), css_filename))
return Markup(css)

@staticmethod
def load_js(version=VERSION_BOOTSTRAP, jquery_version=VERSION_JQUERY,
popper_version=VERSION_POPPER, with_jquery=True, with_popper=True):
def load_js(version=VERSION_BOOTSTRAP, jquery_version=VERSION_JQUERY, # noqa: C901
popper_version=VERSION_POPPER, with_jquery=True, with_popper=True,
bootstrap_sri=None, jquery_sri=None, popper_sri=None):
"""Load Bootstrap and related library's js resources with given version.
.. versionadded:: 0.1.0
Expand All @@ -125,27 +141,60 @@ def load_js(version=VERSION_BOOTSTRAP, jquery_version=VERSION_JQUERY,

serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']

if version == VERSION_BOOTSTRAP and serve_local is False and bootstrap_sri is None:
bootstrap_sri = BOOTSTRAP_JS_INTEGRITY
if jquery_version == VERSION_JQUERY and serve_local is False and jquery_sri is None:
jquery_sri = JQUERY_INTEGRITY

if popper_version == VERSION_POPPER and serve_local is False and popper_sri is None:
popper_sri = POPPER_INTEGRITY
if serve_local:
js = '<script src="%s"></script>' % url_for('bootstrap.static', filename='js/' + js_filename)
if bootstrap_sri:
js = ('<script src="%s" integrity="%s" crossorigin="anonymous"></script>' %
(url_for('bootstrap.static', filename='js/' + js_filename), bootstrap_sri))
else:
js = '<script src="%s"></script>' % url_for('bootstrap.static', filename='js/' + js_filename)
else:
js = '<script src="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/js/%s">' \
'</script>' % (version, js_filename)
if bootstrap_sri:
js = ('<script src="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/js/%s"'
' integrity="%s" crossorigin="anonymous"></script>' % (version, js_filename, bootstrap_sri))
else:
js = ('<script src="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/js/%s">'
'</script>' % (version, js_filename))

if with_jquery:
if serve_local:
jquery = '<script src="%s"></script>' % url_for('bootstrap.static', filename=jquery_filename)
if jquery_sri:
jquery = ('<script src="%s" integrity="%s" crossorigin="anonymous"></script>' %
(url_for('bootstrap.static', filename=jquery_filename), jquery_sri))
else:
jquery = '<script src="%s"></script>' % url_for('bootstrap.static', filename=jquery_filename)
else:
jquery = '<script src="https://cdn.jsdelivr.net/npm/jquery@%s/dist/%s">' \
'</script>' % (jquery_version, jquery_filename)
if jquery_sri:
jquery = ('<script src="https://cdn.jsdelivr.net/npm/jquery@%s/dist/%s"'
' integrity="%s" crossorigin="anonymous"></script>' %
(jquery_version, jquery_filename, jquery_sri))
else:
jquery = ('<script src="https://cdn.jsdelivr.net/npm/jquery@%s/dist/%s">'
'</script>' % (jquery_version, jquery_filename))
else:
jquery = ''

if with_popper:
if serve_local:
popper = '<script src="%s"></script>' % url_for('bootstrap.static', filename=popper_filename)
if popper_sri:
popper = ('<script src="%s" integrity="%s" crossorigin="anonymous"></script>' %
(url_for('bootstrap.static', filename=popper_filename), popper_sri))
else:
popper = '<script src="%s"></script>' % url_for('bootstrap.static', filename=popper_filename)
else:
popper = '<script src="https://cdn.jsdelivr.net/npm/popper.js@%s/dist/umd/%s">' \
'</script>' % (popper_version, popper_filename)
if popper_sri:
popper = ('<script src="https://cdn.jsdelivr.net/npm/popper.js@%s/dist/umd/%s"'
' integrity="%s" crossorigin="anonymous"></script>' %
(popper_version, popper_filename, popper_sri))
else:
popper = ('<script src="https://cdn.jsdelivr.net/npm/popper.js@%s/dist/umd/%s">'
'</script>' % (popper_version, popper_filename))
else:
popper = ''
return Markup('''%s
Expand Down
109 changes: 105 additions & 4 deletions tests/test_bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,64 @@
import pytest
from flask import current_app
from flask_bootstrap import (
VERSION_BOOTSTRAP, VERSION_JQUERY, VERSION_POPPER,
BOOTSTRAP_CSS_INTEGRITY, BOOTSTRAP_JS_INTEGRITY,
JQUERY_INTEGRITY, POPPER_INTEGRITY
)


@pytest.mark.usefixtures('client')
class TestBootstrap:
def test_extension_init(self, bootstrap):
assert 'bootstrap' in current_app.extensions

def test_load_css(self, bootstrap):
def test_load_css_with_default_versions(self, bootstrap):
rv = bootstrap.load_css()
assert 'bootstrap.min.css' in rv
bootstrap_css = ('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/css/%s"'
' integrity="%s" crossorigin="anonymous">' % (VERSION_BOOTSTRAP, 'bootstrap.min.css',
BOOTSTRAP_CSS_INTEGRITY))
assert bootstrap_css in rv

def test_load_js(self, bootstrap):
def test_load_css_with_non_default_versions(self, bootstrap):
def _check_assertions(rv):
assert 'bootstrap.min.css' in rv
assert 'integrity="' not in rv
assert 'crossorigin="anonymous"' not in rv

rv = bootstrap.load_css(version='1.2.3')
_check_assertions(rv)
rv = bootstrap.load_css(version='5.0.0')
_check_assertions(rv)

def test_load_js_with_default_versions(self, bootstrap):
rv = bootstrap.load_js()
assert 'bootstrap.min.js' in rv
bootstrap_js = ('<script src="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/js/%s"'
' integrity="%s" crossorigin="anonymous"></script>' %
(VERSION_BOOTSTRAP, 'bootstrap.min.js', BOOTSTRAP_JS_INTEGRITY))
jquery_js = ('<script src="https://cdn.jsdelivr.net/npm/jquery@%s/dist/%s"'
' integrity="%s" crossorigin="anonymous"></script>' %
(VERSION_JQUERY, 'jquery.min.js', JQUERY_INTEGRITY))
popper_js = ('<script src="https://cdn.jsdelivr.net/npm/popper.js@%s/dist/umd/%s"'
' integrity="%s" crossorigin="anonymous"></script>' %
(VERSION_POPPER, 'popper.min.js', POPPER_INTEGRITY))
assert bootstrap_js in rv
assert jquery_js in rv
assert popper_js in rv

def test_load_js_with_non_default_versions(self, bootstrap):
def _check_assertions(rv):
assert 'bootstrap.min.js' in rv
assert 'jquery.min.js' in rv
assert 'popper.min.js' in rv
assert 'integrity="' not in rv
assert 'crossorigin="anonymous"' not in rv

rv = bootstrap.load_js(version='1.2.3', jquery_version='1.2.3',
popper_version='1.2.3')
_check_assertions(rv)
rv = bootstrap.load_js(version='5.0.0', jquery_version='5.0.0',
popper_version='5.0.0')
_check_assertions(rv)

def test_local_resources(self, bootstrap, client):
current_app.config['BOOTSTRAP_SERVE_LOCAL'] = True
Expand All @@ -24,6 +69,8 @@ def test_local_resources(self, bootstrap, client):
assert 'bootstrap.min.js' in data
assert 'bootstrap.min.css' in data
assert 'jquery.min.js' in data
assert 'integrity="' not in data
assert 'crossorigin="anonymous"' not in data

with client.get('/bootstrap/static/css/bootstrap.min.css') as css_response:
assert css_response.status_code != 404
Expand All @@ -38,6 +85,30 @@ def test_local_resources(self, bootstrap, client):
assert '/bootstrap/static/js/bootstrap.min.js' in js_rv
assert 'https://cdn.jsdelivr.net/npm/bootstrap' not in css_rv
assert 'https://cdn.jsdelivr.net/npm/bootstrap' not in js_rv
assert 'integrity="' not in css_rv
assert 'crossorigin="anonymous"' not in css_rv
assert 'integrity="' not in js_rv
assert 'crossorigin="anonymous"' not in js_rv

def test_local_resources_with_sri(self, bootstrap):
current_app.config['BOOTSTRAP_SERVE_LOCAL'] = True

css_rv = bootstrap.load_css(bootstrap_sri='sha384-bootstrap-sri')
js_rv = bootstrap.load_js(
bootstrap_sri='sha384-bootstrap-sri',
jquery_sri='sha384-jquery-sri',
popper_sri='sha384-popper-sri'
)
assert '/bootstrap/static/css/bootstrap.min.css' in css_rv
assert '/bootstrap/static/js/bootstrap.min.js' in js_rv
assert 'https://cdn.jsdelivr.net/npm/bootstrap' not in css_rv
assert 'https://cdn.jsdelivr.net/npm/bootstrap' not in js_rv
assert 'integrity="sha384-bootstrap-sri"' in css_rv
assert 'crossorigin="anonymous"' in css_rv
assert 'integrity="sha384-bootstrap-sri"' in js_rv
assert 'integrity="sha384-jquery-sri"' in js_rv
assert 'integrity="sha384-popper-sri"' in js_rv
assert 'crossorigin="anonymous"' in js_rv

def test_cdn_resources(self, bootstrap, client):
current_app.config['BOOTSTRAP_SERVE_LOCAL'] = False
Expand All @@ -56,6 +127,36 @@ def test_cdn_resources(self, bootstrap, client):
assert 'https://cdn.jsdelivr.net/npm/bootstrap' in css_rv
assert 'https://cdn.jsdelivr.net/npm/bootstrap' in js_rv

def test_cdn_resources_with_custom_sri_hash(self, bootstrap, client):
current_app.config['BOOTSTRAP_SERVE_LOCAL'] = False

css_rv = bootstrap.load_css(bootstrap_sri='sha384-bootstrap-sri')
js_rv = bootstrap.load_js(
bootstrap_sri='sha384-bootstrap-sri',
jquery_sri='sha384-jquery-sri',
popper_sri='sha384-popper-sri'
)
assert 'integrity="sha384-bootstrap-sri"' in css_rv
assert 'crossorigin="anonymous"' in css_rv
assert 'integrity="sha384-bootstrap-sri"' in js_rv
assert 'integrity="sha384-jquery-sri"' in js_rv
assert 'integrity="sha384-popper-sri"' in js_rv
assert 'crossorigin="anonymous"' in js_rv

def test_disabling_sri(self, bootstrap):
css_rv = bootstrap.load_css(bootstrap_sri=False)
js_rv = bootstrap.load_js(
bootstrap_sri=False,
jquery_sri=False,
popper_sri=False
)
assert 'href="' in css_rv
assert 'integrity="' not in css_rv
assert 'crossorigin="anonymous"' not in css_rv
assert 'src="' in js_rv
assert 'integrity="' not in js_rv
assert 'crossorigin="anonymous"' not in js_rv

@pytest.mark.parametrize(
['with_jquery', 'with_popper'],
[
Expand Down

0 comments on commit 0eff73c

Please sign in to comment.