comparison sat/memory/encryption.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents c161a294fffd
children 118d91c932a7
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT: a jabber client 4 # SAT: a jabber client
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
89 namespace=namespace, 89 namespace=namespace,
90 priority=priority, 90 priority=priority,
91 directed=directed) 91 directed=directed)
92 cls.plugins.append(plugin) 92 cls.plugins.append(plugin)
93 cls.plugins.sort(key=lambda p: p.priority) 93 cls.plugins.sort(key=lambda p: p.priority)
94 log.info(_(u"Encryption plugin registered: {name}").format(name=name)) 94 log.info(_("Encryption plugin registered: {name}").format(name=name))
95 95
96 @classmethod 96 @classmethod
97 def getPlugins(cls): 97 def getPlugins(cls):
98 return cls.plugins 98 return cls.plugins
99 99
101 def getPlugin(cls, namespace): 101 def getPlugin(cls, namespace):
102 try: 102 try:
103 return next(p for p in cls.plugins if p.namespace == namespace) 103 return next(p for p in cls.plugins if p.namespace == namespace)
104 except StopIteration: 104 except StopIteration:
105 raise exceptions.NotFound(_( 105 raise exceptions.NotFound(_(
106 u"Can't find requested encryption plugin: {namespace}").format( 106 "Can't find requested encryption plugin: {namespace}").format(
107 namespace=namespace)) 107 namespace=namespace))
108 108
109 @classmethod 109 @classmethod
110 def getNamespaces(cls): 110 def getNamespaces(cls):
111 """Get available plugin namespaces""" 111 """Get available plugin namespaces"""
121 """ 121 """
122 for p in cls.plugins: 122 for p in cls.plugins:
123 if p.name.lower() == name.lower(): 123 if p.name.lower() == name.lower():
124 return p.namespace 124 return p.namespace
125 raise exceptions.NotFound(_( 125 raise exceptions.NotFound(_(
126 u"Can't find a plugin with the name \"{name}\".".format( 126 "Can't find a plugin with the name \"{name}\".".format(
127 name=name))) 127 name=name)))
128 128
129 def getBridgeData(self, session): 129 def getBridgeData(self, session):
130 """Retrieve session data serialized for bridge. 130 """Retrieve session data serialized for bridge.
131 131
132 @param session(dict): encryption session 132 @param session(dict): encryption session
133 @return (unicode): serialized data for bridge 133 @return (unicode): serialized data for bridge
134 """ 134 """
135 if session is None: 135 if session is None:
136 return u'' 136 return ''
137 plugin = session[u'plugin'] 137 plugin = session['plugin']
138 bridge_data = {'name': plugin.name, 138 bridge_data = {'name': plugin.name,
139 'namespace': plugin.namespace} 139 'namespace': plugin.namespace}
140 if u'directed_devices' in session: 140 if 'directed_devices' in session:
141 bridge_data[u'directed_devices'] = session[u'directed_devices'] 141 bridge_data['directed_devices'] = session['directed_devices']
142 142
143 return data_format.serialise(bridge_data) 143 return data_format.serialise(bridge_data)
144 144
145 def _startEncryption(self, plugin, entity): 145 def _startEncryption(self, plugin, entity):
146 """Start encryption with a plugin 146 """Start encryption with a plugin
149 StartEncryptionn method of plugin will be called if it exists. 149 StartEncryptionn method of plugin will be called if it exists.
150 """ 150 """
151 try: 151 try:
152 start_encryption = plugin.instance.startEncryption 152 start_encryption = plugin.instance.startEncryption
153 except AttributeError: 153 except AttributeError:
154 log.debug(u"No startEncryption method found for {plugin}".format( 154 log.debug("No startEncryption method found for {plugin}".format(
155 plugin = plugin.namespace)) 155 plugin = plugin.namespace))
156 return defer.succeed(None) 156 return defer.succeed(None)
157 else: 157 else:
158 # we copy entity to avoid having the resource changed by stop_encryption 158 # we copy entity to avoid having the resource changed by stop_encryption
159 return defer.maybeDeferred(start_encryption, self.client, copy.copy(entity)) 159 return defer.maybeDeferred(start_encryption, self.client, copy.copy(entity))
165 StopEncryptionn method of plugin will be called if it exists. 165 StopEncryptionn method of plugin will be called if it exists.
166 """ 166 """
167 try: 167 try:
168 stop_encryption = plugin.instance.stopEncryption 168 stop_encryption = plugin.instance.stopEncryption
169 except AttributeError: 169 except AttributeError:
170 log.debug(u"No stopEncryption method found for {plugin}".format( 170 log.debug("No stopEncryption method found for {plugin}".format(
171 plugin = plugin.namespace)) 171 plugin = plugin.namespace))
172 return defer.succeed(None) 172 return defer.succeed(None)
173 else: 173 else:
174 # we copy entity to avoid having the resource changed by stop_encryption 174 # we copy entity to avoid having the resource changed by stop_encryption
175 return defer.maybeDeferred(stop_encryption, self.client, copy.copy(entity)) 175 return defer.maybeDeferred(stop_encryption, self.client, copy.copy(entity))
185 None to select automatically an algorithm 185 None to select automatically an algorithm
186 @param replace(bool): if True and an encrypted session already exists, 186 @param replace(bool): if True and an encrypted session already exists,
187 it will be replaced by the new one 187 it will be replaced by the new one
188 """ 188 """
189 if not self.plugins: 189 if not self.plugins:
190 raise exceptions.NotFound(_(u"No encryption plugin is registered, " 190 raise exceptions.NotFound(_("No encryption plugin is registered, "
191 u"an encryption session can't be started")) 191 "an encryption session can't be started"))
192 192
193 if namespace is None: 193 if namespace is None:
194 plugin = self.plugins[0] 194 plugin = self.plugins[0]
195 else: 195 else:
196 plugin = self.getPlugin(namespace) 196 plugin = self.getPlugin(namespace)
197 197
198 bare_jid = entity.userhostJID() 198 bare_jid = entity.userhostJID()
199 if bare_jid in self._sessions: 199 if bare_jid in self._sessions:
200 # we have already an encryption session with this contact 200 # we have already an encryption session with this contact
201 former_plugin = self._sessions[bare_jid][u"plugin"] 201 former_plugin = self._sessions[bare_jid]["plugin"]
202 if former_plugin.namespace == namespace: 202 if former_plugin.namespace == namespace:
203 log.info(_(u"Session with {bare_jid} is already encrypted with {name}. " 203 log.info(_("Session with {bare_jid} is already encrypted with {name}. "
204 u"Nothing to do.").format( 204 "Nothing to do.").format(
205 bare_jid=bare_jid, name=former_plugin.name)) 205 bare_jid=bare_jid, name=former_plugin.name))
206 return 206 return
207 207
208 if replace: 208 if replace:
209 # there is a conflict, but replacement is requested 209 # there is a conflict, but replacement is requested
210 # so we stop previous encryption to use new one 210 # so we stop previous encryption to use new one
211 del self._sessions[bare_jid] 211 del self._sessions[bare_jid]
212 yield self._stopEncryption(former_plugin, entity) 212 yield self._stopEncryption(former_plugin, entity)
213 else: 213 else:
214 msg = (_(u"Session with {bare_jid} is already encrypted with {name}. " 214 msg = (_("Session with {bare_jid} is already encrypted with {name}. "
215 u"Please stop encryption session before changing algorithm.") 215 "Please stop encryption session before changing algorithm.")
216 .format(bare_jid=bare_jid, name=plugin.name)) 216 .format(bare_jid=bare_jid, name=plugin.name))
217 log.warning(msg) 217 log.warning(msg)
218 raise exceptions.ConflictError(msg) 218 raise exceptions.ConflictError(msg)
219 219
220 data = {"plugin": plugin} 220 data = {"plugin": plugin}
221 if plugin.directed: 221 if plugin.directed:
222 if not entity.resource: 222 if not entity.resource:
223 entity.resource = self.host.memory.getMainResource(self.client, entity) 223 entity.resource = self.host.memory.getMainResource(self.client, entity)
224 if not entity.resource: 224 if not entity.resource:
225 raise exceptions.NotFound( 225 raise exceptions.NotFound(
226 _(u"No resource found for {destinee}, can't encrypt with {name}") 226 _("No resource found for {destinee}, can't encrypt with {name}")
227 .format(destinee=entity.full(), name=plugin.name)) 227 .format(destinee=entity.full(), name=plugin.name))
228 log.info(_(u"No resource specified to encrypt with {name}, using " 228 log.info(_("No resource specified to encrypt with {name}, using "
229 u"{destinee}.").format(destinee=entity.full(), 229 "{destinee}.").format(destinee=entity.full(),
230 name=plugin.name)) 230 name=plugin.name))
231 # indicate that we encrypt only for some devices 231 # indicate that we encrypt only for some devices
232 directed_devices = data[u'directed_devices'] = [entity.resource] 232 directed_devices = data['directed_devices'] = [entity.resource]
233 elif entity.resource: 233 elif entity.resource:
234 raise ValueError(_(u"{name} encryption must be used with bare jids.")) 234 raise ValueError(_("{name} encryption must be used with bare jids."))
235 235
236 yield self._startEncryption(plugin, entity) 236 yield self._startEncryption(plugin, entity)
237 self._sessions[entity.userhostJID()] = data 237 self._sessions[entity.userhostJID()] = data
238 log.info(_(u"Encryption session has been set for {entity_jid} with " 238 log.info(_("Encryption session has been set for {entity_jid} with "
239 u"{encryption_name}").format( 239 "{encryption_name}").format(
240 entity_jid=entity.full(), encryption_name=plugin.name)) 240 entity_jid=entity.full(), encryption_name=plugin.name))
241 self.host.bridge.messageEncryptionStarted( 241 self.host.bridge.messageEncryptionStarted(
242 entity.full(), 242 entity.full(),
243 self.getBridgeData(data), 243 self.getBridgeData(data),
244 self.client.profile) 244 self.client.profile)
245 msg = D_(u"Encryption session started: your messages with {destinee} are " 245 msg = D_("Encryption session started: your messages with {destinee} are "
246 u"now end to end encrypted using {name} algorithm.").format( 246 "now end to end encrypted using {name} algorithm.").format(
247 destinee=entity.full(), name=plugin.name) 247 destinee=entity.full(), name=plugin.name)
248 directed_devices = data.get(u'directed_devices') 248 directed_devices = data.get('directed_devices')
249 if directed_devices: 249 if directed_devices:
250 msg += u"\n" + D_(u"Message are encrypted only for {nb_devices} device(s): " 250 msg += "\n" + D_("Message are encrypted only for {nb_devices} device(s): "
251 u"{devices_list}.").format( 251 "{devices_list}.").format(
252 nb_devices=len(directed_devices), 252 nb_devices=len(directed_devices),
253 devices_list = u', '.join(directed_devices)) 253 devices_list = ', '.join(directed_devices))
254 254
255 self.client.feedback(bare_jid, msg) 255 self.client.feedback(bare_jid, msg)
256 256
257 @defer.inlineCallbacks 257 @defer.inlineCallbacks
258 def stop(self, entity, namespace=None): 258 def stop(self, entity, namespace=None):
264 when specified, used to check we stop the right encryption session 264 when specified, used to check we stop the right encryption session
265 """ 265 """
266 session = self.getSession(entity.userhostJID()) 266 session = self.getSession(entity.userhostJID())
267 if not session: 267 if not session:
268 raise failure.Failure( 268 raise failure.Failure(
269 exceptions.NotFound(_(u"There is no encryption session with this " 269 exceptions.NotFound(_("There is no encryption session with this "
270 u"entity."))) 270 "entity.")))
271 plugin = session['plugin'] 271 plugin = session['plugin']
272 if namespace is not None and plugin.namespace != namespace: 272 if namespace is not None and plugin.namespace != namespace:
273 raise exceptions.InternalError(_( 273 raise exceptions.InternalError(_(
274 u"The encryption session is not run with the expected plugin: encrypted " 274 "The encryption session is not run with the expected plugin: encrypted "
275 u"with {current_name} and was expecting {expected_name}").format( 275 "with {current_name} and was expecting {expected_name}").format(
276 current_name=session[u'plugin'].namespace, 276 current_name=session['plugin'].namespace,
277 expected_name=namespace)) 277 expected_name=namespace))
278 if entity.resource: 278 if entity.resource:
279 try: 279 try:
280 directed_devices = session[u'directed_devices'] 280 directed_devices = session['directed_devices']
281 except KeyError: 281 except KeyError:
282 raise exceptions.NotFound(_( 282 raise exceptions.NotFound(_(
283 u"There is a session for the whole entity (i.e. all devices of the " 283 "There is a session for the whole entity (i.e. all devices of the "
284 u"entity), not a directed one. Please use bare jid if you want to " 284 "entity), not a directed one. Please use bare jid if you want to "
285 u"stop the whole encryption with this entity.")) 285 "stop the whole encryption with this entity."))
286 286
287 try: 287 try:
288 directed_devices.remove(entity.resource) 288 directed_devices.remove(entity.resource)
289 except ValueError: 289 except ValueError:
290 raise exceptions.NotFound(_(u"There is no directed session with this " 290 raise exceptions.NotFound(_("There is no directed session with this "
291 u"entity.")) 291 "entity."))
292 else: 292 else:
293 if not directed_devices: 293 if not directed_devices:
294 # if we have no more directed device sessions, 294 # if we have no more directed device sessions,
295 # we stop the whole session 295 # we stop the whole session
296 # see comment below for deleting session before stopping encryption 296 # see comment below for deleting session before stopping encryption
300 # plugin's stopEncryption may call stop again (that's the case with OTR) 300 # plugin's stopEncryption may call stop again (that's the case with OTR)
301 # so we need to remove plugin from session before calling self._stopEncryption 301 # so we need to remove plugin from session before calling self._stopEncryption
302 del self._sessions[entity.userhostJID()] 302 del self._sessions[entity.userhostJID()]
303 yield self._stopEncryption(plugin, entity) 303 yield self._stopEncryption(plugin, entity)
304 304
305 log.info(_(u"encryption session stopped with entity {entity}").format( 305 log.info(_("encryption session stopped with entity {entity}").format(
306 entity=entity.full())) 306 entity=entity.full()))
307 self.host.bridge.messageEncryptionStopped( 307 self.host.bridge.messageEncryptionStopped(
308 entity.full(), 308 entity.full(),
309 {'name': plugin.name, 309 {'name': plugin.name,
310 'namespace': plugin.namespace, 310 'namespace': plugin.namespace,
311 }, 311 },
312 self.client.profile) 312 self.client.profile)
313 msg = D_(u"Encryption session finished: your messages with {destinee} are " 313 msg = D_("Encryption session finished: your messages with {destinee} are "
314 u"NOT end to end encrypted anymore.\nYour server administrators or " 314 "NOT end to end encrypted anymore.\nYour server administrators or "
315 u"{destinee} server administrators will be able to read them.").format( 315 "{destinee} server administrators will be able to read them.").format(
316 destinee=entity.full()) 316 destinee=entity.full())
317 317
318 self.client.feedback(entity, msg) 318 self.client.feedback(entity, msg)
319 319
320 def getSession(self, entity): 320 def getSession(self, entity):
324 must be a bare jid 324 must be a bare jid
325 @return (dict, None): encryption session data 325 @return (dict, None): encryption session data
326 None if there is not encryption for this session with this jid 326 None if there is not encryption for this session with this jid
327 """ 327 """
328 if entity.resource: 328 if entity.resource:
329 raise ValueError(u"Full jid given when expecting bare jid") 329 raise ValueError("Full jid given when expecting bare jid")
330 return self._sessions.get(entity) 330 return self._sessions.get(entity)
331 331
332 def getTrustUI(self, entity_jid, namespace=None): 332 def getTrustUI(self, entity_jid, namespace=None):
333 """Retrieve encryption UI 333 """Retrieve encryption UI
334 334
344 """ 344 """
345 if namespace is None: 345 if namespace is None:
346 session = self.getSession(entity_jid) 346 session = self.getSession(entity_jid)
347 if not session: 347 if not session:
348 raise exceptions.NotFound( 348 raise exceptions.NotFound(
349 u"No encryption session currently active for {entity_jid}" 349 "No encryption session currently active for {entity_jid}"
350 .format(entity_jid=entity_jid.full())) 350 .format(entity_jid=entity_jid.full()))
351 plugin = session['plugin'] 351 plugin = session['plugin']
352 else: 352 else:
353 plugin = self.getPlugin(namespace) 353 plugin = self.getPlugin(namespace)
354 try: 354 try:
355 get_trust_ui = plugin.instance.getTrustUI 355 get_trust_ui = plugin.instance.getTrustUI
356 except AttributeError: 356 except AttributeError:
357 raise NotImplementedError( 357 raise NotImplementedError(
358 u"Encryption plugin doesn't handle trust management UI") 358 "Encryption plugin doesn't handle trust management UI")
359 else: 359 else:
360 return defer.maybeDeferred(get_trust_ui, self.client, entity_jid) 360 return defer.maybeDeferred(get_trust_ui, self.client, entity_jid)
361 361
362 ## Menus ## 362 ## Menus ##
363 363
364 @classmethod 364 @classmethod
365 def _importMenus(cls, host): 365 def _importMenus(cls, host):
366 host.importMenu( 366 host.importMenu(
367 (D_(u"Encryption"), D_(u"unencrypted (plain text)")), 367 (D_("Encryption"), D_("unencrypted (plain text)")),
368 partial(cls._onMenuUnencrypted, host=host), 368 partial(cls._onMenuUnencrypted, host=host),
369 security_limit=0, 369 security_limit=0,
370 help_string=D_(u"End encrypted session"), 370 help_string=D_("End encrypted session"),
371 type_=C.MENU_SINGLE, 371 type_=C.MENU_SINGLE,
372 ) 372 )
373 for plg in cls.getPlugins(): 373 for plg in cls.getPlugins():
374 host.importMenu( 374 host.importMenu(
375 (D_(u"Encryption"), plg.name), 375 (D_("Encryption"), plg.name),
376 partial(cls._onMenuName, host=host, plg=plg), 376 partial(cls._onMenuName, host=host, plg=plg),
377 security_limit=0, 377 security_limit=0,
378 help_string=D_(u"Start {name} session").format(name=plg.name), 378 help_string=D_("Start {name} session").format(name=plg.name),
379 type_=C.MENU_SINGLE, 379 type_=C.MENU_SINGLE,
380 ) 380 )
381 host.importMenu( 381 host.importMenu(
382 (D_(u"Encryption"), D_(u"⛨ {name} trust").format(name=plg.name)), 382 (D_("Encryption"), D_("⛨ {name} trust").format(name=plg.name)),
383 partial(cls._onMenuTrust, host=host, plg=plg), 383 partial(cls._onMenuTrust, host=host, plg=plg),
384 security_limit=0, 384 security_limit=0,
385 help_string=D_(u"Manage {name} trust").format(name=plg.name), 385 help_string=D_("Manage {name} trust").format(name=plg.name),
386 type_=C.MENU_SINGLE, 386 type_=C.MENU_SINGLE,
387 ) 387 )
388 388
389 @classmethod 389 @classmethod
390 def _onMenuUnencrypted(cls, data, host, profile): 390 def _onMenuUnencrypted(cls, data, host, profile):
391 client = host.getClient(profile) 391 client = host.getClient(profile)
392 peer_jid = jid.JID(data[u'jid']).userhostJID() 392 peer_jid = jid.JID(data['jid']).userhostJID()
393 d = client.encryption.stop(peer_jid) 393 d = client.encryption.stop(peer_jid)
394 d.addCallback(lambda __: {}) 394 d.addCallback(lambda __: {})
395 return d 395 return d
396 396
397 @classmethod 397 @classmethod
398 def _onMenuName(cls, data, host, plg, profile): 398 def _onMenuName(cls, data, host, plg, profile):
399 client = host.getClient(profile) 399 client = host.getClient(profile)
400 peer_jid = jid.JID(data[u'jid']) 400 peer_jid = jid.JID(data['jid'])
401 if not plg.directed: 401 if not plg.directed:
402 peer_jid = peer_jid.userhostJID() 402 peer_jid = peer_jid.userhostJID()
403 d = client.encryption.start(peer_jid, plg.namespace, replace=True) 403 d = client.encryption.start(peer_jid, plg.namespace, replace=True)
404 d.addCallback(lambda __: {}) 404 d.addCallback(lambda __: {})
405 return d 405 return d
406 406
407 @classmethod 407 @classmethod
408 @defer.inlineCallbacks 408 @defer.inlineCallbacks
409 def _onMenuTrust(cls, data, host, plg, profile): 409 def _onMenuTrust(cls, data, host, plg, profile):
410 client = host.getClient(profile) 410 client = host.getClient(profile)
411 peer_jid = jid.JID(data[u'jid']).userhostJID() 411 peer_jid = jid.JID(data['jid']).userhostJID()
412 ui = yield client.encryption.getTrustUI(peer_jid, plg.namespace) 412 ui = yield client.encryption.getTrustUI(peer_jid, plg.namespace)
413 defer.returnValue({u'xmlui': ui.toXml()}) 413 defer.returnValue({'xmlui': ui.toXml()})
414 414
415 ## Triggers ## 415 ## Triggers ##
416 416
417 def setEncryptionFlag(self, mess_data): 417 def setEncryptionFlag(self, mess_data):
418 """Set "encryption" key in mess_data if session with destinee is encrypted""" 418 """Set "encryption" key in mess_data if session with destinee is encrypted"""