Repository: plotly/dash
File: dash/dash.py (Python)

Code before:
            for f in sorted(files_gen):
                path = "/".join([base, f]) if base else f

                full = os.path.join(current, f)

                if f.endswith("js"):
                    self.scripts.append_script(self._add_assets_resource(path, full))
                elif f.endswith("css"):
                    self.css.append_css(self._add_assets_resource(path, full))
                elif f == "favicon.ico":
                    self._favicon = path

    @staticmethod
    def _invalid_resources_handler(err):
        return err.args[0], 404

    @staticmethod
    def _serve_default_favicon():
        return flask.Response(
            pkgutil.get_data("dash", "favicon.ico"), content_type="image/x-icon"
        )

    def csp_hashes_inline_scripts(self, hash_algorithm="sha256"):
        """Calculates CSP hashes (sha + base64) of all inline scripts, such that
        one of the biggest benefits of CSP (disallowing general inline scripts)
        can be utilized together with Dash clientside callbacks (inline scripts).

        :param hash_algorithm: One of the recognized CSP hash algorithms ('sha256', 'sha384', 'sha512').
        :return: List of CSP hash strings of all inline scripts.
        """

        HASH_ALGORITHMS = ["sha256", "sha384", "sha512"]
        if hash_algorithm not in HASH_ALGORITHMS:
            raise ValueError(
                "Possible CSP hash algorithms: " + ", ".join(HASH_ALGORITHMS)
            )

        method = getattr(hashlib, hash_algorithm)

        return [
            "'{hash_algorithm}-{base64_hash}'".format(
                hash_algorithm=hash_algorithm,
                base64_hash=base64.b64encode(
                    method(script.encode("utf-8")).digest()
                ).decode("utf-8"),
            )
            for script in self._inline_scripts + [self.renderer]
        ]

    def get_asset_url(self, path):
        asset = get_asset_path(

Review comment (security):
@anders-kiaer this looks great, and fantastic tests - I love `does_not_raise`, hadn't seen that trick before but I can see it coming in handy elsewhere.

The name is a little long, would it be ambiguous if we just call it `csp_hashes` or `script_hashes`?

Can you add a bit of usage example into the docstring here? Something like:
```
Calculate these hashes after all callbacks are defined, and add them to your CSP headers
immediately before starting the server, for example:

flask_talisman.Talisman(app.server, content_security_policy={
    "default-src": "'self'",
    "script-src": ["'self'"] + app.csp_hashes_inline_scripts()
})
```

Am I understanding correctly that this is what you'd normally do?

I haven't used `flask_talisman` before, but I presume that it works just as well (and with no changes to your code) with `gunicorn` as it does with `run_server`?

Code after:
            for f in sorted(files_gen):
                path = "/".join([base, f]) if base else f

                full = os.path.join(current, f)

                if f.endswith("js"):
                    self.scripts.append_script(self._add_assets_resource(path, full))
                elif f.endswith("css"):
                    self.css.append_css(self._add_assets_resource(path, full))
                elif f == "favicon.ico":
                    self._favicon = path

    @staticmethod
    def _invalid_resources_handler(err):
        return err.args[0], 404

    @staticmethod
    def _serve_default_favicon():
        return flask.Response(
            pkgutil.get_data("dash", "favicon.ico"), content_type="image/x-icon"
        )

    def csp_hashes(self, hash_algorithm="sha256"):
        """Calculates CSP hashes (sha + base64) of all inline scripts, such that
        one of the biggest benefits of CSP (disallowing general inline scripts)
        can be utilized together with Dash clientside callbacks (inline scripts).

        Calculate these hashes after all inline callbacks are defined,
        and add them to your CSP headers before starting the server, for example
        with the flask-talisman package from PyPI:

        flask_talisman.Talisman(app.server, content_security_policy={
            "default-src": "'self'",
            "script-src": ["'self'"] + app.csp_hashes()
        })

        :param hash_algorithm: One of the recognized CSP hash algorithms ('sha256', 'sha384', 'sha512').
        :return: List of CSP hash strings of all inline scripts.
        """

        HASH_ALGORITHMS = ["sha256", "sha384", "sha512"]
        if hash_algorithm not in HASH_ALGORITHMS:
            raise ValueError(
                "Possible CSP hash algorithms: " + ", ".join(HASH_ALGORITHMS)
            )

        method = getattr(hashlib, hash_algorithm)

        return [
            "'{hash_algorithm}-{base64_hash}'".format(
                hash_algorithm=hash_algorithm,