Mercurial > libervia-backend
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 |