avatar: Use fixed avatars for system bots.

This makes a Zulip server more isolated than relying on gravatar, and
avoids complex logistics if in the future we move system bots to live
inside individual realms.

Co-authored-by: PieterCK <pieterceka123@gmail.com>
This commit is contained in:
Tim Abbott 2024-10-14 16:33:13 -07:00
parent 98ac036a5d
commit 46db52dc96
6 changed files with 62 additions and 2 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -13,6 +13,12 @@ from zerver.lib.upload import get_avatar_url
from zerver.lib.url_encoding import append_url_query_string
from zerver.models import UserProfile
SYSTEM_BOTS_AVATAR_FILES = {
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(
user_profile: UserProfile, medium: bool = False, client_gravatar: bool = False
@ -54,6 +60,11 @@ def get_avatar_field(
computing them on the server (mostly to save bandwidth).
"""
# System bots have hardcoded avatars
system_bot_avatar = SYSTEM_BOTS_AVATAR_FILES.get(email)
if system_bot_avatar:
return staticfiles_storage.url(system_bot_avatar)
"""
If our client knows how to calculate gravatar hashes, we
will return None and let the client compute the gravatar

View File

@ -70,6 +70,9 @@ def create_internal_realm() -> None:
bots = UserProfile.objects.filter(email__in=[bot_info[1] for bot_info in internal_bots])
for bot in bots:
bot.bot_owner = bot
# Avatars for system bots are hardcoded, so make sure gravatar
# won't be used..
bot.avatar_source = "U"
bot.save()
# Initialize the email gateway bot as able to forge senders.

View File

@ -0,0 +1,46 @@
# Generated by Django 5.0.9 on 2024-10-14 23:22
from django.conf import settings
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
def set_system_bot_avatar_source_user(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
UserProfile = apps.get_model("zerver", "UserProfile")
UserProfile.objects.filter(
email__in=[
settings.EMAIL_GATEWAY_BOT,
settings.NOTIFICATION_BOT,
settings.WELCOME_BOT,
]
).update(avatar_source="U")
def set_system_bot_avatar_source_gravatar(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
UserProfile = apps.get_model("zerver", "UserProfile")
UserProfile.objects.filter(
email__in=[
settings.EMAIL_GATEWAY_BOT,
settings.NOTIFICATION_BOT,
settings.WELCOME_BOT,
]
).update(avatar_source="G")
class Migration(migrations.Migration):
dependencies = [
("zerver", "0614_remove_realm_move_messages_between_streams_policy"),
]
operations = [
migrations.RunPython(
set_system_bot_avatar_source_user,
elidable=True,
reverse_code=set_system_bot_avatar_source_gravatar,
),
]

View File

@ -1256,12 +1256,12 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
# Test cross_realm_bot avatar access using email.
response = self.api_get(hamlet, "/avatar/welcome-bot@zulip.com", {"foo": "bar"})
redirect_url = response["Location"]
self.assertTrue(redirect_url.endswith(str(avatar_url(cross_realm_bot)) + "&foo=bar"))
self.assertTrue(redirect_url.endswith(str(avatar_url(cross_realm_bot)) + "?foo=bar"))
# Test cross_realm_bot avatar access using id.
response = self.api_get(hamlet, f"/avatar/{cross_realm_bot.id}", {"foo": "bar"})
redirect_url = response["Location"]
self.assertTrue(redirect_url.endswith(str(avatar_url(cross_realm_bot)) + "&foo=bar"))
self.assertTrue(redirect_url.endswith(str(avatar_url(cross_realm_bot)) + "?foo=bar"))
# Without spectators enabled, no unauthenticated access.
response = self.client_get("/avatar/cordelia@zulip.com", {"foo": "bar"})