# -*- 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.

"""Content Security Policy (CSP) generation and handling.

Provides functions to generate nonces, build CSP headers, and handle
CSP violation reports to secure the application against code injection attacks.
"""

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',
    'https://accounts.google.com/gsi/client',
]

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 ''
