diff sat/plugins/plugin_xep_0329.py @ 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 33d9b38b5890
children 57afccb91961
line wrap: on
line diff
--- 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