Updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.core.checks import Tags, register
|
||||
|
||||
from corsheaders.checks import check_settings
|
||||
|
||||
|
||||
class CorsHeadersAppConfig(AppConfig):
|
||||
name = "corsheaders"
|
||||
verbose_name = "django-cors-headers"
|
||||
|
||||
def ready(self) -> None:
|
||||
register(Tags.security)(check_settings)
|
||||
171
ETB-API/venv/lib/python3.12/site-packages/corsheaders/checks.py
Normal file
171
ETB-API/venv/lib/python3.12/site-packages/corsheaders/checks.py
Normal file
@@ -0,0 +1,171 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.checks import CheckMessage, Error
|
||||
|
||||
from corsheaders.conf import conf
|
||||
|
||||
re_type = type(re.compile(""))
|
||||
|
||||
|
||||
def check_settings(**kwargs: Any) -> list[CheckMessage]:
|
||||
errors: list[CheckMessage] = []
|
||||
|
||||
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_ALLOW_PRIVATE_NETWORK, bool):
|
||||
errors.append( # type: ignore [unreachable]
|
||||
Error(
|
||||
"CORS_ALLOW_PRIVATE_NETWORK should be a bool.",
|
||||
id="corsheaders.E015",
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
not isinstance(conf.CORS_PREFLIGHT_MAX_AGE, int) # type: ignore [redundant-expr]
|
||||
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 = urlsplit(origin)
|
||||
if parsed.scheme == "" or parsed.netloc == "":
|
||||
errors.append(
|
||||
Error(
|
||||
f"Origin {repr(origin)} in {allowed_origins_alias} is missing scheme or netloc",
|
||||
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", "query", "fragment"):
|
||||
if getattr(parsed, part) != "":
|
||||
errors.append(
|
||||
Error(
|
||||
f"Origin {repr(origin)} in {allowed_origins_alias} should not have {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(
|
||||
f"{allowed_regexes_alias} should be a sequence of strings and/or compiled regexes.",
|
||||
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 hasattr(settings, "CORS_MODEL"):
|
||||
errors.append(
|
||||
Error(
|
||||
(
|
||||
"The CORS_MODEL setting has been removed - see "
|
||||
+ "django-cors-headers' HISTORY."
|
||||
),
|
||||
id="corsheaders.E012",
|
||||
)
|
||||
)
|
||||
|
||||
if hasattr(settings, "CORS_REPLACE_HTTPS_REFERER"):
|
||||
errors.append(
|
||||
Error(
|
||||
(
|
||||
"The CORS_REPLACE_HTTPS_REFERER setting has been removed"
|
||||
+ " - see django-cors-headers' CHANGELOG."
|
||||
),
|
||||
id="corsheaders.E013",
|
||||
)
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,71 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from re import Pattern
|
||||
from typing import Union, cast
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from corsheaders.defaults import default_headers, default_methods
|
||||
|
||||
|
||||
class Settings:
|
||||
"""
|
||||
Shadow Django's settings with a little logic
|
||||
"""
|
||||
|
||||
@property
|
||||
def CORS_ALLOW_HEADERS(self) -> Sequence[str]:
|
||||
return getattr(settings, "CORS_ALLOW_HEADERS", default_headers)
|
||||
|
||||
@property
|
||||
def CORS_ALLOW_METHODS(self) -> Sequence[str]:
|
||||
return getattr(settings, "CORS_ALLOW_METHODS", default_methods)
|
||||
|
||||
@property
|
||||
def CORS_ALLOW_CREDENTIALS(self) -> bool:
|
||||
return getattr(settings, "CORS_ALLOW_CREDENTIALS", False)
|
||||
|
||||
@property
|
||||
def CORS_ALLOW_PRIVATE_NETWORK(self) -> bool:
|
||||
return getattr(settings, "CORS_ALLOW_PRIVATE_NETWORK", False)
|
||||
|
||||
@property
|
||||
def CORS_PREFLIGHT_MAX_AGE(self) -> int:
|
||||
return getattr(settings, "CORS_PREFLIGHT_MAX_AGE", 86400)
|
||||
|
||||
@property
|
||||
def CORS_ALLOW_ALL_ORIGINS(self) -> bool:
|
||||
return getattr(
|
||||
settings,
|
||||
"CORS_ALLOW_ALL_ORIGINS",
|
||||
getattr(settings, "CORS_ORIGIN_ALLOW_ALL", False),
|
||||
)
|
||||
|
||||
@property
|
||||
def CORS_ALLOWED_ORIGINS(self) -> list[str] | tuple[str]:
|
||||
value = getattr(
|
||||
settings,
|
||||
"CORS_ALLOWED_ORIGINS",
|
||||
getattr(settings, "CORS_ORIGIN_WHITELIST", ()),
|
||||
)
|
||||
return cast(Union[list[str], tuple[str]], value)
|
||||
|
||||
@property
|
||||
def CORS_ALLOWED_ORIGIN_REGEXES(self) -> Sequence[str | Pattern[str]]:
|
||||
return getattr(
|
||||
settings,
|
||||
"CORS_ALLOWED_ORIGIN_REGEXES",
|
||||
getattr(settings, "CORS_ORIGIN_REGEX_WHITELIST", ()),
|
||||
)
|
||||
|
||||
@property
|
||||
def CORS_EXPOSE_HEADERS(self) -> Sequence[str]:
|
||||
return getattr(settings, "CORS_EXPOSE_HEADERS", ())
|
||||
|
||||
@property
|
||||
def CORS_URLS_REGEX(self) -> str | Pattern[str]:
|
||||
return getattr(settings, "CORS_URLS_REGEX", r"^.*$")
|
||||
|
||||
|
||||
conf = Settings()
|
||||
@@ -0,0 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
# Kept here for backwards compatibility
|
||||
|
||||
default_headers = (
|
||||
"accept",
|
||||
"authorization",
|
||||
"content-type",
|
||||
"user-agent",
|
||||
"x-csrftoken",
|
||||
"x-requested-with",
|
||||
)
|
||||
|
||||
default_methods = (
|
||||
"DELETE",
|
||||
"GET",
|
||||
"OPTIONS",
|
||||
"PATCH",
|
||||
"POST",
|
||||
"PUT",
|
||||
)
|
||||
@@ -0,0 +1,166 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Awaitable
|
||||
from typing import Callable
|
||||
from urllib.parse import SplitResult, urlsplit
|
||||
|
||||
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.response import HttpResponseBase
|
||||
from django.utils.cache import patch_vary_headers
|
||||
|
||||
from corsheaders.conf import conf
|
||||
from corsheaders.signals import check_request_enabled
|
||||
|
||||
ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin"
|
||||
ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers"
|
||||
ACCESS_CONTROL_ALLOW_CREDENTIALS = "access-control-allow-credentials"
|
||||
ACCESS_CONTROL_ALLOW_HEADERS = "access-control-allow-headers"
|
||||
ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods"
|
||||
ACCESS_CONTROL_MAX_AGE = "access-control-max-age"
|
||||
ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK = "access-control-request-private-network"
|
||||
ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "access-control-allow-private-network"
|
||||
|
||||
|
||||
class CorsMiddleware:
|
||||
sync_capable = True
|
||||
async_capable = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
get_response: (
|
||||
Callable[[HttpRequest], HttpResponseBase]
|
||||
| Callable[[HttpRequest], Awaitable[HttpResponseBase]]
|
||||
),
|
||||
) -> None:
|
||||
self.get_response = get_response
|
||||
self.async_mode = iscoroutinefunction(self.get_response)
|
||||
|
||||
if self.async_mode:
|
||||
# Mark the class as async-capable, but do the actual switch
|
||||
|
||||
# inside __call__ to avoid swapping out dunder methods
|
||||
markcoroutinefunction(self)
|
||||
|
||||
def __call__(
|
||||
self, request: HttpRequest
|
||||
) -> HttpResponseBase | Awaitable[HttpResponseBase]:
|
||||
if self.async_mode:
|
||||
return self.__acall__(request)
|
||||
response: HttpResponseBase | None = self.check_preflight(request)
|
||||
if response is None:
|
||||
result = self.get_response(request)
|
||||
assert isinstance(result, HttpResponseBase)
|
||||
response = result
|
||||
self.add_response_headers(request, response)
|
||||
return response
|
||||
|
||||
async def __acall__(self, request: HttpRequest) -> HttpResponseBase:
|
||||
response = self.check_preflight(request)
|
||||
if response is None:
|
||||
result = self.get_response(request)
|
||||
assert not isinstance(result, HttpResponseBase)
|
||||
response = await result
|
||||
self.add_response_headers(request, response)
|
||||
return response
|
||||
|
||||
def check_preflight(self, request: HttpRequest) -> HttpResponseBase | None:
|
||||
"""
|
||||
Generate a response for CORS preflight requests.
|
||||
"""
|
||||
request._cors_enabled = self.is_enabled(request) # type: ignore [attr-defined]
|
||||
if (
|
||||
request._cors_enabled # type: ignore [attr-defined]
|
||||
and request.method == "OPTIONS"
|
||||
and "access-control-request-method" in request.headers
|
||||
):
|
||||
return HttpResponse(headers={"content-length": "0"})
|
||||
return None
|
||||
|
||||
def add_response_headers(
|
||||
self, request: HttpRequest, response: HttpResponseBase
|
||||
) -> HttpResponseBase:
|
||||
"""
|
||||
Add the respective CORS headers
|
||||
"""
|
||||
enabled = getattr(request, "_cors_enabled", None)
|
||||
if enabled is None:
|
||||
enabled = self.is_enabled(request)
|
||||
|
||||
if not enabled:
|
||||
return response
|
||||
|
||||
patch_vary_headers(response, ("origin",))
|
||||
|
||||
origin = request.headers.get("origin")
|
||||
if not origin:
|
||||
return response
|
||||
|
||||
try:
|
||||
url = urlsplit(origin)
|
||||
except ValueError:
|
||||
return response
|
||||
|
||||
if (
|
||||
not conf.CORS_ALLOW_ALL_ORIGINS
|
||||
and not self.origin_found_in_white_lists(origin, url)
|
||||
and not self.check_signal(request)
|
||||
):
|
||||
return response
|
||||
|
||||
if conf.CORS_ALLOW_ALL_ORIGINS and not conf.CORS_ALLOW_CREDENTIALS:
|
||||
response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
|
||||
else:
|
||||
response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
|
||||
|
||||
if conf.CORS_ALLOW_CREDENTIALS:
|
||||
response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = "true"
|
||||
|
||||
if len(conf.CORS_EXPOSE_HEADERS):
|
||||
response[ACCESS_CONTROL_EXPOSE_HEADERS] = ", ".join(
|
||||
conf.CORS_EXPOSE_HEADERS
|
||||
)
|
||||
|
||||
if request.method == "OPTIONS":
|
||||
response[ACCESS_CONTROL_ALLOW_HEADERS] = ", ".join(conf.CORS_ALLOW_HEADERS)
|
||||
response[ACCESS_CONTROL_ALLOW_METHODS] = ", ".join(conf.CORS_ALLOW_METHODS)
|
||||
if conf.CORS_PREFLIGHT_MAX_AGE:
|
||||
response[ACCESS_CONTROL_MAX_AGE] = str(conf.CORS_PREFLIGHT_MAX_AGE)
|
||||
|
||||
if (
|
||||
conf.CORS_ALLOW_PRIVATE_NETWORK
|
||||
and request.headers.get(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK) == "true"
|
||||
):
|
||||
response[ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK] = "true"
|
||||
|
||||
return response
|
||||
|
||||
def origin_found_in_white_lists(self, origin: str, url: SplitResult) -> bool:
|
||||
return (
|
||||
(origin == "null" and origin in conf.CORS_ALLOWED_ORIGINS)
|
||||
or self._url_in_whitelist(url)
|
||||
or self.regex_domain_match(origin)
|
||||
)
|
||||
|
||||
def regex_domain_match(self, origin: str) -> bool:
|
||||
return any(
|
||||
re.match(domain_pattern, origin)
|
||||
for domain_pattern in conf.CORS_ALLOWED_ORIGIN_REGEXES
|
||||
)
|
||||
|
||||
def is_enabled(self, request: HttpRequest) -> bool:
|
||||
return bool(
|
||||
re.match(conf.CORS_URLS_REGEX, request.path_info)
|
||||
) or self.check_signal(request)
|
||||
|
||||
def check_signal(self, request: HttpRequest) -> bool:
|
||||
signal_responses = check_request_enabled.send(sender=None, request=request)
|
||||
return any(return_value for function, return_value in signal_responses)
|
||||
|
||||
def _url_in_whitelist(self, url: SplitResult) -> bool:
|
||||
origins = [urlsplit(o) for o in conf.CORS_ALLOWED_ORIGINS]
|
||||
return any(
|
||||
origin.scheme == url.scheme and origin.netloc == url.netloc
|
||||
for origin in origins
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
# If any attached handler returns Truthy, CORS will be allowed for the request.
|
||||
# This can be used to build custom logic into the request handling when the
|
||||
# configuration doesn't work.
|
||||
check_request_enabled = Signal()
|
||||
Reference in New Issue
Block a user