comparison 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
comparison
equal deleted inserted replaced
3358:b14e95f7034f 3359:000b6722bd35
51 } 51 }
52 52
53 NS_FIS = "urn:xmpp:fis:0" 53 NS_FIS = "urn:xmpp:fis:0"
54 NS_FIS_AFFILIATION = "org.salut-a-toi.fis-affiliation" 54 NS_FIS_AFFILIATION = "org.salut-a-toi.fis-affiliation"
55 NS_FIS_CONFIGURATION = "org.salut-a-toi.fis-configuration" 55 NS_FIS_CONFIGURATION = "org.salut-a-toi.fis-configuration"
56 NS_FIS_CREATE = "org.salut-a-toi.fis-create"
56 57
57 IQ_FIS_REQUEST = f'{C.IQ_GET}/query[@xmlns="{NS_FIS}"]' 58 IQ_FIS_REQUEST = f'{C.IQ_GET}/query[@xmlns="{NS_FIS}"]'
58 # not in the standard, but needed, and it's handy to keep it here 59 # not in the standard, but needed, and it's handy to keep it here
59 IQ_FIS_AFFILIATION_GET = f'{C.IQ_GET}/affiliations[@xmlns="{NS_FIS_AFFILIATION}"]' 60 IQ_FIS_AFFILIATION_GET = f'{C.IQ_GET}/affiliations[@xmlns="{NS_FIS_AFFILIATION}"]'
60 IQ_FIS_AFFILIATION_SET = f'{C.IQ_SET}/affiliations[@xmlns="{NS_FIS_AFFILIATION}"]' 61 IQ_FIS_AFFILIATION_SET = f'{C.IQ_SET}/affiliations[@xmlns="{NS_FIS_AFFILIATION}"]'
61 IQ_FIS_CONFIGURATION_GET = f'{C.IQ_GET}/configuration[@xmlns="{NS_FIS_CONFIGURATION}"]' 62 IQ_FIS_CONFIGURATION_GET = f'{C.IQ_GET}/configuration[@xmlns="{NS_FIS_CONFIGURATION}"]'
62 IQ_FIS_CONFIGURATION_SET = f'{C.IQ_SET}/configuration[@xmlns="{NS_FIS_CONFIGURATION}"]' 63 IQ_FIS_CONFIGURATION_SET = f'{C.IQ_SET}/configuration[@xmlns="{NS_FIS_CONFIGURATION}"]'
64 IQ_FIS_CREATE_DIR = f'{C.IQ_SET}/dir[@xmlns="{NS_FIS_CREATE}"]'
63 SINGLE_FILES_DIR = "files" 65 SINGLE_FILES_DIR = "files"
64 TYPE_VIRTUAL = "virtual" 66 TYPE_VIRTUAL = "virtual"
65 TYPE_PATH = "path" 67 TYPE_PATH = "path"
66 SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL) 68 SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL)
67 KEY_TYPE = "type" 69 KEY_TYPE = "type"
334 in_sign="sssa{ss}s", 336 in_sign="sssa{ss}s",
335 out_sign="", 337 out_sign="",
336 method=self._configurationSet, 338 method=self._configurationSet,
337 async_=True, 339 async_=True,
338 ) 340 )
341 host.bridge.addMethod(
342 "FISCreateDir",
343 ".plugin",
344 in_sign="sssa{ss}s",
345 out_sign="",
346 method=self._createDir,
347 async_=True,
348 )
339 host.bridge.addSignal("FISSharedPathNew", ".plugin", signature="sss") 349 host.bridge.addSignal("FISSharedPathNew", ".plugin", signature="sss")
340 host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature="ss") 350 host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature="ss")
341 host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger) 351 host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger)
342 host.registerNamespace("fis", NS_FIS) 352 host.registerNamespace("fis", NS_FIS)
343 353
344 def getHandler(self, client): 354 def getHandler(self, client):
345 return XEP_0329_handler(self) 355 return XEP_0329_handler(self)
346 356
347 def profileConnected(self, client): 357 def profileConnected(self, client):
348 if not client.is_component: 358 if client.is_component:
359 client._file_sharing_allowed_hosts = self.host.memory.getConfig(
360 'component file_sharing', 'http_upload_allowed_hosts_list') or [client.host]
361 else:
349 client._XEP_0329_root_node = ShareNode( 362 client._XEP_0329_root_node = ShareNode(
350 None, 363 None,
351 None, 364 None,
352 TYPE_VIRTUAL, 365 TYPE_VIRTUAL,
353 {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}}, 366 {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}},
724 elt = next(iq_elt.elements(namespace, element)) 737 elt = next(iq_elt.elements(namespace, element))
725 path = Path("/", elt['path']) 738 path = Path("/", elt['path'])
726 if len(path.parts) < 2: 739 if len(path.parts) < 2:
727 raise RootPathException 740 raise RootPathException
728 namespace = elt.getAttribute('namespace') 741 namespace = elt.getAttribute('namespace')
729 files_data = await self.host.memory.getFiles( 742 files_data = await self.host.memory.getfiles(
730 client, 743 client,
731 peer_jid=from_jid, 744 peer_jid=from_jid,
732 path=str(path.parent), 745 path=str(path.parent),
733 name=path.name, 746 name=path.name,
734 namespace=namespace, 747 namespace=namespace,
882 ) 895 )
883 client.sendError(iq_elt, 'bad-request', "invalid affiliation element") 896 client.sendError(iq_elt, 'bad-request', "invalid affiliation element")
884 return 897 return
885 except Exception as e: 898 except Exception as e:
886 log.error( 899 log.error(
887 f"unexepected exception while setting affiliation element: {e}\n" 900 f"unexepected exception while setting affiliation element: {e}\n"
888 f"{affiliations_elt.toXml()}" 901 f"{affiliations_elt.toXml()}"
889 ) 902 )
890 client.sendError(iq_elt, 'internal-server-error', f"{e}") 903 client.sendError(iq_elt, 'internal-server-error', f"{e}")
891 return 904 return
892 905
945 self, 958 self,
946 client: SatXMPPEntity, 959 client: SatXMPPEntity,
947 service: jid.JID, 960 service: jid.JID,
948 namespace: Optional[str], 961 namespace: Optional[str],
949 path: str, 962 path: str,
950 configuration: Dict[jid.JID, str], 963 configuration: Dict[str, str],
951 ): 964 ):
952 if not path: 965 if not path:
953 raise ValueError(f"invalid path: {path!r}") 966 raise ValueError(f"invalid path: {path!r}")
954 iq_elt = client.IQ("set") 967 iq_elt = client.IQ("set")
955 iq_elt['to'] = service.full() 968 iq_elt['to'] = service.full()
988 form = data_form.Form(formType="form", formNamespace=NS_FIS_CONFIGURATION) 1001 form = data_form.Form(formType="form", formNamespace=NS_FIS_CONFIGURATION)
989 form.makeFields({'access_model': access_model}) 1002 form.makeFields({'access_model': access_model})
990 configuration_elt.addChild(form.toElement()) 1003 configuration_elt.addChild(form.toElement())
991 client.send(iq_result_elt) 1004 client.send(iq_result_elt)
992 1005
993 def _onComponentConfigurationSet(self, iq_elt, client): 1006 async def _setConfiguration(self, client, configuration_elt, file_data):
994 iq_elt.handled = True
995 defer.ensureDeferred(self.onComponentConfigurationSet(client, iq_elt))
996
997 async def onComponentConfigurationSet(self, client, iq_elt):
998 try:
999 (
1000 from_jid, configuration_elt, path, namespace, file_data
1001 ) = await self._parseElement(client, iq_elt, "configuration", NS_FIS_CONFIGURATION)
1002 except exceptions.CancelError:
1003 return
1004 except RootPathException:
1005 client.sendError(iq_elt, 'bad-request', "Root path can't be used")
1006 return
1007
1008 from_jid_bare = from_jid.userhostJID()
1009 is_owner = from_jid_bare == file_data.get('owner')
1010 if not is_owner:
1011 log.warning(
1012 f"{from_jid} tried to modify {path} configuration while the owner is "
1013 f"{file_data['owner']}"
1014 )
1015 client.sendError(iq_elt, 'forbidden')
1016 return
1017
1018 form = data_form.findForm(configuration_elt, NS_FIS_CONFIGURATION) 1007 form = data_form.findForm(configuration_elt, NS_FIS_CONFIGURATION)
1019 for name, value in form.items(): 1008 for name, value in form.items():
1020 if name == 'access_model': 1009 if name == 'access_model':
1021 await self.host.memory.setFileAccessModel(client, file_data, value) 1010 await self.host.memory.setFileAccessModel(client, file_data, value)
1022 else: 1011 else:
1023 # TODO: send a IQ error? 1012 # TODO: send a IQ error?
1024 log.warning( 1013 log.warning(
1025 f"Trying to set a not implemented configuration option: {name}") 1014 f"Trying to set a not implemented configuration option: {name}")
1015
1016 def _onComponentConfigurationSet(self, iq_elt, client):
1017 iq_elt.handled = True
1018 defer.ensureDeferred(self.onComponentConfigurationSet(client, iq_elt))
1019
1020 async def onComponentConfigurationSet(self, client, iq_elt):
1021 try:
1022 (
1023 from_jid, configuration_elt, path, namespace, file_data
1024 ) = await self._parseElement(client, iq_elt, "configuration", NS_FIS_CONFIGURATION)
1025 except exceptions.CancelError:
1026 return
1027 except RootPathException:
1028 client.sendError(iq_elt, 'bad-request', "Root path can't be used")
1029 return
1030
1031 from_jid_bare = from_jid.userhostJID()
1032 is_owner = from_jid_bare == file_data.get('owner')
1033 if not is_owner:
1034 log.warning(
1035 f"{from_jid} tried to modify {path} configuration while the owner is "
1036 f"{file_data['owner']}"
1037 )
1038 client.sendError(iq_elt, 'forbidden')
1039 return
1040
1041 await self._setConfiguration(client, configuration_elt, file_data)
1042
1043 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
1044 client.send(iq_result_elt)
1045
1046 # directory creation
1047
1048 def _createDir(self, service_jid_s, namespace, path, configuration, profile):
1049 client = self.host.getClient(profile)
1050 service = jid.JID(service_jid_s)
1051 return defer.ensureDeferred(self.createDir(
1052 client, service, namespace or None, path, configuration or None))
1053
1054 async def createDir(
1055 self,
1056 client: SatXMPPEntity,
1057 service: jid.JID,
1058 namespace: Optional[str],
1059 path: str,
1060 configuration: Optional[Dict[str, str]],
1061 ):
1062 if not path:
1063 raise ValueError(f"invalid path: {path!r}")
1064 iq_elt = client.IQ("set")
1065 iq_elt['to'] = service.full()
1066 create_dir_elt = iq_elt.addElement((NS_FIS_CREATE, "dir"))
1067 if namespace:
1068 create_dir_elt["namespace"] = namespace
1069 create_dir_elt["path"] = path
1070 if configuration:
1071 configuration_elt = create_dir_elt.addElement((NS_FIS_CONFIGURATION, "configuration"))
1072 form = data_form.Form(formType="submit", formNamespace=NS_FIS_CONFIGURATION)
1073 form.makeFields(configuration)
1074 configuration_elt.addChild(form.toElement())
1075 await iq_elt.send()
1076
1077 def _onComponentCreateDir(self, iq_elt, client):
1078 iq_elt.handled = True
1079 defer.ensureDeferred(self.onComponentCreateDir(client, iq_elt))
1080
1081 async def onComponentCreateDir(self, client, iq_elt):
1082 peer_jid, owner = self._compParseJids(client, iq_elt)
1083 if peer_jid.host not in client._file_sharing_allowed_hosts:
1084 client.sendError(iq_elt, 'forbidden')
1085 return
1086 create_dir_elt = next(iq_elt.elements(NS_FIS_CREATE, "dir"))
1087 namespace = create_dir_elt.getAttribute('namespace')
1088 path = Path("/", create_dir_elt['path'])
1089 if len(path.parts) < 2:
1090 client.sendError(iq_elt, 'bad-request', "Root path can't be used")
1091 return
1092 # for root directories, we check permission here
1093 if len(path.parts) == 2 and owner != peer_jid.userhostJID():
1094 log.warning(
1095 f"{peer_jid} is trying to create a dir at {owner}'s repository:\n"
1096 f"path: {path}\nnamespace: {namespace!r}"
1097 )
1098 client.sendError(iq_elt, 'forbidden', "You can't create a directory there")
1099 return
1100 # when going further into the path, the permissions will be checked by getFiles
1101 try:
1102 await self.host.memory.getFiles(
1103 client,
1104 peer_jid=peer_jid,
1105 path=path.parent,
1106 name=path.name,
1107 namespace=namespace,
1108 owner=owner,
1109 )
1110 except exceptions.NotFound:
1111 # this is expected, nothing should be found at this path
1112 pass
1113 else:
1114 log.warning(
1115 f"Conflict when trying to create a directory (from: {peer_jid} "
1116 f"namespace: {namespace!r} path: {path!r})"
1117 )
1118 client.sendError(
1119 iq_elt, 'conflict', "there is already a file or dir at this path")
1120 return
1121
1122 try:
1123 configuration_elt = next(
1124 create_dir_elt.elements(NS_FIS_CONFIGURATION, 'configuration'))
1125 except StopIteration:
1126 configuration_elt = None
1127
1128 await self.host.memory.setFile(client, path.name, path=path.parent, namespace=namespace, owner=owner, peer_jid=peer_jid)
1129
1130 if configuration_elt is not None:
1131 file_data = (await self.host.memory.getFiles(
1132 client,
1133 peer_jid=peer_jid,
1134 path=path.parent,
1135 name=path.name,
1136 namespace=namespace,
1137 owner=owner,
1138 ))[0]
1139
1140 await self._setConfiguration(client, configuration_elt, file_data)
1141
1026 iq_result_elt = xmlstream.toResponse(iq_elt, "result") 1142 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
1027 client.send(iq_result_elt) 1143 client.send(iq_result_elt)
1028 1144
1029 # file methods # 1145 # file methods #
1030 1146
1150 self.xmlstream.addObserver( 1266 self.xmlstream.addObserver(
1151 IQ_FIS_CONFIGURATION_SET, 1267 IQ_FIS_CONFIGURATION_SET,
1152 self.plugin_parent._onComponentConfigurationSet, 1268 self.plugin_parent._onComponentConfigurationSet,
1153 client=self.parent 1269 client=self.parent
1154 ) 1270 )
1271 self.xmlstream.addObserver(
1272 IQ_FIS_CREATE_DIR,
1273 self.plugin_parent._onComponentCreateDir,
1274 client=self.parent
1275 )
1155 else: 1276 else:
1156 self.xmlstream.addObserver( 1277 self.xmlstream.addObserver(
1157 IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent 1278 IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent
1158 ) 1279 )
1159 1280