122 строки
3.4 KiB
Python
122 строки
3.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2021 Google Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import base64
|
|
import copy
|
|
import logging
|
|
import os
|
|
|
|
import flask
|
|
|
|
import settings
|
|
|
|
|
|
REPORT_ONLY = False
|
|
USE_NONCE_ONLY_POLICY = True # Recommended
|
|
REPORT_URI = '/csp'
|
|
NONCE_LENGTH = 30
|
|
|
|
# Note: This is an addition beyond the reference csp.py example code.
|
|
HOST_SOURCES = [
|
|
'https://www.gstatic.com',
|
|
]
|
|
|
|
DEFAULT_POLICY = {
|
|
# Disallow base tags.
|
|
'base-uri': ["'none'"],
|
|
# Disallow Flash, etc.
|
|
'object-src': ["'none'"],
|
|
# Strict CSP with fallbacks for browsers not supporting CSP v3.
|
|
# Nonces or hashes must be enabled in order for this CSP to be effective.
|
|
'script-src': [
|
|
# Propagate trust to dynamically created scripts.
|
|
"'strict-dynamic'",
|
|
# Fallback. Ignored in presence of a nonce
|
|
"'unsafe-inline'",
|
|
# Fallback. Ignored in presence of strict-dynamic.
|
|
'https:',
|
|
'http:'
|
|
] + HOST_SOURCES,
|
|
}
|
|
|
|
# This is a stricter version of the DEFAULT_POLICY.
|
|
NONCE_ONLY_POLICY = {
|
|
# Disallow base tags.
|
|
'base-uri': ["'none'"],
|
|
# Disallow Flash, etc.
|
|
'object-src': ["'none'"],
|
|
# Strict CSP with fallbacks for browsers not supporting CSP v3.
|
|
# Nonces or hashes must be enabled in order for this CSP to be effective.
|
|
'script-src': [
|
|
# Fallback. Ignored in presence of a nonce (which is added below).
|
|
"'unsafe-inline'"
|
|
] + HOST_SOURCES,
|
|
}
|
|
|
|
HEADER_KEY_ENFORCE = 'Content-Security-Policy'
|
|
HEADER_KEY_REPORT_ONLY = 'Content-Security-Policy-Report-Only'
|
|
|
|
|
|
def get_nonce():
|
|
"""Returns a random nonce."""
|
|
length = NONCE_LENGTH
|
|
b_nonce = base64.b64encode(os.urandom(length * 2))[:length]
|
|
return b_nonce.decode()
|
|
|
|
|
|
def get_default_policy(nonce=None):
|
|
"""Returns a copy of the default CSP directives."""
|
|
if USE_NONCE_ONLY_POLICY:
|
|
policy = copy.deepcopy(NONCE_ONLY_POLICY)
|
|
else:
|
|
policy = copy.deepcopy(DEFAULT_POLICY)
|
|
|
|
if nonce:
|
|
policy['script-src'].append('\'nonce-%s\'' % nonce)
|
|
|
|
return policy
|
|
|
|
|
|
def get_csp_header_key():
|
|
"""Returns the right CSP header key (report-only or enforcing mode)."""
|
|
if REPORT_ONLY:
|
|
return HEADER_KEY_REPORT_ONLY
|
|
else:
|
|
return HEADER_KEY_ENFORCE
|
|
|
|
|
|
def build_policy(policy):
|
|
"""Builds the CSP policy string from the internal representation."""
|
|
csp_directives = [
|
|
k + ' ' + ' '.join(v) for k, v in policy.items() if v is not None
|
|
]
|
|
if REPORT_URI:
|
|
csp_directives.append('report-uri %s' % REPORT_URI)
|
|
return '; '.join(csp_directives)
|
|
|
|
|
|
def get_headers(nonce):
|
|
"""Return a dict of CSP headers."""
|
|
csp_header_key = get_csp_header_key()
|
|
csp_directives = build_policy(get_default_policy(nonce=nonce))
|
|
return {csp_header_key: csp_directives}
|
|
|
|
|
|
def report_handler():
|
|
"""Log any CSP violations that are reported to our app."""
|
|
logging.error('CSP Violation: %s' %
|
|
repr(flask.request.data)[:settings.MAX_LOG_LINE])
|
|
return ''
|