comparison sat/plugins/plugin_xep_0329.py @ 3321:8bbd2ed924e8

plugin XEP-0329: added way to change `access_model` using PubSub-like configuration: Those methods are not standard, but have been done in a PubSub-like way to prepare the move to a PubSub based file sharing system.
author Goffi <goffi@goffi.org>
date Sat, 01 Aug 2020 16:14:37 +0200
parents bb92085720c8
children ac9342f359e9
comparison
equal deleted inserted replaced
3320:bb92085720c8 3321:8bbd2ed924e8
56 56
57 IQ_FIS_REQUEST = f'{C.IQ_GET}/query[@xmlns="{NS_FIS}"]' 57 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 58 # 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}"]' 59 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}"]' 60 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_SET = f'{C.IQ_SET}/configuration[@xmlns="{NS_FIS_CONFIGURATION}"]'
61 SINGLE_FILES_DIR = "files" 63 SINGLE_FILES_DIR = "files"
62 TYPE_VIRTUAL = "virtual" 64 TYPE_VIRTUAL = "virtual"
63 TYPE_PATH = "path" 65 TYPE_PATH = "path"
64 SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL) 66 SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL)
65 KEY_TYPE = "type" 67 KEY_TYPE = "type"
315 in_sign="sssa{ss}s", 317 in_sign="sssa{ss}s",
316 out_sign="", 318 out_sign="",
317 method=self._affiliationsSet, 319 method=self._affiliationsSet,
318 async_=True, 320 async_=True,
319 ) 321 )
322 host.bridge.addMethod(
323 "FISConfigurationGet",
324 ".plugin",
325 in_sign="ssss",
326 out_sign="a{ss}",
327 method=self._configurationGet,
328 async_=True,
329 )
330 host.bridge.addMethod(
331 "FISConfigurationSet",
332 ".plugin",
333 in_sign="sssa{ss}s",
334 out_sign="",
335 method=self._configurationSet,
336 async_=True,
337 )
320 host.bridge.addSignal("FISSharedPathNew", ".plugin", signature="sss") 338 host.bridge.addSignal("FISSharedPathNew", ".plugin", signature="sss")
321 host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature="ss") 339 host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature="ss")
322 host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger) 340 host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger)
323 host.registerNamespace("fis", NS_FIS) 341 host.registerNamespace("fis", NS_FIS)
324 342
854 await self.host.memory.setFileAffiliations(client, file_data, affiliations) 872 await self.host.memory.setFileAffiliations(client, file_data, affiliations)
855 873
856 iq_result_elt = xmlstream.toResponse(iq_elt, "result") 874 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
857 client.send(iq_result_elt) 875 client.send(iq_result_elt)
858 876
877 # configuration
878
879 def _configurationGet(self, service_jid_s, namespace, path, profile):
880 client = self.host.getClient(profile)
881 service = jid.JID(service_jid_s)
882 d = defer.ensureDeferred(self.configurationGet(
883 client, service, namespace or None, path))
884 d.addCallback(
885 lambda configuration: {
886 str(entity): affiliation for entity, affiliation in configuration.items()
887 }
888 )
889 return d
890
891 async def configurationGet(
892 self,
893 client: SatXMPPEntity,
894 service: jid.JID,
895 namespace: Optional[str],
896 path: str
897 ) -> Dict[str, str]:
898 if not path:
899 raise ValueError(f"invalid path: {path!r}")
900 iq_elt = client.IQ("get")
901 iq_elt['to'] = service.full()
902 configuration_elt = iq_elt.addElement((NS_FIS_CONFIGURATION, "configuration"))
903 if namespace:
904 configuration_elt["namespace"] = namespace
905 configuration_elt["path"] = path
906 iq_result_elt = await iq_elt.send()
907 try:
908 configuration_elt = next(iq_result_elt.elements(NS_FIS_CONFIGURATION, "configuration"))
909 except StopIteration:
910 raise exceptions.DataError(f"Invalid result to configuration request: {iq_result_elt.toXml()}")
911
912 form = data_form.findForm(configuration_elt, NS_FIS_CONFIGURATION)
913 configuration = {f.var: f.value for f in form.fields.values()}
914
915 return configuration
916
917 def _configurationSet(self, service_jid_s, namespace, path, configuration, profile):
918 client = self.host.getClient(profile)
919 service = jid.JID(service_jid_s)
920 return defer.ensureDeferred(self.configurationSet(
921 client, service, namespace or None, path, configuration))
922
923 async def configurationSet(
924 self,
925 client: SatXMPPEntity,
926 service: jid.JID,
927 namespace: Optional[str],
928 path: str,
929 configuration: Dict[jid.JID, str],
930 ):
931 if not path:
932 raise ValueError(f"invalid path: {path!r}")
933 iq_elt = client.IQ("set")
934 iq_elt['to'] = service.full()
935 configuration_elt = iq_elt.addElement((NS_FIS_CONFIGURATION, "configuration"))
936 if namespace:
937 configuration_elt["namespace"] = namespace
938 configuration_elt["path"] = path
939 form = data_form.Form(formType="submit", formNamespace=NS_FIS_CONFIGURATION)
940 form.makeFields(configuration)
941 configuration_elt.addChild(form.toElement())
942 await iq_elt.send()
943
944 def _onComponentConfigurationGet(self, iq_elt, client):
945 iq_elt.handled = True
946 defer.ensureDeferred(self.onComponentConfigurationGet(client, iq_elt))
947
948 async def onComponentConfigurationGet(self, client, iq_elt):
949 try:
950 (
951 from_jid, configuration_elt, path, namespace, file_data
952 ) = await self._parseElement(client, iq_elt, "configuration", NS_FIS_CONFIGURATION)
953 except exceptions.CancelError:
954 return
955 except RootPathException:
956 client.sendError(iq_elt, 'bad-request', "Root path can't be used")
957 return
958 try:
959 access_type = file_data['access'][C.ACCESS_PERM_READ]['type']
960 except KeyError:
961 access_model = 'whitelist'
962 else:
963 access_model = 'open' if access_type == C.ACCESS_TYPE_PUBLIC else 'whitelist'
964
965 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
966 configuration_elt = iq_result_elt.addElement((NS_FIS_CONFIGURATION, 'configuration'))
967 form = data_form.Form(formType="form", formNamespace=NS_FIS_CONFIGURATION)
968 form.makeFields({'access_model': access_model})
969 configuration_elt.addChild(form.toElement())
970 client.send(iq_result_elt)
971
972 def _onComponentConfigurationSet(self, iq_elt, client):
973 iq_elt.handled = True
974 defer.ensureDeferred(self.onComponentConfigurationSet(client, iq_elt))
975
976 async def onComponentConfigurationSet(self, client, iq_elt):
977 try:
978 (
979 from_jid, configuration_elt, path, namespace, file_data
980 ) = await self._parseElement(client, iq_elt, "configuration", NS_FIS_CONFIGURATION)
981 except exceptions.CancelError:
982 return
983 except RootPathException:
984 client.sendError(iq_elt, 'bad-request', "Root path can't be used")
985 return
986
987 from_jid_bare = from_jid.userhostJID()
988 is_owner = from_jid_bare == file_data.get('owner')
989 if not is_owner:
990 log.warning(
991 f"{from_jid} tried to modify {path} configuration while the owner is "
992 f"{file_data['owner']}"
993 )
994 client.sendError(iq_elt, 'forbidden')
995 return
996
997 form = data_form.findForm(configuration_elt, NS_FIS_CONFIGURATION)
998 for name, value in form.items():
999 if name == 'access_model':
1000 await self.host.memory.setFileAccessModel(client, file_data, value)
1001 else:
1002 # TODO: send a IQ error?
1003 log.warning(
1004 f"Trying to set a not implemented configuration option: {name}")
1005 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
1006 client.send(iq_result_elt)
1007
859 # file methods # 1008 # file methods #
860 1009
861 def _serializeData(self, files_data): 1010 def _serializeData(self, files_data):
862 for file_data in files_data: 1011 for file_data in files_data:
863 for key, value in file_data.items(): 1012 for key, value in file_data.items():