From 3eb8036ebde2a5c3ab9d2db26426861338f2c2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sat, 20 Mar 2021 12:53:58 +0100 Subject: [PATCH] Implement size checking for the avatar This checks the avatar size on the client side (if available) and on the server side against a configuration-defined limit. The default limit is set to use the same value as in the original report, as no sensible limit value is known. Fixes #67. --- snikket_web/__init__.py | 6 +++ snikket_web/templates/unauth.html | 2 +- snikket_web/templates/user_profile.html | 41 ++++++++++++++- snikket_web/translations/messages.pot | 70 ++++++++++++++----------- snikket_web/user.py | 49 +++++++++++------ 5 files changed, 118 insertions(+), 50 deletions(-) diff --git a/snikket_web/__init__.py b/snikket_web/__init__.py index 88860ba..b3c5015 100644 --- a/snikket_web/__init__.py +++ b/snikket_web/__init__.py @@ -155,6 +155,11 @@ class AppConfig: "sv", ], converter=autosplit) apple_store_url = environ.var("") + # Default limit of 1 MiB is what was discovered to be the effective limit + # in #67, hence we set that here for now. + # Future versions may change this default, and the standard deployment + # tools may also very well override it. + max_avatar_size = environ.var(1024*1024, converter=int) _UPPER_CASE = "".join(map(chr, range(ord("A"), ord("Z")+1))) @@ -185,6 +190,7 @@ def create_app() -> quart.Quart: app.config["SITE_NAME"] = config.site_name or config.domain app.config["AVATAR_CACHE_TTL"] = config.avatar_cache_ttl app.config["APPLE_STORE_URL"] = config.apple_store_url + app.config["MAX_AVATAR_SIZE"] = config.max_avatar_size app.context_processor(proc) app.register_error_handler( diff --git a/snikket_web/templates/unauth.html b/snikket_web/templates/unauth.html index 897035c..10fdff3 100644 --- a/snikket_web/templates/unauth.html +++ b/snikket_web/templates/unauth.html @@ -9,7 +9,7 @@
{#- -#} -
+
{%- for category, message in get_flashed_messages(True) -%} {% endblock %} diff --git a/snikket_web/translations/messages.pot b/snikket_web/translations/messages.pot index 493dfe9..efa8aa1 100644 --- a/snikket_web/translations/messages.pot +++ b/snikket_web/translations/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-02-23 07:55+0100\n" +"POT-Creation-Date: 2021-03-20 12:56+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -149,39 +149,39 @@ msgstr "" msgid "Main" msgstr "" -#: snikket_web/invite.py:104 +#: snikket_web/invite.py:106 msgid "Username" msgstr "" -#: snikket_web/invite.py:108 snikket_web/invite.py:175 snikket_web/main.py:42 +#: snikket_web/invite.py:110 snikket_web/invite.py:177 snikket_web/main.py:42 msgid "Password" msgstr "" -#: snikket_web/invite.py:112 snikket_web/invite.py:179 +#: snikket_web/invite.py:114 snikket_web/invite.py:181 msgid "Confirm password" msgstr "" -#: snikket_web/invite.py:116 snikket_web/invite.py:183 +#: snikket_web/invite.py:118 snikket_web/invite.py:185 msgid "The passwords must match" msgstr "" -#: snikket_web/invite.py:121 +#: snikket_web/invite.py:123 msgid "Create account" msgstr "" -#: snikket_web/invite.py:148 +#: snikket_web/invite.py:150 msgid "That username is already taken" msgstr "" -#: snikket_web/invite.py:152 snikket_web/invite.py:216 +#: snikket_web/invite.py:154 snikket_web/invite.py:218 msgid "Registration was declined for unknown reasons" msgstr "" -#: snikket_web/invite.py:156 +#: snikket_web/invite.py:158 msgid "The username is not valid" msgstr "" -#: snikket_web/invite.py:188 snikket_web/templates/user_home.html:32 +#: snikket_web/invite.py:190 snikket_web/templates/user_home.html:32 #: snikket_web/templates/user_passwd.html:29 msgid "Change password" msgstr "" @@ -202,67 +202,77 @@ msgstr "" msgid "Login successful!" msgstr "" -#: snikket_web/user.py:28 +#: snikket_web/user.py:29 msgid "Current password" msgstr "" -#: snikket_web/user.py:33 +#: snikket_web/user.py:34 msgid "New password" msgstr "" -#: snikket_web/user.py:38 +#: snikket_web/user.py:39 msgid "Confirm new password" msgstr "" -#: snikket_web/user.py:42 +#: snikket_web/user.py:43 msgid "The new passwords must match" msgstr "" -#: snikket_web/user.py:49 +#: snikket_web/user.py:50 msgid "Sign out" msgstr "" -#: snikket_web/user.py:54 +#: snikket_web/user.py:55 msgid "Nobody" msgstr "" -#: snikket_web/user.py:55 +#: snikket_web/user.py:56 msgid "Friends only" msgstr "" -#: snikket_web/user.py:56 +#: snikket_web/user.py:57 msgid "Everyone" msgstr "" #: snikket_web/templates/admin_delete_user.html:12 -#: snikket_web/templates/admin_users.html:11 snikket_web/user.py:62 +#: snikket_web/templates/admin_users.html:11 snikket_web/user.py:63 msgid "Display name" msgstr "" -#: snikket_web/user.py:66 +#: snikket_web/user.py:67 msgid "Avatar" msgstr "" -#: snikket_web/user.py:70 +#: snikket_web/user.py:71 msgid "Profile visibility" msgstr "" -#: snikket_web/user.py:75 +#: snikket_web/user.py:76 msgid "Update profile" msgstr "" -#: snikket_web/user.py:100 +#: snikket_web/user.py:101 msgid "Incorrect password" msgstr "" -#: snikket_web/user.py:104 +#: snikket_web/user.py:105 msgid "Password changed" msgstr "" -#: snikket_web/user.py:146 +#: snikket_web/user.py:113 +msgid "" +"The chosen avatar is too big. To be able to upload larger avatars, please" +" use the app" +msgstr "" + +#: snikket_web/user.py:161 msgid "Profile updated" msgstr "" +#: snikket_web/templates/unauth.html:18 snikket_web/user.py:169 +msgid "Error" +msgstr "" + #: snikket_web/templates/_footer.html:4 #, python-format msgid "A Snikket service" @@ -480,7 +490,7 @@ msgstr "" #: snikket_web/templates/admin_reset_user_password.html:25 #: snikket_web/templates/user_logout.html:10 #: snikket_web/templates/user_passwd.html:27 -#: snikket_web/templates/user_profile.html:28 +#: snikket_web/templates/user_profile.html:32 msgid "Back" msgstr "" @@ -1068,10 +1078,6 @@ msgstr "" msgid "Operation successful" msgstr "" -#: snikket_web/templates/unauth.html:18 -msgid "Error" -msgstr "" - #: snikket_web/templates/user_home.html:9 msgid "Welcome!" msgstr "" @@ -1140,11 +1146,11 @@ msgstr "" msgid "Profile" msgstr "" -#: snikket_web/templates/user_profile.html:19 +#: snikket_web/templates/user_profile.html:23 msgid "Visibility" msgstr "" -#: snikket_web/templates/user_profile.html:20 +#: snikket_web/templates/user_profile.html:24 msgid "" "This section allows you to control who can see your profile information, " "like avatar and nickname." diff --git a/snikket_web/user.py b/snikket_web/user.py index 982ad7a..3f14ce6 100644 --- a/snikket_web/user.py +++ b/snikket_web/user.py @@ -9,6 +9,7 @@ from quart import ( redirect, url_for, flash, + current_app, ) import quart.exceptions @@ -109,9 +110,17 @@ async def change_pw() -> typing.Union[str, quart.Response]: return await render_template("user_passwd.html", form=form) +EAVATARTOOBIG = _l( + "The chosen avatar is too big. To be able to upload larger " + "avatars, please use the app" +) + + @bp.route("/profile", methods=["GET", "POST"]) @client.require_session() async def profile() -> typing.Union[str, quart.Response]: + max_avatar_size = current_app.config["MAX_AVATAR_SIZE"] + form = ProfileForm() if request.method != "POST": user_info = await client.get_user_info() @@ -125,30 +134,40 @@ async def profile() -> typing.Union[str, quart.Response]: if form.validate_on_submit(): user_info = await client.get_user_info() + ok = True file_info = (await request.files).get(form.avatar.name) if file_info is not None: mimetype = file_info.mimetype data = file_info.stream.read() - if len(data) > 0: + if len(data) > max_avatar_size: + print(len(data), max_avatar_size) + form.avatar.errors.append(EAVATARTOOBIG) + ok = False + elif len(data) > 0: await client.set_user_avatar(data, mimetype) - if user_info.get("nickname") != form.nickname.data: - await client.set_user_nickname(form.nickname.data) + if ok: + if user_info.get("nickname") != form.nickname.data: + await client.set_user_nickname(form.nickname.data) - access_model = form.profile_access_model.data - await asyncio.gather( - client.set_avatar_access_model(access_model), - client.set_vcard_access_model(access_model), - client.set_nickname_access_model(access_model), - ) + access_model = form.profile_access_model.data + await asyncio.gather( + client.set_avatar_access_model(access_model), + client.set_vcard_access_model(access_model), + client.set_nickname_access_model(access_model), + ) - await flash( - _("Profile updated"), - "success", - ) - return redirect(url_for(".profile")) + await flash( + _("Profile updated"), + "success", + ) + return redirect(url_for(".profile")) - return await render_template("user_profile.html", form=form) + return await render_template("user_profile.html", + form=form, + max_avatar_size=max_avatar_size, + avatar_too_big_warning_header=_l("Error"), + avatar_too_big_warning=EAVATARTOOBIG) @bp.route("/logout", methods=["GET", "POST"])