mirror of
https://github.com/zulip/zulip.git
synced 2025-11-13 02:17:19 +00:00
Users having only account in one realm will not be distracted by realm name in subject lines of every email. Users who have multiple accounts in realms can turn this setting on and receive a corresponding realm name in email's subject. Tweaked by tabbott to rebase and address a few small issues. Fixes #5489.
230 lines
11 KiB
Python
230 lines
11 KiB
Python
from typing import Optional, Any, Dict, Text
|
|
|
|
from django.utils.translation import ugettext as _
|
|
from django.conf import settings
|
|
from django.contrib.auth import authenticate, update_session_auth_hash
|
|
from django.http import HttpRequest, HttpResponse
|
|
from django.shortcuts import redirect, render
|
|
from django.urls import reverse
|
|
|
|
from zerver.decorator import has_request_variables, \
|
|
zulip_login_required, REQ, human_users_only
|
|
from zerver.lib.actions import do_change_password, do_change_notification_settings, \
|
|
do_change_enter_sends, do_change_default_desktop_notifications, \
|
|
do_regenerate_api_key, do_change_avatar_fields, \
|
|
do_set_user_display_setting, validate_email, do_change_user_email, \
|
|
do_start_email_change_process, check_change_full_name
|
|
from zerver.lib.avatar import avatar_url
|
|
from zerver.lib.send_email import send_email, FromAddress
|
|
from zerver.lib.i18n import get_available_language_codes
|
|
from zerver.lib.response import json_success, json_error
|
|
from zerver.lib.upload import upload_avatar_image
|
|
from zerver.lib.validator import check_bool, check_string
|
|
from zerver.lib.request import JsonableError
|
|
from zerver.lib.timezone import get_all_timezones
|
|
from zerver.models import UserProfile, Realm, name_changes_disabled, \
|
|
EmailChangeStatus
|
|
from confirmation.models import get_object_from_key, render_confirmation_key_error, \
|
|
ConfirmationKeyException, Confirmation
|
|
|
|
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse:
|
|
try:
|
|
email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE)
|
|
except ConfirmationKeyException as exception:
|
|
return render_confirmation_key_error(request, exception)
|
|
|
|
new_email = email_change_object.new_email
|
|
old_email = email_change_object.old_email
|
|
user_profile = email_change_object.user_profile
|
|
|
|
if user_profile.realm.email_changes_disabled:
|
|
raise JsonableError(_("Email address changes are disabled in this organization."))
|
|
do_change_user_email(user_profile, new_email)
|
|
|
|
context = {'realm': user_profile.realm, 'new_email': new_email}
|
|
send_email('zerver/emails/notify_change_in_email', to_email=old_email,
|
|
from_name="Zulip Account Security", from_address=FromAddress.SUPPORT,
|
|
context=context)
|
|
|
|
ctx = {
|
|
'new_email': new_email,
|
|
'old_email': old_email,
|
|
}
|
|
return render(request, 'confirmation/confirm_email_change.html', context=ctx)
|
|
|
|
@human_users_only
|
|
@has_request_variables
|
|
def json_change_ui_settings(
|
|
request: HttpRequest, user_profile: UserProfile,
|
|
default_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None)
|
|
) -> HttpResponse:
|
|
result = {}
|
|
|
|
if default_desktop_notifications is not None and \
|
|
user_profile.default_desktop_notifications != default_desktop_notifications:
|
|
do_change_default_desktop_notifications(user_profile, default_desktop_notifications)
|
|
result['default_desktop_notifications'] = default_desktop_notifications
|
|
|
|
return json_success(result)
|
|
|
|
@human_users_only
|
|
@has_request_variables
|
|
def json_change_settings(request: HttpRequest, user_profile: UserProfile,
|
|
full_name: Text=REQ(default=""),
|
|
email: Text=REQ(default=""),
|
|
old_password: Text=REQ(default=""),
|
|
new_password: Text=REQ(default="")) -> HttpResponse:
|
|
if not (full_name or new_password or email):
|
|
return json_error(_("Please fill out all fields."))
|
|
|
|
if new_password != "":
|
|
if not authenticate(username=user_profile.email, password=old_password,
|
|
realm=user_profile.realm):
|
|
return json_error(_("Wrong password!"))
|
|
do_change_password(user_profile, new_password)
|
|
# In Django 1.10, password changes invalidates sessions, see
|
|
# https://docs.djangoproject.com/en/1.10/topics/auth/default/#session-invalidation-on-password-change
|
|
# for details. To avoid this logging the user out of their own
|
|
# session (which would provide a confusing UX at best), we
|
|
# update the session hash here.
|
|
update_session_auth_hash(request, user_profile)
|
|
# 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()
|
|
|
|
result = {} # type: Dict[str, Any]
|
|
new_email = email.strip()
|
|
if user_profile.email != email and new_email != '':
|
|
if user_profile.realm.email_changes_disabled:
|
|
return json_error(_("Email address changes are disabled in this organization."))
|
|
error, skipped = validate_email(user_profile, new_email)
|
|
if error:
|
|
return json_error(error)
|
|
if skipped:
|
|
return json_error(skipped)
|
|
|
|
do_start_email_change_process(user_profile, new_email)
|
|
result['account_email'] = _("Check your email for a confirmation link. ")
|
|
|
|
if user_profile.full_name != full_name and full_name.strip() != "":
|
|
if name_changes_disabled(user_profile.realm):
|
|
# 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:
|
|
# Note that check_change_full_name strips the passed name automatically
|
|
result['full_name'] = check_change_full_name(user_profile, full_name, user_profile)
|
|
|
|
return json_success(result)
|
|
|
|
@human_users_only
|
|
@has_request_variables
|
|
def update_display_settings_backend(
|
|
request: HttpRequest, user_profile: UserProfile,
|
|
twenty_four_hour_time: Optional[bool]=REQ(validator=check_bool, default=None),
|
|
high_contrast_mode: Optional[bool]=REQ(validator=check_bool, default=None),
|
|
night_mode: Optional[bool]=REQ(validator=check_bool, default=None),
|
|
default_language: Optional[bool]=REQ(validator=check_string, default=None),
|
|
left_side_userlist: Optional[bool]=REQ(validator=check_bool, default=None),
|
|
emojiset: Optional[str]=REQ(validator=check_string, default=None),
|
|
timezone: Optional[str]=REQ(validator=check_string, default=None)) -> HttpResponse:
|
|
|
|
if (default_language is not None and
|
|
default_language not in get_available_language_codes()):
|
|
raise JsonableError(_("Invalid language '%s'" % (default_language,)))
|
|
|
|
if (timezone is not None and
|
|
timezone not in get_all_timezones()):
|
|
raise JsonableError(_("Invalid timezone '%s'" % (timezone,)))
|
|
|
|
if (emojiset is not None and
|
|
emojiset not in UserProfile.emojiset_choices()):
|
|
raise JsonableError(_("Invalid emojiset '%s'" % (emojiset,)))
|
|
|
|
request_settings = {k: v for k, v in list(locals().items()) if k in user_profile.property_types}
|
|
result = {} # type: Dict[str, Any]
|
|
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
|
|
|
|
return json_success(result)
|
|
|
|
@human_users_only
|
|
@has_request_variables
|
|
def json_change_notify_settings(
|
|
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_sounds: Optional[bool]=REQ(validator=check_bool, 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),
|
|
pm_content_in_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
|
|
realm_name_in_notifications: Optional[bool]=REQ(validator=check_bool, default=None)) \
|
|
-> HttpResponse:
|
|
result = {}
|
|
|
|
# Stream notification settings.
|
|
|
|
req_vars = {k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types}
|
|
|
|
for k, v in list(req_vars.items()):
|
|
if v is not None and getattr(user_profile, k) != v:
|
|
do_change_notification_settings(user_profile, k, v)
|
|
result[k] = v
|
|
|
|
return json_success(result)
|
|
|
|
def set_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
|
if len(request.FILES) != 1:
|
|
return json_error(_("You must upload exactly one avatar."))
|
|
|
|
user_file = list(request.FILES.values())[0]
|
|
if ((settings.MAX_AVATAR_FILE_SIZE * 1024 * 1024) < user_file.size):
|
|
return json_error(_("Uploaded file is larger than the allowed limit of %s MB") % (
|
|
settings.MAX_AVATAR_FILE_SIZE))
|
|
upload_avatar_image(user_file, user_profile, user_profile)
|
|
do_change_avatar_fields(user_profile, UserProfile.AVATAR_FROM_USER)
|
|
user_avatar_url = avatar_url(user_profile)
|
|
|
|
json_result = dict(
|
|
avatar_url = user_avatar_url
|
|
)
|
|
return json_success(json_result)
|
|
|
|
def delete_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
|
do_change_avatar_fields(user_profile, UserProfile.AVATAR_FROM_GRAVATAR)
|
|
gravatar_url = avatar_url(user_profile)
|
|
|
|
json_result = dict(
|
|
avatar_url = gravatar_url
|
|
)
|
|
return json_success(json_result)
|
|
|
|
# We don't use @human_users_only here, because there are use cases for
|
|
# a bot regenerating its own API key.
|
|
@has_request_variables
|
|
def regenerate_api_key(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
|
do_regenerate_api_key(user_profile, user_profile)
|
|
json_result = dict(
|
|
api_key = user_profile.api_key
|
|
)
|
|
return json_success(json_result)
|
|
|
|
@human_users_only
|
|
@has_request_variables
|
|
def change_enter_sends(request: HttpRequest, user_profile: UserProfile,
|
|
enter_sends: bool=REQ(validator=check_bool)) -> HttpResponse:
|
|
do_change_enter_sends(user_profile, enter_sends)
|
|
return json_success()
|