Mercurial > libervia-backend
comparison sat/plugins/plugin_sec_otr.py @ 2624:56f94936df1e
code style reformatting using black
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 27 Jun 2018 20:14:46 +0200 |
parents | 26edcf3a30eb |
children | 189e38fb11ff |
comparison
equal
deleted
inserted
replaced
2623:49533de4540b | 2624:56f94936df1e |
---|---|
22 | 22 |
23 from sat.core.i18n import _, D_ | 23 from sat.core.i18n import _, D_ |
24 from sat.core.constants import Const as C | 24 from sat.core.constants import Const as C |
25 from sat.core.log import getLogger | 25 from sat.core.log import getLogger |
26 from sat.core import exceptions | 26 from sat.core import exceptions |
27 | |
27 log = getLogger(__name__) | 28 log = getLogger(__name__) |
28 from sat.tools import xml_tools | 29 from sat.tools import xml_tools |
29 from twisted.words.protocols.jabber import jid | 30 from twisted.words.protocols.jabber import jid |
30 from twisted.python import failure | 31 from twisted.python import failure |
31 from twisted.internet import defer | 32 from twisted.internet import defer |
42 C.PI_TYPE: u"SEC", | 43 C.PI_TYPE: u"SEC", |
43 C.PI_PROTOCOLS: [u"XEP-0364"], | 44 C.PI_PROTOCOLS: [u"XEP-0364"], |
44 C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334"], | 45 C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334"], |
45 C.PI_MAIN: u"OTR", | 46 C.PI_MAIN: u"OTR", |
46 C.PI_HANDLER: u"no", | 47 C.PI_HANDLER: u"no", |
47 C.PI_DESCRIPTION: _(u"""Implementation of OTR""") | 48 C.PI_DESCRIPTION: _(u"""Implementation of OTR"""), |
48 } | 49 } |
49 | 50 |
50 NS_OTR = "otr_plugin" | 51 NS_OTR = "otr_plugin" |
51 PRIVATE_KEY = "PRIVATE KEY" | 52 PRIVATE_KEY = "PRIVATE KEY" |
52 OTR_MENU = D_(u'OTR') | 53 OTR_MENU = D_(u"OTR") |
53 AUTH_TXT = D_(u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!") | 54 AUTH_TXT = D_( |
54 DROP_TXT = D_(u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?") | 55 u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!" |
56 ) | |
57 DROP_TXT = D_( | |
58 u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?" | |
59 ) | |
55 # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and") # FIXME: not used at the moment | 60 # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and") # FIXME: not used at the moment |
56 NO_ADV_FEATURES = D_(u"Some of advanced features are disabled !") | 61 NO_ADV_FEATURES = D_(u"Some of advanced features are disabled !") |
57 | 62 |
58 DEFAULT_POLICY_FLAGS = { | 63 DEFAULT_POLICY_FLAGS = {"ALLOW_V1": False, "ALLOW_V2": True, "REQUIRE_ENCRYPTION": True} |
59 'ALLOW_V1':False, | 64 |
60 'ALLOW_V2':True, | 65 OTR_STATE_TRUSTED = "trusted" |
61 'REQUIRE_ENCRYPTION':True, | 66 OTR_STATE_UNTRUSTED = "untrusted" |
62 } | 67 OTR_STATE_UNENCRYPTED = "unencrypted" |
63 | 68 OTR_STATE_ENCRYPTED = "encrypted" |
64 OTR_STATE_TRUSTED = 'trusted' | |
65 OTR_STATE_UNTRUSTED = 'untrusted' | |
66 OTR_STATE_UNENCRYPTED = 'unencrypted' | |
67 OTR_STATE_ENCRYPTED = 'encrypted' | |
68 | 69 |
69 | 70 |
70 class Context(potr.context.Context): | 71 class Context(potr.context.Context): |
71 def __init__(self, host, account, other_jid): | 72 def __init__(self, host, account, other_jid): |
72 super(Context, self).__init__(account, other_jid) | 73 super(Context, self).__init__(account, other_jid) |
87 @param msg_str(str): encrypted message body | 88 @param msg_str(str): encrypted message body |
88 @param appdata(None, dict): None for signal message, | 89 @param appdata(None, dict): None for signal message, |
89 message data when an encrypted message is going to be sent | 90 message data when an encrypted message is going to be sent |
90 """ | 91 """ |
91 assert isinstance(self.peer, jid.JID) | 92 assert isinstance(self.peer, jid.JID) |
92 msg = msg_str.decode('utf-8') | 93 msg = msg_str.decode("utf-8") |
93 client = self.user.client | 94 client = self.user.client |
94 log.debug(u'injecting encrypted message to {to}'.format(to=self.peer)) | 95 log.debug(u"injecting encrypted message to {to}".format(to=self.peer)) |
95 if appdata is None: | 96 if appdata is None: |
96 mess_data = { | 97 mess_data = { |
97 'from': client.jid, | 98 "from": client.jid, |
98 'to': self.peer, | 99 "to": self.peer, |
99 'uid': unicode(uuid.uuid4()), | 100 "uid": unicode(uuid.uuid4()), |
100 'message': {'': msg}, | 101 "message": {"": msg}, |
101 'subject': {}, | 102 "subject": {}, |
102 'type': 'chat', | 103 "type": "chat", |
103 'extra': {}, | 104 "extra": {}, |
104 'timestamp': time.time(), | 105 "timestamp": time.time(), |
105 } | 106 } |
106 client.generateMessageXML(mess_data) | 107 client.generateMessageXML(mess_data) |
107 client.send(mess_data['xml']) | 108 client.send(mess_data["xml"]) |
108 else: | 109 else: |
109 message_elt = appdata[u'xml'] | 110 message_elt = appdata[u"xml"] |
110 assert message_elt.name == u'message' | 111 assert message_elt.name == u"message" |
111 message_elt.addElement("body", content=msg) | 112 message_elt.addElement("body", content=msg) |
112 | 113 |
113 def setState(self, state): | 114 def setState(self, state): |
114 client = self.user.client | 115 client = self.user.client |
115 old_state = self.state | 116 old_state = self.state |
116 super(Context, self).setState(state) | 117 super(Context, self).setState(state) |
117 log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) | 118 log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) |
118 | 119 |
119 if state == potr.context.STATE_PLAINTEXT: | 120 if state == potr.context.STATE_PLAINTEXT: |
120 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()} | 121 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % { |
121 self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile) | 122 "other_jid": self.peer.full() |
123 } | |
124 self.host.bridge.otrState( | |
125 OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile | |
126 ) | |
122 elif state == potr.context.STATE_ENCRYPTED: | 127 elif state == potr.context.STATE_ENCRYPTED: |
123 try: | 128 try: |
124 trusted = self.getCurrentTrust() | 129 trusted = self.getCurrentTrust() |
125 except TypeError: | 130 except TypeError: |
126 trusted = False | 131 trusted = False |
127 trusted_str = _(u"trusted") if trusted else _(u"untrusted") | 132 trusted_str = _(u"trusted") if trusted else _(u"untrusted") |
128 | 133 |
129 if old_state == potr.context.STATE_ENCRYPTED: | 134 if old_state == potr.context.STATE_ENCRYPTED: |
130 feedback = D_(u"{trusted} OTR conversation with {other_jid} REFRESHED").format( | 135 feedback = D_( |
131 trusted = trusted_str, | 136 u"{trusted} OTR conversation with {other_jid} REFRESHED" |
132 other_jid = self.peer.full()) | 137 ).format(trusted=trusted_str, other_jid=self.peer.full()) |
133 else: | 138 else: |
134 feedback = D_(u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}").format( | 139 feedback = D_( |
135 trusted = trusted_str, | 140 u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}" |
136 other_jid = self.peer.full(), | 141 ).format( |
137 extra_info = NO_ADV_FEATURES) | 142 trusted=trusted_str, |
138 self.host.bridge.otrState(OTR_STATE_ENCRYPTED, self.peer.full(), client.profile) | 143 other_jid=self.peer.full(), |
144 extra_info=NO_ADV_FEATURES, | |
145 ) | |
146 self.host.bridge.otrState( | |
147 OTR_STATE_ENCRYPTED, self.peer.full(), client.profile | |
148 ) | |
139 elif state == potr.context.STATE_FINISHED: | 149 elif state == potr.context.STATE_FINISHED: |
140 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(other_jid = self.peer.full()) | 150 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format( |
141 self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile) | 151 other_jid=self.peer.full() |
152 ) | |
153 self.host.bridge.otrState( | |
154 OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile | |
155 ) | |
142 else: | 156 else: |
143 log.error(D_(u"Unknown OTR state")) | 157 log.error(D_(u"Unknown OTR state")) |
144 return | 158 return |
145 | 159 |
146 client.feedback(self.peer, feedback) | 160 client.feedback(self.peer, feedback) |
174 | 188 |
175 def savePrivkey(self): | 189 def savePrivkey(self): |
176 log.debug(u"savePrivkey") | 190 log.debug(u"savePrivkey") |
177 if self.privkey is None: | 191 if self.privkey is None: |
178 raise exceptions.InternalError(_(u"Save is called but privkey is None !")) | 192 raise exceptions.InternalError(_(u"Save is called but privkey is None !")) |
179 priv_key = self.privkey.serializePrivateKey().encode('hex') | 193 priv_key = self.privkey.serializePrivateKey().encode("hex") |
180 d = self.host.memory.encryptValue(priv_key, self.client.profile) | 194 d = self.host.memory.encryptValue(priv_key, self.client.profile) |
195 | |
181 def save_encrypted_key(encrypted_priv_key): | 196 def save_encrypted_key(encrypted_priv_key): |
182 self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key | 197 self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key |
198 | |
183 d.addCallback(save_encrypted_key) | 199 d.addCallback(save_encrypted_key) |
184 | 200 |
185 def loadTrusts(self): | 201 def loadTrusts(self): |
186 trust_data = self.client._otr_data.get('trust', {}) | 202 trust_data = self.client._otr_data.get("trust", {}) |
187 for jid_, jid_data in trust_data.iteritems(): | 203 for jid_, jid_data in trust_data.iteritems(): |
188 for fingerprint, trust_level in jid_data.iteritems(): | 204 for fingerprint, trust_level in jid_data.iteritems(): |
189 log.debug(u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(jid=jid_, fingerprint=fingerprint, trust_level=trust_level)) | 205 log.debug( |
206 u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format( | |
207 jid=jid_, fingerprint=fingerprint, trust_level=trust_level | |
208 ) | |
209 ) | |
190 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level | 210 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level |
191 | 211 |
192 def saveTrusts(self): | 212 def saveTrusts(self): |
193 log.debug(u"saving trusts for {profile}".format(profile=self.client.profile)) | 213 log.debug(u"saving trusts for {profile}".format(profile=self.client.profile)) |
194 log.debug(u"trusts = {}".format(self.client._otr_data['trust'])) | 214 log.debug(u"trusts = {}".format(self.client._otr_data["trust"])) |
195 self.client._otr_data.force('trust') | 215 self.client._otr_data.force("trust") |
196 | 216 |
197 def setTrust(self, other_jid, fingerprint, trustLevel): | 217 def setTrust(self, other_jid, fingerprint, trustLevel): |
198 try: | 218 try: |
199 trust_data = self.client._otr_data['trust'] | 219 trust_data = self.client._otr_data["trust"] |
200 except KeyError: | 220 except KeyError: |
201 trust_data = {} | 221 trust_data = {} |
202 self.client._otr_data['trust'] = trust_data | 222 self.client._otr_data["trust"] = trust_data |
203 jid_data = trust_data.setdefault(other_jid.full(), {}) | 223 jid_data = trust_data.setdefault(other_jid.full(), {}) |
204 jid_data[fingerprint] = trustLevel | 224 jid_data[fingerprint] = trustLevel |
205 super(Account, self).setTrust(other_jid, fingerprint, trustLevel) | 225 super(Account, self).setTrust(other_jid, fingerprint, trustLevel) |
206 | 226 |
207 | 227 |
208 class ContextManager(object): | 228 class ContextManager(object): |
209 | |
210 def __init__(self, host, client): | 229 def __init__(self, host, client): |
211 self.host = host | 230 self.host = host |
212 self.account = Account(host, client) | 231 self.account = Account(host, client) |
213 self.contexts = {} | 232 self.contexts = {} |
214 | 233 |
215 def startContext(self, other_jid): | 234 def startContext(self, other_jid): |
216 assert isinstance(other_jid, jid.JID) | 235 assert isinstance(other_jid, jid.JID) |
217 context = self.contexts.setdefault(other_jid, Context(self.host, self.account, other_jid)) | 236 context = self.contexts.setdefault( |
237 other_jid, Context(self.host, self.account, other_jid) | |
238 ) | |
218 return context | 239 return context |
219 | 240 |
220 def getContextForUser(self, other): | 241 def getContextForUser(self, other): |
221 log.debug(u"getContextForUser [%s]" % other) | 242 log.debug(u"getContextForUser [%s]" % other) |
222 if not other.resource: | 243 if not other.resource: |
223 log.warning(u"getContextForUser called with a bare jid: %s" % other.full()) | 244 log.warning(u"getContextForUser called with a bare jid: %s" % other.full()) |
224 return self.startContext(other) | 245 return self.startContext(other) |
225 | 246 |
226 | 247 |
227 class OTR(object): | 248 class OTR(object): |
228 | |
229 def __init__(self, host): | 249 def __init__(self, host): |
230 log.info(_(u"OTR plugin initialization")) | 250 log.info(_(u"OTR plugin initialization")) |
231 self.host = host | 251 self.host = host |
232 self.context_managers = {} | 252 self.context_managers = {} |
233 self.skipped_profiles = set() # FIXME: OTR should not be skipped per profile, this need to be refactored | 253 self.skipped_profiles = ( |
234 self._p_hints = host.plugins[u'XEP-0334'] | 254 set() |
235 self._p_carbons = host.plugins[u'XEP-0280'] | 255 ) # FIXME: OTR should not be skipped per profile, this need to be refactored |
256 self._p_hints = host.plugins[u"XEP-0334"] | |
257 self._p_carbons = host.plugins[u"XEP-0280"] | |
236 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) | 258 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) |
237 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) | 259 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) |
238 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) | 260 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) |
239 host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR) # FIXME: must be removed, must be done on per-message basis | 261 host.bridge.addMethod( |
240 host.bridge.addSignal("otrState", ".plugin", signature='sss') # args: state, destinee_jid, profile | 262 "skipOTR", ".plugin", in_sign="s", out_sign="", method=self._skipOTR |
241 host.importMenu((OTR_MENU, D_(u"Start/Refresh")), self._otrStartRefresh, security_limit=0, help_string=D_(u"Start or refresh an OTR session"), type_=C.MENU_SINGLE) | 263 ) # FIXME: must be removed, must be done on per-message basis |
242 host.importMenu((OTR_MENU, D_(u"End session")), self._otrSessionEnd, security_limit=0, help_string=D_(u"Finish an OTR session"), type_=C.MENU_SINGLE) | 264 host.bridge.addSignal( |
243 host.importMenu((OTR_MENU, D_(u"Authenticate")), self._otrAuthenticate, security_limit=0, help_string=D_(u"Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) | 265 "otrState", ".plugin", signature="sss" |
244 host.importMenu((OTR_MENU, D_(u"Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE) | 266 ) # args: state, destinee_jid, profile |
267 host.importMenu( | |
268 (OTR_MENU, D_(u"Start/Refresh")), | |
269 self._otrStartRefresh, | |
270 security_limit=0, | |
271 help_string=D_(u"Start or refresh an OTR session"), | |
272 type_=C.MENU_SINGLE, | |
273 ) | |
274 host.importMenu( | |
275 (OTR_MENU, D_(u"End session")), | |
276 self._otrSessionEnd, | |
277 security_limit=0, | |
278 help_string=D_(u"Finish an OTR session"), | |
279 type_=C.MENU_SINGLE, | |
280 ) | |
281 host.importMenu( | |
282 (OTR_MENU, D_(u"Authenticate")), | |
283 self._otrAuthenticate, | |
284 security_limit=0, | |
285 help_string=D_(u"Authenticate user/see your fingerprint"), | |
286 type_=C.MENU_SINGLE, | |
287 ) | |
288 host.importMenu( | |
289 (OTR_MENU, D_(u"Drop private key")), | |
290 self._dropPrivKey, | |
291 security_limit=0, | |
292 type_=C.MENU_SINGLE, | |
293 ) | |
245 host.trigger.add("presenceReceived", self._presenceReceivedTrigger) | 294 host.trigger.add("presenceReceived", self._presenceReceivedTrigger) |
246 | 295 |
247 def _skipOTR(self, profile): | 296 def _skipOTR(self, profile): |
248 """Tell the backend to not handle OTR for this profile. | 297 """Tell the backend to not handle OTR for this profile. |
249 | 298 |
261 ctxMng = client._otr_context_manager = ContextManager(self.host, client) | 310 ctxMng = client._otr_context_manager = ContextManager(self.host, client) |
262 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile) | 311 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile) |
263 yield client._otr_data.load() | 312 yield client._otr_data.load() |
264 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) | 313 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) |
265 if encrypted_priv_key is not None: | 314 if encrypted_priv_key is not None: |
266 priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, client.profile) | 315 priv_key = yield self.host.memory.decryptValue( |
267 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0] | 316 encrypted_priv_key, client.profile |
317 ) | |
318 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey( | |
319 priv_key.decode("hex") | |
320 )[0] | |
268 else: | 321 else: |
269 ctxMng.account.privkey = None | 322 ctxMng.account.privkey = None |
270 ctxMng.account.loadTrusts() | 323 ctxMng.account.loadTrusts() |
271 | 324 |
272 def profileDisconnected(self, client): | 325 def profileDisconnected(self, client): |
283 @param menu_data: %(menu_data)s | 336 @param menu_data: %(menu_data)s |
284 @param profile: %(doc_profile)s | 337 @param profile: %(doc_profile)s |
285 """ | 338 """ |
286 client = self.host.getClient(profile) | 339 client = self.host.getClient(profile) |
287 try: | 340 try: |
288 to_jid = jid.JID(menu_data['jid']) | 341 to_jid = jid.JID(menu_data["jid"]) |
289 except KeyError: | 342 except KeyError: |
290 log.error(_(u"jid key is not present !")) | 343 log.error(_(u"jid key is not present !")) |
291 return defer.fail(exceptions.DataError) | 344 return defer.fail(exceptions.DataError) |
292 self.startRefresh(client, to_jid) | 345 self.startRefresh(client, to_jid) |
293 return {} | 346 return {} |
296 """Start or refresh an OTR session | 349 """Start or refresh an OTR session |
297 | 350 |
298 @param to_jid(jid.JID): jid to start encrypted session with | 351 @param to_jid(jid.JID): jid to start encrypted session with |
299 """ | 352 """ |
300 if not to_jid.resource: | 353 if not to_jid.resource: |
301 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 354 to_jid.resource = self.host.memory.getMainResource( |
355 client, to_jid | |
356 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored | |
302 otrctx = client._otr_context_manager.getContextForUser(to_jid) | 357 otrctx = client._otr_context_manager.getContextForUser(to_jid) |
303 query = otrctx.sendMessage(0, '?OTRv?') | 358 query = otrctx.sendMessage(0, "?OTRv?") |
304 otrctx.inject(query) | 359 otrctx.inject(query) |
305 | 360 |
306 def _otrSessionEnd(self, menu_data, profile): | 361 def _otrSessionEnd(self, menu_data, profile): |
307 """End an OTR session | 362 """End an OTR session |
308 | 363 |
309 @param menu_data: %(menu_data)s | 364 @param menu_data: %(menu_data)s |
310 @param profile: %(doc_profile)s | 365 @param profile: %(doc_profile)s |
311 """ | 366 """ |
312 client = self.host.getClient(profile) | 367 client = self.host.getClient(profile) |
313 try: | 368 try: |
314 to_jid = jid.JID(menu_data['jid']) | 369 to_jid = jid.JID(menu_data["jid"]) |
315 except KeyError: | 370 except KeyError: |
316 log.error(_(u"jid key is not present !")) | 371 log.error(_(u"jid key is not present !")) |
317 return defer.fail(exceptions.DataError) | 372 return defer.fail(exceptions.DataError) |
318 self.endSession(client, to_jid) | 373 self.endSession(client, to_jid) |
319 return {} | 374 return {} |
320 | 375 |
321 def endSession(self, client, to_jid): | 376 def endSession(self, client, to_jid): |
322 """End an OTR session""" | 377 """End an OTR session""" |
323 if not to_jid.resource: | 378 if not to_jid.resource: |
324 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 379 to_jid.resource = self.host.memory.getMainResource( |
380 client, to_jid | |
381 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored | |
325 otrctx = client._otr_context_manager.getContextForUser(to_jid) | 382 otrctx = client._otr_context_manager.getContextForUser(to_jid) |
326 otrctx.disconnect() | 383 otrctx.disconnect() |
327 return {} | 384 return {} |
328 | 385 |
329 def _otrAuthenticate(self, menu_data, profile): | 386 def _otrAuthenticate(self, menu_data, profile): |
332 @param menu_data: %(menu_data)s | 389 @param menu_data: %(menu_data)s |
333 @param profile: %(doc_profile)s | 390 @param profile: %(doc_profile)s |
334 """ | 391 """ |
335 client = self.host.getClient(profile) | 392 client = self.host.getClient(profile) |
336 try: | 393 try: |
337 to_jid = jid.JID(menu_data['jid']) | 394 to_jid = jid.JID(menu_data["jid"]) |
338 except KeyError: | 395 except KeyError: |
339 log.error(_(u"jid key is not present !")) | 396 log.error(_(u"jid key is not present !")) |
340 return defer.fail(exceptions.DataError) | 397 return defer.fail(exceptions.DataError) |
341 return self.authenticate(client, to_jid) | 398 return self.authenticate(client, to_jid) |
342 | 399 |
343 def authenticate(self, client, to_jid): | 400 def authenticate(self, client, to_jid): |
344 """Authenticate other user and see our own fingerprint""" | 401 """Authenticate other user and see our own fingerprint""" |
345 if not to_jid.resource: | 402 if not to_jid.resource: |
346 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 403 to_jid.resource = self.host.memory.getMainResource( |
404 client, to_jid | |
405 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored | |
347 ctxMng = client._otr_context_manager | 406 ctxMng = client._otr_context_manager |
348 otrctx = ctxMng.getContextForUser(to_jid) | 407 otrctx = ctxMng.getContextForUser(to_jid) |
349 priv_key = ctxMng.account.privkey | 408 priv_key = ctxMng.account.privkey |
350 | 409 |
351 if priv_key is None: | 410 if priv_key is None: |
352 # we have no private key yet | 411 # we have no private key yet |
353 dialog = xml_tools.XMLUI(C.XMLUI_DIALOG, | 412 dialog = xml_tools.XMLUI( |
354 dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, | 413 C.XMLUI_DIALOG, |
355 C.XMLUI_DATA_MESS: _(u"You have no private key yet, start an OTR conversation to have one"), | 414 dialog_opt={ |
356 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING | 415 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, |
357 }, | 416 C.XMLUI_DATA_MESS: _( |
358 title = _(u"No private key"), | 417 u"You have no private key yet, start an OTR conversation to have one" |
359 ) | 418 ), |
360 return {'xmlui': dialog.toXml()} | 419 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING, |
420 }, | |
421 title=_(u"No private key"), | |
422 ) | |
423 return {"xmlui": dialog.toXml()} | |
361 | 424 |
362 other_fingerprint = otrctx.getCurrentKey() | 425 other_fingerprint = otrctx.getCurrentKey() |
363 | 426 |
364 if other_fingerprint is None: | 427 if other_fingerprint is None: |
365 # we have a private key, but not the fingerprint of our correspondent | 428 # we have a private key, but not the fingerprint of our correspondent |
366 dialog = xml_tools.XMLUI(C.XMLUI_DIALOG, | 429 dialog = xml_tools.XMLUI( |
367 dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, | 430 C.XMLUI_DIALOG, |
368 C.XMLUI_DATA_MESS: _(u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one.").format(fingerprint=priv_key), | 431 dialog_opt={ |
369 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO | 432 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, |
370 }, | 433 C.XMLUI_DATA_MESS: _( |
371 title = _(u"Fingerprint"), | 434 u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one." |
372 ) | 435 ).format(fingerprint=priv_key), |
373 return {'xmlui': dialog.toXml()} | 436 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO, |
437 }, | |
438 title=_(u"Fingerprint"), | |
439 ) | |
440 return {"xmlui": dialog.toXml()} | |
374 | 441 |
375 def setTrust(raw_data, profile): | 442 def setTrust(raw_data, profile): |
376 # This method is called when authentication form is submited | 443 # This method is called when authentication form is submited |
377 data = xml_tools.XMLUIResult2DataFormResult(raw_data) | 444 data = xml_tools.XMLUIResult2DataFormResult(raw_data) |
378 if data['match'] == 'yes': | 445 if data["match"] == "yes": |
379 otrctx.setCurrentTrust(OTR_STATE_TRUSTED) | 446 otrctx.setCurrentTrust(OTR_STATE_TRUSTED) |
380 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") | 447 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") |
381 self.host.bridge.otrState(OTR_STATE_TRUSTED, to_jid.full(), client.profile) | 448 self.host.bridge.otrState( |
449 OTR_STATE_TRUSTED, to_jid.full(), client.profile | |
450 ) | |
382 else: | 451 else: |
383 otrctx.setCurrentTrust('') | 452 otrctx.setCurrentTrust("") |
384 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") | 453 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") |
385 self.host.bridge.otrState(OTR_STATE_UNTRUSTED, to_jid.full(), client.profile) | 454 self.host.bridge.otrState( |
386 note = xml_tools.XMLUI(C.XMLUI_DIALOG, dialog_opt = { | 455 OTR_STATE_UNTRUSTED, to_jid.full(), client.profile |
387 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, | 456 ) |
388 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer)} | 457 note = xml_tools.XMLUI( |
389 ) | 458 C.XMLUI_DIALOG, |
390 return {'xmlui': note.toXml()} | 459 dialog_opt={ |
460 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, | |
461 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer), | |
462 }, | |
463 ) | |
464 return {"xmlui": note.toXml()} | |
391 | 465 |
392 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) | 466 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) |
393 trusted = bool(otrctx.getCurrentTrust()) | 467 trusted = bool(otrctx.getCurrentTrust()) |
394 | 468 |
395 xmlui = xml_tools.XMLUI(C.XMLUI_FORM, title=_('Authentication (%s)') % to_jid.full(), submit_id=submit_id) | 469 xmlui = xml_tools.XMLUI( |
470 C.XMLUI_FORM, | |
471 title=_("Authentication (%s)") % to_jid.full(), | |
472 submit_id=submit_id, | |
473 ) | |
396 xmlui.addText(_(AUTH_TXT)) | 474 xmlui.addText(_(AUTH_TXT)) |
397 xmlui.addDivider() | 475 xmlui.addDivider() |
398 xmlui.addText(D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)) | 476 xmlui.addText( |
399 xmlui.addText(D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(fingerprint=other_fingerprint)) | 477 D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key) |
400 xmlui.addDivider('blank') | 478 ) |
401 xmlui.changeContainer('pairs') | 479 xmlui.addText( |
402 xmlui.addLabel(D_(u'Is your correspondent fingerprint the same as here ?')) | 480 D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format( |
403 xmlui.addList("match", [('yes', _('yes')),('no', _('no'))], ['yes' if trusted else 'no']) | 481 fingerprint=other_fingerprint |
404 return {'xmlui': xmlui.toXml()} | 482 ) |
483 ) | |
484 xmlui.addDivider("blank") | |
485 xmlui.changeContainer("pairs") | |
486 xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?")) | |
487 xmlui.addList( | |
488 "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"] | |
489 ) | |
490 return {"xmlui": xmlui.toXml()} | |
405 | 491 |
406 def _dropPrivKey(self, menu_data, profile): | 492 def _dropPrivKey(self, menu_data, profile): |
407 """Drop our private Key | 493 """Drop our private Key |
408 | 494 |
409 @param menu_data: %(menu_data)s | 495 @param menu_data: %(menu_data)s |
410 @param profile: %(doc_profile)s | 496 @param profile: %(doc_profile)s |
411 """ | 497 """ |
412 client = self.host.getClient(profile) | 498 client = self.host.getClient(profile) |
413 try: | 499 try: |
414 to_jid = jid.JID(menu_data['jid']) | 500 to_jid = jid.JID(menu_data["jid"]) |
415 if not to_jid.resource: | 501 if not to_jid.resource: |
416 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 502 to_jid.resource = self.host.memory.getMainResource( |
503 client, to_jid | |
504 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored | |
417 except KeyError: | 505 except KeyError: |
418 log.error(_(u"jid key is not present !")) | 506 log.error(_(u"jid key is not present !")) |
419 return defer.fail(exceptions.DataError) | 507 return defer.fail(exceptions.DataError) |
420 | 508 |
421 ctxMng = client._otr_context_manager | 509 ctxMng = client._otr_context_manager |
422 if ctxMng.account.privkey is None: | 510 if ctxMng.account.privkey is None: |
423 return {'xmlui': xml_tools.note(_(u"You don't have a private key yet !")).toXml()} | 511 return { |
512 "xmlui": xml_tools.note(_(u"You don't have a private key yet !")).toXml() | |
513 } | |
424 | 514 |
425 def dropKey(data, profile): | 515 def dropKey(data, profile): |
426 if C.bool(data['answer']): | 516 if C.bool(data["answer"]): |
427 # we end all sessions | 517 # we end all sessions |
428 for context in ctxMng.contexts.values(): | 518 for context in ctxMng.contexts.values(): |
429 context.disconnect() | 519 context.disconnect() |
430 ctxMng.account.privkey = None | 520 ctxMng.account.privkey = None |
431 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey will generate a new key, and save it | 521 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey will generate a new key, and save it |
432 return {'xmlui': xml_tools.note(D_(u"Your private key has been dropped")).toXml()} | 522 return { |
523 "xmlui": xml_tools.note( | |
524 D_(u"Your private key has been dropped") | |
525 ).toXml() | |
526 } | |
433 return {} | 527 return {} |
434 | 528 |
435 submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True) | 529 submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True) |
436 | 530 |
437 confirm = xml_tools.XMLUI(C.XMLUI_DIALOG, title=_(u'Confirm private key drop'), dialog_opt = {'type': C.XMLUI_DIALOG_CONFIRM, 'message': _(DROP_TXT)}, submit_id = submit_id) | 531 confirm = xml_tools.XMLUI( |
438 return {'xmlui': confirm.toXml()} | 532 C.XMLUI_DIALOG, |
533 title=_(u"Confirm private key drop"), | |
534 dialog_opt={"type": C.XMLUI_DIALOG_CONFIRM, "message": _(DROP_TXT)}, | |
535 submit_id=submit_id, | |
536 ) | |
537 return {"xmlui": confirm.toXml()} | |
439 | 538 |
440 def _receivedTreatment(self, data, client): | 539 def _receivedTreatment(self, data, client): |
441 from_jid = data['from'] | 540 from_jid = data["from"] |
442 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) | 541 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) |
443 otrctx = client._otr_context_manager.getContextForUser(from_jid) | 542 otrctx = client._otr_context_manager.getContextForUser(from_jid) |
444 | 543 |
445 try: | 544 try: |
446 message = data['message'].itervalues().next() # FIXME: Q&D fix for message refactoring, message is now a dict | 545 message = ( |
447 res = otrctx.receiveMessage(message.encode('utf-8')) | 546 data["message"].itervalues().next() |
547 ) # FIXME: Q&D fix for message refactoring, message is now a dict | |
548 res = otrctx.receiveMessage(message.encode("utf-8")) | |
448 except potr.context.UnencryptedMessage: | 549 except potr.context.UnencryptedMessage: |
449 encrypted = False | 550 encrypted = False |
450 if otrctx.state == potr.context.STATE_ENCRYPTED: | 551 if otrctx.state == potr.context.STATE_ENCRYPTED: |
451 log.warning(u"Received unencrypted message in an encrypted context (from {jid})".format( | 552 log.warning( |
452 jid = from_jid.full())) | 553 u"Received unencrypted message in an encrypted context (from {jid})".format( |
453 | 554 jid=from_jid.full() |
454 feedback=D_(u"WARNING: received unencrypted data in a supposedly encrypted context"), | 555 ) |
556 ) | |
557 | |
558 feedback = ( | |
559 D_( | |
560 u"WARNING: received unencrypted data in a supposedly encrypted context" | |
561 ), | |
562 ) | |
455 client.feedback(from_jid, feedback) | 563 client.feedback(from_jid, feedback) |
456 except StopIteration: | 564 except StopIteration: |
457 return data | 565 return data |
458 else: | 566 else: |
459 encrypted = True | 567 encrypted = True |
460 | 568 |
461 if encrypted: | 569 if encrypted: |
462 if res[0] != None: | 570 if res[0] != None: |
463 # decrypted messages handling. | 571 # decrypted messages handling. |
464 # receiveMessage() will return a tuple, the first part of which will be the decrypted message | 572 # receiveMessage() will return a tuple, the first part of which will be the decrypted message |
465 data['message'] = {'':res[0].decode('utf-8')} # FIXME: Q&D fix for message refactoring, message is now a dict | 573 data["message"] = { |
574 "": res[0].decode("utf-8") | |
575 } # FIXME: Q&D fix for message refactoring, message is now a dict | |
466 try: | 576 try: |
467 # we want to keep message in history, even if no store is requested in message hints | 577 # we want to keep message in history, even if no store is requested in message hints |
468 del data[u'history'] | 578 del data[u"history"] |
469 except KeyError: | 579 except KeyError: |
470 pass | 580 pass |
471 # TODO: add skip history as an option, but by default we don't skip it | 581 # TODO: add skip history as an option, but by default we don't skip it |
472 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to frontends, but we don't want it in history | 582 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to frontends, but we don't want it in history |
473 else: | 583 else: |
474 log.warning(u"An encrypted message was expected, but got {}".format(data['message'])) | 584 log.warning( |
475 raise failure.Failure(exceptions.CancelError('Cancelled by OTR')) # no message at all (no history, no signal) | 585 u"An encrypted message was expected, but got {}".format( |
586 data["message"] | |
587 ) | |
588 ) | |
589 raise failure.Failure( | |
590 exceptions.CancelError("Cancelled by OTR") | |
591 ) # no message at all (no history, no signal) | |
476 return data | 592 return data |
477 | 593 |
478 def _receivedTreatmentForSkippedProfiles(self, data): | 594 def _receivedTreatmentForSkippedProfiles(self, data): |
479 """This profile must be skipped because the frontend manages OTR itself, | 595 """This profile must be skipped because the frontend manages OTR itself, |
480 | 596 |
481 but we still need to check if the message must be stored in history or not | 597 but we still need to check if the message must be stored in history or not |
482 """ | 598 """ |
483 # XXX: FIXME: this should not be done on a per-profile basis, but per-message | 599 # XXX: FIXME: this should not be done on a per-profile basis, but per-message |
484 try: | 600 try: |
485 message = data['message'].itervalues().next().encode('utf-8') # FIXME: Q&D fix for message refactoring, message is now a dict | 601 message = ( |
602 data["message"].itervalues().next().encode("utf-8") | |
603 ) # FIXME: Q&D fix for message refactoring, message is now a dict | |
486 except StopIteration: | 604 except StopIteration: |
487 return data | 605 return data |
488 if message.startswith(potr.proto.OTRTAG): | 606 if message.startswith(potr.proto.OTRTAG): |
489 # FIXME: it may be better to cancel the message and send it direclty to bridge | 607 # FIXME: it may be better to cancel the message and send it direclty to bridge |
490 # this is used by Libervia, but this may send garbage message to other frontends | 608 # this is used by Libervia, but this may send garbage message to other frontends |
491 # if they are used at the same time as Libervia. | 609 # if they are used at the same time as Libervia. |
492 # Hard to avoid with decryption on Libervia though. | 610 # Hard to avoid with decryption on Libervia though. |
493 data[u'history'] = C.HISTORY_SKIP | 611 data[u"history"] = C.HISTORY_SKIP |
494 return data | 612 return data |
495 | 613 |
496 def MessageReceivedTrigger(self, client, message_elt, post_treat): | 614 def MessageReceivedTrigger(self, client, message_elt, post_treat): |
497 if message_elt.getAttribute('type') == C.MESS_TYPE_GROUPCHAT: | 615 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: |
498 # OTR is not possible in group chats | 616 # OTR is not possible in group chats |
499 return True | 617 return True |
500 if client.profile in self.skipped_profiles: | 618 if client.profile in self.skipped_profiles: |
501 post_treat.addCallback(self._receivedTreatmentForSkippedProfiles) | 619 post_treat.addCallback(self._receivedTreatmentForSkippedProfiles) |
502 else: | 620 else: |
503 post_treat.addCallback(self._receivedTreatment, client) | 621 post_treat.addCallback(self._receivedTreatment, client) |
504 return True | 622 return True |
505 | 623 |
506 def _sendMessageDataTrigger(self, client, mess_data): | 624 def _sendMessageDataTrigger(self, client, mess_data): |
507 if not 'OTR' in mess_data: | 625 if not "OTR" in mess_data: |
508 return | 626 return |
509 otrctx = mess_data['OTR'] | 627 otrctx = mess_data["OTR"] |
510 message_elt = mess_data['xml'] | 628 message_elt = mess_data["xml"] |
511 to_jid = mess_data['to'] | 629 to_jid = mess_data["to"] |
512 if otrctx.state == potr.context.STATE_ENCRYPTED: | 630 if otrctx.state == potr.context.STATE_ENCRYPTED: |
513 log.debug(u"encrypting message") | 631 log.debug(u"encrypting message") |
514 body = None | 632 body = None |
515 for child in list(message_elt.children): | 633 for child in list(message_elt.children): |
516 if child.name == 'body': | 634 if child.name == "body": |
517 # we remove all unencrypted body, | 635 # we remove all unencrypted body, |
518 # and will only encrypt the first one | 636 # and will only encrypt the first one |
519 if body is None: | 637 if body is None: |
520 body = child | 638 body = child |
521 message_elt.children.remove(child) | 639 message_elt.children.remove(child) |
522 elif child.name == 'html': | 640 elif child.name == "html": |
523 # we don't want any XHTML-IM element | 641 # we don't want any XHTML-IM element |
524 message_elt.children.remove(child) | 642 message_elt.children.remove(child) |
525 if body is None: | 643 if body is None: |
526 log.warning(u"No message found") | 644 log.warning(u"No message found") |
527 else: | 645 else: |
528 self._p_carbons.setPrivate(message_elt) | 646 self._p_carbons.setPrivate(message_elt) |
529 otrctx.sendMessage(0, unicode(body).encode('utf-8'), appdata=mess_data) | 647 otrctx.sendMessage(0, unicode(body).encode("utf-8"), appdata=mess_data) |
530 else: | 648 else: |
531 feedback = D_(u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. " | 649 feedback = D_( |
532 u"Either close your own side, or refresh the session.") | 650 u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. " |
651 u"Either close your own side, or refresh the session." | |
652 ) | |
533 log.warning(_(u"Message discarded because closed encryption channel")) | 653 log.warning(_(u"Message discarded because closed encryption channel")) |
534 client.feedback(to_jid, feedback) | 654 client.feedback(to_jid, feedback) |
535 raise failure.Failure(exceptions.CancelError(u'Cancelled by OTR plugin')) | 655 raise failure.Failure(exceptions.CancelError(u"Cancelled by OTR plugin")) |
536 | 656 |
537 def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): | 657 def sendMessageTrigger( |
538 if mess_data['type'] == 'groupchat': | 658 self, client, mess_data, pre_xml_treatments, post_xml_treatments |
659 ): | |
660 if mess_data["type"] == "groupchat": | |
539 return True | 661 return True |
540 if client.profile in self.skipped_profiles: # FIXME: should not be done on a per-profile basis | 662 if ( |
663 client.profile in self.skipped_profiles | |
664 ): # FIXME: should not be done on a per-profile basis | |
541 return True | 665 return True |
542 to_jid = copy.copy(mess_data['to']) | 666 to_jid = copy.copy(mess_data["to"]) |
543 if not to_jid.resource: | 667 if not to_jid.resource: |
544 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: full jid may not be known | 668 to_jid.resource = self.host.memory.getMainResource( |
669 client, to_jid | |
670 ) # FIXME: full jid may not be known | |
545 otrctx = client._otr_context_manager.getContextForUser(to_jid) | 671 otrctx = client._otr_context_manager.getContextForUser(to_jid) |
546 if otrctx.state != potr.context.STATE_PLAINTEXT: | 672 if otrctx.state != potr.context.STATE_PLAINTEXT: |
547 self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_COPY) | 673 self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_COPY) |
548 self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_PERMANENT_STORE) | 674 self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_PERMANENT_STORE) |
549 mess_data['OTR'] = otrctx # this indicate that encryption is needed in sendMessageData trigger | 675 mess_data[ |
550 if not mess_data['to'].resource: # if not resource was given, we force it here | 676 "OTR" |
551 mess_data['to'] = to_jid | 677 ] = ( |
678 otrctx | |
679 ) # this indicate that encryption is needed in sendMessageData trigger | |
680 if not mess_data[ | |
681 "to" | |
682 ].resource: # if not resource was given, we force it here | |
683 mess_data["to"] = to_jid | |
552 return True | 684 return True |
553 | 685 |
554 def _presenceReceivedTrigger(self, entity, show, priority, statuses, profile): | 686 def _presenceReceivedTrigger(self, entity, show, priority, statuses, profile): |
555 if show != C.PRESENCE_UNAVAILABLE: | 687 if show != C.PRESENCE_UNAVAILABLE: |
556 return True | 688 return True |
557 client = self.host.getClient(profile) | 689 client = self.host.getClient(profile) |
558 if not entity.resource: | 690 if not entity.resource: |
559 try: | 691 try: |
560 entity.resource = self.host.memory.getMainResource(client, entity) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 692 entity.resource = self.host.memory.getMainResource( |
693 client, entity | |
694 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored | |
561 except exceptions.UnknownEntityError: | 695 except exceptions.UnknownEntityError: |
562 return True # entity was not connected | 696 return True # entity was not connected |
563 if entity in client._otr_context_manager.contexts: | 697 if entity in client._otr_context_manager.contexts: |
564 otrctx = client._otr_context_manager.getContextForUser(entity) | 698 otrctx = client._otr_context_manager.getContextForUser(entity) |
565 otrctx.disconnect() | 699 otrctx.disconnect() |