avatar: Ensure system bots' avatar URLs follow convention.

Previously, requesting system bots URLs did not return any -medium.png
variants and SVG file was also used for notification bots' avatar, which
was problematic.

In this commit, the -medium.png variants is added for the avatars of
system bots and zulip-icon-square.svg is also converted into
notification-bot.png for the notification bot. The get_avatar_url method
has been updated to return the "medium" file variants for the system
bots.

Additionally, the system bots' avatar files is moved to a dedicated
directory to simplify the hashing logic for these files. Now, all files
in the "images/static_avatars/" directory will be hashed.
This commit is contained in:
PieterCK 2024-10-22 23:05:32 +07:00 committed by Tim Abbott
parent 81c4e45d01
commit 516d1ab82b
11 changed files with 49 additions and 14 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

View File

@ -12,14 +12,11 @@ from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE
from zerver.lib.upload import get_avatar_url from zerver.lib.upload import get_avatar_url
from zerver.lib.url_encoding import append_url_query_string from zerver.lib.url_encoding import append_url_query_string
from zerver.models import UserProfile from zerver.models import UserProfile
from zerver.models.users import is_cross_realm_bot_email
SYSTEM_BOTS_AVATAR_FILES = { STATIC_AVATARS_DIR = "images/static_avatars/"
# This is also used in zerver/lib/storage.py to ensure
# these files are hashed when served as static files. DEFAULT_AVATAR_FILE = "images/default-avatar.png"
settings.WELCOME_BOT: "images/welcome-bot.png",
settings.NOTIFICATION_BOT: "images/logo/zulip-icon-square.svg",
settings.EMAIL_GATEWAY_BOT: "images/email-gateway-bot.png",
}
def avatar_url( def avatar_url(
@ -36,6 +33,22 @@ def avatar_url(
) )
def get_system_bots_avatar_file_name(email: str) -> str:
system_bot_avatar_name_map = {
settings.WELCOME_BOT: "welcome-bot",
settings.NOTIFICATION_BOT: "notification-bot",
settings.EMAIL_GATEWAY_BOT: "emailgateway",
}
return urljoin(STATIC_AVATARS_DIR, system_bot_avatar_name_map.get(email, "unknown"))
def get_static_avatar_url(email: str, medium: bool) -> str:
avatar_file_name = get_system_bots_avatar_file_name(email)
avatar_file_name += "-medium.png" if medium else ".png"
return staticfiles_storage.url(avatar_file_name)
def get_avatar_field( def get_avatar_field(
user_id: int, user_id: int,
realm_id: int, realm_id: int,
@ -63,9 +76,8 @@ def get_avatar_field(
""" """
# System bots have hardcoded avatars # System bots have hardcoded avatars
system_bot_avatar = SYSTEM_BOTS_AVATAR_FILES.get(email) if is_cross_realm_bot_email(email):
if system_bot_avatar: return get_static_avatar_url(email, medium)
return staticfiles_storage.url(system_bot_avatar)
""" """
If our client knows how to calculate gravatar hashes, we If our client knows how to calculate gravatar hashes, we

View File

@ -10,7 +10,7 @@ from django.core.files.base import File
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from typing_extensions import override from typing_extensions import override
from zerver.lib.avatar import SYSTEM_BOTS_AVATAR_FILES from zerver.lib.avatar import STATIC_AVATARS_DIR
if settings.DEBUG: if settings.DEBUG:
from django.contrib.staticfiles.finders import find from django.contrib.staticfiles.finders import find
@ -40,11 +40,11 @@ class IgnoreBundlesManifestStaticFilesStorage(ManifestStaticFilesStorage):
# use a no-op hash function for these already-hashed # use a no-op hash function for these already-hashed
# assets. # assets.
return name return name
if name in SYSTEM_BOTS_AVATAR_FILES.values(): if name.startswith(STATIC_AVATARS_DIR):
# For these avatar files, we want to make sure they are # For these avatar files, we want to make sure they are
# so they can hit our Nginx caching block for static files. # so they can hit our Nginx caching block for static files.
# We don't need to worry about stale caches since system bot # We don't need to worry about stale caches since these are
# avatars rarely change. # only used by the system bots.
return super().hashed_name(name, content, filename) return super().hashed_name(name, content, filename)
if name == "generated/emoji/emoji_api.json": if name == "generated/emoji/emoji_api.json":
# Unlike most .json files, we do want to hash this file; # Unlike most .json files, we do want to hash this file;

View File

@ -1532,6 +1532,29 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
result = self.client_post("/json/users/me/avatar", {"file": fp}) result = self.client_post("/json/users/me/avatar", {"file": fp})
self.assert_json_error(result, "Uploaded file is larger than the allowed limit of 0 MiB") self.assert_json_error(result, "Uploaded file is larger than the allowed limit of 0 MiB")
def test_system_bot_avatars_url(self) -> None:
self.login("hamlet")
system_bot_emails = [
settings.NOTIFICATION_BOT,
settings.WELCOME_BOT,
settings.EMAIL_GATEWAY_BOT,
]
internal_realm = get_realm(settings.SYSTEM_BOT_REALM)
for email in system_bot_emails:
system_bot = get_system_bot(email, internal_realm.id)
response = self.client_get(f"/avatar/{email}")
redirect_url = response["Location"]
self.assertEqual(redirect_url, str(avatar_url(system_bot)))
self.assertTrue(str(redirect_url).endswith(".png"))
self.assertFalse(str(redirect_url).endswith("unknown.png"))
response = self.client_get(f"/avatar/{email}/medium")
redirect_url = response["Location"]
self.assertEqual(redirect_url, str(avatar_url(system_bot, medium=True)))
self.assertTrue(str(redirect_url).endswith("-medium.png"))
self.assertFalse(str(redirect_url).endswith("unknown-medium.png"))
class RealmIconTest(UploadSerializeMixin, ZulipTestCase): class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
def test_multiple_upload_failure(self) -> None: def test_multiple_upload_failure(self) -> None: