changeset 3359:000b6722bd35

plugin XEP-0329: added `FISCreateDir` method: This method is used to create a directory for component file sharing, and optionally set its configuration.
author Goffi <goffi@goffi.org>
date Thu, 17 Sep 2020 16:42:00 +0200
parents b14e95f7034f
children 57afccb91961
files sat/plugins/plugin_comp_file_sharing.py sat/plugins/plugin_exp_invitation.py sat/plugins/plugin_xep_0313.py sat/plugins/plugin_xep_0329.py
diffstat 4 files changed, 136 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/sat/plugins/plugin_comp_file_sharing.py	Thu Sep 17 16:34:22 2020 +0200
+++ b/sat/plugins/plugin_comp_file_sharing.py	Thu Sep 17 16:42:00 2020 +0200
@@ -391,8 +391,6 @@
             client._file_sharing_base_url = f"https://{client.host}:{self.http_port}"
         else:
             client._file_sharing_base_url = public_base_url
-        client._file_sharing_allowed_hosts = self.host.memory.getConfig(
-            'component file_sharing', 'http_upload_allowed_hosts_list') or [client.host]
         path = client.file_tmp_dir = os.path.join(
             self.host.memory.getConfig("", "local_dir"),
             C.FILES_TMP_DIR,
@@ -596,6 +594,7 @@
     def _onHTTPUpload(self, client, request):
         # filename should be already cleaned, but it's better to double check
         assert '/' not in request.filename
+        # client._file_sharing_allowed_hosts is set in plugin XEP-0329
         if request.from_.host not in client._file_sharing_allowed_hosts:
             raise error.StanzaError("forbidden")
 
--- a/sat/plugins/plugin_exp_invitation.py	Thu Sep 17 16:34:22 2020 +0200
+++ b/sat/plugins/plugin_exp_invitation.py	Thu Sep 17 16:42:00 2020 +0200
@@ -1,7 +1,6 @@
 #!/usr/bin/env python3
 
-
-# SAT plugin to detect language (experimental)
+# SàT plugin to manage invitations
 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org)
 
 # This program is free software: you can redistribute it and/or modify
--- a/sat/plugins/plugin_xep_0313.py	Thu Sep 17 16:34:22 2020 +0200
+++ b/sat/plugins/plugin_xep_0313.py	Thu Sep 17 16:42:00 2020 +0200
@@ -87,6 +87,7 @@
             if not last_mess:
                 log.info(_("It seems that we have no MAM history yet"))
                 stanza_id = None
+                # FIXME: we should restrict starting of the archive, as it can be huge
             else:
                 stanza_id = last_mess[0][-1]['stanza_id']
         rsm_req = rsm.RSMRequest(max_=100, after=stanza_id)
--- a/sat/plugins/plugin_xep_0329.py	Thu Sep 17 16:34:22 2020 +0200
+++ b/sat/plugins/plugin_xep_0329.py	Thu Sep 17 16:42:00 2020 +0200
@@ -53,6 +53,7 @@
 NS_FIS = "urn:xmpp:fis:0"
 NS_FIS_AFFILIATION = "org.salut-a-toi.fis-affiliation"
 NS_FIS_CONFIGURATION = "org.salut-a-toi.fis-configuration"
+NS_FIS_CREATE = "org.salut-a-toi.fis-create"
 
 IQ_FIS_REQUEST = f'{C.IQ_GET}/query[@xmlns="{NS_FIS}"]'
 # not in the standard, but needed, and it's handy to keep it here
@@ -60,6 +61,7 @@
 IQ_FIS_AFFILIATION_SET = f'{C.IQ_SET}/affiliations[@xmlns="{NS_FIS_AFFILIATION}"]'
 IQ_FIS_CONFIGURATION_GET = f'{C.IQ_GET}/configuration[@xmlns="{NS_FIS_CONFIGURATION}"]'
 IQ_FIS_CONFIGURATION_SET = f'{C.IQ_SET}/configuration[@xmlns="{NS_FIS_CONFIGURATION}"]'
+IQ_FIS_CREATE_DIR = f'{C.IQ_SET}/dir[@xmlns="{NS_FIS_CREATE}"]'
 SINGLE_FILES_DIR = "files"
 TYPE_VIRTUAL = "virtual"
 TYPE_PATH = "path"
@@ -336,6 +338,14 @@
             method=self._configurationSet,
             async_=True,
         )
+        host.bridge.addMethod(
+            "FISCreateDir",
+            ".plugin",
+            in_sign="sssa{ss}s",
+            out_sign="",
+            method=self._createDir,
+            async_=True,
+        )
         host.bridge.addSignal("FISSharedPathNew", ".plugin", signature="sss")
         host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature="ss")
         host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger)
@@ -345,7 +355,10 @@
         return XEP_0329_handler(self)
 
     def profileConnected(self, client):
-        if not client.is_component:
+        if client.is_component:
+            client._file_sharing_allowed_hosts = self.host.memory.getConfig(
+                'component file_sharing', 'http_upload_allowed_hosts_list') or [client.host]
+        else:
             client._XEP_0329_root_node = ShareNode(
                 None,
                 None,
@@ -726,7 +739,7 @@
         if len(path.parts) < 2:
             raise RootPathException
         namespace = elt.getAttribute('namespace')
-        files_data = await self.host.memory.getFiles(
+        files_data = await self.host.memory.getfiles(
             client,
             peer_jid=from_jid,
             path=str(path.parent),
@@ -884,7 +897,7 @@
                 return
         except Exception as e:
                 log.error(
-                    f"unexepected exception while setting  affiliation element: {e}\n"
+                    f"unexepected exception while setting affiliation element: {e}\n"
                     f"{affiliations_elt.toXml()}"
                 )
                 client.sendError(iq_elt, 'internal-server-error', f"{e}")
@@ -947,7 +960,7 @@
         service: jid.JID,
         namespace: Optional[str],
         path: str,
-        configuration: Dict[jid.JID, str],
+        configuration: Dict[str, str],
     ):
         if not path:
             raise ValueError(f"invalid path: {path!r}")
@@ -990,6 +1003,16 @@
         configuration_elt.addChild(form.toElement())
         client.send(iq_result_elt)
 
+    async def _setConfiguration(self, client, configuration_elt, file_data):
+        form = data_form.findForm(configuration_elt, NS_FIS_CONFIGURATION)
+        for name, value in form.items():
+            if name == 'access_model':
+                await self.host.memory.setFileAccessModel(client, file_data, value)
+            else:
+                # TODO: send a IQ error?
+                log.warning(
+                    f"Trying to set a not implemented configuration option: {name}")
+
     def _onComponentConfigurationSet(self, iq_elt, client):
         iq_elt.handled = True
         defer.ensureDeferred(self.onComponentConfigurationSet(client, iq_elt))
@@ -1015,14 +1038,107 @@
             client.sendError(iq_elt, 'forbidden')
             return
 
-        form = data_form.findForm(configuration_elt, NS_FIS_CONFIGURATION)
-        for name, value in form.items():
-            if name == 'access_model':
-                await self.host.memory.setFileAccessModel(client, file_data, value)
-            else:
-                # TODO: send a IQ error?
-                log.warning(
-                    f"Trying to set a not implemented configuration option: {name}")
+        await self._setConfiguration(client, configuration_elt, file_data)
+
+        iq_result_elt = xmlstream.toResponse(iq_elt, "result")
+        client.send(iq_result_elt)
+
+    # directory creation
+
+    def _createDir(self, service_jid_s, namespace, path, configuration, profile):
+        client = self.host.getClient(profile)
+        service = jid.JID(service_jid_s)
+        return defer.ensureDeferred(self.createDir(
+            client, service, namespace or None, path, configuration or None))
+
+    async def createDir(
+        self,
+        client: SatXMPPEntity,
+        service: jid.JID,
+        namespace: Optional[str],
+        path: str,
+        configuration: Optional[Dict[str, str]],
+    ):
+        if not path:
+            raise ValueError(f"invalid path: {path!r}")
+        iq_elt = client.IQ("set")
+        iq_elt['to'] = service.full()
+        create_dir_elt = iq_elt.addElement((NS_FIS_CREATE, "dir"))
+        if namespace:
+            create_dir_elt["namespace"] = namespace
+        create_dir_elt["path"] = path
+        if configuration:
+            configuration_elt = create_dir_elt.addElement((NS_FIS_CONFIGURATION, "configuration"))
+            form = data_form.Form(formType="submit", formNamespace=NS_FIS_CONFIGURATION)
+            form.makeFields(configuration)
+            configuration_elt.addChild(form.toElement())
+        await iq_elt.send()
+
+    def _onComponentCreateDir(self, iq_elt, client):
+        iq_elt.handled = True
+        defer.ensureDeferred(self.onComponentCreateDir(client, iq_elt))
+
+    async def onComponentCreateDir(self, client, iq_elt):
+        peer_jid, owner = self._compParseJids(client, iq_elt)
+        if peer_jid.host not in client._file_sharing_allowed_hosts:
+            client.sendError(iq_elt, 'forbidden')
+            return
+        create_dir_elt = next(iq_elt.elements(NS_FIS_CREATE, "dir"))
+        namespace = create_dir_elt.getAttribute('namespace')
+        path = Path("/", create_dir_elt['path'])
+        if len(path.parts) < 2:
+            client.sendError(iq_elt, 'bad-request', "Root path can't be used")
+            return
+        # for root directories, we check permission here
+        if len(path.parts) == 2 and owner != peer_jid.userhostJID():
+            log.warning(
+                f"{peer_jid} is trying to create a dir at {owner}'s repository:\n"
+                f"path: {path}\nnamespace: {namespace!r}"
+            )
+            client.sendError(iq_elt, 'forbidden', "You can't create a directory there")
+            return
+        # when going further into the path, the permissions will be checked by getFiles
+        try:
+            await self.host.memory.getFiles(
+                client,
+                peer_jid=peer_jid,
+                path=path.parent,
+                name=path.name,
+                namespace=namespace,
+                owner=owner,
+            )
+        except exceptions.NotFound:
+            # this is expected, nothing should be found at this path
+            pass
+        else:
+            log.warning(
+                f"Conflict when trying to create a directory (from: {peer_jid} "
+                f"namespace: {namespace!r} path: {path!r})"
+            )
+            client.sendError(
+                iq_elt, 'conflict', "there is already a file or dir at this path")
+            return
+
+        try:
+            configuration_elt = next(
+                create_dir_elt.elements(NS_FIS_CONFIGURATION, 'configuration'))
+        except StopIteration:
+            configuration_elt = None
+
+        await self.host.memory.setFile(client, path.name, path=path.parent, namespace=namespace, owner=owner, peer_jid=peer_jid)
+
+        if configuration_elt is not None:
+            file_data = (await self.host.memory.getFiles(
+                client,
+                peer_jid=peer_jid,
+                path=path.parent,
+                name=path.name,
+                namespace=namespace,
+                owner=owner,
+            ))[0]
+
+            await self._setConfiguration(client, configuration_elt, file_data)
+
         iq_result_elt = xmlstream.toResponse(iq_elt, "result")
         client.send(iq_result_elt)
 
@@ -1152,6 +1268,11 @@
                 self.plugin_parent._onComponentConfigurationSet,
                 client=self.parent
             )
+            self.xmlstream.addObserver(
+                IQ_FIS_CREATE_DIR,
+                self.plugin_parent._onComponentCreateDir,
+                client=self.parent
+            )
         else:
             self.xmlstream.addObserver(
                 IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent