plugin XEP-0077, XEP-0100: Adapt to component, and modernize:
- Plugin XEP-0077 can now be used with component, allowing to register methods to return registration form, and to (un)register. - Plugin XEP-0077 now advertises its feature in disco. - Plugin XEP-0100 has been modernized a bit: it is one of the older plugin in Libervia, and it has now some type hints, models and async methods. - Plugin XEP-0100's bridge method `gateways_find` now returns a serialised dict with relevant data. Former XMLUI version has been moved to `gateways_find_xmlui`. rel 449
This commit is contained in:
parent
5c056c049b
commit
c0a9fa611a
1 changed files with 469 additions and 168 deletions
|
|
@ -17,15 +17,23 @@
|
|||
# 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/>.
|
||||
|
||||
from libervia.backend.core.i18n import _, D_
|
||||
from libervia.backend.core.constants import Const as C
|
||||
from typing import cast
|
||||
from pydantic import BaseModel, Field
|
||||
from twisted.internet import defer, reactor
|
||||
from twisted.words.protocols.jabber import jid
|
||||
from wokkel import disco
|
||||
|
||||
from libervia.backend.core import exceptions
|
||||
from libervia.backend.tools import xml_tools
|
||||
from libervia.backend.core.constants import Const as C
|
||||
from libervia.backend.core.core_types import SatXMPPEntity
|
||||
from libervia.backend.core.i18n import D_, _
|
||||
from libervia.backend.core.log import getLogger
|
||||
from libervia.backend.models.core import DiscoIdentity
|
||||
from libervia.backend.models.types import StrictJIDType
|
||||
from libervia.backend.tools import xml_tools
|
||||
|
||||
log = getLogger(__name__)
|
||||
from twisted.words.protocols.jabber import jid
|
||||
from twisted.internet import reactor, defer
|
||||
|
||||
|
||||
PLUGIN_INFO = {
|
||||
C.PI_NAME: "Gateways Plugin",
|
||||
|
|
@ -38,8 +46,12 @@ PLUGIN_INFO = {
|
|||
}
|
||||
|
||||
WARNING_MSG = D_(
|
||||
"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts.
|
||||
But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company)."""
|
||||
"Please exercise caution. Gateways facilitate the use of external instant messaging "
|
||||
"platforms (legacy IM), enabling you to view your contacts as XMPP contacts. "
|
||||
"However, this process routes all messages through the external legacy IM server, "
|
||||
"raising significant privacy concerns. Specifically, it is possible for the external "
|
||||
"server, often operated by a private company, to monitor, record, and analyze all "
|
||||
"messages that traverse the gateway."
|
||||
)
|
||||
|
||||
GATEWAY_TIMEOUT = 10 # time to wait before cancelling a gateway disco info, in seconds
|
||||
|
|
@ -54,9 +66,22 @@ TYPE_DESCRIPTIONS = {
|
|||
"gadu-gadu": D_("Gadu-Gadu"),
|
||||
"aim": D_("AOL Instant Messenger"),
|
||||
"msn": D_("Windows Live Messenger"),
|
||||
"smtp": D_("Email"),
|
||||
}
|
||||
|
||||
|
||||
class GatewayData(BaseModel):
|
||||
entity: StrictJIDType
|
||||
identities: list[DiscoIdentity]
|
||||
|
||||
|
||||
class FoundGateways(BaseModel):
|
||||
available: list[GatewayData]
|
||||
unavailable: list[StrictJIDType] = Field(
|
||||
description="Gateways registered but not answering."
|
||||
)
|
||||
|
||||
|
||||
class XEP_0100(object):
|
||||
def __init__(self, host):
|
||||
log.info(_("Gateways plugin initialization"))
|
||||
|
|
@ -69,7 +94,14 @@ class XEP_0100(object):
|
|||
".plugin",
|
||||
in_sign="ss",
|
||||
out_sign="s",
|
||||
method=self._find_gateways,
|
||||
method=self._gateways_find,
|
||||
)
|
||||
host.bridge.add_method(
|
||||
"gateways_find_xmlui",
|
||||
".plugin",
|
||||
in_sign="ss",
|
||||
out_sign="s",
|
||||
method=self._gateways_find_xmlui,
|
||||
)
|
||||
host.bridge.add_method(
|
||||
"gateway_register",
|
||||
|
|
@ -77,6 +109,7 @@ class XEP_0100(object):
|
|||
in_sign="ss",
|
||||
out_sign="s",
|
||||
method=self._gateway_register,
|
||||
async_=True,
|
||||
)
|
||||
self.__menu_id = host.register_callback(self._gateways_menu, with_data=True)
|
||||
self.__selected_id = host.register_callback(
|
||||
|
|
@ -101,7 +134,7 @@ class XEP_0100(object):
|
|||
)
|
||||
except RuntimeError:
|
||||
raise exceptions.DataError(_("Invalid JID"))
|
||||
d = self.gateways_find(jid_, profile)
|
||||
d = self.gateways_find_raw(jid_, profile)
|
||||
d.addCallback(self._gateways_result_2_xmlui, jid_)
|
||||
d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()})
|
||||
return d
|
||||
|
|
@ -158,7 +191,8 @@ class XEP_0100(object):
|
|||
if category != "gateway":
|
||||
log.error(
|
||||
_(
|
||||
'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"'
|
||||
'INTERNAL ERROR: identity category should always be "gateway" in '
|
||||
'_getTypeString, got "%s"'
|
||||
)
|
||||
% category
|
||||
)
|
||||
|
|
@ -174,22 +208,83 @@ class XEP_0100(object):
|
|||
self.host.presence_set(jid_, profile_key=profile)
|
||||
|
||||
def _gateway_register(self, target_jid_s, profile_key=C.PROF_KEY_NONE):
|
||||
d = self.gateway_register(jid.JID(target_jid_s), profile_key)
|
||||
client = self.host.get_client(profile_key)
|
||||
d = self.gateway_register(client, jid.JID(target_jid_s))
|
||||
d.addCallback(lambda xmlui: xmlui.toXml())
|
||||
return d
|
||||
|
||||
def gateway_register(self, target_jid, profile_key=C.PROF_KEY_NONE):
|
||||
def gateway_register(
|
||||
self, client: SatXMPPEntity, target_jid: jid.JID
|
||||
) -> defer.Deferred:
|
||||
"""Register gateway using in-band registration, then log-in to gateway"""
|
||||
profile = self.host.memory.get_profile_name(profile_key)
|
||||
assert profile
|
||||
d = self.host.plugins["XEP-0077"].in_band_register(
|
||||
target_jid, self._registration_successful, profile
|
||||
return defer.ensureDeferred(
|
||||
self.host.plugins["XEP-0077"].in_band_register(
|
||||
client, target_jid, self._registration_successful
|
||||
)
|
||||
)
|
||||
|
||||
def _gateways_find(self, target_jid_s: str, profile_key: str) -> defer.Deferred[str]:
|
||||
client = self.host.get_client(profile_key)
|
||||
target_jid = jid.JID(target_jid_s) if target_jid_s else client.server_jid
|
||||
d = defer.ensureDeferred(self.gateways_find(client, target_jid))
|
||||
d.addCallback(lambda found_gateways: found_gateways.model_dump_json())
|
||||
# The Deferred will actually return a str due to `model_dump_json`, but type
|
||||
# checker doesn't get that.
|
||||
d = cast(defer.Deferred[str], d)
|
||||
return d
|
||||
|
||||
def _infos_received(self, dl_result, items, target, client):
|
||||
"""Find disco infos about entity, to check if it is a gateway"""
|
||||
async def gateways_find(
|
||||
self, client: SatXMPPEntity, target: jid.JID
|
||||
) -> FoundGateways:
|
||||
"""Find gateways and convert FoundGateways instance."""
|
||||
gateways_data = await self.gateways_find_raw(client, target)
|
||||
available = []
|
||||
unavailable = []
|
||||
for gw_available, data in gateways_data:
|
||||
if gw_available:
|
||||
data = cast(tuple[jid.JID, list[tuple[tuple[str, str], str]]], data)
|
||||
identities = []
|
||||
for (category, type_), name in data[1]:
|
||||
identities.append(
|
||||
DiscoIdentity(name=name, category=category, type=type_)
|
||||
)
|
||||
available.append(GatewayData(entity=data[0], identities=identities))
|
||||
else:
|
||||
disco_item = cast(disco.DiscoItem, data[1])
|
||||
unavailable.append(disco_item.entity)
|
||||
return FoundGateways(available=available, unavailable=unavailable)
|
||||
|
||||
def _gateways_find_xmlui(
|
||||
self, target_jid_s: str, profile_key: str
|
||||
) -> defer.Deferred[str]:
|
||||
target_jid = jid.JID(target_jid_s)
|
||||
client = self.host.get_client(profile_key)
|
||||
d = defer.ensureDeferred(self.gateways_find_raw(client, target_jid))
|
||||
d.addCallback(self._gateways_result_2_xmlui, target_jid)
|
||||
d.addCallback(lambda xmlui: xmlui.toXml())
|
||||
d = cast(defer.Deferred[str], d)
|
||||
return d
|
||||
|
||||
async def gateways_find_raw(self, client: SatXMPPEntity, target: jid.JID) -> list:
|
||||
"""Find gateways in the target JID, using discovery protocol"""
|
||||
log.debug(
|
||||
_("find gateways (target = {target}, profile = {profile})").format(
|
||||
target=target.full(), profile=client.profile
|
||||
)
|
||||
)
|
||||
disco = await client.disco.requestItems(target)
|
||||
if len(disco._items) == 0:
|
||||
log.debug(_("No gateway found"))
|
||||
return []
|
||||
|
||||
defers_ = []
|
||||
for item in disco._items:
|
||||
log.debug(_("item found: {}").format(item.entity))
|
||||
defers_.append(client.disco.requestInfo(item.entity))
|
||||
deferred_list = defer.DeferredList(defers_)
|
||||
dl_result = await deferred_list
|
||||
reactor.callLater(GATEWAY_TIMEOUT, deferred_list.cancel)
|
||||
items = disco._items
|
||||
ret = []
|
||||
for idx, (success, result) in enumerate(dl_result):
|
||||
if not success:
|
||||
|
|
@ -209,58 +304,20 @@ class XEP_0100(object):
|
|||
if identity[0] == "gateway"
|
||||
]
|
||||
if gateways:
|
||||
log.info(
|
||||
_("Found gateway [%(jid)s]: %(identity_name)s")
|
||||
% {
|
||||
"jid": entity.full(),
|
||||
"identity_name": " - ".join(
|
||||
log.debug(
|
||||
_("Found gateway [{jid}]: {identity_name}").format(
|
||||
jid=entity.full(),
|
||||
identity_name=" - ".join(
|
||||
[gateway[1] for gateway in gateways]
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
ret.append((success, (entity, gateways)))
|
||||
else:
|
||||
log.info(
|
||||
_("Skipping [%(jid)s] which is not a gateway")
|
||||
% {"jid": entity.full()}
|
||||
log.debug(
|
||||
_("Skipping [{jid}] which is not a gateway").format(
|
||||
jid=entity.full()
|
||||
)
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
def _items_received(self, disco, target, client):
|
||||
"""Look for items with disco protocol, and ask infos for each one"""
|
||||
|
||||
if len(disco._items) == 0:
|
||||
log.debug(_("No gateway found"))
|
||||
return []
|
||||
|
||||
_defers = []
|
||||
for item in disco._items:
|
||||
log.debug(_("item found: %s") % item.entity)
|
||||
_defers.append(client.disco.requestInfo(item.entity))
|
||||
dl = defer.DeferredList(_defers)
|
||||
dl.addCallback(
|
||||
self._infos_received, items=disco._items, target=target, client=client
|
||||
)
|
||||
reactor.callLater(GATEWAY_TIMEOUT, dl.cancel)
|
||||
return dl
|
||||
|
||||
def _find_gateways(self, target_jid_s, profile_key):
|
||||
target_jid = jid.JID(target_jid_s)
|
||||
profile = self.host.memory.get_profile_name(profile_key)
|
||||
if not profile:
|
||||
raise exceptions.ProfileUnknownError
|
||||
d = self.gateways_find(target_jid, profile)
|
||||
d.addCallback(self._gateways_result_2_xmlui, target_jid)
|
||||
d.addCallback(lambda xmlui: xmlui.toXml())
|
||||
return d
|
||||
|
||||
def gateways_find(self, target, profile):
|
||||
"""Find gateways in the target JID, using discovery protocol"""
|
||||
client = self.host.get_client(profile)
|
||||
log.debug(
|
||||
_("find gateways (target = %(target)s, profile = %(profile)s)")
|
||||
% {"target": target.full(), "profile": profile}
|
||||
)
|
||||
d = client.disco.requestItems(target)
|
||||
d.addCallback(self._items_received, target=target, client=client)
|
||||
return d
|
||||
|
|
|
|||
Loading…
Reference in a new issue