# HG changeset patch # User Goffi # Date 1600353720 -7200 # Node ID 000b6722bd35b6c3543636f3f7bfdd4a7e16c722 # Parent b14e95f7034f34eeb414894e641bb98c165fc99e plugin XEP-0329: added `FISCreateDir` method: This method is used to create a directory for component file sharing, and optionally set its configuration. diff -r b14e95f7034f -r 000b6722bd35 sat/plugins/plugin_comp_file_sharing.py --- 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") diff -r b14e95f7034f -r 000b6722bd35 sat/plugins/plugin_exp_invitation.py --- 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 diff -r b14e95f7034f -r 000b6722bd35 sat/plugins/plugin_xep_0313.py --- 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) diff -r b14e95f7034f -r 000b6722bd35 sat/plugins/plugin_xep_0329.py --- 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