diff --git a/docs/releasenotes.rst b/docs/releasenotes.rst index 490c8324a1d00fc19f26879309e541731c511337..1401020b0502e01a70d17a0378a38d0b94c460e8 100644 --- a/docs/releasenotes.rst +++ b/docs/releasenotes.rst @@ -73,6 +73,10 @@ Upgraded to Ansible 10.4.x. Dropped support for Debian 11 ciphers), in addition to TLSv1.2, for client-to-server communications. + * `XEP-0363: HTTP File Upload + `_ is now enabled for + each configured domain. + **Bug fixes:** * ``common`` role diff --git a/docs/rolereference.rst b/docs/rolereference.rst index c0609ca80f46a347e6a452d28a2ac1410e678c44..1a38dd13e0fae9b52c29f4dc744bd1f4f18f206b 100644 --- a/docs/rolereference.rst +++ b/docs/rolereference.rst @@ -900,6 +900,8 @@ 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``. Prosody expects a specific directory structure in LDAP when doing look-ups: diff --git a/roles/xmpp_server/files/ferm_xmpp.conf b/roles/xmpp_server/files/ferm_xmpp.conf index f1b2e8fb0f9d425e5d0f1c88e6d46928af22aaa3..9d519ee08e2e9c2d43a74f09c0e13e122812d05e 100644 --- a/roles/xmpp_server/files/ferm_xmpp.conf +++ b/roles/xmpp_server/files/ferm_xmpp.conf @@ -8,6 +8,8 @@ domain (ip ip6) { proto tcp dport 5000 ACCEPT; # XMPP server connections. proto tcp dport 5269 ACCEPT; + # HTTP file upload. + proto tcp dport 5281 ACCEPT; } } } diff --git a/roles/xmpp_server/molecule/default/prepare.yml b/roles/xmpp_server/molecule/default/prepare.yml index c0a1bdfa75f95dc6c88b37fbab3f4865b1c0856b..231d1f6b7d3474345e4fb79ae525ddb4d7e3a0e0 100644 --- a/roles/xmpp_server/molecule/default/prepare.yml +++ b/roles/xmpp_server/molecule/default/prepare.yml @@ -29,15 +29,18 @@ - domain1 - proxy.domain1 - conference.domain1 + - upload.domain1 - name: parameters-optional-bookworm_xmpp fqdn: - parameters-optional - domain2 - proxy.domain2 - conference.domain2 + - upload.domain2 - domain3 - proxy.domain3 - conference.domain3 + - upload.domain3 - name: Set-up link to generated X.509 material ansible.builtin.file: @@ -91,11 +94,11 @@ with_dict: 192.168.56.11: "ldap-server backup-server" 192.168.56.21: "client-bookworm" - 192.168.56.31: "parameters-mandatory domain1 proxy.domain1 conference.domain1" - 192.168.56.32: "parameters-optional domain2 proxy.domain2 conference.domain2 domain3 proxy.domain3 conference.domain3" + 192.168.56.31: "parameters-mandatory domain1 proxy.domain1 conference.domain1 upload.domain1" + 192.168.56.32: "parameters-optional domain2 proxy.domain2 conference.domain2 upload.domain2 domain3 proxy.domain3 conference.domain3 upload.domain3" fd00::192:168:56:21: "client-bookworm" - fd00::192:168:56:31: "parameters-mandatory domain1 proxy.domain1 conference.domain1" - fd00::192:168:56:32: "parameters-optional domain2 proxy.domain2 conference.domain2 domain3 proxy.domain3 conference.domain3" + fd00::192:168:56:31: "parameters-mandatory domain1 proxy.domain1 conference.domain1 upload.domain1" + fd00::192:168:56:32: "parameters-optional domain2 proxy.domain2 conference.domain2 upload.domain2 domain3 proxy.domain3 conference.domain3 upload.domain3" - name: Prepare, helpers hosts: clients diff --git a/roles/xmpp_server/molecule/default/tests/test_client.py b/roles/xmpp_server/molecule/default/tests/test_client.py index ad658a68c96c11198a2bef133c5065d3bc5b10eb..c099fd7c3bb6cf1ecbb38e22f5b38d3a0992609c 100644 --- a/roles/xmpp_server/molecule/default/tests/test_client.py +++ b/roles/xmpp_server/molecule/default/tests/test_client.py @@ -1,4 +1,5 @@ import os +import uuid import pytest @@ -10,7 +11,7 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( @pytest.mark.parametrize('server', ['parameters-mandatory', 'parameters-optional']) -@pytest.mark.parametrize('port', [5222, 5223, 5000, 5269]) +@pytest.mark.parametrize('port', [5222, 5223, 5000, 5269, 5281]) @pytest.mark.parametrize('ip_protocol', [4, 6]) def test_connectivity(host, server, port, ip_protocol): """ @@ -99,3 +100,56 @@ def test_unauthorized_users_rejected(host, target_username, target_domain): f"{target_username}@{target_domain}") assert send.rc != 0 assert "Unable to authorize you with the authentication credentials you've sent" 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"], +]) +def test_http_file_upload(host, username, password, domain, server): + """ + Tests if http file upload works correctly. + """ + + # Prepare file for transfer. + expected_content = str(uuid.uuid4()) + create_sample_file = host.run("echo -n %s > /tmp/http_file_upload_sample.txt", expected_content) + assert create_sample_file.rc == 0 + + # Path where uploaded file will end-up. + upload_directory_path = f"/var/lib/prosody/upload%2e{domain}/http_file_share" + + # Find the host that serves the domain. Used for validating uploaded content. + ansible_facts = host.ansible("setup")["ansible_facts"] + ansible_distribution_release = ansible_facts['ansible_distribution_release'] + ansible_runner = testinfra.utils.ansible_runner.AnsibleRunner(os.environ['MOLECULE_INVENTORY_FILE']) + server_host = ansible_runner.get_host(ansible_runner.get_hosts(f'{server}-{ansible_distribution_release}')[0]) + + # Clean up leftovers from previous run. + with server_host.sudo(): + server_host.run("rm -rf %s/*", upload_directory_path) + + # Upload the file. + 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 + assert "No http upload component found." not in send.stderr + + # Verify content on server. + with server_host.sudo(): + upload_directory = server_host.file(upload_directory_path) + assert upload_directory.is_directory + assert upload_directory.user == "prosody" + assert upload_directory.group == "prosody" + assert upload_directory.mode == 0o750 + assert len(upload_directory.listdir()) == 1 + + uploaded_file_name = upload_directory.listdir()[0] + uploaded_file = server_host.file(os.path.join(upload_directory_path, uploaded_file_name)) + assert uploaded_file.is_file + assert uploaded_file.user == "prosody" + assert uploaded_file.group == "prosody" + assert uploaded_file.mode == 0o640 + assert uploaded_file.content_string == expected_content diff --git a/roles/xmpp_server/molecule/default/tests/test_default.py b/roles/xmpp_server/molecule/default/tests/test_default.py index a01a89747d7492345a3362c8d3739694b7f6b997..52b8ed30841314093a6c5c16633a13018d4f8c87 100644 --- a/roles/xmpp_server/molecule/default/tests/test_default.py +++ b/roles/xmpp_server/molecule/default/tests/test_default.py @@ -281,6 +281,7 @@ def test_ldap_client_configuration(host): # @TODO: Tests which were not implemented due to lack of out-of-box tools: # -# - Proxy capability. -# - MUC. +# - Proxy component. +# - MUC component. +# - HTTP file share access control. # - Server administration through XMPP. diff --git a/roles/xmpp_server/molecule/default/tests/test_mandatory.py b/roles/xmpp_server/molecule/default/tests/test_mandatory.py index 047f851531495d449253f93ff5371c9a1367ba02..b4a41a2e40115c4edc5dd7926a2186d7ab22a9f7 100644 --- a/roles/xmpp_server/molecule/default/tests/test_mandatory.py +++ b/roles/xmpp_server/molecule/default/tests/test_mandatory.py @@ -36,7 +36,9 @@ def test_prosody_configuration_file_content(host): Component "conference.domain1" "muc" restrict_room_creation = "local" Component "proxy.domain1" "proxy65" - proxy65_acl = { "domain1" }""" in config.content_string + proxy65_acl = { "domain1" } +Component "upload.domain1" "http_file_share" + http_file_share_access = { "domain1" }""" 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 c5587ad5e6690d0e1ac916e37798d2725a8d089e..03595804781891e90a3d81a5c920c7ff0a7e48c2 100644 --- a/roles/xmpp_server/molecule/default/tests/test_optional.py +++ b/roles/xmpp_server/molecule/default/tests/test_optional.py @@ -36,13 +36,17 @@ def test_prosody_configuration_file_content(host): Component "conference.domain2" "muc" restrict_room_creation = "local" Component "proxy.domain2" "proxy65" - proxy65_acl = { "domain2" }""" in config.content_string + proxy65_acl = { "domain2" } +Component "upload.domain2" "http_file_share" + http_file_share_access = { "domain2" }""" in config.content_string assert """VirtualHost "domain3" Component "conference.domain3" "muc" restrict_room_creation = "local" Component "proxy.domain3" "proxy65" - proxy65_acl = { "domain3" }""" in config.content_string + proxy65_acl = { "domain3" } +Component "upload.domain3" "http_file_share" + http_file_share_access = { "domain3" }""" 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 110a533ff1296277da43a095996070ec654c05b7..d3e14b3fb35d79e4f31a5974b361f633ed531bc3 100644 --- a/roles/xmpp_server/templates/prosody.cfg.lua.j2 +++ b/roles/xmpp_server/templates/prosody.cfg.lua.j2 @@ -110,4 +110,6 @@ Component "conference.{{ domain }}" "muc" restrict_room_creation = "local" Component "proxy.{{ domain }}" "proxy65" proxy65_acl = { "{{ domain }}" } +Component "upload.{{ domain }}" "http_file_share" + http_file_share_access = { "{{ domain }}" } {% endfor -%}