mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
Hash the salt, user-id, and now avatar version into the filename. This allows the URL contents to be immutable, and thus to be marked as immutable and cacheable. Since avatars are served unauthenticated, hashing with a server-side salt makes the current and past avatars not enumerable. This requires plumbing the current (or future) avatar version through various parts of the upload process. Since this already requires a full migration of current avatars, also take the opportunity to fix the missing `.png` on S3 uploads (#12852). We switch from SHA-1 to SHA-256, but truncate it such that avatar URL data does not substantially increase in size. Fixes: #12852.
42 lines
1.6 KiB
Python
42 lines
1.6 KiB
Python
import hashlib
|
|
|
|
from django.conf import settings
|
|
|
|
from zerver.models import UserProfile
|
|
|
|
|
|
def gravatar_hash(email: str) -> str:
|
|
"""Compute the Gravatar hash for an email address."""
|
|
# Non-ASCII characters aren't permitted by the currently active e-mail
|
|
# RFCs. However, the IETF has published https://tools.ietf.org/html/rfc4952,
|
|
# outlining internationalization of email addresses, and regardless if we
|
|
# typo an address or someone manages to give us a non-ASCII address, let's
|
|
# not error out on it.
|
|
return hashlib.md5(email.lower().encode()).hexdigest()
|
|
|
|
|
|
def user_avatar_hash(uid: str, version: str) -> str:
|
|
# WARNING: If this method is changed, you may need to do a migration
|
|
# similar to zerver/migrations/0060_move_avatars_to_be_uid_based.py .
|
|
|
|
# The salt prevents unauthenticated clients from enumerating the
|
|
# avatars of all users.
|
|
user_key = uid + ":" + version + ":" + settings.AVATAR_SALT
|
|
return hashlib.sha256(user_key.encode()).hexdigest()[:40]
|
|
|
|
|
|
def user_avatar_path(user_profile: UserProfile, future: bool = False) -> str:
|
|
# 'future' is if this is for the current avatar version, of the next one.
|
|
return user_avatar_base_path_from_ids(
|
|
user_profile.id, user_profile.avatar_version + (1 if future else 0), user_profile.realm_id
|
|
)
|
|
|
|
|
|
def user_avatar_base_path_from_ids(user_profile_id: int, version: int, realm_id: int) -> str:
|
|
user_id_hash = user_avatar_hash(str(user_profile_id), str(version))
|
|
return f"{realm_id}/{user_id_hash}"
|
|
|
|
|
|
def user_avatar_content_hash(ldap_avatar: bytes) -> str:
|
|
return hashlib.sha256(ldap_avatar).hexdigest()
|