comparison sat/memory/encryption.py @ 4037:524856bd7b19

massive refactoring to switch from camelCase to snake_case: historically, Libervia (SàT before) was using camelCase as allowed by PEP8 when using a pre-PEP8 code, to use the same coding style as in Twisted. However, snake_case is more readable and it's better to follow PEP8 best practices, so it has been decided to move on full snake_case. Because Libervia has a huge codebase, this ended with a ugly mix of camelCase and snake_case. To fix that, this patch does a big refactoring by renaming every function and method (including bridge) that are not coming from Twisted or Wokkel, to use fully snake_case. This is a massive change, and may result in some bugs.
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:54:42 +0200
parents cc2705225778
children c23cad65ae99
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
48 48
49 @property 49 @property
50 def host(self): 50 def host(self):
51 return self.client.host_app 51 return self.client.host_app
52 52
53 async def loadSessions(self): 53 async def load_sessions(self):
54 """Load persistent sessions""" 54 """Load persistent sessions"""
55 await self._stored_session.load() 55 await self._stored_session.load()
56 start_d_list = [] 56 start_d_list = []
57 for entity_jid_s, namespace in self._stored_session.items(): 57 for entity_jid_s, namespace in self._stored_session.items():
58 entity = jid.JID(entity_jid_s) 58 entity = jid.JID(entity_jid_s)
67 "Could not restart {namespace!r} encryption with {entity}: {err}" 67 "Could not restart {namespace!r} encryption with {entity}: {err}"
68 ).format(namespace=namespace, entity=entity_jid_s, err=err)) 68 ).format(namespace=namespace, entity=entity_jid_s, err=err))
69 log.info(_("encryption sessions restored")) 69 log.info(_("encryption sessions restored"))
70 70
71 @classmethod 71 @classmethod
72 def registerPlugin(cls, plg_instance, name, namespace, priority=0, directed=False): 72 def register_plugin(cls, plg_instance, name, namespace, priority=0, directed=False):
73 """Register a plugin handling an encryption algorithm 73 """Register a plugin handling an encryption algorithm
74 74
75 @param plg_instance(object): instance of the plugin 75 @param plg_instance(object): instance of the plugin
76 it must have the following methods: 76 it must have the following methods:
77 - getTrustUI(entity): return a XMLUI for trust management 77 - get_trust_ui(entity): return a XMLUI for trust management
78 entity(jid.JID): entity to manage 78 entity(jid.JID): entity to manage
79 The returned XMLUI must be a form 79 The returned XMLUI must be a form
80 if may have the following methods: 80 if may have the following methods:
81 - startEncryption(entity): start encrypted session 81 - start_encryption(entity): start encrypted session
82 entity(jid.JID): entity to start encrypted session with 82 entity(jid.JID): entity to start encrypted session with
83 - stopEncryption(entity): start encrypted session 83 - stop_encryption(entity): start encrypted session
84 entity(jid.JID): entity to stop encrypted session with 84 entity(jid.JID): entity to stop encrypted session with
85 if they don't exists, those 2 methods will be ignored. 85 if they don't exists, those 2 methods will be ignored.
86 86
87 @param name(unicode): human readable name of the encryption algorithm 87 @param name(unicode): human readable name of the encryption algorithm
88 @param namespace(unicode): namespace of the encryption algorithm 88 @param namespace(unicode): namespace of the encryption algorithm
113 @classmethod 113 @classmethod
114 def getPlugins(cls): 114 def getPlugins(cls):
115 return cls.plugins 115 return cls.plugins
116 116
117 @classmethod 117 @classmethod
118 def getPlugin(cls, namespace): 118 def get_plugin(cls, namespace):
119 try: 119 try:
120 return next(p for p in cls.plugins if p.namespace == namespace) 120 return next(p for p in cls.plugins if p.namespace == namespace)
121 except StopIteration: 121 except StopIteration:
122 raise exceptions.NotFound(_( 122 raise exceptions.NotFound(_(
123 "Can't find requested encryption plugin: {namespace}").format( 123 "Can't find requested encryption plugin: {namespace}").format(
124 namespace=namespace)) 124 namespace=namespace))
125 125
126 @classmethod 126 @classmethod
127 def getNamespaces(cls): 127 def get_namespaces(cls):
128 """Get available plugin namespaces""" 128 """Get available plugin namespaces"""
129 return {p.namespace for p in cls.getPlugins()} 129 return {p.namespace for p in cls.getPlugins()}
130 130
131 @classmethod 131 @classmethod
132 def getNSFromName(cls, name): 132 def get_ns_from_name(cls, name):
133 """Retrieve plugin namespace from its name 133 """Retrieve plugin namespace from its name
134 134
135 @param name(unicode): name of the plugin (case insensitive) 135 @param name(unicode): name of the plugin (case insensitive)
136 @return (unicode): namespace of the plugin 136 @return (unicode): namespace of the plugin
137 @raise exceptions.NotFound: there is not encryption plugin of this name 137 @raise exceptions.NotFound: there is not encryption plugin of this name
141 return p.namespace 141 return p.namespace
142 raise exceptions.NotFound(_( 142 raise exceptions.NotFound(_(
143 "Can't find a plugin with the name \"{name}\".".format( 143 "Can't find a plugin with the name \"{name}\".".format(
144 name=name))) 144 name=name)))
145 145
146 def getBridgeData(self, session): 146 def get_bridge_data(self, session):
147 """Retrieve session data serialized for bridge. 147 """Retrieve session data serialized for bridge.
148 148
149 @param session(dict): encryption session 149 @param session(dict): encryption session
150 @return (unicode): serialized data for bridge 150 @return (unicode): serialized data for bridge
151 """ 151 """
157 if 'directed_devices' in session: 157 if 'directed_devices' in session:
158 bridge_data['directed_devices'] = session['directed_devices'] 158 bridge_data['directed_devices'] = session['directed_devices']
159 159
160 return data_format.serialise(bridge_data) 160 return data_format.serialise(bridge_data)
161 161
162 async def _startEncryption(self, plugin, entity): 162 async def _start_encryption(self, plugin, entity):
163 """Start encryption with a plugin 163 """Start encryption with a plugin
164 164
165 This method must be called just before adding a plugin session. 165 This method must be called just before adding a plugin session.
166 StartEncryptionn method of plugin will be called if it exists. 166 StartEncryptionn method of plugin will be called if it exists.
167 """ 167 """
168 if not plugin.directed: 168 if not plugin.directed:
169 await self._stored_session.aset(entity.userhost(), plugin.namespace) 169 await self._stored_session.aset(entity.userhost(), plugin.namespace)
170 try: 170 try:
171 start_encryption = plugin.instance.startEncryption 171 start_encryption = plugin.instance.start_encryption
172 except AttributeError: 172 except AttributeError:
173 log.debug(f"No startEncryption method found for {plugin.namespace}") 173 log.debug(f"No start_encryption method found for {plugin.namespace}")
174 else: 174 else:
175 # we copy entity to avoid having the resource changed by stop_encryption 175 # we copy entity to avoid having the resource changed by stop_encryption
176 await utils.asDeferred(start_encryption, self.client, copy.copy(entity)) 176 await utils.as_deferred(start_encryption, self.client, copy.copy(entity))
177 177
178 async def _stopEncryption(self, plugin, entity): 178 async def _stop_encryption(self, plugin, entity):
179 """Stop encryption with a plugin 179 """Stop encryption with a plugin
180 180
181 This method must be called just before removing a plugin session. 181 This method must be called just before removing a plugin session.
182 StopEncryptionn method of plugin will be called if it exists. 182 StopEncryptionn method of plugin will be called if it exists.
183 """ 183 """
184 try: 184 try:
185 await self._stored_session.adel(entity.userhost()) 185 await self._stored_session.adel(entity.userhost())
186 except KeyError: 186 except KeyError:
187 pass 187 pass
188 try: 188 try:
189 stop_encryption = plugin.instance.stopEncryption 189 stop_encryption = plugin.instance.stop_encryption
190 except AttributeError: 190 except AttributeError:
191 log.debug(f"No stopEncryption method found for {plugin.namespace}") 191 log.debug(f"No stop_encryption method found for {plugin.namespace}")
192 else: 192 else:
193 # we copy entity to avoid having the resource changed by stop_encryption 193 # we copy entity to avoid having the resource changed by stop_encryption
194 return utils.asDeferred(stop_encryption, self.client, copy.copy(entity)) 194 return utils.as_deferred(stop_encryption, self.client, copy.copy(entity))
195 195
196 async def start(self, entity, namespace=None, replace=False): 196 async def start(self, entity, namespace=None, replace=False):
197 """Start an encryption session with an entity 197 """Start an encryption session with an entity
198 198
199 @param entity(jid.JID): entity to start an encryption session with 199 @param entity(jid.JID): entity to start an encryption session with
209 "an encryption session can't be started")) 209 "an encryption session can't be started"))
210 210
211 if namespace is None: 211 if namespace is None:
212 plugin = self.plugins[0] 212 plugin = self.plugins[0]
213 else: 213 else:
214 plugin = self.getPlugin(namespace) 214 plugin = self.get_plugin(namespace)
215 215
216 bare_jid = entity.userhostJID() 216 bare_jid = entity.userhostJID()
217 if bare_jid in self._sessions: 217 if bare_jid in self._sessions:
218 # we have already an encryption session with this contact 218 # we have already an encryption session with this contact
219 former_plugin = self._sessions[bare_jid]["plugin"] 219 former_plugin = self._sessions[bare_jid]["plugin"]
225 225
226 if replace: 226 if replace:
227 # there is a conflict, but replacement is requested 227 # there is a conflict, but replacement is requested
228 # so we stop previous encryption to use new one 228 # so we stop previous encryption to use new one
229 del self._sessions[bare_jid] 229 del self._sessions[bare_jid]
230 await self._stopEncryption(former_plugin, entity) 230 await self._stop_encryption(former_plugin, entity)
231 else: 231 else:
232 msg = (_("Session with {bare_jid} is already encrypted with {name}. " 232 msg = (_("Session with {bare_jid} is already encrypted with {name}. "
233 "Please stop encryption session before changing algorithm.") 233 "Please stop encryption session before changing algorithm.")
234 .format(bare_jid=bare_jid, name=plugin.name)) 234 .format(bare_jid=bare_jid, name=plugin.name))
235 log.warning(msg) 235 log.warning(msg)
236 raise exceptions.ConflictError(msg) 236 raise exceptions.ConflictError(msg)
237 237
238 data = {"plugin": plugin} 238 data = {"plugin": plugin}
239 if plugin.directed: 239 if plugin.directed:
240 if not entity.resource: 240 if not entity.resource:
241 entity.resource = self.host.memory.getMainResource(self.client, entity) 241 entity.resource = self.host.memory.main_resource_get(self.client, entity)
242 if not entity.resource: 242 if not entity.resource:
243 raise exceptions.NotFound( 243 raise exceptions.NotFound(
244 _("No resource found for {destinee}, can't encrypt with {name}") 244 _("No resource found for {destinee}, can't encrypt with {name}")
245 .format(destinee=entity.full(), name=plugin.name)) 245 .format(destinee=entity.full(), name=plugin.name))
246 log.info(_("No resource specified to encrypt with {name}, using " 246 log.info(_("No resource specified to encrypt with {name}, using "
249 # indicate that we encrypt only for some devices 249 # indicate that we encrypt only for some devices
250 directed_devices = data['directed_devices'] = [entity.resource] 250 directed_devices = data['directed_devices'] = [entity.resource]
251 elif entity.resource: 251 elif entity.resource:
252 raise ValueError(_("{name} encryption must be used with bare jids.")) 252 raise ValueError(_("{name} encryption must be used with bare jids."))
253 253
254 await self._startEncryption(plugin, entity) 254 await self._start_encryption(plugin, entity)
255 self._sessions[entity.userhostJID()] = data 255 self._sessions[entity.userhostJID()] = data
256 log.info(_("Encryption session has been set for {entity_jid} with " 256 log.info(_("Encryption session has been set for {entity_jid} with "
257 "{encryption_name}").format( 257 "{encryption_name}").format(
258 entity_jid=entity.full(), encryption_name=plugin.name)) 258 entity_jid=entity.full(), encryption_name=plugin.name))
259 self.host.bridge.messageEncryptionStarted( 259 self.host.bridge.message_encryption_started(
260 entity.full(), 260 entity.full(),
261 self.getBridgeData(data), 261 self.get_bridge_data(data),
262 self.client.profile) 262 self.client.profile)
263 msg = D_("Encryption session started: your messages with {destinee} are " 263 msg = D_("Encryption session started: your messages with {destinee} are "
264 "now end to end encrypted using {name} algorithm.").format( 264 "now end to end encrypted using {name} algorithm.").format(
265 destinee=entity.full(), name=plugin.name) 265 destinee=entity.full(), name=plugin.name)
266 directed_devices = data.get('directed_devices') 266 directed_devices = data.get('directed_devices')
310 if not directed_devices: 310 if not directed_devices:
311 # if we have no more directed device sessions, 311 # if we have no more directed device sessions,
312 # we stop the whole session 312 # we stop the whole session
313 # see comment below for deleting session before stopping encryption 313 # see comment below for deleting session before stopping encryption
314 del self._sessions[entity.userhostJID()] 314 del self._sessions[entity.userhostJID()]
315 await self._stopEncryption(plugin, entity) 315 await self._stop_encryption(plugin, entity)
316 else: 316 else:
317 # plugin's stopEncryption may call stop again (that's the case with OTR) 317 # plugin's stop_encryption may call stop again (that's the case with OTR)
318 # so we need to remove plugin from session before calling self._stopEncryption 318 # so we need to remove plugin from session before calling self._stop_encryption
319 del self._sessions[entity.userhostJID()] 319 del self._sessions[entity.userhostJID()]
320 await self._stopEncryption(plugin, entity) 320 await self._stop_encryption(plugin, entity)
321 321
322 log.info(_("encryption session stopped with entity {entity}").format( 322 log.info(_("encryption session stopped with entity {entity}").format(
323 entity=entity.full())) 323 entity=entity.full()))
324 self.host.bridge.messageEncryptionStopped( 324 self.host.bridge.message_encryption_stopped(
325 entity.full(), 325 entity.full(),
326 {'name': plugin.name, 326 {'name': plugin.name,
327 'namespace': plugin.namespace, 327 'namespace': plugin.namespace,
328 }, 328 },
329 self.client.profile) 329 self.client.profile)
356 session = self.getSession(entity) 356 session = self.getSession(entity)
357 if session is None: 357 if session is None:
358 return None 358 return None
359 return session["plugin"].namespace 359 return session["plugin"].namespace
360 360
361 def getTrustUI(self, entity_jid, namespace=None): 361 def get_trust_ui(self, entity_jid, namespace=None):
362 """Retrieve encryption UI 362 """Retrieve encryption UI
363 363
364 @param entity_jid(jid.JID): get the UI for this entity 364 @param entity_jid(jid.JID): get the UI for this entity
365 must be a bare jid 365 must be a bare jid
366 @param namespace(unicode): namespace of the algorithm to manage 366 @param namespace(unicode): namespace of the algorithm to manage
377 raise exceptions.NotFound( 377 raise exceptions.NotFound(
378 "No encryption session currently active for {entity_jid}" 378 "No encryption session currently active for {entity_jid}"
379 .format(entity_jid=entity_jid.full())) 379 .format(entity_jid=entity_jid.full()))
380 plugin = session['plugin'] 380 plugin = session['plugin']
381 else: 381 else:
382 plugin = self.getPlugin(namespace) 382 plugin = self.get_plugin(namespace)
383 try: 383 try:
384 get_trust_ui = plugin.instance.getTrustUI 384 get_trust_ui = plugin.instance.get_trust_ui
385 except AttributeError: 385 except AttributeError:
386 raise NotImplementedError( 386 raise NotImplementedError(
387 "Encryption plugin doesn't handle trust management UI") 387 "Encryption plugin doesn't handle trust management UI")
388 else: 388 else:
389 return utils.asDeferred(get_trust_ui, self.client, entity_jid) 389 return utils.as_deferred(get_trust_ui, self.client, entity_jid)
390 390
391 ## Menus ## 391 ## Menus ##
392 392
393 @classmethod 393 @classmethod
394 def _importMenus(cls, host): 394 def _import_menus(cls, host):
395 host.importMenu( 395 host.import_menu(
396 (D_("Encryption"), D_("unencrypted (plain text)")), 396 (D_("Encryption"), D_("unencrypted (plain text)")),
397 partial(cls._onMenuUnencrypted, host=host), 397 partial(cls._on_menu_unencrypted, host=host),
398 security_limit=0, 398 security_limit=0,
399 help_string=D_("End encrypted session"), 399 help_string=D_("End encrypted session"),
400 type_=C.MENU_SINGLE, 400 type_=C.MENU_SINGLE,
401 ) 401 )
402 for plg in cls.getPlugins(): 402 for plg in cls.getPlugins():
403 host.importMenu( 403 host.import_menu(
404 (D_("Encryption"), plg.name), 404 (D_("Encryption"), plg.name),
405 partial(cls._onMenuName, host=host, plg=plg), 405 partial(cls._on_menu_name, host=host, plg=plg),
406 security_limit=0, 406 security_limit=0,
407 help_string=D_("Start {name} session").format(name=plg.name), 407 help_string=D_("Start {name} session").format(name=plg.name),
408 type_=C.MENU_SINGLE, 408 type_=C.MENU_SINGLE,
409 ) 409 )
410 host.importMenu( 410 host.import_menu(
411 (D_("Encryption"), D_("⛨ {name} trust").format(name=plg.name)), 411 (D_("Encryption"), D_("⛨ {name} trust").format(name=plg.name)),
412 partial(cls._onMenuTrust, host=host, plg=plg), 412 partial(cls._on_menu_trust, host=host, plg=plg),
413 security_limit=0, 413 security_limit=0,
414 help_string=D_("Manage {name} trust").format(name=plg.name), 414 help_string=D_("Manage {name} trust").format(name=plg.name),
415 type_=C.MENU_SINGLE, 415 type_=C.MENU_SINGLE,
416 ) 416 )
417 417
418 @classmethod 418 @classmethod
419 def _onMenuUnencrypted(cls, data, host, profile): 419 def _on_menu_unencrypted(cls, data, host, profile):
420 client = host.getClient(profile) 420 client = host.get_client(profile)
421 peer_jid = jid.JID(data['jid']).userhostJID() 421 peer_jid = jid.JID(data['jid']).userhostJID()
422 d = defer.ensureDeferred(client.encryption.stop(peer_jid)) 422 d = defer.ensureDeferred(client.encryption.stop(peer_jid))
423 d.addCallback(lambda __: {}) 423 d.addCallback(lambda __: {})
424 return d 424 return d
425 425
426 @classmethod 426 @classmethod
427 def _onMenuName(cls, data, host, plg, profile): 427 def _on_menu_name(cls, data, host, plg, profile):
428 client = host.getClient(profile) 428 client = host.get_client(profile)
429 peer_jid = jid.JID(data['jid']) 429 peer_jid = jid.JID(data['jid'])
430 if not plg.directed: 430 if not plg.directed:
431 peer_jid = peer_jid.userhostJID() 431 peer_jid = peer_jid.userhostJID()
432 d = defer.ensureDeferred( 432 d = defer.ensureDeferred(
433 client.encryption.start(peer_jid, plg.namespace, replace=True)) 433 client.encryption.start(peer_jid, plg.namespace, replace=True))
434 d.addCallback(lambda __: {}) 434 d.addCallback(lambda __: {})
435 return d 435 return d
436 436
437 @classmethod 437 @classmethod
438 @defer.inlineCallbacks 438 @defer.inlineCallbacks
439 def _onMenuTrust(cls, data, host, plg, profile): 439 def _on_menu_trust(cls, data, host, plg, profile):
440 client = host.getClient(profile) 440 client = host.get_client(profile)
441 peer_jid = jid.JID(data['jid']).userhostJID() 441 peer_jid = jid.JID(data['jid']).userhostJID()
442 ui = yield client.encryption.getTrustUI(peer_jid, plg.namespace) 442 ui = yield client.encryption.get_trust_ui(peer_jid, plg.namespace)
443 defer.returnValue({'xmlui': ui.toXml()}) 443 defer.returnValue({'xmlui': ui.toXml()})
444 444
445 ## Triggers ## 445 ## Triggers ##
446 446
447 def setEncryptionFlag(self, mess_data): 447 def set_encryption_flag(self, mess_data):
448 """Set "encryption" key in mess_data if session with destinee is encrypted""" 448 """Set "encryption" key in mess_data if session with destinee is encrypted"""
449 to_jid = mess_data['to'] 449 to_jid = mess_data['to']
450 encryption = self._sessions.get(to_jid.userhostJID()) 450 encryption = self._sessions.get(to_jid.userhostJID())
451 if encryption is not None: 451 if encryption is not None:
452 plugin = encryption['plugin'] 452 plugin = encryption['plugin']
453 if mess_data["type"] == "groupchat" and plugin.directed: 453 if mess_data["type"] == "groupchat" and plugin.directed:
454 raise exceptions.InternalError( 454 raise exceptions.InternalError(
455 f"encryption flag must not be set for groupchat if encryption algorithm " 455 f"encryption flag must not be set for groupchat if encryption algorithm "
456 f"({encryption['plugin'].name}) is directed!") 456 f"({encryption['plugin'].name}) is directed!")
457 mess_data[C.MESS_KEY_ENCRYPTION] = encryption 457 mess_data[C.MESS_KEY_ENCRYPTION] = encryption
458 self.markAsEncrypted(mess_data, plugin.namespace) 458 self.mark_as_encrypted(mess_data, plugin.namespace)
459 459
460 ## Misc ## 460 ## Misc ##
461 461
462 def markAsEncrypted(self, mess_data, namespace): 462 def mark_as_encrypted(self, mess_data, namespace):
463 """Helper method to mark a message as having been e2e encrypted. 463 """Helper method to mark a message as having been e2e encrypted.
464 464
465 This should be used in the post_treat workflow of messageReceived trigger of 465 This should be used in the post_treat workflow of messageReceived trigger of
466 the plugin 466 the plugin
467 @param mess_data(dict): message data as used in post treat workflow 467 @param mess_data(dict): message data as used in post treat workflow
481 ) 481 )
482 defer.ensureDeferred(self.start(from_bare_jid, namespace)) 482 defer.ensureDeferred(self.start(from_bare_jid, namespace))
483 483
484 return mess_data 484 return mess_data
485 485
486 def isEncryptionRequested( 486 def is_encryption_requested(
487 self, 487 self,
488 mess_data: MessageData, 488 mess_data: MessageData,
489 namespace: Optional[str] = None 489 namespace: Optional[str] = None
490 ) -> bool: 490 ) -> bool:
491 """Helper method to check if encryption is requested in an outgoind message 491 """Helper method to check if encryption is requested in an outgoind message
511 @return (bool): True if the encrypted flag is present 511 @return (bool): True if the encrypted flag is present
512 """ 512 """
513 return mess_data['extra'].get(C.MESS_KEY_ENCRYPTED, False) 513 return mess_data['extra'].get(C.MESS_KEY_ENCRYPTED, False)
514 514
515 515
516 def markAsTrusted(self, mess_data): 516 def mark_as_trusted(self, mess_data):
517 """Helper methor to mark a message as sent from a trusted entity. 517 """Helper methor to mark a message as sent from a trusted entity.
518 518
519 This should be used in the post_treat workflow of messageReceived trigger of 519 This should be used in the post_treat workflow of messageReceived trigger of
520 the plugin 520 the plugin
521 @param mess_data(dict): message data as used in post treat workflow 521 @param mess_data(dict): message data as used in post treat workflow
522 """ 522 """
523 mess_data[C.MESS_KEY_TRUSTED] = True 523 mess_data[C.MESS_KEY_TRUSTED] = True
524 return mess_data 524 return mess_data
525 525
526 def markAsUntrusted(self, mess_data): 526 def mark_as_untrusted(self, mess_data):
527 """Helper methor to mark a message as sent from an untrusted entity. 527 """Helper methor to mark a message as sent from an untrusted entity.
528 528
529 This should be used in the post_treat workflow of messageReceived trigger of 529 This should be used in the post_treat workflow of messageReceived trigger of
530 the plugin 530 the plugin
531 @param mess_data(dict): message data as used in post treat workflow 531 @param mess_data(dict): message data as used in post treat workflow