2020-06-11 00:54:34 +02:00
|
|
|
from typing import Any, Dict, Optional
|
2015-11-23 17:09:21 +01:00
|
|
|
|
2020-10-27 00:56:50 +01:00
|
|
|
import pytz
|
2015-11-23 17:09:21 +01:00
|
|
|
from django.conf import settings
|
2016-11-17 08:56:01 +01:00
|
|
|
from django.contrib.auth import authenticate, update_session_auth_hash
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2016-06-05 02:15:26 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
2019-02-02 23:53:22 +01:00
|
|
|
from django.shortcuts import render
|
2020-09-16 19:30:05 +02:00
|
|
|
from django.utils.html import escape
|
|
|
|
from django.utils.safestring import SafeString
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.utils.translation import ugettext as _
|
2020-10-17 03:42:50 +02:00
|
|
|
from django.utils.translation import ugettext_lazy
|
2015-11-23 17:09:21 +01:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from confirmation.models import (
|
|
|
|
Confirmation,
|
|
|
|
ConfirmationKeyException,
|
|
|
|
get_object_from_key,
|
|
|
|
render_confirmation_key_error,
|
|
|
|
)
|
|
|
|
from zerver.decorator import REQ, has_request_variables, human_users_only
|
|
|
|
from zerver.lib.actions import (
|
|
|
|
check_change_full_name,
|
|
|
|
do_change_avatar_fields,
|
|
|
|
do_change_enter_sends,
|
|
|
|
do_change_notification_settings,
|
|
|
|
do_change_password,
|
|
|
|
do_change_user_delivery_email,
|
|
|
|
do_regenerate_api_key,
|
|
|
|
do_set_user_display_setting,
|
|
|
|
do_start_email_change_process,
|
|
|
|
get_available_notification_sounds,
|
|
|
|
validate_email_is_valid,
|
|
|
|
)
|
2015-11-23 17:09:21 +01:00
|
|
|
from zerver.lib.avatar import avatar_url
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.email_validation import (
|
|
|
|
get_realm_email_validator,
|
|
|
|
validate_email_not_already_in_realm,
|
|
|
|
)
|
2016-08-05 22:28:25 +02:00
|
|
|
from zerver.lib.i18n import get_available_language_codes
|
2020-04-01 13:13:06 +02:00
|
|
|
from zerver.lib.rate_limiter import RateLimited
|
2016-07-31 10:02:42 +02:00
|
|
|
from zerver.lib.request import JsonableError
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.response import json_error, json_success
|
|
|
|
from zerver.lib.send_email import FromAddress, send_email
|
|
|
|
from zerver.lib.upload import upload_avatar_image
|
|
|
|
from zerver.lib.validator import check_bool, check_int, check_int_in, check_string, check_string_in
|
|
|
|
from zerver.models import UserProfile, avatar_changes_disabled, name_changes_disabled
|
|
|
|
from zproject.backends import check_password_strength, email_belongs_to_ldap
|
2017-01-20 12:27:38 +01:00
|
|
|
|
2020-10-17 03:42:50 +02:00
|
|
|
AVATAR_CHANGES_DISABLED_ERROR = ugettext_lazy("Avatar changes are disabled in this organization.")
|
2019-04-23 04:51:04 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse:
|
2017-07-22 00:27:45 +02:00
|
|
|
try:
|
2017-11-07 20:45:11 +01:00
|
|
|
email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE)
|
2017-07-22 00:27:45 +02:00
|
|
|
except ConfirmationKeyException as exception:
|
|
|
|
return render_confirmation_key_error(request, exception)
|
2017-01-20 12:27:38 +01:00
|
|
|
|
2017-11-07 20:45:11 +01:00
|
|
|
new_email = email_change_object.new_email
|
|
|
|
old_email = email_change_object.old_email
|
|
|
|
user_profile = email_change_object.user_profile
|
2017-01-20 12:27:38 +01:00
|
|
|
|
2018-02-02 16:54:26 +01:00
|
|
|
if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin:
|
2017-11-07 20:48:32 +01:00
|
|
|
raise JsonableError(_("Email address changes are disabled in this organization."))
|
2018-08-02 08:47:13 +02:00
|
|
|
|
|
|
|
do_change_user_delivery_email(user_profile, new_email)
|
2017-07-22 00:27:45 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
context = {"realm_name": user_profile.realm.name, "new_email": new_email}
|
2020-02-14 13:58:58 +01:00
|
|
|
language = user_profile.default_language
|
2021-02-12 08:19:30 +01:00
|
|
|
send_email(
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver/emails/notify_change_in_email",
|
2021-02-12 08:19:30 +01:00
|
|
|
to_emails=[old_email],
|
|
|
|
from_name=FromAddress.security_email_from_name(user_profile=user_profile),
|
|
|
|
from_address=FromAddress.SUPPORT,
|
|
|
|
language=language,
|
|
|
|
context=context,
|
|
|
|
realm=user_profile.realm,
|
|
|
|
)
|
2017-01-20 12:27:38 +01:00
|
|
|
|
|
|
|
ctx = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"new_email_html_tag": SafeString(
|
2021-02-12 08:19:30 +01:00
|
|
|
f'<a href="mailto:{escape(new_email)}">{escape(new_email)}</a>'
|
|
|
|
),
|
2021-02-12 08:20:45 +01:00
|
|
|
"old_email_html_tag": SafeString(
|
2021-02-12 08:19:30 +01:00
|
|
|
f'<a href="mailto:{escape(old_email)}">{escape(old_email)}</a>'
|
|
|
|
),
|
2017-01-20 12:27:38 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
return render(request, "confirmation/confirm_email_change.html", context=ctx)
|
2015-11-23 17:09:21 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-07-31 20:44:52 +02:00
|
|
|
@human_users_only
|
2015-11-23 17:09:21 +01:00
|
|
|
@has_request_variables
|
2021-02-12 08:19:30 +01:00
|
|
|
def json_change_settings(
|
|
|
|
request: HttpRequest,
|
|
|
|
user_profile: UserProfile,
|
|
|
|
full_name: str = REQ(default=""),
|
|
|
|
email: str = REQ(default=""),
|
|
|
|
old_password: str = REQ(default=""),
|
|
|
|
new_password: str = REQ(default=""),
|
|
|
|
) -> HttpResponse:
|
2017-01-20 12:27:38 +01:00
|
|
|
if not (full_name or new_password or email):
|
2018-01-11 20:28:44 +01:00
|
|
|
return json_error(_("Please fill out all fields."))
|
2016-07-23 22:22:25 +02:00
|
|
|
|
2018-01-11 20:28:18 +01:00
|
|
|
if new_password != "":
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
return_data: Dict[str, Any] = {}
|
2018-08-02 08:47:13 +02:00
|
|
|
if email_belongs_to_ldap(user_profile.realm, user_profile.delivery_email):
|
2018-05-29 07:25:08 +02:00
|
|
|
return json_error(_("Your Zulip password is managed in LDAP"))
|
2019-12-30 02:21:51 +01:00
|
|
|
|
|
|
|
try:
|
2021-02-12 08:19:30 +01:00
|
|
|
if not authenticate(
|
|
|
|
request,
|
|
|
|
username=user_profile.delivery_email,
|
|
|
|
password=old_password,
|
|
|
|
realm=user_profile.realm,
|
|
|
|
return_data=return_data,
|
|
|
|
):
|
2019-12-30 02:21:51 +01:00
|
|
|
return json_error(_("Wrong password!"))
|
|
|
|
except RateLimited as e:
|
2020-11-27 16:33:01 +01:00
|
|
|
assert e.secs_to_freedom is not None
|
|
|
|
secs_to_freedom = int(e.secs_to_freedom)
|
2019-12-30 02:21:51 +01:00
|
|
|
return json_error(
|
2021-02-12 08:19:30 +01:00
|
|
|
_("You're making too many attempts! Try again in {} seconds.").format(
|
|
|
|
secs_to_freedom
|
|
|
|
),
|
2019-12-30 02:21:51 +01:00
|
|
|
)
|
|
|
|
|
auth: Use zxcvbn to ensure password strength on server side.
For a long time, we've been only doing the zxcvbn password strength
checks on the browser, which is helpful, but means users could through
hackery (or a bug in the frontend validation code) manage to set a
too-weak password. We fix this by running our password strength
validation on the backend as well, using python-zxcvbn.
In theory, a bug in python-zxcvbn could result in it producing a
different opinion than the frontend version; if so, it'd be a pretty
bad bug in the library, and hopefully we'd hear about it from users,
report upstream, and get it fixed that way. Alternatively, we can
switch to shelling out to node like we do for KaTeX.
Fixes #6880.
2019-11-18 08:11:03 +01:00
|
|
|
if not check_password_strength(new_password):
|
|
|
|
return json_error(_("New password is too weak!"))
|
2019-12-30 02:21:51 +01:00
|
|
|
|
2015-11-23 17:09:21 +01:00
|
|
|
do_change_password(user_profile, new_password)
|
2016-11-17 08:56:01 +01:00
|
|
|
# In Django 1.10, password changes invalidates sessions, see
|
|
|
|
# https://docs.djangoproject.com/en/1.10/topics/auth/default/#session-invalidation-on-password-change
|
2017-07-05 11:47:21 +02:00
|
|
|
# for details. To avoid this logging the user out of their own
|
2016-11-17 08:56:01 +01:00
|
|
|
# session (which would provide a confusing UX at best), we
|
|
|
|
# update the session hash here.
|
|
|
|
update_session_auth_hash(request, user_profile)
|
2016-12-16 11:38:21 +01:00
|
|
|
# We also save the session to the DB immediately to mitigate
|
|
|
|
# race conditions. In theory, there is still a race condition
|
|
|
|
# and to completely avoid it we will have to use some kind of
|
|
|
|
# mutex lock in `django.contrib.auth.get_user` where session
|
|
|
|
# is verified. To make that lock work we will have to control
|
|
|
|
# the AuthenticationMiddleware which is currently controlled
|
|
|
|
# by Django,
|
|
|
|
request.session.save()
|
2015-11-23 17:09:21 +01:00
|
|
|
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
result: Dict[str, Any] = {}
|
2017-01-20 12:27:38 +01:00
|
|
|
new_email = email.strip()
|
2021-02-12 08:20:45 +01:00
|
|
|
if user_profile.delivery_email != new_email and new_email != "":
|
2018-02-02 16:54:26 +01:00
|
|
|
if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin:
|
2017-03-05 02:18:42 +01:00
|
|
|
return json_error(_("Email address changes are disabled in this organization."))
|
2020-03-04 14:40:30 +01:00
|
|
|
|
|
|
|
error = validate_email_is_valid(
|
|
|
|
new_email,
|
|
|
|
get_realm_email_validator(user_profile.realm),
|
|
|
|
)
|
2017-09-25 07:18:42 +02:00
|
|
|
if error:
|
|
|
|
return json_error(error)
|
2020-03-04 14:40:30 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
validate_email_not_already_in_realm(
|
|
|
|
user_profile.realm,
|
|
|
|
new_email,
|
2020-03-05 17:24:47 +01:00
|
|
|
verbose=False,
|
2020-03-04 14:40:30 +01:00
|
|
|
)
|
|
|
|
except ValidationError as e:
|
2020-03-05 17:24:47 +01:00
|
|
|
return json_error(e.message)
|
2017-01-20 12:27:38 +01:00
|
|
|
|
|
|
|
do_start_email_change_process(user_profile, new_email)
|
2021-02-12 08:20:45 +01:00
|
|
|
result["account_email"] = _("Check your email for a confirmation link. ")
|
2017-01-20 12:27:38 +01:00
|
|
|
|
2015-11-23 17:09:21 +01:00
|
|
|
if user_profile.full_name != full_name and full_name.strip() != "":
|
2018-02-02 16:54:26 +01:00
|
|
|
if name_changes_disabled(user_profile.realm) and not user_profile.is_realm_admin:
|
2015-11-23 17:09:21 +01:00
|
|
|
# Failingly silently is fine -- they can't do it through the UI, so
|
|
|
|
# they'd have to be trying to break the rules.
|
|
|
|
pass
|
|
|
|
else:
|
2017-02-08 04:39:55 +01:00
|
|
|
# Note that check_change_full_name strips the passed name automatically
|
2021-02-12 08:20:45 +01:00
|
|
|
result["full_name"] = check_change_full_name(user_profile, full_name, user_profile)
|
2015-11-23 17:09:21 +01:00
|
|
|
|
|
|
|
return json_success(result)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
emojiset_choices = {emojiset["key"] for emojiset in UserProfile.emojiset_choices()}
|
2021-03-10 13:56:10 +01:00
|
|
|
default_view_options = ["recent_topics", "all_messages"]
|
2020-04-04 00:06:39 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-04-16 22:10:56 +02:00
|
|
|
@human_users_only
|
2016-06-23 11:32:45 +02:00
|
|
|
@has_request_variables
|
2017-11-16 02:37:54 +01:00
|
|
|
def update_display_settings_backend(
|
2021-02-12 08:19:30 +01:00
|
|
|
request: HttpRequest,
|
|
|
|
user_profile: UserProfile,
|
|
|
|
twenty_four_hour_time: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
dense_mode: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
starred_message_counts: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
fluid_layout_width: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
high_contrast_mode: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
color_scheme: Optional[int] = REQ(
|
|
|
|
validator=check_int_in(UserProfile.COLOR_SCHEME_CHOICES), default=None
|
|
|
|
),
|
|
|
|
translate_emoticons: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
default_language: Optional[str] = REQ(validator=check_string, default=None),
|
2021-03-10 13:56:10 +01:00
|
|
|
default_view: Optional[str] = REQ(
|
|
|
|
validator=check_string_in(default_view_options), default=None
|
|
|
|
),
|
2021-02-12 08:19:30 +01:00
|
|
|
left_side_userlist: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
emojiset: Optional[str] = REQ(validator=check_string_in(emojiset_choices), default=None),
|
|
|
|
demote_inactive_streams: Optional[int] = REQ(
|
|
|
|
validator=check_int_in(UserProfile.DEMOTE_STREAMS_CHOICES), default=None
|
|
|
|
),
|
|
|
|
timezone: Optional[str] = REQ(validator=check_string_in(pytz.all_timezones_set), default=None),
|
|
|
|
) -> HttpResponse:
|
2020-04-04 00:59:04 +02:00
|
|
|
|
|
|
|
# We can't use REQ for this widget because
|
|
|
|
# get_available_language_codes requires provisioning to be
|
|
|
|
# complete.
|
2021-02-12 08:19:30 +01:00
|
|
|
if default_language is not None and default_language not in get_available_language_codes():
|
2020-04-04 00:59:04 +02:00
|
|
|
raise JsonableError(_("Invalid default_language"))
|
|
|
|
|
2017-05-22 21:07:35 +02:00
|
|
|
request_settings = {k: v for k, v in list(locals().items()) if k in user_profile.property_types}
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
result: Dict[str, Any] = {}
|
2017-05-22 21:07:35 +02:00
|
|
|
for k, v in list(request_settings.items()):
|
|
|
|
if v is not None and getattr(user_profile, k) != v:
|
|
|
|
do_set_user_display_setting(user_profile, k, v)
|
|
|
|
result[k] = v
|
2017-03-14 10:53:09 +01:00
|
|
|
|
2016-06-23 11:32:45 +02:00
|
|
|
return json_success(result)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-04-16 22:10:56 +02:00
|
|
|
@human_users_only
|
2015-11-23 17:09:21 +01:00
|
|
|
@has_request_variables
|
2017-12-04 09:56:07 +01:00
|
|
|
def json_change_notify_settings(
|
2021-02-12 08:19:30 +01:00
|
|
|
request: HttpRequest,
|
|
|
|
user_profile: UserProfile,
|
|
|
|
enable_stream_desktop_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_stream_email_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_stream_push_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_stream_audible_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
wildcard_mentions_notify: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
notification_sound: Optional[str] = REQ(validator=check_string, default=None),
|
|
|
|
enable_desktop_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_sounds: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_offline_email_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_offline_push_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_online_push_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_digest_emails: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
enable_login_emails: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
message_content_in_email_notifications: Optional[bool] = REQ(
|
|
|
|
validator=check_bool, default=None
|
|
|
|
),
|
|
|
|
pm_content_in_desktop_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
desktop_icon_count_display: Optional[int] = REQ(validator=check_int, default=None),
|
|
|
|
realm_name_in_notifications: Optional[bool] = REQ(validator=check_bool, default=None),
|
|
|
|
presence_enabled: Optional[bool] = REQ(validator=check_bool, default=None),
|
2017-11-29 13:42:39 +01:00
|
|
|
) -> HttpResponse:
|
2015-11-23 17:09:21 +01:00
|
|
|
result = {}
|
|
|
|
|
|
|
|
# Stream notification settings.
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
if (
|
|
|
|
notification_sound is not None
|
|
|
|
and notification_sound not in get_available_notification_sounds()
|
|
|
|
):
|
2020-06-15 23:22:24 +02:00
|
|
|
raise JsonableError(_("Invalid notification sound '{}'").format(notification_sound))
|
2018-01-11 21:36:11 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
req_vars = {
|
|
|
|
k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types
|
|
|
|
}
|
2015-11-23 17:09:21 +01:00
|
|
|
|
2017-05-23 03:19:21 +02:00
|
|
|
for k, v in list(req_vars.items()):
|
|
|
|
if v is not None and getattr(user_profile, k) != v:
|
2020-07-12 23:41:53 +02:00
|
|
|
do_change_notification_settings(user_profile, k, v, acting_user=user_profile)
|
2017-05-23 03:19:21 +02:00
|
|
|
result[k] = v
|
2016-12-07 17:29:12 +01:00
|
|
|
|
2015-11-23 17:09:21 +01:00
|
|
|
return json_success(result)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def set_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
2015-11-23 17:09:21 +01:00
|
|
|
if len(request.FILES) != 1:
|
2016-05-25 15:02:02 +02:00
|
|
|
return json_error(_("You must upload exactly one avatar."))
|
2015-11-23 17:09:21 +01:00
|
|
|
|
2019-04-23 04:51:04 +02:00
|
|
|
if avatar_changes_disabled(user_profile.realm) and not user_profile.is_realm_admin:
|
2020-10-17 03:42:50 +02:00
|
|
|
return json_error(str(AVATAR_CHANGES_DISABLED_ERROR))
|
2019-04-23 04:51:04 +02:00
|
|
|
|
2016-01-25 01:27:18 +01:00
|
|
|
user_file = list(request.FILES.values())[0]
|
2021-02-12 08:19:30 +01:00
|
|
|
if (settings.MAX_AVATAR_FILE_SIZE * 1024 * 1024) < user_file.size:
|
|
|
|
return json_error(
|
|
|
|
_("Uploaded file is larger than the allowed limit of {} MiB").format(
|
|
|
|
settings.MAX_AVATAR_FILE_SIZE,
|
|
|
|
)
|
|
|
|
)
|
2017-03-02 16:21:46 +01:00
|
|
|
upload_avatar_image(user_file, user_profile, user_profile)
|
2020-06-29 12:47:44 +02:00
|
|
|
do_change_avatar_fields(user_profile, UserProfile.AVATAR_FROM_USER, acting_user=user_profile)
|
2015-11-23 17:09:21 +01:00
|
|
|
user_avatar_url = avatar_url(user_profile)
|
|
|
|
|
|
|
|
json_result = dict(
|
2021-02-12 08:19:30 +01:00
|
|
|
avatar_url=user_avatar_url,
|
2015-11-23 17:09:21 +01:00
|
|
|
)
|
|
|
|
return json_success(json_result)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def delete_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
2019-04-23 04:51:04 +02:00
|
|
|
if avatar_changes_disabled(user_profile.realm) and not user_profile.is_realm_admin:
|
2020-10-17 03:42:50 +02:00
|
|
|
return json_error(str(AVATAR_CHANGES_DISABLED_ERROR))
|
2019-04-23 04:51:04 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
do_change_avatar_fields(
|
|
|
|
user_profile, UserProfile.AVATAR_FROM_GRAVATAR, acting_user=user_profile
|
|
|
|
)
|
2016-12-21 18:34:03 +01:00
|
|
|
gravatar_url = avatar_url(user_profile)
|
|
|
|
|
|
|
|
json_result = dict(
|
2021-02-12 08:19:30 +01:00
|
|
|
avatar_url=gravatar_url,
|
2016-12-21 18:34:03 +01:00
|
|
|
)
|
|
|
|
return json_success(json_result)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-10-28 00:16:13 +02:00
|
|
|
# We don't use @human_users_only here, because there are use cases for
|
|
|
|
# a bot regenerating its own API key.
|
2015-11-23 17:09:21 +01:00
|
|
|
@has_request_variables
|
2017-11-27 09:28:57 +01:00
|
|
|
def regenerate_api_key(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
2018-08-10 21:03:32 +02:00
|
|
|
new_api_key = do_regenerate_api_key(user_profile, user_profile)
|
2015-11-23 17:09:21 +01:00
|
|
|
json_result = dict(
|
2021-02-12 08:19:30 +01:00
|
|
|
api_key=new_api_key,
|
2015-11-23 17:09:21 +01:00
|
|
|
)
|
|
|
|
return json_success(json_result)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-10-28 00:16:13 +02:00
|
|
|
@human_users_only
|
2015-11-23 17:09:21 +01:00
|
|
|
@has_request_variables
|
2021-02-12 08:19:30 +01:00
|
|
|
def change_enter_sends(
|
|
|
|
request: HttpRequest, user_profile: UserProfile, enter_sends: bool = REQ(validator=check_bool)
|
|
|
|
) -> HttpResponse:
|
2015-11-23 17:09:21 +01:00
|
|
|
do_change_enter_sends(user_profile, enter_sends)
|
|
|
|
return json_success()
|