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

Commit dfcfeca

Browse files
committed
Add tests
1 parent 8ccac94 commit dfcfeca

File tree

4 files changed

+282
-8
lines changed

4 files changed

+282
-8
lines changed

synapse/handlers/profile.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ async def set_avatar_url(
293293
400, "Avatar URL is too long (max %i)" % (MAX_AVATAR_URL_LEN,)
294294
)
295295

296-
await self.check_avatar_size_and_content_type(new_avatar_url)
296+
await self.check_avatar_size_and_mime_type(new_avatar_url)
297297

298298
avatar_url_to_set: Optional[str] = new_avatar_url
299299
if new_avatar_url == "":
@@ -316,7 +316,7 @@ async def set_avatar_url(
316316

317317
await self._update_join_states(requester, target_user)
318318

319-
async def check_avatar_size_and_content_type(self, mxc: str) -> None:
319+
async def check_avatar_size_and_mime_type(self, mxc: str) -> None:
320320
"""Check that the size and content type of the avatar at the given MXC URI are
321321
within the configured limits.
322322
@@ -327,11 +327,11 @@ async def check_avatar_size_and_content_type(self, mxc: str) -> None:
327327
SynapseError with an M_FORBIDDEN error code if the avatar doesn't fit within
328328
the limits allowed by the configuration.
329329
"""
330-
if not await self._check_avatar_size_and_content_type(mxc):
330+
if not await self._check_avatar_size_and_mime_type(mxc):
331331
raise SynapseError(403, "This avatar is not allowed", Codes.FORBIDDEN)
332332

333333
@cached()
334-
async def _check_avatar_size_and_content_type(self, mxc: str) -> bool:
334+
async def _check_avatar_size_and_mime_type(self, mxc: str) -> bool:
335335
"""Check that the size and content type of the avatar at the given MXC URI are
336336
within the configured limits.
337337

synapse/handlers/room_member.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,8 +591,8 @@ async def update_membership_locked(
591591
)
592592

593593
if "avatar_url" in content:
594-
await self.profile_handler.check_avatar_size_and_content_type(
595-
content["avatar_url"],
594+
await self.profile_handler.check_avatar_size_and_mime_type(
595+
content["avatar_url"]
596596
)
597597

598598
# The event content should *not* include the authorising user as

tests/handlers/test_profile.py

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
14+
from typing import Any, Dict
1515
from unittest.mock import Mock
1616

1717
import synapse.types
1818
from synapse.api.errors import AuthError, SynapseError
1919
from synapse.rest import admin
20+
from synapse.server import HomeServer
2021
from synapse.types import UserID
2122

2223
from tests import unittest
@@ -46,7 +47,7 @@ def register_query_handler(query_type, handler):
4647
)
4748
return hs
4849

49-
def prepare(self, reactor, clock, hs):
50+
def prepare(self, reactor, clock, hs: HomeServer):
5051
self.store = hs.get_datastore()
5152

5253
self.frank = UserID.from_string("@1234abcd:test")
@@ -248,3 +249,104 @@ def test_set_my_avatar_if_disabled(self):
248249
),
249250
SynapseError,
250251
)
252+
253+
def test_avatar_constraints_no_config(self):
254+
"""Tests that the method to check an avatar against configured constraints skips
255+
all of its check if no constraint is configured.
256+
"""
257+
# The first check that's done by this method is whether the file exists; if we
258+
# don't get an error on a non-existing file then it means all of the checks were
259+
# successfully skipped.
260+
allowed = self.get_success(
261+
self.handler._check_avatar_size_and_mime_type("mxc://test/unknown_file")
262+
)
263+
self.assertTrue(allowed)
264+
265+
@unittest.override_config(
266+
{
267+
"max_avatar_size": 50,
268+
}
269+
)
270+
def test_avatar_constraints_missing(self):
271+
"""Tests that an avatar isn't allowed if the file at the given MXC URI couldn't
272+
be found.
273+
"""
274+
allowed = self.get_success(
275+
self.handler._check_avatar_size_and_mime_type("mxc://test/unknown_file")
276+
)
277+
self.assertFalse(allowed)
278+
279+
@unittest.override_config(
280+
{
281+
"max_avatar_size": 50,
282+
}
283+
)
284+
def test_avatar_constraints_file_size(self):
285+
"""Tests that a file that's above the allowed file size is forbidden but one
286+
that's below it is allowed.
287+
"""
288+
self._setup_local_files(
289+
{
290+
"small": {"size": 40},
291+
"big": {"size": 60},
292+
}
293+
)
294+
295+
allowed = self.get_success(
296+
self.handler._check_avatar_size_and_mime_type("mxc://test/small")
297+
)
298+
self.assertTrue(allowed)
299+
300+
allowed = self.get_success(
301+
self.handler._check_avatar_size_and_mime_type("mxc://test/big")
302+
)
303+
self.assertFalse(allowed)
304+
305+
@unittest.override_config(
306+
{
307+
"allowed_avatar_mimetypes": ["image/png"],
308+
}
309+
)
310+
def test_avatar_constraint_mime_type(self):
311+
"""Tests that a file with an unauthorised MIME type is forbidden but one with
312+
an authorised content type is allowed.
313+
"""
314+
self._setup_local_files(
315+
{
316+
"good": {"mimetype": "image/png"},
317+
"bad": {"mimetype": "application/octet-stream"},
318+
}
319+
)
320+
321+
allowed = self.get_success(
322+
self.handler._check_avatar_size_and_mime_type("mxc://test/good")
323+
)
324+
self.assertTrue(allowed)
325+
326+
allowed = self.get_success(
327+
self.handler._check_avatar_size_and_mime_type("mxc://test/bad")
328+
)
329+
self.assertFalse(allowed)
330+
331+
def _setup_local_files(self, names_and_props: Dict[str, Dict[str, Any]]):
332+
"""Stores metadata about files in the database.
333+
334+
Args:
335+
names_and_props: A dictionary with one entry per file, with the key being the
336+
file's name, and the value being a dictionary of properties. Supported
337+
properties are "mimetype" (for the file's type) and "size" (for the
338+
file's size).
339+
"""
340+
store = self.hs.get_datastore()
341+
342+
for name, props in names_and_props.items():
343+
self.get_success(
344+
store.store_local_media(
345+
media_id=name,
346+
media_type=props.get("mimetype", "image/png"),
347+
time_now_ms=self.clock.time_msec(),
348+
upload_name=None,
349+
media_length=props.get("size", 50),
350+
user_id=UserID.from_string("@rin:test"),
351+
)
352+
)

tests/rest/client/test_profile.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@
1313
# limitations under the License.
1414

1515
"""Tests REST events for /profile paths."""
16+
from typing import Any, Dict
17+
18+
from synapse.api.errors import Codes
1619
from synapse.rest import admin
1720
from synapse.rest.client import login, profile, room
21+
from synapse.types import UserID
1822

1923
from tests import unittest
2024

@@ -25,6 +29,7 @@ class ProfileTestCase(unittest.HomeserverTestCase):
2529
admin.register_servlets_for_client_rest_resource,
2630
login.register_servlets,
2731
profile.register_servlets,
32+
room.register_servlets,
2833
]
2934

3035
def make_homeserver(self, reactor, clock):
@@ -150,6 +155,173 @@ def _get_avatar_url(self, name=None):
150155
self.assertEqual(channel.code, 200, channel.result)
151156
return channel.json_body.get("avatar_url")
152157

158+
@unittest.override_config(
159+
{
160+
"max_avatar_size": 50,
161+
}
162+
)
163+
def test_avatar_size_limit_global(self):
164+
"""Tests that the maximum size limit for avatars is enforced when updating a
165+
global profile.
166+
"""
167+
self._setup_local_files(
168+
{
169+
"small": {"size": 40},
170+
"big": {"size": 60},
171+
}
172+
)
173+
174+
channel = self.make_request(
175+
"PUT",
176+
"/profile/%s/avatar_url" % (self.owner,),
177+
content={"avatar_url": "mxc://test/big"},
178+
access_token=self.owner_tok,
179+
)
180+
self.assertEqual(channel.code, 403, channel.result)
181+
self.assertEqual(
182+
channel.json_body["errcode"], Codes.FORBIDDEN, channel.json_body
183+
)
184+
185+
channel = self.make_request(
186+
"PUT",
187+
"/profile/%s/avatar_url" % (self.owner,),
188+
content={"avatar_url": "mxc://test/small"},
189+
access_token=self.owner_tok,
190+
)
191+
self.assertEqual(channel.code, 200, channel.result)
192+
193+
@unittest.override_config(
194+
{
195+
"max_avatar_size": 50,
196+
}
197+
)
198+
def test_avatar_size_limit_per_room(self):
199+
"""Tests that the maximum size limit for avatars is enforced when updating a
200+
per-room profile.
201+
"""
202+
self._setup_local_files(
203+
{
204+
"small": {"size": 40},
205+
"big": {"size": 60},
206+
}
207+
)
208+
209+
room_id = self.helper.create_room_as(tok=self.owner_tok)
210+
211+
channel = self.make_request(
212+
"PUT",
213+
"/rooms/%s/state/m.room.member/%s" % (room_id, self.owner),
214+
content={"membership": "join", "avatar_url": "mxc://test/big"},
215+
access_token=self.owner_tok,
216+
)
217+
self.assertEqual(channel.code, 403, channel.result)
218+
self.assertEqual(
219+
channel.json_body["errcode"], Codes.FORBIDDEN, channel.json_body
220+
)
221+
222+
channel = self.make_request(
223+
"PUT",
224+
"/rooms/%s/state/m.room.member/%s" % (room_id, self.owner),
225+
content={"membership": "join", "avatar_url": "mxc://test/small"},
226+
access_token=self.owner_tok,
227+
)
228+
self.assertEqual(channel.code, 200, channel.result)
229+
230+
@unittest.override_config(
231+
{
232+
"allowed_avatar_mimetypes": ["image/png"],
233+
}
234+
)
235+
def test_avatar_allowed_mime_type_global(self):
236+
"""Tests that the MIME type whitelist for avatars is enforced when updating a
237+
global profile.
238+
"""
239+
self._setup_local_files(
240+
{
241+
"good": {"mimetype": "image/png"},
242+
"bad": {"mimetype": "application/octet-stream"},
243+
}
244+
)
245+
246+
channel = self.make_request(
247+
"PUT",
248+
"/profile/%s/avatar_url" % (self.owner,),
249+
content={"avatar_url": "mxc://test/bad"},
250+
access_token=self.owner_tok,
251+
)
252+
self.assertEqual(channel.code, 403, channel.result)
253+
self.assertEqual(
254+
channel.json_body["errcode"], Codes.FORBIDDEN, channel.json_body
255+
)
256+
257+
channel = self.make_request(
258+
"PUT",
259+
"/profile/%s/avatar_url" % (self.owner,),
260+
content={"avatar_url": "mxc://test/good"},
261+
access_token=self.owner_tok,
262+
)
263+
self.assertEqual(channel.code, 200, channel.result)
264+
265+
@unittest.override_config(
266+
{
267+
"allowed_avatar_mimetypes": ["image/png"],
268+
}
269+
)
270+
def test_avatar_allowed_mime_type_per_room(self):
271+
"""Tests that the MIME type whitelist for avatars is enforced when updating a
272+
per-room profile.
273+
"""
274+
self._setup_local_files(
275+
{
276+
"good": {"mimetype": "image/png"},
277+
"bad": {"mimetype": "application/octet-stream"},
278+
}
279+
)
280+
281+
room_id = self.helper.create_room_as(tok=self.owner_tok)
282+
283+
channel = self.make_request(
284+
"PUT",
285+
"/rooms/%s/state/m.room.member/%s" % (room_id, self.owner),
286+
content={"membership": "join", "avatar_url": "mxc://test/bad"},
287+
access_token=self.owner_tok,
288+
)
289+
self.assertEqual(channel.code, 403, channel.result)
290+
self.assertEqual(
291+
channel.json_body["errcode"], Codes.FORBIDDEN, channel.json_body
292+
)
293+
294+
channel = self.make_request(
295+
"PUT",
296+
"/rooms/%s/state/m.room.member/%s" % (room_id, self.owner),
297+
content={"membership": "join", "avatar_url": "mxc://test/good"},
298+
access_token=self.owner_tok,
299+
)
300+
self.assertEqual(channel.code, 200, channel.result)
301+
302+
def _setup_local_files(self, names_and_props: Dict[str, Dict[str, Any]]):
303+
"""Stores metadata about files in the database.
304+
305+
Args:
306+
names_and_props: A dictionary with one entry per file, with the key being the
307+
file's name, and the value being a dictionary of properties. Supported
308+
properties are "mimetype" (for the file's type) and "size" (for the
309+
file's size).
310+
"""
311+
store = self.hs.get_datastore()
312+
313+
for name, props in names_and_props.items():
314+
self.get_success(
315+
store.store_local_media(
316+
media_id=name,
317+
media_type=props.get("mimetype", "image/png"),
318+
time_now_ms=self.clock.time_msec(),
319+
upload_name=None,
320+
media_length=props.get("size", 50),
321+
user_id=UserID.from_string("@rin:test"),
322+
)
323+
)
324+
153325

154326
class ProfilesRestrictedTestCase(unittest.HomeserverTestCase):
155327

0 commit comments

Comments
 (0)