snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
109
zerver/signals.py
Normal file
109
zerver/signals.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import zoneinfo
|
||||
from email.utils import format_datetime as email_format_datetime
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import get_current_timezone_name as timezone_get_current_timezone_name
|
||||
from django.utils.timezone import now as timezone_now
|
||||
from django.utils.translation import gettext as _
|
||||
from ua_parser import parse_os, parse_user_agent
|
||||
|
||||
from confirmation.models import one_click_unsubscribe_link
|
||||
from zerver.lib.queue import queue_json_publish_rollback_unsafe
|
||||
from zerver.lib.send_email import FromAddress
|
||||
from zerver.lib.timestamp import format_datetime_to_string
|
||||
from zerver.lib.timezone import canonicalize_timezone
|
||||
from zerver.models import UserProfile
|
||||
|
||||
JUST_CREATED_THRESHOLD = 60
|
||||
|
||||
|
||||
def get_device_browser(user_agent: str) -> str | None:
|
||||
if "zulip" in user_agent.lower():
|
||||
return "Zulip"
|
||||
|
||||
if browser := parse_user_agent(user_agent):
|
||||
browser_family = browser.family
|
||||
if browser_family == "IE":
|
||||
return "Internet Explorer"
|
||||
elif browser_family != "Other":
|
||||
return browser_family
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_device_os(user_agent: str) -> str | None:
|
||||
if os := parse_os(user_agent):
|
||||
os_family = os.family
|
||||
if os_family == "Mac OS X":
|
||||
return "macOS"
|
||||
elif os_family != "Other":
|
||||
return os_family
|
||||
return None
|
||||
|
||||
|
||||
@receiver(user_logged_in, dispatch_uid="only_on_login")
|
||||
def email_on_new_login(sender: Any, user: UserProfile, request: Any, **kwargs: Any) -> None:
|
||||
if not user.enable_login_emails:
|
||||
return
|
||||
|
||||
if user.delivery_email == "":
|
||||
# Do not attempt to send new login emails for users without an email address.
|
||||
# The assertions here are to help document the only circumstance under which
|
||||
# this condition should be possible.
|
||||
assert (
|
||||
user.realm.demo_organization_scheduled_deletion_date is not None and user.is_realm_owner
|
||||
)
|
||||
return
|
||||
|
||||
# We import here to minimize the dependencies of this module,
|
||||
# since it runs as part of `manage.py` initialization
|
||||
from zerver.context_processors import common_context
|
||||
|
||||
if not settings.SEND_LOGIN_EMAILS:
|
||||
return
|
||||
|
||||
if request:
|
||||
# If the user's account was just created, avoid sending an email.
|
||||
if (timezone_now() - user.date_joined).total_seconds() <= JUST_CREATED_THRESHOLD:
|
||||
return
|
||||
|
||||
user_agent = request.headers.get("User-Agent", "")
|
||||
|
||||
context = common_context(user)
|
||||
context["user_email"] = user.delivery_email
|
||||
user_tz = user.timezone
|
||||
if user_tz == "":
|
||||
user_tz = timezone_get_current_timezone_name()
|
||||
local_time = timezone_now().astimezone(zoneinfo.ZoneInfo(canonicalize_timezone(user_tz)))
|
||||
context["login_time"] = format_datetime_to_string(local_time, user.twenty_four_hour_time)
|
||||
context["device_ip"] = request.META.get("REMOTE_ADDR") or _("Unknown IP address")
|
||||
context["device_os"] = get_device_os(user_agent) or _("an unknown operating system")
|
||||
context["device_browser"] = get_device_browser(user_agent) or _("An unknown browser")
|
||||
context["unsubscribe_link"] = one_click_unsubscribe_link(user, "login")
|
||||
|
||||
email_dict = {
|
||||
"template_prefix": "zerver/emails/notify_new_login",
|
||||
"to_user_ids": [user.id],
|
||||
"from_name": FromAddress.security_email_from_name(user_profile=user),
|
||||
"from_address": FromAddress.NOREPLY,
|
||||
"context": context,
|
||||
"date": email_format_datetime(local_time),
|
||||
}
|
||||
queue_json_publish_rollback_unsafe("email_senders", email_dict)
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
def clear_call_tokens_on_logout(
|
||||
sender: object, *, user: UserProfile | None, **kwargs: object
|
||||
) -> None:
|
||||
# Loaded lazily so django.setup() succeeds before static asset generation
|
||||
from zerver.actions.video_calls import do_set_video_call_provider_token
|
||||
|
||||
if user is not None:
|
||||
if user.third_party_api_state.get("zoom") is not None:
|
||||
do_set_video_call_provider_token(user, "zoom", None)
|
||||
if user.third_party_api_state.get("webex") is not None:
|
||||
do_set_video_call_provider_token(user, "webex", None)
|
||||
Reference in New Issue
Block a user