Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
Add the ability to restrict max avatar filesize and content-type (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
anoadragon453 authored Dec 12, 2019
1 parent c7e206b commit f4d1ab0
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog.d/19.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `max_avatar_size` and `allowed_avatar_mimetypes` to restrict the size of user avatars and their file type respectively.
24 changes: 24 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,30 @@ uploads_path: "DATADIR/uploads"
#
#max_upload_size: 10M

# The largest allowed size for a user avatar. If not defined, no
# restriction will be imposed.
#
# Note that this only applies when an avatar is changed globally.
# Per-room avatar changes are not affected. See allow_per_room_profiles
# for disabling that functionality.
#
# Note that user avatar changes will not work if this is set without
# using Synapse's local media repo.
#
#max_avatar_size: 10M

# Allow mimetypes for a user avatar. If not defined, no restriction will
# be imposed.
#
# Note that this only applies when an avatar is changed globally.
# Per-room avatar changes are not affected. See allow_per_room_profiles
# for disabling that functionality.
#
# Note that user avatar changes will not work if this is set without
# using Synapse's local media repo.
#
#allowed_avatar_mimetypes: ["image/png", "image/jpeg", "image/gif"]

# Maximum number of pixels that will be thumbnailed
#
#max_image_pixels: 32M
Expand Down
30 changes: 30 additions & 0 deletions synapse/config/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ def read_config(self, config):
self.max_image_pixels = self.parse_size(config.get("max_image_pixels", "32M"))
self.max_spider_size = self.parse_size(config.get("max_spider_size", "10M"))

self.max_avatar_size = config.get("max_avatar_size")
if self.max_avatar_size:
self.max_avatar_size = self.parse_size(self.max_avatar_size)

self.allowed_avatar_mimetypes = config.get("allowed_avatar_mimetypes", [])

self.media_store_path = self.ensure_directory(config["media_store_path"])

backup_media_store_path = config.get("backup_media_store_path")
Expand Down Expand Up @@ -247,6 +253,30 @@ def default_config(self, data_dir_path, **kwargs):
#
#max_upload_size: 10M
# The largest allowed size for a user avatar. If not defined, no
# restriction will be imposed.
#
# Note that this only applies when an avatar is changed globally.
# Per-room avatar changes are not affected. See allow_per_room_profiles
# for disabling that functionality.
#
# Note that user avatar changes will not work if this is set without
# using Synapse's local media repo.
#
#max_avatar_size: 10M
# Allow mimetypes for a user avatar. If not defined, no restriction will
# be imposed.
#
# Note that this only applies when an avatar is changed globally.
# Per-room avatar changes are not affected. See allow_per_room_profiles
# for disabling that functionality.
#
# Note that user avatar changes will not work if this is set without
# using Synapse's local media repo.
#
#allowed_avatar_mimetypes: ["image/png", "image/jpeg", "image/gif"]
# Maximum number of pixels that will be thumbnailed
#
#max_image_pixels: 32M
Expand Down
46 changes: 46 additions & 0 deletions synapse/handlers/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def __init__(self, hs):

self.http_client = hs.get_simple_http_client()

self.max_avatar_size = hs.config.max_avatar_size
self.allowed_avatar_mimetypes = hs.config.allowed_avatar_mimetypes

if hs.config.worker_app is None:
self.clock.looping_call(
self._start_update_remote_profile_cache, self.PROFILE_UPDATE_MS,
Expand Down Expand Up @@ -368,6 +371,35 @@ def set_avatar_url(self, target_user, requester, new_avatar_url, by_admin=False)
400, "Avatar URL is too long (max %i)" % (MAX_AVATAR_URL_LEN, ),
)

# Enforce a max avatar size if one is defined
if self.max_avatar_size or self.allowed_avatar_mimetypes:
media_id = self._validate_and_parse_media_id_from_avatar_url(new_avatar_url)

# Check that this media exists locally
media_info = yield self.store.get_local_media(media_id)
if not media_info:
raise SynapseError(
400, "Unknown media id supplied", errcode=Codes.NOT_FOUND
)

# Ensure avatar does not exceed max allowed avatar size
media_size = media_info["media_length"]
if self.max_avatar_size and media_size > self.max_avatar_size:
raise SynapseError(
400, "Avatars must be less than %s bytes in size" %
(self.max_avatar_size,), errcode=Codes.TOO_LARGE,
)

# Ensure the avatar's file type is allowed
if (
self.allowed_avatar_mimetypes
and media_info["media_type"] not in self.allowed_avatar_mimetypes
):
raise SynapseError(
400, "Avatar file type '%s' not allowed" %
media_info["media_type"],
)

yield self.store.set_profile_avatar_url(
target_user.localpart, new_avatar_url, new_batchnum,
)
Expand All @@ -383,6 +415,20 @@ def set_avatar_url(self, target_user, requester, new_avatar_url, by_admin=False)
# start a profile replication push
run_in_background(self._replicate_profiles)

def _validate_and_parse_media_id_from_avatar_url(self, mxc):
"""Validate and parse a provided avatar url and return the local media id
Args:
mxc (str): A mxc URL
Returns:
str: The ID of the media
"""
avatar_pieces = mxc.split("/")
if len(avatar_pieces) != 4 or avatar_pieces[0] != "mxc:":
raise SynapseError(400, "Invalid avatar URL '%s' supplied" % mxc)
return avatar_pieces[-1]

@defer.inlineCallbacks
def on_profile_query(self, args):
user = UserID.from_string(args["user_id"])
Expand Down
5 changes: 3 additions & 2 deletions synapse/rest/client/v1/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,13 @@ def on_PUT(self, request, user_id):

content = parse_json_object_from_request(request)
try:
new_name = content["avatar_url"]
new_avatar_url = content["avatar_url"]
except Exception:
defer.returnValue((400, "Unable to parse name"))

yield self.profile_handler.set_avatar_url(
user, requester, new_name, is_admin)
user, requester, new_avatar_url, is_admin
)

if self.hs.config.shadow_server:
shadow_user = UserID(
Expand Down

0 comments on commit f4d1ab0

Please sign in to comment.