From ef35c565bb0af8a2176ad9dc697f20ca070cd2aa 2025-02-13 22:37:03 From: Branko Majic Date: 2025-02-13 22:37:03 Subject: [PATCH] MAR-242: Added role parameters for xmpp_server role to configure HTTP file upload limits (XEP-0363): - Refactor the daily quota tests to be a bit more flexible. --- diff --git a/docs/rolereference.rst b/docs/rolereference.rst index 1a38dd13e0fae9b52c29f4dc744bd1f4f18f206b..04b277d967a834a4c0b4f966840dbbebcb86829c 100644 --- a/docs/rolereference.rst +++ b/docs/rolereference.rst @@ -900,8 +900,17 @@ Prosody is configured as follows: service is set-up, with FQDN set to ``conference.DOMAIN``. * For each domain specified, a dedicated file proxy service will be set-up, with FQDN set to ``proxy.DOMAIN``. -* For each domain specified, a dedicated http file share service will be set-up, - with FQDN set to ``upload.DOMAIN``. +* For each domain specified, a dedicated http file share service is + set-up, with FQDN set to ``upload.DOMAIN``. Service is configured + with maximum upload file size limit, as well as per-user daily + quota. This allows clients to use `XEP-0363: HTTP File Upload + `_ for exchanging files. + + .. warning:: + Due to `bug related to global quotas + `_, the role currently does not + configure global quotas in any way. This might change in the + future. Prosody expects a specific directory structure in LDAP when doing look-ups: @@ -968,6 +977,15 @@ Parameters Fully qualified domain name, hostname, or IP address of the LDAP server used for user authentication and listing. +**xmpp_http_file_share_daily_quota** (integer, optional, ``104857600``) + Daily quota for individual users - maximum file size in bytes that a + particular user can upload per day (`XEP-0363: HTTP File Upload + `_). + +**xmpp_http_file_share_size_limit** (integer, optional, ``10485760``) + Maximum file size in bytes to allow for upload (`XEP-0363: HTTP File + Upload `_). + **xmpp_server_archive_expiration** (string, optional, ``never``) Expiration period for messages stored server-side using `XEP-0313: Message Archive Management diff --git a/roles/xmpp_server/defaults/main.yml b/roles/xmpp_server/defaults/main.yml index aecf13a40ea3f59b1b389d451a0903f6fea8bfa0..f244e1512f952d2841aae293d41704aa6320a828 100644 --- a/roles/xmpp_server/defaults/main.yml +++ b/roles/xmpp_server/defaults/main.yml @@ -1,6 +1,8 @@ --- enable_backup: false +xmpp_http_file_share_daily_quota: 104857600 # 100MiB +xmpp_http_file_share_size_limit: 10485760 # 10MiB xmpp_server_archive_expiration: "never" xmpp_server_tls_protocol: "tlsv1_2+" diff --git a/roles/xmpp_server/molecule/default/group_vars/parameters-optional.yml b/roles/xmpp_server/molecule/default/group_vars/parameters-optional.yml index 03f50d157ea8f309f44fc6020922f77ec2e832d6..bfb2a8a4f5e7cdddd193fa3d0cf5980ada482195 100644 --- a/roles/xmpp_server/molecule/default/group_vars/parameters-optional.yml +++ b/roles/xmpp_server/molecule/default/group_vars/parameters-optional.yml @@ -6,6 +6,8 @@ xmpp_administrators: xmpp_domains: - domain2 - domain3 +xmpp_http_file_share_daily_quota: 73400320 # 70MiB +xmpp_http_file_share_size_limit: 20971520 # 20MiB xmpp_ldap_base_dn: dc=local xmpp_ldap_password: prosodypassword xmpp_ldap_server: ldap-server diff --git a/roles/xmpp_server/molecule/default/tests/test_client.py b/roles/xmpp_server/molecule/default/tests/test_client.py index 4d8248ef65306b0815b8ac1783721f90f7d48385..66cd9fc98353f956b8924022c41b883cfec17ccc 100644 --- a/roles/xmpp_server/molecule/default/tests/test_client.py +++ b/roles/xmpp_server/molecule/default/tests/test_client.py @@ -145,19 +145,17 @@ def test_http_file_upload(host, server_host, username, password, domain): assert uploaded_file.content_string == expected_content -@pytest.mark.parametrize("username, password, domain, server", [ - ["john.doe", "johnpassword", "domain1", "parameters-mandatory"], - ["jane.doe", "janepassword", "domain2", "parameters-optional"], - ["mick.doe", "mickpassword", "domain3", "parameters-optional"], +@pytest.mark.parametrize("username, password, domain, server, file_size_limit", [ + ["john.doe", "johnpassword", "domain1", "parameters-mandatory", 10 * 1024 * 1024], + ["jane.doe", "janepassword", "domain2", "parameters-optional", 20 * 1024 * 1024], + ["mick.doe", "mickpassword", "domain3", "parameters-optional", 20 * 1024 * 1024], ]) @pytest.mark.usefixtures("server_clean_domain_uploads") -def test_http_file_share_size_limit(host, username, password, domain): +def test_http_file_share_size_limit(host, username, password, domain, file_size_limit): """ Tests the maximum file size for files uploaded via XEP-0363. """ - file_size_limit = 10 * 1024 * 1024 - # Test exact size limit. create_sample_file = host.run("dd if=/dev/zero of=/tmp/http_file_upload_sample.txt bs=%sB count=1", str(file_size_limit)) assert create_sample_file.rc == 0 @@ -177,30 +175,30 @@ def test_http_file_share_size_limit(host, username, password, domain): assert "file-too-large" in send.stderr -@pytest.mark.parametrize("username, password, domain, server", [ - ["john.doe", "johnpassword", "domain1", "parameters-mandatory"], - ["jane.doe", "janepassword", "domain2", "parameters-optional"], - ["mick.doe", "mickpassword", "domain3", "parameters-optional"], +@pytest.mark.parametrize("username, password, domain, server, file_size_limit, user_daily_quota", [ + ["john.doe", "johnpassword", "domain1", "parameters-mandatory", 10 * 1024 * 1024, 100 * 1024 * 1024], + ["jane.doe", "janepassword", "domain2", "parameters-optional", 20 * 1024 * 1024, 70 * 1024 * 1024], + ["mick.doe", "mickpassword", "domain3", "parameters-optional", 20 * 1024 * 1024, 70 * 1024 * 1024], ]) @pytest.mark.usefixtures("server_clean_domain_uploads") -def test_http_file_share_daily_quota(host, username, password, domain): +def test_http_file_share_daily_quota(host, username, password, domain, file_size_limit, user_daily_quota): """ Tests the user's daily quota for files uploaded via XEP-0363. """ - # Equivalent of 100MiB. - file_size_limit = 10 * 1024 * 1024 - file_count = 10 + remaining_quota = user_daily_quota + while remaining_quota > 0: + file_size = file_size_limit if file_size_limit < remaining_quota else remaining_quota + create_sample_file = host.run("dd if=/dev/zero of=/tmp/http_file_upload_sample.txt bs=%sB count=1", str(file_size)) + assert create_sample_file.rc == 0 - # Fill-up the daily quota. - create_sample_file = host.run("dd if=/dev/zero of=/tmp/http_file_upload_sample.txt bs=%sB count=1", str(file_size_limit)) - assert create_sample_file.rc == 0 - for _ in range(file_count): send = host.run(f"go-sendxmpp --debug --username {username}@{domain} --password {password} --jserver {domain}:5222 " f"--http-upload /tmp/http_file_upload_sample.txt " f"{username}@{domain}") assert send.rc == 0 + remaining_quota -= file_size + # Test exceeded daily quota. create_sample_file = host.run("dd if=/dev/zero of=/tmp/http_file_upload_sample.txt bs=1B count=1") assert create_sample_file.rc == 0 diff --git a/roles/xmpp_server/molecule/default/tests/test_mandatory.py b/roles/xmpp_server/molecule/default/tests/test_mandatory.py index b4a41a2e40115c4edc5dd7926a2186d7ab22a9f7..6de74cecda178ca8a52a14e1f07095524d159672 100644 --- a/roles/xmpp_server/molecule/default/tests/test_mandatory.py +++ b/roles/xmpp_server/molecule/default/tests/test_mandatory.py @@ -38,7 +38,9 @@ Component "conference.domain1" "muc" Component "proxy.domain1" "proxy65" proxy65_acl = { "domain1" } Component "upload.domain1" "http_file_share" - http_file_share_access = { "domain1" }""" in config.content_string + http_file_share_access = { "domain1" } + http_file_share_size_limit = 10485760 + http_file_share_daily_quota = 104857600""" in config.content_string def test_xmpp_server_uses_correct_dh_parameters(host): diff --git a/roles/xmpp_server/molecule/default/tests/test_optional.py b/roles/xmpp_server/molecule/default/tests/test_optional.py index 03595804781891e90a3d81a5c920c7ff0a7e48c2..0001ae1d11c0d176ca3d1e009c1c93b9de2efa48 100644 --- a/roles/xmpp_server/molecule/default/tests/test_optional.py +++ b/roles/xmpp_server/molecule/default/tests/test_optional.py @@ -38,7 +38,9 @@ Component "conference.domain2" "muc" Component "proxy.domain2" "proxy65" proxy65_acl = { "domain2" } Component "upload.domain2" "http_file_share" - http_file_share_access = { "domain2" }""" in config.content_string + http_file_share_access = { "domain2" } + http_file_share_size_limit = 20971520 + http_file_share_daily_quota = 73400320""" in config.content_string assert """VirtualHost "domain3" Component "conference.domain3" "muc" @@ -46,7 +48,9 @@ Component "conference.domain3" "muc" Component "proxy.domain3" "proxy65" proxy65_acl = { "domain3" } Component "upload.domain3" "http_file_share" - http_file_share_access = { "domain3" }""" in config.content_string + http_file_share_access = { "domain3" } + http_file_share_size_limit = 20971520 + http_file_share_daily_quota = 73400320""" in config.content_string @pytest.mark.parametrize("port", [ diff --git a/roles/xmpp_server/templates/prosody.cfg.lua.j2 b/roles/xmpp_server/templates/prosody.cfg.lua.j2 index d3e14b3fb35d79e4f31a5974b361f633ed531bc3..4d250ec853a3be2d3a5f281ca1df087d888da4fd 100644 --- a/roles/xmpp_server/templates/prosody.cfg.lua.j2 +++ b/roles/xmpp_server/templates/prosody.cfg.lua.j2 @@ -112,4 +112,6 @@ Component "proxy.{{ domain }}" "proxy65" proxy65_acl = { "{{ domain }}" } Component "upload.{{ domain }}" "http_file_share" http_file_share_access = { "{{ domain }}" } + http_file_share_size_limit = {{ xmpp_http_file_share_size_limit }} + http_file_share_daily_quota = {{ xmpp_http_file_share_daily_quota }} {% endfor -%}