Added login api

This commit is contained in:
infidel
2022-11-30 15:58:16 +07:00
parent 263218ed9e
commit d5d3401265
7319 changed files with 912655 additions and 22 deletions

View File

@@ -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)

View File

@@ -0,0 +1,164 @@
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
)

View File

@@ -0,0 +1,70 @@
from __future__ import annotations
from typing import List, Pattern, Sequence, Tuple, Union, cast
from django.conf import settings
# Kept here for backwards compatibility
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_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"^.*$")
@property
def CORS_REPLACE_HTTPS_REFERER(self) -> bool:
return getattr(settings, "CORS_REPLACE_HTTPS_REFERER", False)
conf = Settings()

View File

@@ -0,0 +1,15 @@
from __future__ import annotations
default_headers = (
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
)
default_methods = ("DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT")

View File

@@ -0,0 +1,196 @@
from __future__ import annotations
import re
from typing import Any
from urllib.parse import ParseResult, urlparse
from django.http import HttpRequest, HttpResponse
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
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"
class CorsPostCsrfMiddleware(MiddlewareMixin):
def _https_referer_replace_reverse(self, request: HttpRequest) -> None:
"""
Put the HTTP_REFERER back to its original value and delete the
temporary storage
"""
if conf.CORS_REPLACE_HTTPS_REFERER and "ORIGINAL_HTTP_REFERER" in request.META:
http_referer = request.META["ORIGINAL_HTTP_REFERER"]
request.META["HTTP_REFERER"] = http_referer
del request.META["ORIGINAL_HTTP_REFERER"]
def process_request(self, request: HttpRequest) -> None:
self._https_referer_replace_reverse(request)
return None
def process_view(
self,
request: HttpRequest,
callback: Any,
callback_args: Any,
callback_kwargs: Any,
) -> None:
self._https_referer_replace_reverse(request)
return None
class CorsMiddleware(MiddlewareMixin):
def _https_referer_replace(self, request: HttpRequest) -> None:
"""
When https is enabled, django CSRF checking includes referer checking
which breaks when using CORS. This function updates the HTTP_REFERER
header to make sure it matches HTTP_HOST, provided that our cors logic
succeeds
"""
origin = request.META.get("HTTP_ORIGIN")
if (
request.is_secure()
and origin
and "ORIGINAL_HTTP_REFERER" not in request.META
):
url = urlparse(origin)
if (
not conf.CORS_ALLOW_ALL_ORIGINS
and not self.origin_found_in_white_lists(origin, url)
):
return
try:
http_referer = request.META["HTTP_REFERER"]
http_host = "https://%s/" % request.META["HTTP_HOST"]
request.META = request.META.copy()
request.META["ORIGINAL_HTTP_REFERER"] = http_referer
request.META["HTTP_REFERER"] = http_host
except KeyError:
pass
def process_request(self, request: HttpRequest) -> HttpResponse | None:
"""
If CORS preflight header, then create an
empty body response (200 OK) and return it
Django won't bother calling any other request
view/exception middleware along with the requested view;
it will call any response middlewares
"""
request._cors_enabled = self.is_enabled(request)
if request._cors_enabled:
if conf.CORS_REPLACE_HTTPS_REFERER:
self._https_referer_replace(request)
if (
request.method == "OPTIONS"
and "HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META
):
response = HttpResponse()
response["Content-Length"] = "0"
return response
return None
def process_view(
self,
request: HttpRequest,
callback: Any,
callback_args: Any,
callback_kwargs: Any,
) -> None:
"""
Do the referer replacement here as well
"""
if request._cors_enabled and conf.CORS_REPLACE_HTTPS_REFERER:
self._https_referer_replace(request)
return None
def process_response(
self, request: HttpRequest, response: HttpResponse
) -> HttpResponse:
"""
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.META.get("HTTP_ORIGIN")
if not origin:
return response
try:
url = urlparse(origin)
except ValueError:
return response
if conf.CORS_ALLOW_CREDENTIALS:
response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = "true"
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 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)
return response
def origin_found_in_white_lists(self, origin: str, url: ParseResult) -> 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: ParseResult) -> bool:
origins = [urlparse(o) for o in conf.CORS_ALLOWED_ORIGINS]
return any(
origin.scheme == url.scheme and origin.netloc == url.netloc
for origin in origins
)

View File

@@ -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()