Skip to content

Commit

Permalink
Set the security headers
Browse files Browse the repository at this point in the history
Fixes #333

Signed-off-by: Aurélien Bompard <aurelien@bompard.org>
  • Loading branch information
abompard committed Feb 18, 2021
1 parent a914213 commit a5de5c4
Show file tree
Hide file tree
Showing 67 changed files with 7,884 additions and 49 deletions.
75 changes: 75 additions & 0 deletions data-uri-to-svg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python

"""
Replace data: URIs for SVGs in CSS files with URIs pointing to the extracted
SVG file.
This allows us to avoid adding the data: scheme to our Content Security Policy,
because it is insecure.
See https://github.com/fedora-infra/noggin/issues/333
If you update the CSS file, you need to run this script on it again.
"""

import argparse
import os
import re
from urllib.parse import unquote


DATA_URI_RE = re.compile(r"url\(\"data:image/svg\+xml,([^\"]+)\"\)")


def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("css_file", metavar="css-file", nargs="+")
args = parser.parse_args()
return args


class Replacer:
def __init__(self, filename):
self.filename = filename
self.directory = os.path.dirname(filename)
self.svgs = []
self.replacements = 0

@property
def target_filename(self):
return os.path.splitext(self.filename)[0] + ".nodata.css"

def _get_svg(self, match):
svg = match.group(1)
try:
svg_index = self.svgs.index(svg)
except ValueError:
svg_index = len(self.svgs)
self.svgs.append(svg)
with open(os.path.join(self.directory, f"{svg_index}.svg"), "w") as svg_file:
svg_file.write(unquote(svg))
self.replacements += 1
return f"url(\"{svg_index}.svg\")"

def run(self):
with open(self.filename) as css_file:
with open(self.target_filename, "w") as new_css_file:
new_css_file.write(
"/* This file has been generated by data-uri-to-svg.py */\n\n"
)
for line in css_file:
new_css_file.write(DATA_URI_RE.sub(self._get_svg, line))
print(
f"Replaced {self.replacements} data URIs with {len(self.svgs)} "
f"SVG files in {self.target_filename}."
)


def main():
args = parse_args()
for css_file in args.css_file:
Replacer(css_file).run()


if __name__ == "__main__":
main()
14 changes: 14 additions & 0 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,17 @@ To update all created languages with the newest strings in messages.pot, use::
To compile the translations in updated .mo files into what noggin can use, use the command::

poetry run pybabel compile -d noggin/translations


UI and themes
-------------

Noggin has support for themes, have a look at the existing themes for inspiration.

Some notes regarding our Content Security Policy:

- inline ``<script>`` tags must have a ``nonce`` attribute, look at the other templates for the proper Jinja snippet.
- CSS files can't use the ``data:`` scheme for images. Bootstrap makes use of that, for example.
You can convert a CSS file that uses the ``data:`` scheme for SVGs with the ``data-uri-to-svg.py`` script, it will
extract the files and replace the ``url()`` instructions. You can then just use the new file it created in the
HTML template.
19 changes: 19 additions & 0 deletions noggin/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from logging.config import dictConfig

import flask_talisman
from flask import Flask
from flask_healthz import healthz
from flask_mail import Mail
Expand Down Expand Up @@ -30,6 +31,9 @@
# Catch IPA errors
ipa_error_handler = IPAErrorHandler()

# Security headers
talisman = flask_talisman.Talisman()


def create_app(config=None):
"""See https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/"""
Expand Down Expand Up @@ -67,6 +71,21 @@ def create_app(config=None):
mailer.init_app(app)
ipa_error_handler.init_app(app)
theme.init_app(app, whitenoise=whitenoise)
talisman.init_app(
app,
force_https=app.config.get("SESSION_COOKIE_SECURE", True),
frame_options=flask_talisman.DENY,
referrer_policy="same-origin",
content_security_policy={
"default-src": "'self'",
"script-src": [
# https://csp.withgoogle.com/docs/strict-csp.html#example
"'strict-dynamic'",
],
"img-src": ["'self'", "seccdn.libravatar.org"],
},
content_security_policy_nonce_in=['script-src'],
)

# Register views
import_all("noggin.controller")
Expand Down
6 changes: 3 additions & 3 deletions noggin/templates/_form_macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@
{% endmacro %}

{% macro unsaved_changes() %}
<script src="{{ url_for('static', filename='js/vendor/jquery.dirty-0.7.2/jquery.dirty.js') }}"></script>
<script>$("form").dirty({preventLeaving: true})</script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/vendor/jquery.dirty-0.7.2/jquery.dirty.js') }}"></script>
<script nonce="{{ csp_nonce() }}">$("form").dirty({preventLeaving: true})</script>
{% endmacro %}

{% macro selectize_css() %}
<link href="{{ url_for('static', filename='js/vendor/selectize-0.12.6/selectize.bootstrap3.css') }}" rel="stylesheet" type="text/css">
{% endmacro %}

{% macro selectize() %}
<script src="{{ url_for('static', filename='js/vendor/selectize-0.12.6/selectize.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/vendor/selectize-0.12.6/selectize.min.js') }}"></script>
{% endmacro %}
2 changes: 1 addition & 1 deletion noggin/templates/group.html
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ <h4>{{ _("Sponsors") }} <span class="badge badge-secondary">{{sponsors|length}}<

{% block scripts %}
{{ super () }}
<script>
<script nonce="{{ csp_nonce() }}">
{% if current_user_is_sponsor and sponsor_form %}
var typeahead = $('#new_member_username').typeahead(
{ highlight: true},
Expand Down
16 changes: 8 additions & 8 deletions noggin/templates/master.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
{% block footer %}{% endblock %}
{% block scripts %}
{% if current_user %}
<script src="{{ url_for('static', filename='js/vendor/corejs-typeahead-1.2.1/bloodhound.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/vendor/corejs-typeahead-1.2.1/typeahead.jquery.min.js') }}"></script>
<script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/vendor/corejs-typeahead-1.2.1/bloodhound.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/vendor/corejs-typeahead-1.2.1/typeahead.jquery.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}">
var URL_SEARCH = {{ url_for('root.search_json')|tojson }};

/* Translated strings for use in search.js. These are the labels that are shown
in the searchbox dropdown */
var GROUPS_SEARCH_LABEL = '{{ _("Groups") }}'
var USERS_SEARCH_LABEL = '{{ _("Users") }}'
</script>
<script src="{{ url_for('static', filename='js/search.js') }}"></script>
<script src="{{ url_for('static', filename='js/vendor/moment-2.24.0/moment.js') }}"></script>
<script src="{{ url_for('static', filename='js/vendor/moment-timezone-0.5.28-2019c/moment-timezone-with-data-10-year-range.js') }}"></script>
<script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/search.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/vendor/moment-2.24.0/moment.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/vendor/moment-timezone-0.5.28-2019c/moment-timezone-with-data-10-year-range.js') }}"></script>
<script nonce="{{ csp_nonce() }}">
$(function () {
$('[data-toggle="popover"]').popover()
})
Expand Down
2 changes: 1 addition & 1 deletion noggin/templates/registration-spamcheck-wait.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ <h4 class="alert-heading">
</div>

{% if user.status_note == "spamcheck_awaiting" %}
<script>
<script nonce="{{ csp_nonce() }}">
// refresh page every five seconds
setTimeout(function(){ location.reload(); }, 5000);
</script>
Expand Down
8 changes: 5 additions & 3 deletions noggin/templates/user-settings-otp.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ <h5 id="pageheading">{{ _("OTP Tokens") }}</h5>
{% block scripts %}
{{ super() }}
{% if otp_uri %}
<script src="{{ url_for('static', filename='js/vendor/jquery-qrcode-1.0/jquery.qrcode.min.js') }}"></script>
<script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/vendor/jquery-qrcode-1.0/jquery.qrcode.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}">
$(document).ready(function() {
$('#otp-modal').modal('show');
$('#otp-qrcode').qrcode("{{ otp_uri|safe }}");
Expand All @@ -118,6 +118,8 @@ <h5 id="pageheading">{{ _("OTP Tokens") }}</h5>
</script>
{% endif %}
{% if addotpform.errors %}
<script>$(document).ready(function() {$('#add-token-modal').modal('show');});</script>
<script nonce="{{ csp_nonce() }}">
$(document).ready(function() {$('#add-token-modal').modal('show');});
</script>
{% endif %}
{% endblock %}
2 changes: 1 addition & 1 deletion noggin/templates/user-settings-profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
{{ super () }}
{{ macros.unsaved_changes() }}
{{ macros.selectize() }}
<script>
<script nonce="{{ csp_nonce() }}">
$('#ircnick').selectize({
plugins: ['remove_button'],
delimiter: ',',
Expand Down
8 changes: 4 additions & 4 deletions noggin/templates/user.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ <h5 class="mb-2" id="user_username">{{ user.username }}</h5>
{% endif %}
</div>
</div>
<div class="text-info ml-auto" style="width:5em;">{{ _('sponsor') if group in managed_groups }}</div>
<div class="text-success" style="width:4em;">{{ _('member') if group in member_groups }}</div>
<div class="text-info ml-auto px-2 {{ 'invisible' if group not in managed_groups }}">{{ _('sponsor') }}</div>
<div class="text-success px-2 {{ 'invisible' if group not in member_groups }}">{{ _('member') }}</div>
</li>
{% else %}
<li class="list-group-item h4 text-muted h-100 text-center d-flex align-items-center justify-content-center bg-light">
Expand All @@ -135,12 +135,12 @@ <h5 class="mb-2" id="user_username">{{ user.username }}</h5>
{% block scripts %}
{{super()}}
{% if user.timezone %}
<script>
setInterval("generate_local_time();",1000);
<script nonce="{{ csp_nonce() }}">
function generate_local_time(){
var currenttime = moment().tz("{{user.timezone}}").format('dddd, h:mm:ss a')
$("#user-time").html(currenttime)
}
setInterval(generate_local_time, 1000);
</script>
{% endif %}
{% endblock %}
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/0.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/5.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/6.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/7.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/8.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/centos/static/vendor/bootstrap-4.3.1/9.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions noggin/themes/centos/templates/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{% block head %}
<link rel="stylesheet" href="{{ url_for('theme.static', filename='vendor/font-awesome-4.7.0/css/font-awesome.min.css') }}" type="text/css">
<link rel="stylesheet" href="{{ url_for('theme.static', filename='vendor/bootstrap-4.3.1/bootstrap.min.css') }}" type="text/css">
<link rel="stylesheet" href="{{ url_for('theme.static', filename='vendor/bootstrap-4.3.1/bootstrap.min.nodata.css') }}" type="text/css">
<link rel="stylesheet" href="{{ url_for('theme.static', filename='fonts/Montserrat.css') }}" type="text/css">
<link rel="stylesheet" href="{{ url_for('theme.static', filename='css/default.css') }}" type="text/css">
{% endblock %}
Expand Down Expand Up @@ -65,9 +65,9 @@
{% endblock %}

{% block scripts %}
<script src="{{ url_for('theme.static', filename='vendor/jquery/jquery-3.3.1.min.js') }}"></script>
<script src="{{ url_for('theme.static', filename='vendor/popper-1.14.7/popper.min.js') }}"></script>
<script src="{{ url_for('theme.static', filename='vendor/bootstrap-4.3.1/bootstrap.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('theme.static', filename='vendor/jquery/jquery-3.3.1.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('theme.static', filename='vendor/popper-1.14.7/popper.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('theme.static', filename='vendor/bootstrap-4.3.1/bootstrap.min.js') }}"></script>
{{ super() }}
{% endblock %}

Expand Down
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/0.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/5.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/6.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/7.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/8.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/default/static/vendor/bootstrap-4.3.1/9.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions noggin/themes/default/templates/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


{% block head %}
<link href="{{ url_for('theme.static', filename='vendor/bootstrap-4.3.1/bootstrap.min.css') }}" rel="stylesheet" type="text/css">
<link href="{{ url_for('theme.static', filename='vendor/bootstrap-4.3.1/bootstrap.min.nodata.css') }}" rel="stylesheet" type="text/css">
<link href="{{ url_for('theme.static', filename='css/default.css') }}" rel="stylesheet" type="text/css">
{% endblock %}

Expand Down Expand Up @@ -64,9 +64,9 @@
{% endblock %}

{% block scripts %}
<script src="{{ url_for('theme.static', filename='vendor/jquery/jquery-3.3.1.min.js') }}"></script>
<script src="{{ url_for('theme.static', filename='vendor/popper-1.14.7/popper.min.js') }}"></script>
<script src="{{ url_for('theme.static', filename='vendor/bootstrap-4.3.1/bootstrap.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('theme.static', filename='vendor/jquery/jquery-3.3.1.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('theme.static', filename='vendor/popper-1.14.7/popper.min.js') }}"></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('theme.static', filename='vendor/bootstrap-4.3.1/bootstrap.min.js') }}"></script>
{{ super() }}
{% endblock %}

Expand Down
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/0.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/5.svg
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/6.svg
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/7.svg
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/8.svg
1 change: 1 addition & 0 deletions noggin/themes/fas/static/js/fedora-bootstrap/9.svg

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading

0 comments on commit a5de5c4

Please sign in to comment.