-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Configurable limits on avatars #11846
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Allow configuring a maximum file size as well as a list of allowed content types for avatars. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,8 @@ | |
create_requester, | ||
get_domain_from_id, | ||
) | ||
from synapse.util.caches.descriptors import cached | ||
from synapse.util.stringutils import parse_and_validate_mxc_uri | ||
|
||
if TYPE_CHECKING: | ||
from synapse.server import HomeServer | ||
|
@@ -64,6 +66,11 @@ def __init__(self, hs: "HomeServer"): | |
self.user_directory_handler = hs.get_user_directory_handler() | ||
self.request_ratelimiter = hs.get_request_ratelimiter() | ||
|
||
self.max_avatar_size = hs.config.server.max_avatar_size | ||
self.allowed_avatar_mimetypes = hs.config.server.allowed_avatar_mimetypes | ||
|
||
self.server_name = hs.config.server.server_name | ||
|
||
if hs.config.worker.run_background_tasks: | ||
self.clock.looping_call( | ||
self._update_remote_profile_cache, self.PROFILE_UPDATE_MS | ||
|
@@ -286,6 +293,9 @@ async def set_avatar_url( | |
400, "Avatar URL is too long (max %i)" % (MAX_AVATAR_URL_LEN,) | ||
) | ||
|
||
if not await self.check_avatar_size_and_mime_type(new_avatar_url): | ||
raise SynapseError(403, "This avatar is not allowed", Codes.FORBIDDEN) | ||
|
||
avatar_url_to_set: Optional[str] = new_avatar_url | ||
if new_avatar_url == "": | ||
avatar_url_to_set = None | ||
|
@@ -307,6 +317,63 @@ async def set_avatar_url( | |
|
||
await self._update_join_states(requester, target_user) | ||
|
||
@cached() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a hug deal, but do we expect this to be called often enough with the same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, because this is called both when setting your global profile and when setting a per-room one. Except setting a global profile then updates your per-room profile in every room you're in. So if I'm in hundreds of rooms then a single global avatar change is going to trigger this function to be called hundreds of times with the same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right! Pretty much trying to make #1297 not worse! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep exactly! |
||
async def check_avatar_size_and_mime_type(self, mxc: str) -> bool: | ||
"""Check that the size and content type of the avatar at the given MXC URI are | ||
within the configured limits. | ||
|
||
Args: | ||
mxc: The MXC URI at which the avatar can be found. | ||
|
||
Returns: | ||
A boolean indicating whether the file can be allowed to be set as an avatar. | ||
""" | ||
if not self.max_avatar_size and not self.allowed_avatar_mimetypes: | ||
return True | ||
|
||
server_name, _, media_id = parse_and_validate_mxc_uri(mxc) | ||
|
||
if server_name == self.server_name: | ||
media_info = await self.store.get_local_media(media_id) | ||
else: | ||
media_info = await self.store.get_cached_remote_media(server_name, media_id) | ||
|
||
if media_info is None: | ||
# Both configuration options need to access the file's metadata, and | ||
# retrieving remote avatars just for this becomes a bit of a faff, especially | ||
# if e.g. the file is too big. It's also generally safe to assume most files | ||
# used as avatar are uploaded locally, or if the upload didn't happen as part | ||
# of a PUT request on /avatar_url that the file was at least previewed by the | ||
# user locally (and therefore downloaded to the remote media cache). | ||
logger.warning("Forbidding avatar change to %s: avatar not on server", mxc) | ||
return False | ||
|
||
if self.max_avatar_size: | ||
# Ensure avatar does not exceed max allowed avatar size | ||
if media_info["media_length"] > self.max_avatar_size: | ||
logger.warning( | ||
"Forbidding avatar change to %s: %d bytes is above the allowed size " | ||
"limit", | ||
mxc, | ||
media_info["media_length"], | ||
) | ||
return False | ||
|
||
if self.allowed_avatar_mimetypes: | ||
# Ensure the avatar's file type is allowed | ||
if ( | ||
self.allowed_avatar_mimetypes | ||
and media_info["media_type"] not in self.allowed_avatar_mimetypes | ||
babolivier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
): | ||
logger.warning( | ||
"Forbidding avatar change to %s: mimetype %s not allowed", | ||
mxc, | ||
media_info["media_type"], | ||
) | ||
return False | ||
|
||
return True | ||
|
||
async def on_profile_query(self, args: JsonDict) -> JsonDict: | ||
"""Handles federation profile query requests.""" | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.