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.lib.url_encoding import append_url_query_string
from zerver.models import UserProfile 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( def avatar_url(
user_profile: UserProfile, medium: bool = False, client_gravatar: bool = False 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). 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 If our client knows how to calculate gravatar hashes, we
will return None and let the client compute the gravatar 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]) bots = UserProfile.objects.filter(email__in=[bot_info[1] for bot_info in internal_bots])
for bot in bots: for bot in bots:
bot.bot_owner = bot bot.bot_owner = bot
# Avatars for system bots are hardcoded, so make sure gravatar
# won't be used..
bot.avatar_source = "U"
bot.save() bot.save()
# Initialize the email gateway bot as able to forge senders. # 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. # Test cross_realm_bot avatar access using email.
response = self.api_get(hamlet, "/avatar/welcome-bot@zulip.com", {"foo": "bar"}) response = self.api_get(hamlet, "/avatar/welcome-bot@zulip.com", {"foo": "bar"})
redirect_url = response["Location"] 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. # Test cross_realm_bot avatar access using id.
response = self.api_get(hamlet, f"/avatar/{cross_realm_bot.id}", {"foo": "bar"}) response = self.api_get(hamlet, f"/avatar/{cross_realm_bot.id}", {"foo": "bar"})
redirect_url = response["Location"] 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. # Without spectators enabled, no unauthenticated access.
response = self.client_get("/avatar/cordelia@zulip.com", {"foo": "bar"}) response = self.client_get("/avatar/cordelia@zulip.com", {"foo": "bar"})