from __future__ import annotations import re from collections.abc import Sequence from typing import Any from urllib.parse import urlparse from django.apps import AppConfig from django.conf import settings from django.core.checks import Error from corsheaders.conf import conf re_type = type(re.compile("")) def check_settings(app_configs: list[AppConfig], **kwargs: Any) -> list[Error]: errors = [] if not is_sequence(conf.CORS_ALLOW_HEADERS, str): errors.append( Error( "CORS_ALLOW_HEADERS should be a sequence of strings.", id="corsheaders.E001", ) ) if not is_sequence(conf.CORS_ALLOW_METHODS, str): errors.append( Error( "CORS_ALLOW_METHODS should be a sequence of strings.", id="corsheaders.E002", ) ) if not isinstance(conf.CORS_ALLOW_CREDENTIALS, bool): errors.append( # type: ignore [unreachable] Error("CORS_ALLOW_CREDENTIALS should be a bool.", id="corsheaders.E003") ) if ( not isinstance(conf.CORS_PREFLIGHT_MAX_AGE, int) or conf.CORS_PREFLIGHT_MAX_AGE < 0 ): errors.append( Error( ( "CORS_PREFLIGHT_MAX_AGE should be an integer greater than " + "or equal to zero." ), id="corsheaders.E004", ) ) if not isinstance(conf.CORS_ALLOW_ALL_ORIGINS, bool): if hasattr(settings, "CORS_ALLOW_ALL_ORIGINS"): # type: ignore [unreachable] allow_all_alias = "CORS_ALLOW_ALL_ORIGINS" else: allow_all_alias = "CORS_ORIGIN_ALLOW_ALL" errors.append( Error( f"{allow_all_alias} should be a bool.", id="corsheaders.E005", ) ) if hasattr(settings, "CORS_ALLOWED_ORIGINS"): allowed_origins_alias = "CORS_ALLOWED_ORIGINS" else: allowed_origins_alias = "CORS_ORIGIN_WHITELIST" if not is_sequence(conf.CORS_ALLOWED_ORIGINS, str): errors.append( Error( f"{allowed_origins_alias} should be a sequence of strings.", id="corsheaders.E006", ) ) else: special_origin_values = ( # From 'security sensitive' contexts "null", # From files on Chrome on Android # https://bugs.chromium.org/p/chromium/issues/detail?id=991107 "file://", ) for origin in conf.CORS_ALLOWED_ORIGINS: if origin in special_origin_values: continue parsed = urlparse(origin) if parsed.scheme == "" or parsed.netloc == "": errors.append( Error( "Origin {} in {} is missing scheme or netloc".format( repr(origin), allowed_origins_alias ), id="corsheaders.E013", hint=( "Add a scheme (e.g. https://) or netloc (e.g. " + "example.com)." ), ) ) else: # Only do this check in this case because if the scheme is not # provided, netloc ends up in path for part in ("path", "params", "query", "fragment"): if getattr(parsed, part) != "": errors.append( Error( "Origin {} in {} should not have {}".format( repr(origin), allowed_origins_alias, part ), id="corsheaders.E014", ) ) if hasattr(settings, "CORS_ALLOWED_ORIGIN_REGEXES"): allowed_regexes_alias = "CORS_ALLOWED_ORIGIN_REGEXES" else: allowed_regexes_alias = "CORS_ORIGIN_REGEX_WHITELIST" if not is_sequence(conf.CORS_ALLOWED_ORIGIN_REGEXES, (str, re_type)): errors.append( Error( "{} should be a sequence of strings and/or compiled regexes.".format( allowed_regexes_alias ), id="corsheaders.E007", ) ) if not is_sequence(conf.CORS_EXPOSE_HEADERS, str): errors.append( Error("CORS_EXPOSE_HEADERS should be a sequence.", id="corsheaders.E008") ) if not isinstance(conf.CORS_URLS_REGEX, (str, re_type)): errors.append( Error("CORS_URLS_REGEX should be a string or regex.", id="corsheaders.E009") ) if not isinstance(conf.CORS_REPLACE_HTTPS_REFERER, bool): errors.append( # type: ignore [unreachable] Error("CORS_REPLACE_HTTPS_REFERER should be a bool.", id="corsheaders.E011") ) if hasattr(settings, "CORS_MODEL"): errors.append( Error( ( "The CORS_MODEL setting has been removed - see " + "django-cors-headers' HISTORY." ), id="corsheaders.E012", ) ) return errors def is_sequence(thing: Any, type_or_types: type[Any] | tuple[type[Any], ...]) -> bool: return isinstance(thing, Sequence) and all( isinstance(x, type_or_types) for x in thing )