core (memory/encryption): encryption session are now restored on client connection

This commit is contained in:
Goffi 2020-03-22 18:39:12 +01:00
commit 900daa7ad9
4 changed files with 61 additions and 34 deletions

View file

@ -849,12 +849,14 @@ class SAT(service.Service):
profile_key=C.PROF_KEY_NONE):
client = self.getClient(profile_key)
to_jid = jid.JID(to_jid_s)
return client.encryption.start(to_jid, namespace or None, replace)
return defer.ensureDeferred(
client.encryption.start(to_jid, namespace or None, replace))
def _messageEncryptionStop(self, to_jid_s, profile_key=C.PROF_KEY_NONE):
client = self.getClient(profile_key)
to_jid = jid.JID(to_jid_s)
return client.encryption.stop(to_jid)
return defer.ensureDeferred(
client.encryption.stop(to_jid))
def _messageEncryptionGet(self, to_jid_s, profile_key=C.PROF_KEY_NONE):
client = self.getClient(profile_key)

View file

@ -209,6 +209,8 @@ class SatXMPPEntity:
port, max_retries,
)
await entity.encryption.loadSessions()
entity._createSubProtocols()
entity.fallBack = SatFallbackHandler(host)

View file

@ -17,18 +17,20 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import copy
from functools import partial
from sat.core.i18n import D_, _
from sat.core.constants import Const as C
from sat.core import exceptions
from collections import namedtuple
from sat.core.log import getLogger
from sat.tools.common import data_format
from twisted.words.protocols.jabber import jid
from twisted.internet import defer
from twisted.python import failure
import copy
log = getLogger(__name__)
from sat.core.i18n import D_, _
from sat.core.constants import Const as C
from sat.core import exceptions
from sat.core.log import getLogger
from sat.tools.common import data_format
from sat.tools import utils
from sat.memory import persistent
log = getLogger(__name__)
@ -39,18 +41,38 @@ EncryptionPlugin = namedtuple("EncryptionPlugin", ("instance",
"directed"))
class EncryptionHandler(object):
class EncryptionHandler:
"""Class to handle encryption sessions for a client"""
plugins = [] # plugin able to encrypt messages
def __init__(self, client):
self.client = client
self._sessions = {} # bare_jid ==> encryption_data
self._stored_session = persistent.PersistentDict(
"core:encryption", profile=client.profile)
@property
def host(self):
return self.client.host_app
async def loadSessions(self):
"""Load persistent sessions"""
await self._stored_session.load()
start_d_list = []
for entity_jid_s, namespace in self._stored_session.items():
entity = jid.JID(entity_jid_s)
start_d_list.append(defer.ensureDeferred(self.start(entity, namespace)))
if start_d_list:
result = await defer.DeferredList(start_d_list)
for idx, (success, err) in enumerate(result):
if not success:
entity_jid_s, namespace = list(self._stored_session.items())[idx]
log.warning(_(
"Could not restart {namespace!r} encryption with {entity}: {err}"
).format(namespace=namespace, entity=entity_jid_s, err=err))
log.info(_("encryption sessions restored"))
@classmethod
def registerPlugin(cls, plg_instance, name, namespace, priority=0, directed=False):
"""Register a plugin handling an encryption algorithm
@ -142,40 +164,41 @@ class EncryptionHandler(object):
return data_format.serialise(bridge_data)
def _startEncryption(self, plugin, entity):
async def _startEncryption(self, plugin, entity):
"""Start encryption with a plugin
This method must be called just before adding a plugin session.
StartEncryptionn method of plugin will be called if it exists.
"""
if not plugin.directed:
await self._stored_session.aset(entity.userhost(), plugin.namespace)
try:
start_encryption = plugin.instance.startEncryption
except AttributeError:
log.debug("No startEncryption method found for {plugin}".format(
plugin = plugin.namespace))
return defer.succeed(None)
log.debug(f"No startEncryption method found for {plugin.namespace}")
else:
# we copy entity to avoid having the resource changed by stop_encryption
return defer.maybeDeferred(start_encryption, self.client, copy.copy(entity))
await utils.asDeferred(start_encryption, self.client, copy.copy(entity))
def _stopEncryption(self, plugin, entity):
async def _stopEncryption(self, plugin, entity):
"""Stop encryption with a plugin
This method must be called just before removing a plugin session.
StopEncryptionn method of plugin will be called if it exists.
"""
try:
await self._stored_session.adel(entity.userhost())
except KeyError:
pass
try:
stop_encryption = plugin.instance.stopEncryption
except AttributeError:
log.debug("No stopEncryption method found for {plugin}".format(
plugin = plugin.namespace))
return defer.succeed(None)
log.debug(f"No stopEncryption method found for {plugin.namespace}")
else:
# we copy entity to avoid having the resource changed by stop_encryption
return defer.maybeDeferred(stop_encryption, self.client, copy.copy(entity))
return utils.asDeferred(stop_encryption, self.client, copy.copy(entity))
@defer.inlineCallbacks
def start(self, entity, namespace=None, replace=False):
async def start(self, entity, namespace=None, replace=False):
"""Start an encryption session with an entity
@param entity(jid.JID): entity to start an encryption session with
@ -209,7 +232,7 @@ class EncryptionHandler(object):
# there is a conflict, but replacement is requested
# so we stop previous encryption to use new one
del self._sessions[bare_jid]
yield self._stopEncryption(former_plugin, entity)
await self._stopEncryption(former_plugin, entity)
else:
msg = (_("Session with {bare_jid} is already encrypted with {name}. "
"Please stop encryption session before changing algorithm.")
@ -233,7 +256,7 @@ class EncryptionHandler(object):
elif entity.resource:
raise ValueError(_("{name} encryption must be used with bare jids."))
yield self._startEncryption(plugin, entity)
await self._startEncryption(plugin, entity)
self._sessions[entity.userhostJID()] = data
log.info(_("Encryption session has been set for {entity_jid} with "
"{encryption_name}").format(
@ -254,14 +277,13 @@ class EncryptionHandler(object):
self.client.feedback(bare_jid, msg)
@defer.inlineCallbacks
def stop(self, entity, namespace=None):
async def stop(self, entity, namespace=None):
"""Stop an encryption session with an entity
@param entity(jid.JID): entity with who the encryption session must be stopped
must be bare jid if the algorithm encrypt for all devices
@param namespace(unicode): namespace of the session to stop
when specified, used to check we stop the right encryption session
when specified, used to check that we stop the right encryption session
"""
session = self.getSession(entity.userhostJID())
if not session:
@ -295,12 +317,12 @@ class EncryptionHandler(object):
# we stop the whole session
# see comment below for deleting session before stopping encryption
del self._sessions[entity.userhostJID()]
yield self._stopEncryption(plugin, entity)
await self._stopEncryption(plugin, entity)
else:
# plugin's stopEncryption may call stop again (that's the case with OTR)
# so we need to remove plugin from session before calling self._stopEncryption
del self._sessions[entity.userhostJID()]
yield self._stopEncryption(plugin, entity)
await self._stopEncryption(plugin, entity)
log.info(_("encryption session stopped with entity {entity}").format(
entity=entity.full()))
@ -390,7 +412,7 @@ class EncryptionHandler(object):
def _onMenuUnencrypted(cls, data, host, profile):
client = host.getClient(profile)
peer_jid = jid.JID(data['jid']).userhostJID()
d = client.encryption.stop(peer_jid)
d = defer.ensureDeferred(client.encryption.stop(peer_jid))
d.addCallback(lambda __: {})
return d
@ -400,7 +422,8 @@ class EncryptionHandler(object):
peer_jid = jid.JID(data['jid'])
if not plg.directed:
peer_jid = peer_jid.userhostJID()
d = client.encryption.start(peer_jid, plg.namespace, replace=True)
d = defer.ensureDeferred(
client.encryption.start(peer_jid, plg.namespace, replace=True))
d.addCallback(lambda __: {})
return d

View file

@ -169,12 +169,12 @@ class Context(potr.context.Context):
feedback = _("/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {
"other_jid": self.peer.full()
}
d = client.encryption.stop(self.peer, NS_OTR)
d = defer.ensureDeferred(client.encryption.stop(self.peer, NS_OTR))
d.addCallback(self.stopCb, feedback=feedback)
d.addErrback(self.stopEb)
return
elif state == potr.context.STATE_ENCRYPTED:
client.encryption.start(self.peer, NS_OTR)
defer.ensureDeferred(client.encryption.start(self.peer, NS_OTR))
try:
trusted = self.isTrusted()
except TypeError:
@ -201,7 +201,7 @@ class Context(potr.context.Context):
feedback = D_("OTR conversation with {other_jid} is FINISHED").format(
other_jid=self.peer.full()
)
d = client.encryption.stop(self.peer, NS_OTR)
d = defer.ensureDeferred(client.encryption.stop(self.peer, NS_OTR))
d.addCallback(self.stopCb, feedback=feedback)
d.addErrback(self.stopEb)
return
@ -808,7 +808,7 @@ class OTR(object):
otrctx = client._otr_context_manager.getContextForUser(to_jid)
if otrctx.state != potr.context.STATE_PLAINTEXT:
client.encryption.start(to_jid, NS_OTR)
defer.ensureDeferred(client.encryption.start(to_jid, NS_OTR))
client.encryption.setEncryptionFlag(mess_data)
if not mess_data["to"].resource:
# if not resource was given, we force it here