settings: Add backend code for using user email_address_visibility setting.

This commits update the code to use user-level email_address_visibility
setting instead of realm-level to set or update the value of UserProfile.email
field and to send the emails to clients.

Major changes are -

- UserProfile.email field is set while creating the user according to
RealmUserDefault.email_address_visbility.

- UserProfile.email field is updated according to change in the setting.

- 'email_address_visibility' is added to person objects in user add event
and in avatar change event.

- client_gravatar can be different for different users when computing
avatar_url for messages and user objects since email available to clients
is dependent on user-level setting.

- For bots, email_address_visibility is set to EVERYONE while creating
them irrespective of realm-default value.

- Test changes are basically setting user-level setting instead of realm
setting and modifying the checks accordingly.
This commit is contained in:
Sahil Batra 2021-10-26 12:45:16 +05:30 committed by Tim Abbott
parent ea0b2286e0
commit 0ed5f76063
28 changed files with 331 additions and 185 deletions

View File

@ -8,11 +8,8 @@ from django.utils.timezone import now as timezone_now
from corporate.lib.stripe import add_months, update_sponsorship_status from corporate.lib.stripe import add_months, update_sponsorship_status
from corporate.models import Customer, CustomerPlan, LicenseLedger, get_customer_by_realm from corporate.models import Customer, CustomerPlan, LicenseLedger, get_customer_by_realm
from zerver.actions.invites import do_create_multiuse_invite_link from zerver.actions.invites import do_create_multiuse_invite_link
from zerver.actions.realm_settings import ( from zerver.actions.realm_settings import do_change_realm_org_type, do_send_realm_reactivation_email
do_change_realm_org_type, from zerver.actions.user_settings import do_change_user_setting
do_send_realm_reactivation_email,
do_set_realm_property,
)
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import reset_emails_in_zulip_realm from zerver.lib.test_helpers import reset_emails_in_zulip_realm
from zerver.models import ( from zerver.models import (
@ -218,10 +215,10 @@ class TestSupportEndpoint(ZulipTestCase):
self.login("iago") self.login("iago")
do_set_realm_property( do_change_user_setting(
get_realm("zulip"), self.example_user("hamlet"),
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_NOBODY, UserProfile.EMAIL_ADDRESS_VISIBILITY_NOBODY,
acting_user=None, acting_user=None,
) )

View File

@ -95,8 +95,7 @@ def set_realm_permissions_based_on_org_type(realm: Realm) -> None:
realm.org_type == Realm.ORG_TYPES["education_nonprofit"]["id"] realm.org_type == Realm.ORG_TYPES["education_nonprofit"]["id"]
or realm.org_type == Realm.ORG_TYPES["education"]["id"] or realm.org_type == Realm.ORG_TYPES["education"]["id"]
): ):
# Limit email address visibility and user creation to administrators. # Limit user creation to administrators.
realm.email_address_visibility = Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS
realm.invite_to_realm_policy = Realm.POLICY_ADMINS_ONLY realm.invite_to_realm_policy = Realm.POLICY_ADMINS_ONLY
# Restrict public stream creation to staff, but allow private # Restrict public stream creation to staff, but allow private
# streams (useful for study groups, etc.). # streams (useful for study groups, etc.).
@ -211,7 +210,19 @@ def do_create_realm(
event_time=realm.date_created, event_time=realm.date_created,
) )
RealmUserDefault.objects.create(realm=realm) realm_default_email_address_visibility = RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_EVERYONE
if (
realm.org_type == Realm.ORG_TYPES["education_nonprofit"]["id"]
or realm.org_type == Realm.ORG_TYPES["education"]["id"]
):
# Email address of users should be initially visible to admins only.
realm_default_email_address_visibility = (
RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ADMINS
)
RealmUserDefault.objects.create(
realm=realm, email_address_visibility=realm_default_email_address_visibility
)
create_system_user_groups_for_realm(realm) create_system_user_groups_for_realm(realm)

View File

@ -305,9 +305,7 @@ def notify_created_user(user_profile: UserProfile) -> None:
user_ids_with_real_email_access = [] user_ids_with_real_email_access = []
user_ids_without_real_email_access = [] user_ids_without_real_email_access = []
for user in active_users: for user in active_users:
if can_access_delivery_email( if can_access_delivery_email(user, user_profile.id, user_row["email_address_visibility"]):
user, user_profile.id, user_profile.realm.email_address_visibility, user_row["is_bot"]
):
user_ids_with_real_email_access.append(user.id) user_ids_with_real_email_access.append(user.id)
else: else:
user_ids_without_real_email_access.append(user.id) user_ids_without_real_email_access.append(user.id)

View File

@ -13,8 +13,10 @@ from zerver.lib.avatar import avatar_url
from zerver.lib.cache import ( from zerver.lib.cache import (
cache_delete, cache_delete,
delete_user_profile_caches, delete_user_profile_caches,
flush_user_profile,
user_profile_by_api_key_cache_key, user_profile_by_api_key_cache_key,
) )
from zerver.lib.create_user import get_display_email_address
from zerver.lib.i18n import get_language_name from zerver.lib.i18n import get_language_name
from zerver.lib.queue import queue_json_publish from zerver.lib.queue import queue_json_publish
from zerver.lib.send_email import FromAddress, clear_scheduled_emails, send_email from zerver.lib.send_email import FromAddress, clear_scheduled_emails, send_email
@ -64,10 +66,10 @@ def send_delivery_email_update_events(
for active_user in active_users: for active_user in active_users:
could_access_delivery_email_previously = can_access_delivery_email( could_access_delivery_email_previously = can_access_delivery_email(
active_user, user_profile.id, old_visibility_setting, user_profile.is_bot active_user, user_profile.id, old_visibility_setting
) )
can_access_delivery_email_now = can_access_delivery_email( can_access_delivery_email_now = can_access_delivery_email(
active_user, user_profile.id, visibility_setting, user_profile.is_bot active_user, user_profile.id, visibility_setting
) )
if could_access_delivery_email_previously != can_access_delivery_email_now: if could_access_delivery_email_previously != can_access_delivery_email_now:
@ -502,6 +504,26 @@ def do_change_user_setting(
) )
) )
if setting_name == "email_address_visibility":
send_delivery_email_update_events(
user_profile, old_value, user_profile.email_address_visibility
)
if UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE not in [old_value, setting_value]:
# We use real email addresses on UserProfile.email only if
# EMAIL_ADDRESS_VISIBILITY_EVERYONE is configured, so
# changes between values that will not require changing
# that field, so we can save work and return here.
return
user_profile.email = get_display_email_address(user_profile)
user_profile.save(update_fields=["email"])
transaction.on_commit(lambda: flush_user_profile(sender=UserProfile, instance=user_profile))
send_user_email_update_event(user_profile)
notify_avatar_url_change(user_profile)
if setting_name == "enable_drafts_synchronization" and setting_value is False: if setting_name == "enable_drafts_synchronization" and setting_value is False:
# Delete all of the drafts from the backend but don't send delete events # Delete all of the drafts from the backend but don't send delete events
# for them since all that's happened is that we stopped syncing changes, # for them since all that's happened is that we stopped syncing changes,

View File

@ -34,7 +34,14 @@ def bulk_create_users(
UserProfile.objects.filter(realm=realm).values_list("email", flat=True) UserProfile.objects.filter(realm=realm).values_list("email", flat=True)
) )
users = sorted(user_raw for user_raw in users_raw if user_raw[0] not in existing_users) users = sorted(user_raw for user_raw in users_raw if user_raw[0] not in existing_users)
realm_user_default = RealmUserDefault.objects.get(realm=realm) realm_user_default = RealmUserDefault.objects.get(realm=realm)
if bot_type is None:
email_address_visibility = realm_user_default.email_address_visibility
else:
# There is no privacy motivation for limiting access to bot email addresses,
# so we hardcode them to EMAIL_ADDRESS_VISIBILITY_EVERYONE.
email_address_visibility = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
# Now create user_profiles # Now create user_profiles
profiles_to_create: List[UserProfile] = [] profiles_to_create: List[UserProfile] = []
@ -51,6 +58,7 @@ def bulk_create_users(
tos_version, tos_version,
timezone, timezone,
tutorial_status=UserProfile.TUTORIAL_FINISHED, tutorial_status=UserProfile.TUTORIAL_FINISHED,
email_address_visibility=email_address_visibility,
) )
if bot_type is None: if bot_type is None:
@ -67,7 +75,7 @@ def bulk_create_users(
setattr(profile, settings_name, value) setattr(profile, settings_name, value)
profiles_to_create.append(profile) profiles_to_create.append(profile)
if realm.email_address_visibility == Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE: if email_address_visibility == UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
UserProfile.objects.bulk_create(profiles_to_create) UserProfile.objects.bulk_create(profiles_to_create)
else: else:
for user_profile in profiles_to_create: for user_profile in profiles_to_create:

View File

@ -499,6 +499,7 @@ realm_user_dict_fields: List[str] = [
"delivery_email", "delivery_email",
"bot_type", "bot_type",
"long_term_idle", "long_term_idle",
"email_address_visibility",
] ]

View File

@ -91,6 +91,8 @@ def create_user_profile(
tutorial_status: str = UserProfile.TUTORIAL_WAITING, tutorial_status: str = UserProfile.TUTORIAL_WAITING,
force_id: Optional[int] = None, force_id: Optional[int] = None,
force_date_joined: Optional[datetime] = None, force_date_joined: Optional[datetime] = None,
*,
email_address_visibility: int,
) -> UserProfile: ) -> UserProfile:
if force_date_joined is None: if force_date_joined is None:
date_joined = timezone_now() date_joined = timezone_now()
@ -120,6 +122,7 @@ def create_user_profile(
onboarding_steps=orjson.dumps([]).decode(), onboarding_steps=orjson.dumps([]).decode(),
default_language=default_language, default_language=default_language,
delivery_email=email, delivery_email=email,
email_address_visibility=email_address_visibility,
**extra_kwargs, **extra_kwargs,
) )
if bot_type or not active: if bot_type or not active:
@ -154,6 +157,14 @@ def create_user(
force_date_joined: Optional[datetime] = None, force_date_joined: Optional[datetime] = None,
enable_marketing_emails: Optional[bool] = None, enable_marketing_emails: Optional[bool] = None,
) -> UserProfile: ) -> UserProfile:
realm_user_default = RealmUserDefault.objects.get(realm=realm)
if bot_type is None:
email_address_visibility = realm_user_default.email_address_visibility
else:
# There is no privacy motivation for limiting access to bot email addresses,
# so we hardcode them to EMAIL_ADDRESS_VISIBILITY_EVERYONE.
email_address_visibility = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
user_profile = create_user_profile( user_profile = create_user_profile(
realm, realm,
email, email,
@ -168,6 +179,7 @@ def create_user(
default_language, default_language,
force_id=force_id, force_id=force_id,
force_date_joined=force_date_joined, force_date_joined=force_date_joined,
email_address_visibility=email_address_visibility,
) )
user_profile.avatar_source = avatar_source user_profile.avatar_source = avatar_source
user_profile.timezone = timezone user_profile.timezone = timezone
@ -189,7 +201,6 @@ def create_user(
# save is not required. # save is not required.
copy_default_settings(source_profile, user_profile) copy_default_settings(source_profile, user_profile)
elif bot_type is None: elif bot_type is None:
realm_user_default = RealmUserDefault.objects.get(realm=realm)
copy_default_settings(realm_user_default, user_profile) copy_default_settings(realm_user_default, user_profile)
else: else:
# This will be executed only for bots. # This will be executed only for bots.

View File

@ -767,6 +767,14 @@ def apply_event(
if event["op"] == "add": if event["op"] == "add":
person = copy.deepcopy(person) person = copy.deepcopy(person)
if client_gravatar:
email_address_visibility = UserProfile.objects.get(
id=person_user_id
).email_address_visibility
if email_address_visibility != UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
client_gravatar = False
if client_gravatar and person["avatar_url"].startswith("https://secure.gravatar.com/"): if client_gravatar and person["avatar_url"].startswith("https://secure.gravatar.com/"):
person["avatar_url"] = None person["avatar_url"] = None
person["is_active"] = True person["is_active"] = True
@ -856,17 +864,27 @@ def apply_event(
if not was_admin and now_admin: if not was_admin and now_admin:
state["realm_bots"] = get_owned_bot_dicts(user_profile) state["realm_bots"] = get_owned_bot_dicts(user_profile)
if (
client_gravatar
and "avatar_url" in person
# Respect the client_gravatar setting in the `users` data.
and person["avatar_url"].startswith("https://secure.gravatar.com/")
):
person["avatar_url"] = None
person["avatar_url_medium"] = None
if person_user_id in state["raw_users"]: if person_user_id in state["raw_users"]:
p = state["raw_users"][person_user_id] p = state["raw_users"][person_user_id]
if "avatar_url" in person:
# Respect the client_gravatar setting in the `users` data.
if client_gravatar:
email_address_visibility = UserProfile.objects.get(
id=person_user_id
).email_address_visibility
if (
email_address_visibility
!= UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
):
client_gravatar = False
if client_gravatar and person["avatar_url"].startswith(
"https://secure.gravatar.com/"
):
person["avatar_url"] = None
person["avatar_url_medium"] = None
for field in p: for field in p:
if field in person: if field in person:
p[field] = person[field] p[field] = person[field]
@ -1399,11 +1417,6 @@ def do_events_register(
stream_typing_notifications = client_capabilities.get("stream_typing_notifications", False) stream_typing_notifications = client_capabilities.get("stream_typing_notifications", False)
user_settings_object = client_capabilities.get("user_settings_object", False) user_settings_object = client_capabilities.get("user_settings_object", False)
if realm.email_address_visibility != Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
# If real email addresses are not available to the user, their
# clients cannot compute gravatars, so we force-set it to false.
client_gravatar = False
if fetch_event_types is not None: if fetch_event_types is not None:
event_types_set: Optional[Set[str]] = set(fetch_event_types) event_types_set: Optional[Set[str]] = set(fetch_event_types)
elif event_types is not None: elif event_types is not None:

View File

@ -373,6 +373,13 @@ class MessageDict:
if not skip_copy: if not skip_copy:
obj = copy.copy(obj) obj = copy.copy(obj)
if obj["sender_email_address_visibility"] != UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
# If email address of the sender is only available to administrators,
# clients cannot compute gravatars, so we force-set it to false.
# If we plumbed the current user's role, we could allow client_gravatar=True
# here if the current user's role has access to the target user's email address.
client_gravatar = False
MessageDict.set_sender_avatar(obj, client_gravatar) MessageDict.set_sender_avatar(obj, client_gravatar)
if apply_markdown: if apply_markdown:
obj["content_type"] = "text/html" obj["content_type"] = "text/html"
@ -390,6 +397,7 @@ class MessageDict:
del obj["recipient_type"] del obj["recipient_type"]
del obj["recipient_type_id"] del obj["recipient_type_id"]
del obj["sender_is_mirror_dummy"] del obj["sender_is_mirror_dummy"]
del obj["sender_email_address_visibility"]
return obj return obj
@staticmethod @staticmethod
@ -600,6 +608,7 @@ class MessageDict:
"avatar_source", "avatar_source",
"avatar_version", "avatar_version",
"is_mirror_dummy", "is_mirror_dummy",
"email_address_visibility",
) )
rows = query_for_ids(query, sender_ids, "zerver_userprofile.id") rows = query_for_ids(query, sender_ids, "zerver_userprofile.id")
@ -616,6 +625,7 @@ class MessageDict:
obj["sender_avatar_source"] = user_row["avatar_source"] obj["sender_avatar_source"] = user_row["avatar_source"]
obj["sender_avatar_version"] = user_row["avatar_version"] obj["sender_avatar_version"] = user_row["avatar_version"]
obj["sender_is_mirror_dummy"] = user_row["is_mirror_dummy"] obj["sender_is_mirror_dummy"] = user_row["is_mirror_dummy"]
obj["sender_email_address_visibility"] = user_row["email_address_visibility"]
@staticmethod @staticmethod
def hydrate_recipient_info(obj: Dict[str, Any], display_recipient: DisplayRecipientT) -> None: def hydrate_recipient_info(obj: Dict[str, Any], display_recipient: DisplayRecipientT) -> None:

View File

@ -38,7 +38,8 @@ from django.urls import URLResolver
from moto.s3 import mock_s3 from moto.s3 import mock_s3
from mypy_boto3_s3.service_resource import Bucket from mypy_boto3_s3.service_resource import Bucket
from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.realm_settings import do_set_realm_user_default_setting
from zerver.actions.user_settings import do_change_user_setting
from zerver.lib import cache from zerver.lib import cache
from zerver.lib.avatar import avatar_url from zerver.lib.avatar import avatar_url
from zerver.lib.cache import get_cache_backend from zerver.lib.cache import get_cache_backend
@ -49,7 +50,7 @@ from zerver.lib.upload.s3 import S3UploadBackend
from zerver.models import ( from zerver.models import (
Client, Client,
Message, Message,
Realm, RealmUserDefault,
Subscription, Subscription,
UserMessage, UserMessage,
UserProfile, UserProfile,
@ -199,12 +200,21 @@ def stdout_suppressed() -> Iterator[IO[str]]:
def reset_emails_in_zulip_realm() -> None: def reset_emails_in_zulip_realm() -> None:
realm = get_realm("zulip") realm = get_realm("zulip")
do_set_realm_property( realm_user_default = RealmUserDefault.objects.get(realm=realm)
realm, do_set_realm_user_default_setting(
realm_user_default,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE, RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None, acting_user=None,
) )
users = UserProfile.objects.filter(realm=realm)
for user in users:
do_change_user_setting(
user,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None,
)
def get_test_image_file(filename: str) -> IO[bytes]: def get_test_image_file(filename: str) -> IO[bytes]:

View File

@ -399,23 +399,23 @@ def can_access_delivery_email(
user_profile: UserProfile, user_profile: UserProfile,
target_user_id: int, target_user_id: int,
email_address_visibility: int, email_address_visibility: int,
target_user_is_bot: bool,
) -> bool: ) -> bool:
if target_user_id == user_profile.id: if target_user_id == user_profile.id:
return True return True
if target_user_is_bot: # Bots always have email_address_visibility as EMAIL_ADDRESS_VISIBILITY_EVERYONE.
if email_address_visibility == UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
return True return True
if email_address_visibility == Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE: if email_address_visibility == UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS:
return True
if email_address_visibility == Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS:
return user_profile.is_realm_admin return user_profile.is_realm_admin
if email_address_visibility == Realm.EMAIL_ADDRESS_VISIBILITY_MODERATORS: if email_address_visibility == UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS:
return user_profile.is_realm_admin or user_profile.is_moderator return user_profile.is_realm_admin or user_profile.is_moderator
if email_address_visibility == UserProfile.EMAIL_ADDRESS_VISIBILITY_MEMBERS:
return not user_profile.is_guest
return False return False
@ -492,7 +492,7 @@ def format_user_row(
) )
if acting_user is not None and can_access_delivery_email( if acting_user is not None and can_access_delivery_email(
acting_user, row["id"], realm.email_address_visibility, row["is_bot"] acting_user, row["id"], row["email_address_visibility"]
): ):
result["delivery_email"] = row["delivery_email"] result["delivery_email"] = row["delivery_email"]
else: else:
@ -618,12 +618,15 @@ def get_raw_user_data(
for row in user_dicts: for row in user_dicts:
if profiles_by_user_id is not None: if profiles_by_user_id is not None:
custom_profile_field_data = profiles_by_user_id.get(row["id"], {}) custom_profile_field_data = profiles_by_user_id.get(row["id"], {})
client_gravatar_for_user = (
client_gravatar
and row["email_address_visibility"] == UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
)
result[row["id"]] = format_user_row( result[row["id"]] = format_user_row(
realm, realm,
acting_user=acting_user, acting_user=acting_user,
row=row, row=row,
client_gravatar=client_gravatar, client_gravatar=client_gravatar_for_user,
user_avatar_url_field_optional=user_avatar_url_field_optional, user_avatar_url_field_optional=user_avatar_url_field_optional,
custom_profile_field_data=custom_profile_field_data, custom_profile_field_data=custom_profile_field_data,
) )
@ -655,8 +658,7 @@ def get_users_with_access_to_real_email(user_profile: UserProfile) -> List[int]:
if can_access_delivery_email( if can_access_delivery_email(
user, user,
user_profile.id, user_profile.id,
user_profile.realm.email_address_visibility, user_profile.email_address_visibility,
user_profile.is_bot,
): ):
user_ids_with_real_email_access.append(user.id) user_ids_with_real_email_access.append(user.id)

View File

@ -2077,9 +2077,8 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type
return allowed_bot_types return allowed_bot_types
def email_address_is_realm_public(self) -> bool: def email_address_is_realm_public(self) -> bool:
if self.realm.email_address_visibility == Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE: # Bots always have EMAIL_ADDRESS_VISIBILITY_EVERYONE.
return True if self.email_address_visibility == UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
if self.is_bot:
return True return True
return False return False

View File

@ -570,7 +570,7 @@ paths:
description: | description: |
When the Zulip display email address of a user changes, When the Zulip display email address of a user changes,
either due to the user's email address changing, or either due to the user's email address changing, or
due to changes in the organization's due to changes in the user's
[email address visibility][help-email-visibility]. [email address visibility][help-email-visibility].
[help-email-visibility]: /help/restrict-visibility-of-email-addresses [help-email-visibility]: /help/restrict-visibility-of-email-addresses
@ -7242,8 +7242,8 @@ paths:
description: | description: |
The user's real email address. This value will be `None` if you cannot The user's real email address. This value will be `None` if you cannot
access this user's real email address. For bot users, this field is access this user's real email address. For bot users, this field is
always set to the real email of the bot, since everyone can access the always set to the real email of the bot, because bot users always have
bot's email irrespective of `email_address_visibility` setting. `email_address_visibility` as `EMAIL_ADDRESS_VISIBILITY_EVERYONE`.
**Changes**: Prior to Zulip 7.0 (feature level 163), this field was present **Changes**: Prior to Zulip 7.0 (feature level 163), this field was present
only when `email_address_visibility` was restricted and you had access to only when `email_address_visibility` was restricted and you had access to
@ -16942,8 +16942,8 @@ components:
description: | description: |
The user's real email address. This value will be `None` if you cannot The user's real email address. This value will be `None` if you cannot
access user's real email address. For bot users, this field is always access user's real email address. For bot users, this field is always
set to the real email of the bot, since everyone can access the bot's set to the real email of the bot, because bot users always have
email irrespective of `email_address_visibility` setting. `email_address_visibility` as `EMAIL_ADDRESS_VISIBILITY_EVERYONE`.
**Changes**: Prior to Zulip 7.0 (feature level 163), this field was present **Changes**: Prior to Zulip 7.0 (feature level 163), this field was present
only when `email_address_visibility` was restricted and you had access to the only when `email_address_visibility` was restricted and you had access to the

View File

@ -61,7 +61,7 @@ from zerver.actions.realm_settings import (
do_reactivate_realm, do_reactivate_realm,
do_set_realm_property, do_set_realm_property,
) )
from zerver.actions.user_settings import do_change_password from zerver.actions.user_settings import do_change_password, do_change_user_setting
from zerver.actions.users import change_user_is_active, do_deactivate_user from zerver.actions.users import change_user_is_active, do_deactivate_user
from zerver.lib.avatar import avatar_url from zerver.lib.avatar import avatar_url
from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.avatar_hash import user_avatar_path
@ -6117,11 +6117,10 @@ class TestZulipLDAPUserPopulator(ZulipLDAPTestCase):
def test_update_with_hidden_emails(self) -> None: def test_update_with_hidden_emails(self) -> None:
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
realm = get_realm("zulip") do_change_user_setting(
do_set_realm_property( hamlet,
realm,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
hamlet.refresh_from_db() hamlet.refresh_from_db()

View File

@ -9,7 +9,7 @@ from django.test import override_settings
from zulip_bots.custom_exceptions import ConfigValidationError from zulip_bots.custom_exceptions import ConfigValidationError
from zerver.actions.bots import do_change_bot_owner from zerver.actions.bots import do_change_bot_owner
from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.realm_settings import do_set_realm_user_default_setting
from zerver.actions.streams import do_change_stream_permission from zerver.actions.streams import do_change_stream_permission
from zerver.actions.users import do_change_can_create_users, do_change_user_role, do_deactivate_user from zerver.actions.users import do_change_can_create_users, do_change_user_role, do_deactivate_user
from zerver.lib.bot_config import ConfigError, get_bot_config from zerver.lib.bot_config import ConfigError, get_bot_config
@ -19,6 +19,7 @@ from zerver.lib.test_classes import UploadSerializeMixin, ZulipTestCase
from zerver.lib.test_helpers import avatar_disk_path, get_test_image_file from zerver.lib.test_helpers import avatar_disk_path, get_test_image_file
from zerver.models import ( from zerver.models import (
Realm, Realm,
RealmUserDefault,
Service, Service,
UserProfile, UserProfile,
get_bot_services, get_bot_services,
@ -318,10 +319,11 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
# Test that we don't mangle the email field with # Test that we don't mangle the email field with
# email_address_visibility limited to admins # email_address_visibility limited to admins
user = self.example_user("hamlet") user = self.example_user("hamlet")
do_set_realm_property( realm_user_default = RealmUserDefault.objects.get(realm=user.realm)
user.realm, do_set_realm_user_default_setting(
realm_user_default,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
user.refresh_from_db() user.refresh_from_db()

View File

@ -9,6 +9,7 @@ from zerver.actions.custom_profile_fields import (
try_add_realm_custom_profile_field, try_add_realm_custom_profile_field,
try_reorder_realm_custom_profile_fields, try_reorder_realm_custom_profile_fields,
) )
from zerver.actions.user_settings import do_change_user_setting
from zerver.lib.external_accounts import DEFAULT_EXTERNAL_ACCOUNTS from zerver.lib.external_accounts import DEFAULT_EXTERNAL_ACCOUNTS
from zerver.lib.markdown import markdown_convert from zerver.lib.markdown import markdown_convert
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
@ -16,6 +17,7 @@ from zerver.lib.types import ProfileDataElementUpdateDict, ProfileDataElementVal
from zerver.models import ( from zerver.models import (
CustomProfileField, CustomProfileField,
CustomProfileFieldValue, CustomProfileFieldValue,
UserProfile,
custom_profile_fields_for_realm, custom_profile_fields_for_realm,
get_realm, get_realm,
) )
@ -979,6 +981,12 @@ class ListCustomProfileFieldTest(CustomProfileFieldTestCase):
def test_get_custom_profile_fields_from_api_for_single_user(self) -> None: def test_get_custom_profile_fields_from_api_for_single_user(self) -> None:
self.login("iago") self.login("iago")
do_change_user_setting(
self.example_user("iago"),
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None,
)
expected_keys = { expected_keys = {
"result", "result",
"msg", "msg",

View File

@ -15,11 +15,8 @@ from django.utils.timezone import now as timezone_now
from zerver.actions.create_realm import do_create_realm from zerver.actions.create_realm import do_create_realm
from zerver.actions.create_user import do_reactivate_user from zerver.actions.create_user import do_reactivate_user
from zerver.actions.realm_settings import ( from zerver.actions.realm_settings import do_deactivate_realm, do_reactivate_realm
do_deactivate_realm, from zerver.actions.user_settings import do_change_user_setting
do_reactivate_realm,
do_set_realm_property,
)
from zerver.actions.users import change_user_is_active, do_deactivate_user from zerver.actions.users import change_user_is_active, do_deactivate_user
from zerver.decorator import ( from zerver.decorator import (
authenticate_notify, authenticate_notify,
@ -1239,10 +1236,10 @@ class FetchAPIKeyTest(ZulipTestCase):
def test_fetch_api_key_email_address_visibility(self) -> None: def test_fetch_api_key_email_address_visibility(self) -> None:
user = self.example_user("cordelia") user = self.example_user("cordelia")
do_set_realm_property( do_change_user_setting(
user.realm, user,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )

View File

@ -15,12 +15,11 @@ from confirmation.models import (
) )
from zerver.actions.create_user import do_reactivate_user from zerver.actions.create_user import do_reactivate_user
from zerver.actions.realm_settings import do_deactivate_realm, do_set_realm_property from zerver.actions.realm_settings import do_deactivate_realm, do_set_realm_property
from zerver.actions.user_settings import do_start_email_change_process from zerver.actions.user_settings import do_change_user_setting, do_start_email_change_process
from zerver.actions.users import do_deactivate_user from zerver.actions.users import do_deactivate_user
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.models import ( from zerver.models import (
EmailChangeStatus, EmailChangeStatus,
Realm,
UserProfile, UserProfile,
get_realm, get_realm,
get_user, get_user,
@ -89,12 +88,6 @@ class EmailChangeTestCase(ZulipTestCase):
def test_confirm_email_change(self) -> None: def test_confirm_email_change(self) -> None:
user_profile = self.example_user("hamlet") user_profile = self.example_user("hamlet")
do_set_realm_property(
user_profile.realm,
"email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None,
)
old_email = user_profile.delivery_email old_email = user_profile.delivery_email
new_email = '"<li>hamlet-new<li>"@zulip.com' new_email = '"<li>hamlet-new<li>"@zulip.com'
@ -270,10 +263,10 @@ class EmailChangeTestCase(ZulipTestCase):
def test_change_delivery_email_end_to_end_with_admins_visibility(self) -> None: def test_change_delivery_email_end_to_end_with_admins_visibility(self) -> None:
user_profile = self.example_user("hamlet") user_profile = self.example_user("hamlet")
do_set_realm_property( do_change_user_setting(
user_profile.realm, user_profile,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )

View File

@ -13,18 +13,22 @@ from version import API_FEATURE_LEVEL, ZULIP_MERGE_BASE, ZULIP_VERSION
from zerver.actions.custom_profile_fields import try_update_realm_custom_profile_field from zerver.actions.custom_profile_fields import try_update_realm_custom_profile_field
from zerver.actions.message_send import check_send_message from zerver.actions.message_send import check_send_message
from zerver.actions.presence import do_update_user_presence from zerver.actions.presence import do_update_user_presence
from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.user_settings import do_change_user_setting
from zerver.actions.users import do_change_user_role from zerver.actions.users import do_change_user_role
from zerver.lib.event_schema import check_restart_event from zerver.lib.event_schema import check_restart_event
from zerver.lib.events import fetch_initial_state_data from zerver.lib.events import fetch_initial_state_data
from zerver.lib.exceptions import AccessDeniedError from zerver.lib.exceptions import AccessDeniedError
from zerver.lib.request import RequestVariableMissingError from zerver.lib.request import RequestVariableMissingError
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import HostRequestMock, dummy_handler, stub_event_queue_user_events from zerver.lib.test_helpers import (
HostRequestMock,
dummy_handler,
reset_emails_in_zulip_realm,
stub_event_queue_user_events,
)
from zerver.lib.users import get_api_key, get_raw_user_data from zerver.lib.users import get_api_key, get_raw_user_data
from zerver.models import ( from zerver.models import (
CustomProfileField, CustomProfileField,
Realm,
UserMessage, UserMessage,
UserPresence, UserPresence,
UserProfile, UserProfile,
@ -466,6 +470,13 @@ class GetEventsTest(ZulipTestCase):
self.assertEqual(message["content"], "<p><strong>hello</strong></p>") self.assertEqual(message["content"], "<p><strong>hello</strong></p>")
self.assertIn("gravatar.com", message["avatar_url"]) self.assertIn("gravatar.com", message["avatar_url"])
do_change_user_setting(
user_profile,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None,
)
message = get_message(apply_markdown=False, client_gravatar=True) message = get_message(apply_markdown=False, client_gravatar=True)
self.assertEqual(message["display_recipient"], "Denmark") self.assertEqual(message["display_recipient"], "Denmark")
self.assertEqual(message["content"], "**hello**") self.assertEqual(message["content"], "**hello**")
@ -605,13 +616,14 @@ class FetchInitialStateDataTest(ZulipTestCase):
def test_delivery_email_presence_for_non_admins(self) -> None: def test_delivery_email_presence_for_non_admins(self) -> None:
user_profile = self.example_user("aaron") user_profile = self.example_user("aaron")
hamlet = self.example_user("hamlet")
self.assertFalse(user_profile.is_realm_admin) self.assertFalse(user_profile.is_realm_admin)
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
do_set_realm_property( do_change_user_setting(
user_profile.realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE, UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None, acting_user=None,
) )
result = fetch_initial_state_data(user_profile) result = fetch_initial_state_data(user_profile)
@ -619,10 +631,10 @@ class FetchInitialStateDataTest(ZulipTestCase):
(hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id) (hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id)
self.assertEqual(hamlet_obj["delivery_email"], hamlet.delivery_email) self.assertEqual(hamlet_obj["delivery_email"], hamlet.delivery_email)
do_set_realm_property( do_change_user_setting(
user_profile.realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
result = fetch_initial_state_data(user_profile) result = fetch_initial_state_data(user_profile)
@ -632,13 +644,14 @@ class FetchInitialStateDataTest(ZulipTestCase):
def test_delivery_email_presence_for_admins(self) -> None: def test_delivery_email_presence_for_admins(self) -> None:
user_profile = self.example_user("iago") user_profile = self.example_user("iago")
hamlet = self.example_user("hamlet")
self.assertTrue(user_profile.is_realm_admin) self.assertTrue(user_profile.is_realm_admin)
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
do_set_realm_property( do_change_user_setting(
user_profile.realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE, UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None, acting_user=None,
) )
result = fetch_initial_state_data(user_profile) result = fetch_initial_state_data(user_profile)
@ -646,15 +659,15 @@ class FetchInitialStateDataTest(ZulipTestCase):
(hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id) (hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id)
self.assertEqual(hamlet_obj["delivery_email"], hamlet.delivery_email) self.assertEqual(hamlet_obj["delivery_email"], hamlet.delivery_email)
do_set_realm_property( do_change_user_setting(
user_profile.realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
result = fetch_initial_state_data(user_profile) result = fetch_initial_state_data(user_profile)
for key, value in result["raw_users"].items(): (hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id)
self.assertIn("delivery_email", value) self.assertIn("delivery_email", hamlet_obj)
def test_user_avatar_url_field_optional(self) -> None: def test_user_avatar_url_field_optional(self) -> None:
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
@ -691,6 +704,8 @@ class FetchInitialStateDataTest(ZulipTestCase):
and urlsplit(user_dict["avatar_url"]).hostname == "secure.gravatar.com" and urlsplit(user_dict["avatar_url"]).hostname == "secure.gravatar.com"
] ]
reset_emails_in_zulip_realm()
# Test again with client_gravatar = True # Test again with client_gravatar = True
result = fetch_initial_state_data( result = fetch_initial_state_data(
user_profile=hamlet, user_profile=hamlet,
@ -949,6 +964,7 @@ class ClientDescriptorsTest(ZulipTestCase):
sender_avatar_source=UserProfile.AVATAR_FROM_GRAVATAR, sender_avatar_source=UserProfile.AVATAR_FROM_GRAVATAR,
sender_avatar_version=1, sender_avatar_version=1,
sender_is_mirror_dummy=None, sender_is_mirror_dummy=None,
sender_email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
recipient_type=None, recipient_type=None,
recipient_type_id=None, recipient_type_id=None,
), ),

View File

@ -528,6 +528,13 @@ class NormalActionsTest(BaseAction):
check_message("events[0]", events[0]) check_message("events[0]", events[0])
assert isinstance(events[0]["message"]["avatar_url"], str) assert isinstance(events[0]["message"]["avatar_url"], str)
do_change_user_setting(
self.example_user("hamlet"),
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None,
)
events = self.verify_action( events = self.verify_action(
lambda: self.send_stream_message( lambda: self.send_stream_message(
self.example_user("hamlet"), "Verona", "hello", capture_on_commit_callbacks=False self.example_user("hamlet"), "Verona", "hello", capture_on_commit_callbacks=False
@ -1160,10 +1167,11 @@ class NormalActionsTest(BaseAction):
check_user_group_add_members("events[2]", events[2]) check_user_group_add_members("events[2]", events[2])
def test_register_events_email_address_visibility(self) -> None: def test_register_events_email_address_visibility(self) -> None:
do_set_realm_property( realm_user_default = RealmUserDefault.objects.get(realm=self.user_profile.realm)
self.user_profile.realm, do_set_realm_user_default_setting(
realm_user_default,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
@ -1486,6 +1494,12 @@ class NormalActionsTest(BaseAction):
assert isinstance(events[0]["person"]["avatar_url"], str) assert isinstance(events[0]["person"]["avatar_url"], str)
assert isinstance(events[0]["person"]["avatar_url_medium"], str) assert isinstance(events[0]["person"]["avatar_url_medium"], str)
do_change_user_setting(
self.user_profile,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=self.user_profile,
)
events = self.verify_action( events = self.verify_action(
lambda: do_change_avatar_fields( lambda: do_change_avatar_fields(
self.user_profile, UserProfile.AVATAR_FROM_GRAVATAR, acting_user=self.user_profile self.user_profile, UserProfile.AVATAR_FROM_GRAVATAR, acting_user=self.user_profile
@ -1501,11 +1515,11 @@ class NormalActionsTest(BaseAction):
) )
check_realm_user_update("events[0]", events[0], "full_name") check_realm_user_update("events[0]", events[0], "full_name")
def test_change_user_delivery_email_email_address_visibility_admins(self) -> None: def test_change_user_delivery_email_email_address_visibilty_admins(self) -> None:
do_set_realm_property( do_change_user_setting(
self.user_profile.realm, self.user_profile,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
# Important: We need to refresh from the database here so that # Important: We need to refresh from the database here so that
@ -1521,10 +1535,10 @@ class NormalActionsTest(BaseAction):
assert isinstance(events[1]["person"]["avatar_url_medium"], str) assert isinstance(events[1]["person"]["avatar_url_medium"], str)
def test_change_user_delivery_email_email_address_visibility_everyone(self) -> None: def test_change_user_delivery_email_email_address_visibility_everyone(self) -> None:
do_set_realm_property( do_change_user_setting(
self.user_profile.realm, self.user_profile,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE, UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None, acting_user=None,
) )
# Important: We need to refresh from the database here so that # Important: We need to refresh from the database here so that
@ -2800,6 +2814,19 @@ class UserDisplayActionTest(BaseAction):
raise AssertionError(f"No test created for {setting_name}") raise AssertionError(f"No test created for {setting_name}")
for value in values: for value in values:
if setting_name == "email_address_visibility":
# When "email_address_visibility" setting is changed, there is at least
# one event with type "user_settings" sent to the modified user itself.
num_events = 1
old_value = getattr(self.user_profile, setting_name)
if UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE in [old_value, value]:
# In case when either the old value or new value of setting is
# UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE, "email" field of
# UserProfile object is updated and thus two additional events, for
# changing email and avatar_url field, are sent.
num_events = 3
events = self.verify_action( events = self.verify_action(
lambda: do_change_user_setting( lambda: do_change_user_setting(
self.user_profile, setting_name, value, acting_user=self.user_profile self.user_profile, setting_name, value, acting_user=self.user_profile
@ -2839,37 +2866,38 @@ class UserDisplayActionTest(BaseAction):
check_realm_user_update("events[2]", events[2], "timezone") check_realm_user_update("events[2]", events[2], "timezone")
def test_delivery_email_events_on_changing_email_address_visibility(self) -> None: def test_delivery_email_events_on_changing_email_address_visibility(self) -> None:
cordelia = self.example_user("cordelia")
do_change_user_role(self.user_profile, UserProfile.ROLE_MODERATOR, acting_user=None) do_change_user_role(self.user_profile, UserProfile.ROLE_MODERATOR, acting_user=None)
do_set_realm_property( do_change_user_setting(
self.user_profile.realm, cordelia,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_MODERATORS, UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
acting_user=None, acting_user=None,
) )
events = self.verify_action( events = self.verify_action(
lambda: do_set_realm_property( lambda: do_change_user_setting(
self.user_profile.realm, cordelia,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=self.user_profile, acting_user=self.user_profile,
), ),
num_events=10, user_settings_object=True,
) )
check_realm_user_update("events[1]", events[1], "delivery_email") check_realm_user_update("events[0]", events[0], "delivery_email")
self.assertIsNone(events[1]["person"]["delivery_email"]) self.assertIsNone(events[0]["person"]["delivery_email"])
events = self.verify_action( events = self.verify_action(
lambda: do_set_realm_property( lambda: do_change_user_setting(
self.user_profile.realm, cordelia,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_MODERATORS, UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
acting_user=self.user_profile, acting_user=self.user_profile,
), ),
num_events=10, user_settings_object=True,
) )
check_realm_user_update("events[1]", events[1], "delivery_email") check_realm_user_update("events[0]", events[0], "delivery_email")
self.assertIsNotNone(events[1]["person"]["delivery_email"]) self.assertEqual(events[0]["person"]["delivery_email"], cordelia.delivery_email)
class SubscribeActionTest(BaseAction): class SubscribeActionTest(BaseAction):

View File

@ -15,6 +15,7 @@ from analytics.models import RealmCount
from zerver.actions.message_edit import do_update_message from zerver.actions.message_edit import do_update_message
from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.realm_settings import do_set_realm_property
from zerver.actions.uploads import do_claim_attachments from zerver.actions.uploads import do_claim_attachments
from zerver.actions.user_settings import do_change_user_setting
from zerver.actions.users import do_deactivate_user from zerver.actions.users import do_deactivate_user
from zerver.lib.avatar import avatar_url from zerver.lib.avatar import avatar_url
from zerver.lib.exceptions import JsonableError from zerver.lib.exceptions import JsonableError
@ -1681,31 +1682,37 @@ class GetOldMessagesTest(ZulipTestCase):
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
self.login_user(hamlet) self.login_user(hamlet)
do_set_realm_property( do_change_user_setting(
hamlet.realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE, UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None, acting_user=None,
) )
self.send_personal_message(hamlet, self.example_user("iago")) self.send_personal_message(hamlet, self.example_user("iago"))
result = self.get_and_check_messages(dict(client_gravatar=orjson.dumps(False).decode())) result = self.get_and_check_messages(
dict(anchor="newest", client_gravatar=orjson.dumps(False).decode())
)
message = result["messages"][0] message = result["messages"][0]
self.assertIn("gravatar.com", message["avatar_url"]) self.assertIn("gravatar.com", message["avatar_url"])
result = self.get_and_check_messages(dict(client_gravatar=orjson.dumps(True).decode())) result = self.get_and_check_messages(
dict(anchor="newest", client_gravatar=orjson.dumps(True).decode())
)
message = result["messages"][0] message = result["messages"][0]
self.assertEqual(message["avatar_url"], None) self.assertEqual(message["avatar_url"], None)
# Now verify client_gravatar doesn't run with EMAIL_ADDRESS_VISIBILITY_ADMINS # Now verify client_gravatar doesn't run with EMAIL_ADDRESS_VISIBILITY_ADMINS
do_set_realm_property( do_change_user_setting(
hamlet.realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
result = self.get_and_check_messages(dict(client_gravatar=orjson.dumps(True).decode())) result = self.get_and_check_messages(
dict(anchor="newest", client_gravatar=orjson.dumps(True).decode())
)
message = result["messages"][0] message = result["messages"][0]
self.assertIn("gravatar.com", message["avatar_url"]) self.assertIn("gravatar.com", message["avatar_url"])

View File

@ -169,6 +169,7 @@ class MirroredMessageUsersTest(ZulipTestCase):
kwargs["bot_owner"] = None kwargs["bot_owner"] = None
kwargs["tos_version"] = None kwargs["tos_version"] = None
kwargs["timezone"] = timezone_now() kwargs["timezone"] = timezone_now()
kwargs["email_address_visibility"] = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
create_user_profile(**kwargs).save() create_user_profile(**kwargs).save()
raise IntegrityError raise IntegrityError

View File

@ -66,6 +66,7 @@ class DoRestCallTests(ZulipTestCase):
"sender_full_name": bot_user.full_name, "sender_full_name": bot_user.full_name,
"sender_avatar_source": UserProfile.AVATAR_FROM_GRAVATAR, "sender_avatar_source": UserProfile.AVATAR_FROM_GRAVATAR,
"sender_avatar_version": 1, "sender_avatar_version": 1,
"sender_email_address_visibility": UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
"recipient_type": "stream", "recipient_type": "stream",
"recipient_type_id": 999, "recipient_type_id": 999,
"sender_is_mirror_dummy": False, "sender_is_mirror_dummy": False,

View File

@ -4593,6 +4593,7 @@ class UserSignUpTest(InviteUserBase):
hamlet_in_zulip.high_contrast_mode = True hamlet_in_zulip.high_contrast_mode = True
hamlet_in_zulip.enter_sends = True hamlet_in_zulip.enter_sends = True
hamlet_in_zulip.tutorial_status = UserProfile.TUTORIAL_FINISHED hamlet_in_zulip.tutorial_status = UserProfile.TUTORIAL_FINISHED
hamlet_in_zulip.email_address_visibility = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
hamlet_in_zulip.save() hamlet_in_zulip.save()
result = self.client_post("/accounts/home/", {"email": email}, subdomain=subdomain) result = self.client_post("/accounts/home/", {"email": email}, subdomain=subdomain)
@ -4632,6 +4633,7 @@ class UserSignUpTest(InviteUserBase):
hamlet_in_zulip.high_contrast_mode = True hamlet_in_zulip.high_contrast_mode = True
hamlet_in_zulip.enter_sends = True hamlet_in_zulip.enter_sends = True
hamlet_in_zulip.tutorial_status = UserProfile.TUTORIAL_FINISHED hamlet_in_zulip.tutorial_status = UserProfile.TUTORIAL_FINISHED
hamlet_in_zulip.email_address_visibility = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
hamlet_in_zulip.save() hamlet_in_zulip.save()
# Now we'll be making requests to another subdomain, so we need to logout # Now we'll be making requests to another subdomain, so we need to logout
@ -4676,6 +4678,9 @@ class UserSignUpTest(InviteUserBase):
self.assertEqual(hamlet_in_lear.enter_sends, True) self.assertEqual(hamlet_in_lear.enter_sends, True)
self.assertEqual(hamlet_in_lear.enable_stream_audible_notifications, False) self.assertEqual(hamlet_in_lear.enable_stream_audible_notifications, False)
self.assertEqual(hamlet_in_lear.tutorial_status, UserProfile.TUTORIAL_FINISHED) self.assertEqual(hamlet_in_lear.tutorial_status, UserProfile.TUTORIAL_FINISHED)
self.assertEqual(
hamlet_in_lear.email_address_visibility, UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
)
zulip_path_id = avatar_disk_path(hamlet_in_zulip) zulip_path_id = avatar_disk_path(hamlet_in_zulip)
lear_path_id = avatar_disk_path(hamlet_in_lear) lear_path_id = avatar_disk_path(hamlet_in_lear)

View File

@ -17,7 +17,7 @@ from zerver.actions.invites import do_create_multiuse_invite_link, do_invite_use
from zerver.actions.message_send import RecipientInfoResult, get_recipient_info from zerver.actions.message_send import RecipientInfoResult, get_recipient_info
from zerver.actions.muted_users import do_mute_user from zerver.actions.muted_users import do_mute_user
from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.realm_settings import do_set_realm_property
from zerver.actions.user_settings import bulk_regenerate_api_keys from zerver.actions.user_settings import bulk_regenerate_api_keys, do_change_user_setting
from zerver.actions.users import ( from zerver.actions.users import (
change_user_is_active, change_user_is_active,
do_change_can_create_users, do_change_can_create_users,
@ -54,7 +54,6 @@ from zerver.models import (
InvalidFakeEmailDomainError, InvalidFakeEmailDomainError,
Message, Message,
PreregistrationUser, PreregistrationUser,
Realm,
RealmDomain, RealmDomain,
RealmUserDefault, RealmUserDefault,
Recipient, Recipient,
@ -328,31 +327,17 @@ class PermissionTest(ZulipTestCase):
# Now, switch email address visibility, check client_gravatar # Now, switch email address visibility, check client_gravatar
# is automatically disabled for the user. # is automatically disabled for the user.
with self.captureOnCommitCallbacks(execute=True): with self.captureOnCommitCallbacks(execute=True):
do_set_realm_property( do_change_user_setting(
user.realm, user,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
result = self.client_get("/json/users", {"client_gravatar": "true"}) result = self.client_get("/json/users", {"client_gravatar": "true"})
members = self.assert_json_success(result)["members"] members = self.assert_json_success(result)["members"]
hamlet = find_dict(members, "user_id", user.id) hamlet = find_dict(members, "user_id", user.id)
self.assertEqual(hamlet["email"], f"user{user.id}@zulip.testserver") self.assertEqual(hamlet["email"], f"user{user.id}@zulip.testserver")
# Note that the Gravatar URL should still be computed from the
# `delivery_email`; otherwise, we won't be able to serve the
# user's Gravatar.
self.assertEqual(hamlet["avatar_url"], get_gravatar_url(user.delivery_email, 1)) self.assertEqual(hamlet["avatar_url"], get_gravatar_url(user.delivery_email, 1))
self.assertEqual(hamlet["delivery_email"], user.delivery_email)
# Also verify the /events code path. This is a bit hacky, but
# basically we want to verify client_gravatar is being
# overridden.
with mock.patch(
"zerver.lib.events.request_event_queue", return_value=None
) as mock_request_event_queue:
with self.assertRaises(JsonableError):
do_events_register(user, user.realm, get_client("website"), client_gravatar=True)
self.assertEqual(mock_request_event_queue.call_args_list[0][0][3], False)
# client_gravatar is still turned off for admins. In theory, # client_gravatar is still turned off for admins. In theory,
# it doesn't need to be, but client-side changes would be # it doesn't need to be, but client-side changes would be
@ -834,8 +819,11 @@ class QueryCountTest(ZulipTestCase):
class BulkCreateUserTest(ZulipTestCase): class BulkCreateUserTest(ZulipTestCase):
def test_create_users(self) -> None: def test_create_users(self) -> None:
realm = get_realm("zulip") realm = get_realm("zulip")
realm.email_address_visibility = Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS realm_user_default = RealmUserDefault.objects.get(realm=realm)
realm.save() realm_user_default.email_address_visibility = (
RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ADMINS
)
realm_user_default.save()
name_list = [ name_list = [
("Fred Flintstone", "fred@zulip.com"), ("Fred Flintstone", "fred@zulip.com"),
@ -855,8 +843,10 @@ class BulkCreateUserTest(ZulipTestCase):
self.assertEqual(lisa.is_bot, False) self.assertEqual(lisa.is_bot, False)
self.assertEqual(lisa.bot_type, None) self.assertEqual(lisa.bot_type, None)
realm.email_address_visibility = Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE realm_user_default.email_address_visibility = (
realm.save() RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_EVERYONE
)
realm_user_default.save()
name_list = [ name_list = [
("Bono", "bono@zulip.com"), ("Bono", "bono@zulip.com"),
@ -2132,7 +2122,9 @@ class GetProfileTest(ZulipTestCase):
def test_get_all_profiles_avatar_urls(self) -> None: def test_get_all_profiles_avatar_urls(self) -> None:
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
result = self.api_get(hamlet, "/api/v1/users") result = self.api_get(
hamlet, "/api/v1/users", {"client_gravatar": orjson.dumps(False).decode()}
)
response_dict = self.assert_json_success(result) response_dict = self.assert_json_success(result)
(my_user,) = (user for user in response_dict["members"] if user["email"] == hamlet.email) (my_user,) = (user for user in response_dict["members"] if user["email"] == hamlet.email)
@ -2145,11 +2137,10 @@ class GetProfileTest(ZulipTestCase):
def test_user_email_according_to_email_address_visibility_setting(self) -> None: def test_user_email_according_to_email_address_visibility_setting(self) -> None:
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
realm = hamlet.realm do_change_user_setting(
do_set_realm_property( hamlet,
realm,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_NOBODY, UserProfile.EMAIL_ADDRESS_VISIBILITY_NOBODY,
acting_user=None, acting_user=None,
) )
@ -2160,10 +2151,10 @@ class GetProfileTest(ZulipTestCase):
self.assertEqual(result["user"].get("delivery_email"), None) self.assertEqual(result["user"].get("delivery_email"), None)
self.assertEqual(result["user"].get("email"), f"user{hamlet.id}@zulip.testserver") self.assertEqual(result["user"].get("email"), f"user{hamlet.id}@zulip.testserver")
do_set_realm_property( do_change_user_setting(
realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS, UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None, acting_user=None,
) )
@ -2180,10 +2171,10 @@ class GetProfileTest(ZulipTestCase):
self.assertEqual(result["user"].get("delivery_email"), None) self.assertEqual(result["user"].get("delivery_email"), None)
self.assertEqual(result["user"].get("email"), f"user{hamlet.id}@zulip.testserver") self.assertEqual(result["user"].get("email"), f"user{hamlet.id}@zulip.testserver")
do_set_realm_property( do_change_user_setting(
realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_MODERATORS, UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
acting_user=None, acting_user=None,
) )
@ -2200,10 +2191,30 @@ class GetProfileTest(ZulipTestCase):
self.assertEqual(result["user"].get("delivery_email"), None) self.assertEqual(result["user"].get("delivery_email"), None)
self.assertEqual(result["user"].get("email"), f"user{hamlet.id}@zulip.testserver") self.assertEqual(result["user"].get("email"), f"user{hamlet.id}@zulip.testserver")
do_set_realm_property( do_change_user_setting(
realm, hamlet,
"email_address_visibility", "email_address_visibility",
Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE, UserProfile.EMAIL_ADDRESS_VISIBILITY_MEMBERS,
acting_user=None,
)
# Check that normal user can access email when setting is set to
# EMAIL_ADDRESS_VISIBILITY_MEMBERS.
result = orjson.loads(self.client_get(f"/json/users/{hamlet.id}").content)
self.assertEqual(result["user"].get("delivery_email"), hamlet.delivery_email)
self.assertEqual(result["user"].get("email"), f"user{hamlet.id}@zulip.testserver")
# Check that guest cannot access email when setting is set to
# EMAIL_ADDRESS_VISIBILITY_MEMBERS.
self.login("polonius")
result = orjson.loads(self.client_get(f"/json/users/{hamlet.id}").content)
self.assertEqual(result["user"].get("delivery_email"), None)
self.assertEqual(result["user"].get("email"), f"user{hamlet.id}@zulip.testserver")
do_change_user_setting(
hamlet,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None, acting_user=None,
) )

View File

@ -25,7 +25,7 @@ from zerver.lib.sqlalchemy_utils import get_sqlalchemy_connection
from zerver.lib.topic import DB_TOPIC_NAME, MATCH_TOPIC, topic_column_sa from zerver.lib.topic import DB_TOPIC_NAME, MATCH_TOPIC, topic_column_sa
from zerver.lib.utils import statsd from zerver.lib.utils import statsd
from zerver.lib.validator import check_bool, check_int, check_list, to_non_negative_int from zerver.lib.validator import check_bool, check_int, check_list, to_non_negative_int
from zerver.models import Realm, UserMessage, UserProfile from zerver.models import UserMessage, UserProfile
MAX_MESSAGES_PER_FETCH = 5000 MAX_MESSAGES_PER_FETCH = 5000
@ -137,12 +137,10 @@ def get_messages_backend(
assert realm is not None assert realm is not None
if ( if is_web_public_query:
is_web_public_query # client_gravatar here is just the user-requested value. "finalize_payload" function
or realm.email_address_visibility != Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE # is responsible for sending avatar_url based on each individual sender's
): # email_address_visibility setting.
# If email addresses are only available to administrators,
# clients cannot compute gravatars, so we force-set it to false.
client_gravatar = False client_gravatar = False
if narrow is not None: if narrow is not None:

View File

@ -89,7 +89,6 @@ from zerver.models import (
EmailContainsPlusError, EmailContainsPlusError,
InvalidFakeEmailDomainError, InvalidFakeEmailDomainError,
Message, Message,
Realm,
Service, Service,
Stream, Stream,
UserProfile, UserProfile,
@ -637,10 +636,6 @@ def get_user_data(
compress very poorly compared to other data. compress very poorly compared to other data.
""" """
realm = user_profile.realm realm = user_profile.realm
if realm.email_address_visibility != Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
# If email addresses are only available to administrators,
# clients cannot compute gravatars, so we force-set it to false.
client_gravatar = False
members = get_raw_user_data( members = get_raw_user_data(
realm, realm,

View File

@ -333,6 +333,9 @@ class Command(BaseCommand):
realm_user_default = RealmUserDefault.objects.get(realm=zulip_realm) realm_user_default = RealmUserDefault.objects.get(realm=zulip_realm)
realm_user_default.enter_sends = True realm_user_default.enter_sends = True
realm_user_default.email_address_visibility = (
RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ADMINS
)
realm_user_default.save() realm_user_default.save()
if options["test_suite"]: if options["test_suite"]: