2022-06-28 00:43:57 +02:00
|
|
|
import sys
|
2019-02-02 23:53:55 +01:00
|
|
|
from typing import Any, Optional
|
2017-11-16 00:50:28 +01:00
|
|
|
|
2017-01-30 23:19:38 +01:00
|
|
|
from django.conf import settings
|
2019-11-16 09:26:28 +01:00
|
|
|
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
2017-11-16 00:50:28 +01:00
|
|
|
from django.dispatch import receiver
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.utils.timezone import get_current_timezone_name as timezone_get_current_timezone_name
|
2017-04-15 04:03:56 +02:00
|
|
|
from django.utils.timezone import now as timezone_now
|
2021-04-16 00:57:30 +02:00
|
|
|
from django.utils.translation import gettext as _
|
2017-11-16 00:50:28 +01:00
|
|
|
|
2018-11-07 16:54:23 +01:00
|
|
|
from confirmation.models import one_click_unsubscribe_link
|
2017-11-29 08:34:09 +01:00
|
|
|
from zerver.lib.queue import queue_json_publish
|
2018-04-26 20:11:45 +02:00
|
|
|
from zerver.lib.send_email import FromAddress
|
2024-02-28 01:36:17 +01:00
|
|
|
from zerver.lib.timezone import canonicalize_timezone
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.models import UserProfile
|
2017-01-30 23:19:38 +01:00
|
|
|
|
2022-06-28 00:43:57 +02:00
|
|
|
if sys.version_info < (3, 9): # nocoverage
|
|
|
|
from backports import zoneinfo
|
|
|
|
else: # nocoverage
|
|
|
|
import zoneinfo
|
|
|
|
|
2018-08-10 00:58:44 +02:00
|
|
|
JUST_CREATED_THRESHOLD = 60
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-27 07:33:05 +01:00
|
|
|
def get_device_browser(user_agent: str) -> Optional[str]:
|
2017-01-30 23:19:38 +01:00
|
|
|
user_agent = user_agent.lower()
|
2017-07-03 19:10:50 +02:00
|
|
|
if "zulip" in user_agent:
|
|
|
|
return "Zulip"
|
|
|
|
elif "edge" in user_agent:
|
2017-06-22 06:30:33 +02:00
|
|
|
return "Edge"
|
2017-06-22 06:34:26 +02:00
|
|
|
elif "opera" in user_agent or "opr/" in user_agent:
|
|
|
|
return "Opera"
|
2017-11-19 11:08:43 +01:00
|
|
|
elif ("chrome" in user_agent or "crios" in user_agent) and "chromium" not in user_agent:
|
2021-02-12 08:20:45 +01:00
|
|
|
return "Chrome"
|
2017-01-30 23:19:38 +01:00
|
|
|
elif "firefox" in user_agent and "seamonkey" not in user_agent and "chrome" not in user_agent:
|
|
|
|
return "Firefox"
|
|
|
|
elif "chromium" in user_agent:
|
|
|
|
return "Chromium"
|
|
|
|
elif "safari" in user_agent and "chrome" not in user_agent and "chromium" not in user_agent:
|
|
|
|
return "Safari"
|
|
|
|
elif "msie" in user_agent or "trident" in user_agent:
|
|
|
|
return "Internet Explorer"
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2017-11-27 07:33:05 +01:00
|
|
|
def get_device_os(user_agent: str) -> Optional[str]:
|
2017-01-30 23:19:38 +01:00
|
|
|
user_agent = user_agent.lower()
|
|
|
|
if "windows" in user_agent:
|
|
|
|
return "Windows"
|
|
|
|
elif "macintosh" in user_agent:
|
2017-08-26 09:33:47 +02:00
|
|
|
return "macOS"
|
2017-01-30 23:19:38 +01:00
|
|
|
elif "linux" in user_agent and "android" not in user_agent:
|
|
|
|
return "Linux"
|
|
|
|
elif "android" in user_agent:
|
|
|
|
return "Android"
|
2017-07-07 22:34:25 +02:00
|
|
|
elif "ios" in user_agent:
|
|
|
|
return "iOS"
|
2017-01-30 23:19:38 +01:00
|
|
|
elif "like mac os x" in user_agent:
|
|
|
|
return "iOS"
|
2018-09-21 18:10:53 +02:00
|
|
|
elif " cros " in user_agent:
|
|
|
|
return "ChromeOS"
|
2017-01-30 23:19:38 +01:00
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@receiver(user_logged_in, dispatch_uid="only_on_login")
|
2017-11-27 07:33:05 +01:00
|
|
|
def email_on_new_login(sender: Any, user: UserProfile, request: Any, **kwargs: Any) -> None:
|
2018-08-24 07:28:51 +02:00
|
|
|
if not user.enable_login_emails:
|
|
|
|
return
|
2021-12-23 01:41:41 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2017-03-26 01:27:45 +01:00
|
|
|
# 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
|
|
|
|
|
2017-01-30 23:19:38 +01:00
|
|
|
if not settings.SEND_LOGIN_EMAILS:
|
|
|
|
return
|
|
|
|
|
|
|
|
if request:
|
2017-08-23 01:14:45 +02:00
|
|
|
# If the user's account was just created, avoid sending an email.
|
2018-08-10 00:58:44 +02:00
|
|
|
if (timezone_now() - user.date_joined).total_seconds() <= JUST_CREATED_THRESHOLD:
|
2017-08-23 01:14:45 +02:00
|
|
|
return
|
2017-01-30 23:19:38 +01:00
|
|
|
|
2022-05-12 06:54:12 +02:00
|
|
|
user_agent = request.headers.get("User-Agent", "").lower()
|
2017-01-30 23:19:38 +01:00
|
|
|
|
|
|
|
context = common_context(user)
|
2021-02-12 08:20:45 +01:00
|
|
|
context["user_email"] = user.delivery_email
|
2018-05-27 00:17:08 +02:00
|
|
|
user_tz = user.timezone
|
2021-02-12 08:20:45 +01:00
|
|
|
if user_tz == "":
|
2018-05-27 00:17:08 +02:00
|
|
|
user_tz = timezone_get_current_timezone_name()
|
2024-02-28 01:36:17 +01:00
|
|
|
local_time = timezone_now().astimezone(zoneinfo.ZoneInfo(canonicalize_timezone(user_tz)))
|
2018-08-13 23:00:51 +02:00
|
|
|
if user.twenty_four_hour_time:
|
2021-02-12 08:20:45 +01:00
|
|
|
hhmm_string = local_time.strftime("%H:%M")
|
2018-08-13 23:00:51 +02:00
|
|
|
else:
|
2023-11-27 18:47:30 +01:00
|
|
|
hhmm_string = local_time.strftime("%I:%M %p")
|
2021-02-12 08:20:45 +01:00
|
|
|
context["login_time"] = local_time.strftime(f"%A, %B %d, %Y at {hhmm_string} %Z")
|
|
|
|
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")
|
2017-01-30 23:19:38 +01:00
|
|
|
|
2017-11-29 08:34:09 +01:00
|
|
|
email_dict = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"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,
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
2017-11-29 08:34:09 +01:00
|
|
|
queue_json_publish("email_senders", email_dict)
|
2019-11-16 09:26:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
@receiver(user_logged_out)
|
|
|
|
def clear_zoom_token_on_logout(
|
|
|
|
sender: object, *, user: Optional[UserProfile], **kwargs: object
|
|
|
|
) -> None:
|
2022-02-22 22:35:32 +01:00
|
|
|
# Loaded lazily so django.setup() succeeds before static asset generation
|
2022-04-14 23:29:39 +02:00
|
|
|
from zerver.actions.video_calls import do_set_zoom_token
|
2022-02-22 22:35:32 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
if user is not None and user.zoom_token is not None:
|
|
|
|
do_set_zoom_token(user, None)
|