mirror of https://github.com/zulip/zulip.git
django: Use HttpRequest.headers.
Fixes #14769. Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
d98e3ecb0d
commit
0043c0b6b2
|
@ -29,7 +29,7 @@ def stripe_webhook(request: HttpRequest) -> HttpResponse:
|
|||
try:
|
||||
stripe_event = stripe.Webhook.construct_event(
|
||||
request.body,
|
||||
request.META.get("HTTP_STRIPE_SIGNATURE"),
|
||||
request.headers.get("Stripe-Signature"),
|
||||
stripe_webhook_endpoint_secret,
|
||||
)
|
||||
except ValueError:
|
||||
|
|
|
@ -610,7 +610,7 @@ event = validate_extract_webhook_http_header(request, header, integration_name)
|
|||
```
|
||||
|
||||
`request` is the `HttpRequest` object passed to your main webhook function. `header`
|
||||
is the name of the custom header you'd like to extract, such as `X_EVENT_KEY`, and
|
||||
is the name of the custom header you'd like to extract, such as `X-Event-Key`, and
|
||||
`integration_name` is the name of the third-party service in question, such as
|
||||
`GitHub`.
|
||||
|
||||
|
|
|
@ -652,7 +652,7 @@ def authenticated_rest_api_view(
|
|||
try:
|
||||
# Grab the base64-encoded authentication string, decode it, and split it into
|
||||
# the email and API key
|
||||
auth_type, credentials = request.META["HTTP_AUTHORIZATION"].split()
|
||||
auth_type, credentials = request.headers["Authorization"].split()
|
||||
# case insensitive per RFC 1945
|
||||
if auth_type.lower() != "basic":
|
||||
raise JsonableError(_("This endpoint requires HTTP basic authentication."))
|
||||
|
@ -716,7 +716,7 @@ def process_as_post(view_func: ViewFuncT) -> ViewFuncT:
|
|||
|
||||
if not request.POST:
|
||||
# Only take action if POST is empty.
|
||||
if request.META.get("CONTENT_TYPE", "").startswith("multipart"):
|
||||
if request.content_type == "multipart/form-data":
|
||||
# Note that request._files is just the private attribute that backs the
|
||||
# FILES property, so we are essentially setting request.FILES here. (In
|
||||
# Django 1.5 FILES was still a read-only property.)
|
||||
|
|
|
@ -72,12 +72,12 @@ def get_and_set_request_language(
|
|||
|
||||
|
||||
def get_browser_language_code(request: HttpRequest) -> Optional[str]:
|
||||
accept_lang_header = request.META.get("HTTP_ACCEPT_LANGUAGE")
|
||||
accept_lang_header = request.headers.get("Accept-Language")
|
||||
if accept_lang_header is None:
|
||||
return None
|
||||
|
||||
available_language_codes = get_available_language_codes()
|
||||
for accept_lang, priority in parse_accept_lang_header(request.META.get("HTTP_ACCEPT_LANGUAGE")):
|
||||
for accept_lang, priority in parse_accept_lang_header(accept_lang_header):
|
||||
if accept_lang == "*":
|
||||
return None
|
||||
if accept_lang in available_language_codes:
|
||||
|
|
|
@ -264,16 +264,12 @@ class ZulipWebhookFormatter(ZulipFormatter):
|
|||
except orjson.JSONDecodeError:
|
||||
pass
|
||||
|
||||
custom_header_template = "{header}: {value}\n"
|
||||
header_text = "".join(
|
||||
f"{header}: {value}\n"
|
||||
for header, value in request.headers.items()
|
||||
if header.lower().startswith("x-")
|
||||
)
|
||||
|
||||
header_text = ""
|
||||
for header in request.META.keys():
|
||||
if header.lower().startswith("http_x"):
|
||||
header_text += custom_header_template.format(
|
||||
header=header, value=request.META[header]
|
||||
)
|
||||
|
||||
header_message = header_text if header_text else None
|
||||
from zerver.lib.request import RequestNotes
|
||||
|
||||
client = RequestNotes.get_notes(request).client
|
||||
|
@ -283,7 +279,7 @@ class ZulipWebhookFormatter(ZulipFormatter):
|
|||
setattr(record, "client", client.name)
|
||||
setattr(record, "url", request.META.get("PATH_INFO", None))
|
||||
setattr(record, "content_type", request.content_type)
|
||||
setattr(record, "custom_headers", header_message)
|
||||
setattr(record, "custom_headers", header_text or None)
|
||||
setattr(record, "payload", payload)
|
||||
return super().format(record)
|
||||
|
||||
|
|
|
@ -113,10 +113,7 @@ def rest_dispatch(request: HttpRequest, **kwargs: Any) -> HttpResponse:
|
|||
|
||||
# for some special views (e.g. serving a file that has been
|
||||
# uploaded), we support using the same URL for web and API clients.
|
||||
if (
|
||||
"override_api_url_scheme" in view_flags
|
||||
and request.META.get("HTTP_AUTHORIZATION", None) is not None
|
||||
):
|
||||
if "override_api_url_scheme" in view_flags and "Authorization" in request.headers:
|
||||
# This request uses standard API based authentication.
|
||||
# For override_api_url_scheme views, we skip our normal
|
||||
# rate limiting, because there are good reasons clients
|
||||
|
@ -126,7 +123,7 @@ def rest_dispatch(request: HttpRequest, **kwargs: Any) -> HttpResponse:
|
|||
elif "override_api_url_scheme" in view_flags and request.GET.get("api_key") is not None:
|
||||
# This request uses legacy API authentication. We
|
||||
# unfortunately need that in the React Native mobile apps,
|
||||
# because there's no way to set HTTP_AUTHORIZATION in
|
||||
# because there's no way to set the Authorization header in
|
||||
# React Native. See last block for rate limiting notes.
|
||||
target_function = authenticated_uploads_api_view(skip_rate_limiting=True)(
|
||||
target_function
|
||||
|
@ -141,7 +138,7 @@ def rest_dispatch(request: HttpRequest, **kwargs: Any) -> HttpResponse:
|
|||
|
||||
# most clients (mobile, bots, etc) use HTTP basic auth and REST calls, where instead of
|
||||
# username:password, we use email:apiKey
|
||||
elif request.META.get("HTTP_AUTHORIZATION", None):
|
||||
elif "Authorization" in request.headers:
|
||||
# Wrap function with decorator to authenticate the user before
|
||||
# proceeding
|
||||
target_function = authenticated_rest_api_view(
|
||||
|
|
|
@ -38,9 +38,6 @@ that this integration expects!
|
|||
SETUP_MESSAGE_TEMPLATE = "{integration} webhook has been successfully configured"
|
||||
SETUP_MESSAGE_USER_PART = " by {user_name}"
|
||||
|
||||
# Django prefixes all custom HTTP headers with `HTTP_`
|
||||
DJANGO_HTTP_PREFIX = "HTTP_"
|
||||
|
||||
|
||||
def get_setup_webhook_message(integration: str, user_name: Optional[str] = None) -> str:
|
||||
content = SETUP_MESSAGE_TEMPLATE.format(integration=integration)
|
||||
|
@ -166,7 +163,7 @@ def validate_extract_webhook_http_header(
|
|||
) -> Optional[str]:
|
||||
assert request.user.is_authenticated
|
||||
|
||||
extracted_header = request.META.get(DJANGO_HTTP_PREFIX + header)
|
||||
extracted_header = request.headers.get(header)
|
||||
if extracted_header is None and fatal:
|
||||
message_body = MISSING_EVENT_HEADER_MESSAGE.format(
|
||||
bot_name=request.user.full_name,
|
||||
|
|
|
@ -322,8 +322,8 @@ def parse_client(
|
|||
# USER_AGENT.
|
||||
if req_client is not None:
|
||||
return req_client, None
|
||||
if "HTTP_USER_AGENT" in request.META:
|
||||
user_agent: Optional[Dict[str, str]] = parse_user_agent(request.META["HTTP_USER_AGENT"])
|
||||
if "User-Agent" in request.headers:
|
||||
user_agent: Optional[Dict[str, str]] = parse_user_agent(request.headers["User-Agent"])
|
||||
else:
|
||||
user_agent = None
|
||||
if user_agent is None:
|
||||
|
@ -453,7 +453,7 @@ class JsonErrorHandler(MiddlewareMixin):
|
|||
self, request: HttpRequest, exception: Exception
|
||||
) -> Optional[HttpResponse]:
|
||||
if isinstance(exception, MissingAuthenticationError):
|
||||
if "text/html" in request.META.get("HTTP_ACCEPT", ""):
|
||||
if "text/html" in request.headers.get("Accept", ""):
|
||||
# If this looks like a request from a top-level page in a
|
||||
# browser, send the user to the login page.
|
||||
#
|
||||
|
@ -632,9 +632,9 @@ class SetRemoteAddrFromRealIpHeader(MiddlewareMixin):
|
|||
|
||||
def process_request(self, request: HttpRequest) -> None:
|
||||
try:
|
||||
real_ip = request.META["HTTP_X_REAL_IP"]
|
||||
real_ip = request.headers["X-Real-IP"]
|
||||
except KeyError:
|
||||
return None
|
||||
pass
|
||||
else:
|
||||
request.META["REMOTE_ADDR"] = real_ip
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ def email_on_new_login(sender: Any, user: UserProfile, request: Any, **kwargs: A
|
|||
if (timezone_now() - user.date_joined).total_seconds() <= JUST_CREATED_THRESHOLD:
|
||||
return
|
||||
|
||||
user_agent = request.META.get("HTTP_USER_AGENT", "").lower()
|
||||
user_agent = request.headers.get("User-Agent", "").lower()
|
||||
|
||||
context = common_context(user)
|
||||
context["user_email"] = user.delivery_email
|
||||
|
|
|
@ -80,7 +80,7 @@ class TestIntegrationsDevPanel(ZulipTestCase):
|
|||
data = {
|
||||
"url": url,
|
||||
"body": body,
|
||||
"custom_headers": orjson.dumps({"X_GITHUB_EVENT": "ping"}).decode(),
|
||||
"custom_headers": orjson.dumps({"X-GitHub-Event": "ping"}).decode(),
|
||||
"is_json": "true",
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ class WebhooksCommonTestCase(ZulipTestCase):
|
|||
request.user = webhook_bot
|
||||
|
||||
header_value = validate_extract_webhook_http_header(
|
||||
request, "X_CUSTOM_HEADER", "test_webhook"
|
||||
request, "X-Custom-Header", "test_webhook"
|
||||
)
|
||||
|
||||
self.assertEqual(header_value, "custom_value")
|
||||
|
@ -45,15 +45,15 @@ class WebhooksCommonTestCase(ZulipTestCase):
|
|||
request.user = webhook_bot
|
||||
request.path = "some/random/path"
|
||||
|
||||
exception_msg = "Missing the HTTP event header 'X_CUSTOM_HEADER'"
|
||||
exception_msg = "Missing the HTTP event header 'X-Custom-Header'"
|
||||
with self.assertRaisesRegex(MissingHTTPEventHeader, exception_msg):
|
||||
validate_extract_webhook_http_header(request, "X_CUSTOM_HEADER", "test_webhook")
|
||||
validate_extract_webhook_http_header(request, "X-Custom-Header", "test_webhook")
|
||||
|
||||
msg = self.get_last_message()
|
||||
expected_message = MISSING_EVENT_HEADER_MESSAGE.format(
|
||||
bot_name=webhook_bot.full_name,
|
||||
request_path=request.path,
|
||||
header_name="X_CUSTOM_HEADER",
|
||||
header_name="X-Custom-Header",
|
||||
integration_name="test_webhook",
|
||||
support_email=FromAddress.SUPPORT,
|
||||
).rstrip()
|
||||
|
@ -190,7 +190,7 @@ class MissingEventHeaderTestCase(WebhookTestCase):
|
|||
self.get_body("ticket_state_changed"),
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
self.assert_json_error(result, "Missing the HTTP event header 'X_GROOVE_EVENT'")
|
||||
self.assert_json_error(result, "Missing the HTTP event header 'X-Groove-Event'")
|
||||
|
||||
realm = get_realm("zulip")
|
||||
webhook_bot = get_user("webhook-bot@zulip.com", realm)
|
||||
|
@ -200,7 +200,7 @@ class MissingEventHeaderTestCase(WebhookTestCase):
|
|||
expected_message = MISSING_EVENT_HEADER_MESSAGE.format(
|
||||
bot_name=webhook_bot.full_name,
|
||||
request_path="/api/v1/external/groove",
|
||||
header_name="X_GROOVE_EVENT",
|
||||
header_name="X-Groove-Event",
|
||||
integration_name="Groove",
|
||||
support_email=FromAddress.SUPPORT,
|
||||
).rstrip()
|
||||
|
|
|
@ -538,7 +538,7 @@ def oauth_redirect_to_root(
|
|||
def handle_desktop_flow(func: ViewFuncT) -> ViewFuncT:
|
||||
@wraps(func)
|
||||
def wrapper(request: HttpRequest, *args: object, **kwargs: object) -> HttpResponse:
|
||||
user_agent = parse_user_agent(request.META.get("HTTP_USER_AGENT", "Missing User-Agent"))
|
||||
user_agent = parse_user_agent(request.headers.get("User-Agent", "Missing User-Agent"))
|
||||
if user_agent["name"] == "ZulipElectron":
|
||||
return render(request, "zerver/desktop_login.html")
|
||||
|
||||
|
@ -929,7 +929,7 @@ def get_auth_backends_data(request: HttpRequest) -> Dict[str, Any]:
|
|||
|
||||
|
||||
def check_server_incompatibility(request: HttpRequest) -> bool:
|
||||
user_agent = parse_user_agent(request.META.get("HTTP_USER_AGENT", "Missing User-Agent"))
|
||||
user_agent = parse_user_agent(request.headers.get("User-Agent", "Missing User-Agent"))
|
||||
return user_agent["name"] == "ZulipInvalid"
|
||||
|
||||
|
||||
|
|
|
@ -14,17 +14,17 @@ android_min_app_version = "16.2.96"
|
|||
|
||||
|
||||
def check_global_compatibility(request: HttpRequest) -> HttpResponse:
|
||||
if request.META.get("HTTP_USER_AGENT") is None:
|
||||
if "User-Agent" not in request.headers:
|
||||
raise JsonableError(_("User-Agent header missing from request"))
|
||||
|
||||
# This string should not be tagged for translation, since old
|
||||
# clients are checking for an extra string.
|
||||
legacy_compatibility_error_message = "Client is too old"
|
||||
user_agent = parse_user_agent(request.META["HTTP_USER_AGENT"])
|
||||
user_agent = parse_user_agent(request.headers["User-Agent"])
|
||||
if user_agent["name"] == "ZulipInvalid":
|
||||
raise JsonableError(legacy_compatibility_error_message)
|
||||
if user_agent["name"] == "ZulipMobile":
|
||||
user_os = find_mobile_os(request.META["HTTP_USER_AGENT"])
|
||||
user_os = find_mobile_os(request.headers["User-Agent"])
|
||||
if user_os == "android" and version_lt(user_agent["version"], android_min_app_version):
|
||||
raise JsonableError(legacy_compatibility_error_message)
|
||||
return json_success(request)
|
||||
|
|
|
@ -126,7 +126,7 @@ def home(request: HttpRequest) -> HttpResponse:
|
|||
|
||||
def home_real(request: HttpRequest) -> HttpResponse:
|
||||
# Before we do any real work, check if the app is banned.
|
||||
client_user_agent = request.META.get("HTTP_USER_AGENT", "")
|
||||
client_user_agent = request.headers.get("User-Agent", "")
|
||||
(insecure_desktop_app, banned_desktop_app, auto_update_broken) = is_outdated_desktop_app(
|
||||
client_user_agent
|
||||
)
|
||||
|
|
|
@ -183,7 +183,7 @@ def get_type(request: HttpRequest, payload: WildValue) -> str:
|
|||
pull_request_template = "pull_request_{}"
|
||||
# Note that we only need the HTTP header to determine pullrequest events.
|
||||
# We rely on the payload itself to determine the other ones.
|
||||
event_key = validate_extract_webhook_http_header(request, "X_EVENT_KEY", "BitBucket")
|
||||
event_key = validate_extract_webhook_http_header(request, "X-Event-Key", "BitBucket")
|
||||
assert event_key is not None
|
||||
action = re.match("pullrequest:(?P<action>.*)$", event_key)
|
||||
if action:
|
||||
|
@ -191,7 +191,7 @@ def get_type(request: HttpRequest, payload: WildValue) -> str:
|
|||
if action_group in PULL_REQUEST_SUPPORTED_ACTIONS:
|
||||
return pull_request_template.format(action_group)
|
||||
else:
|
||||
event_key = validate_extract_webhook_http_header(request, "X_EVENT_KEY", "BitBucket")
|
||||
event_key = validate_extract_webhook_http_header(request, "X-Event-Key", "BitBucket")
|
||||
if event_key == "repo:updated":
|
||||
return event_key
|
||||
|
||||
|
|
|
@ -9,7 +9,10 @@ from zerver.lib.exceptions import UnsupportedWebhookEventType
|
|||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.validator import WildValue, check_int, check_none_or, check_string, to_wild_value
|
||||
from zerver.lib.webhooks.common import check_send_webhook_message
|
||||
from zerver.lib.webhooks.common import (
|
||||
check_send_webhook_message,
|
||||
validate_extract_webhook_http_header,
|
||||
)
|
||||
from zerver.lib.webhooks.git import (
|
||||
CONTENT_MESSAGE_TEMPLATE,
|
||||
TOPIC_WITH_BRANCH_TEMPLATE,
|
||||
|
@ -433,10 +436,14 @@ def api_bitbucket3_webhook(
|
|||
branches: Optional[str] = REQ(default=None),
|
||||
user_specified_topic: Optional[str] = REQ("topic", default=None),
|
||||
) -> HttpResponse:
|
||||
eventkey: Optional[str]
|
||||
if "eventKey" in payload:
|
||||
eventkey = payload["eventKey"].tame(check_string)
|
||||
else:
|
||||
eventkey = request.META["HTTP_X_EVENT_KEY"]
|
||||
eventkey = validate_extract_webhook_http_header(
|
||||
request, "X-Event-Key", "BitBucket", fatal=True
|
||||
)
|
||||
assert eventkey is not None
|
||||
handler = EVENT_HANDLER_MAP.get(eventkey)
|
||||
if handler is None:
|
||||
raise UnsupportedWebhookEventType(eventkey)
|
||||
|
|
|
@ -56,7 +56,7 @@ def api_gitea_webhook(
|
|||
) -> HttpResponse:
|
||||
return gogs_webhook_main(
|
||||
"Gitea",
|
||||
"X_GITEA_EVENT",
|
||||
"X-Gitea-Event",
|
||||
format_pull_request_event,
|
||||
request,
|
||||
user_profile,
|
||||
|
|
|
@ -746,10 +746,10 @@ def api_github_webhook(
|
|||
"""
|
||||
GitHub sends the event as an HTTP header. We have our
|
||||
own Zulip-specific concept of an event that often maps
|
||||
directly to the X_GITHUB_EVENT header's event, but we sometimes
|
||||
directly to the X-GitHub-Event header's event, but we sometimes
|
||||
refine it based on the payload.
|
||||
"""
|
||||
header_event = validate_extract_webhook_http_header(request, "X_GITHUB_EVENT", "GitHub")
|
||||
header_event = validate_extract_webhook_http_header(request, "X-GitHub-Event", "GitHub")
|
||||
if header_event is None:
|
||||
raise UnsupportedWebhookEventType("no header provided")
|
||||
|
||||
|
|
|
@ -493,7 +493,7 @@ def get_subject_based_on_event(
|
|||
|
||||
|
||||
def get_event(request: HttpRequest, payload: WildValue, branches: Optional[str]) -> Optional[str]:
|
||||
event = validate_extract_webhook_http_header(request, "X_GITLAB_EVENT", "GitLab")
|
||||
event = validate_extract_webhook_http_header(request, "X-GitLab-Event", "GitLab")
|
||||
if event == "System Hook":
|
||||
# Convert the event name to a GitLab event title
|
||||
event_name = payload.get("event_name", payload["object_kind"]).tame(check_string)
|
||||
|
|
|
@ -160,7 +160,7 @@ def api_gogs_webhook(
|
|||
) -> HttpResponse:
|
||||
return gogs_webhook_main(
|
||||
"Gogs",
|
||||
"X_GOGS_EVENT",
|
||||
"X-Gogs-Event",
|
||||
format_pull_request_event,
|
||||
request,
|
||||
user_profile,
|
||||
|
|
|
@ -97,7 +97,7 @@ def api_groove_webhook(
|
|||
user_profile: UserProfile,
|
||||
payload: Dict[str, Any] = REQ(argument_type="body"),
|
||||
) -> HttpResponse:
|
||||
event = validate_extract_webhook_http_header(request, "X_GROOVE_EVENT", "Groove")
|
||||
event = validate_extract_webhook_http_header(request, "X-Groove-Event", "Groove")
|
||||
assert event is not None
|
||||
handler = EVENTS_FUNCTION_MAPPER.get(event)
|
||||
if handler is None:
|
||||
|
|
|
@ -51,7 +51,7 @@ def api_netlify_webhook(
|
|||
def get_template(request: HttpRequest, payload: Dict[str, Any]) -> Tuple[str, str]:
|
||||
|
||||
message_template = "The build [{build_name}]({build_url}) on branch {branch_name} "
|
||||
event = validate_extract_webhook_http_header(request, "X_NETLIFY_EVENT", "Netlify")
|
||||
event = validate_extract_webhook_http_header(request, "X-Netlify-Event", "Netlify")
|
||||
|
||||
if event == "deploy_failed":
|
||||
message_template += payload["error_message"]
|
||||
|
|
|
@ -183,7 +183,7 @@ def api_reviewboard_webhook(
|
|||
payload: Dict[str, Sequence[Dict[str, Any]]] = REQ(argument_type="body"),
|
||||
) -> HttpResponse:
|
||||
event_type = validate_extract_webhook_http_header(
|
||||
request, "X_REVIEWBOARD_EVENT", "Review Board"
|
||||
request, "X-ReviewBoard-Event", "Review Board"
|
||||
)
|
||||
assert event_type is not None
|
||||
|
||||
|
|
Loading…
Reference in New Issue