core, memory (sqla), plugins XEP-0277, XEP-0346: various fixes:

- `is_local`: now use `server_jid`.
- `aio_func`: pass the missing `self` argument (`aio_fund` is unbound).
- Declare `pubsub_service` in `core_types.SatXMPPEntity`
- plugin XEP-0277: fix handling of `MbData.from_element` when node is not specified (so we
  use default node).
- plugin XEP-0346: `IdentityData`: fix field access, following move to Pydantic model.
This commit is contained in:
Goffi 2026-10-26 12:13:42 +01:00
commit 50ffc8c3b4
6 changed files with 26 additions and 25 deletions

View file

@ -110,6 +110,8 @@ class SatXMPPEntity(ABC):
conn_deferred: defer.Deferred conn_deferred: defer.Deferred
xmlstream: mod_xmlstream.XmlStream xmlstream: mod_xmlstream.XmlStream
tor: "Tor | None" = None tor: "Tor | None" = None
# Default pubsub service (set by XEP-0060)
pubsub_service: "t_jid.JID | None" = None
def __init__(self) -> None: def __init__(self) -> None:
self.plugin_data: dict[str, Any] = {} self.plugin_data: dict[str, Any] = {}
@ -374,5 +376,5 @@ class SatXMPPClient(SatXMPPEntity):
class SatXMPPComponent(SatXMPPEntity): class SatXMPPComponent(SatXMPPEntity):
def is_local(self, jid_: t_jid.JID) -> bool: def is_local(self, jid_: t_jid.JID) -> bool:
"""Returns True if jid_ use a domain or subdomain of component's host""" """Returns True if jid_ is an account on the local server"""
raise NotImplementedError raise NotImplementedError

View file

@ -1322,10 +1322,8 @@ class SatXMPPComponent(SatXMPPEntity, core_types.SatXMPPComponent, component.Com
return False return False
def is_local(self, jid_: jid.JID) -> bool: def is_local(self, jid_: jid.JID) -> bool:
"""Returns True if jid_ use a domain or subdomain of component's host""" """Return True if jid_ is an account on the local server."""
local_host = self.host.split(".") return jid_.host == self.server_jid.host
assert local_host
return jid_.host.split(".")[-len(local_host) :] == local_host
def _create_sub_protocols(self): def _create_sub_protocols(self):
self.messageProt = SatMessageProtocol() self.messageProt = SatMessageProtocol()

View file

@ -164,7 +164,7 @@ class Storage:
This method is called on shutdown. This method is called on shutdown.
""" """
if self.engine.dialect.name == "sqlite": if self.engine.dialect.name == "sqlite":
await self.wal_checkpoint.aio_func() await self.wal_checkpoint.aio_func(self)
await self.engine.dispose() await self.engine.dispose()
async def migrate_apply(self, *args: str, log_output: bool = False) -> None: async def migrate_apply(self, *args: str, log_output: bool = False) -> None:
@ -1464,7 +1464,7 @@ class Storage:
create_kwargs = {} create_kwargs = {}
try: try:
return await self.set_pubsub_node.aio_func( return await self.set_pubsub_node.aio_func(
client, service, name, **create_kwargs self, client, service, name, **create_kwargs
) )
except IntegrityError as e: except IntegrityError as e:
if "unique" in str(e.orig).lower(): if "unique" in str(e.orig).lower():
@ -1472,6 +1472,7 @@ class Storage:
# get_pubsub_node above # get_pubsub_node above
log.debug("ignoring UNIQUE constraint error") log.debug("ignoring UNIQUE constraint error")
return await self.get_pubsub_node.aio_func( return await self.get_pubsub_node.aio_func(
self,
client, client,
service, service,
name=name, name=name,
@ -2266,7 +2267,7 @@ class Storage:
If affiliation is None, the entity's affiliation is removed. If affiliation is None, the entity's affiliation is removed.
@raise exceptions.NotFound: Node doesn't exist. @raise exceptions.NotFound: Node doesn't exist.
""" """
node = await self.get_pubsub_node.aio_func(client, service, node_name) node = await self.get_pubsub_node.aio_func(self, client, service, node_name)
if node is None: if node is None:
raise exceptions.NotFound(f"Node {node_name!r} not found") raise exceptions.NotFound(f"Node {node_name!r} not found")

View file

@ -214,8 +214,9 @@ class MbData(BaseModel):
data = { data = {
"id": id_, "id": id_,
"service": service, "service": service,
"node": node,
} }
if node is not None:
data["node"] = node
# Set URI if node is provided # Set URI if node is provided
# FIXME: node should alway be set in the future, check FIXME in method signature. # FIXME: node should alway be set in the future, check FIXME in method signature.

View file

@ -810,7 +810,7 @@ class PubsubSchema(object):
if fill_author: if fill_author:
if not values.get("author"): if not values.get("author"):
id_data = await self._i.get_identity(client, None, ["nicknames"]) id_data = await self._i.get_identity(client, None, ["nicknames"])
values["author"] = id_data["nicknames"][0] values["author"] = id_data.nicknames[0]
if not values.get("author_jid"): if not values.get("author_jid"):
values["author_jid"] = client.jid.full() values["author_jid"] = client.jid.full()
item_id = await self.send_data_form_item( item_id = await self.send_data_form_item(

View file

@ -380,18 +380,20 @@ async def mock_get_pubsub_node(client, service, node, with_subscriptions=False,
return fake_cached_node return fake_cached_node
def mock_client(jid): def mock_client(client_jid):
client = MagicMock() client = MagicMock()
client.jid = jid client.jid = client_jid
client.host = "test.example" client.host = "test.example"
# same derivation as SatXMPPComponent.server_jid
client.server_jid = jid.JID(client_jid.host.split(".", 1)[-1])
client._ap_storage.get = AsyncMock() client._ap_storage.get = AsyncMock()
client._ap_storage.aset = AsyncMock() client._ap_storage.aset = AsyncMock()
client.is_local = lambda jid_: SatXMPPComponent.is_local(client, jid_) client.is_local = lambda jid_: SatXMPPComponent.is_local(client, jid_)
return client return client
def get_virtual_client(jid): def get_virtual_client(client_jid):
return mock_client(jid) return mock_client(client_jid)
class FakeTReqPostResponse: class FakeTReqPostResponse:
@ -450,12 +452,12 @@ class TestActivityPubGateway:
== "some_other_node---simple@test.example" == "some_other_node---simple@test.example"
) )
# local pubsub node # pubsub node on non local pubsub service (exact domain match)
with patch.object(ap_gateway, "is_pubsub") as is_pubsub: with patch.object(ap_gateway, "is_pubsub") as is_pubsub:
is_pubsub.return_value = True is_pubsub.return_value = True
assert ( assert (
await get_account(jid_=jid.JID("pubsub.test.example"), node="some_node") await get_account(jid_=jid.JID("pubsub.test.example"), node="some_node")
== "some_node@pubsub.test.example" == "___some_node.40pubsub.2etest.2eexample@ap.test.example"
) )
# non local pubsub node # non local pubsub node
@ -505,16 +507,13 @@ class TestActivityPubGateway:
with patch.object(ap_gateway, "is_pubsub") as is_pubsub: with patch.object(ap_gateway, "is_pubsub") as is_pubsub:
is_pubsub.return_value = True is_pubsub.return_value = True
# simple local node # node on non local pubsub service is not allowed with local_only
assert await get_jid_node("toto@pubsub.test.example") == ( with pytest.raises(exceptions.PermissionError):
jid.JID("pubsub.test.example"), await get_jid_node("toto@pubsub.test.example")
"toto",
)
# encoded local node # same with an encoded node
assert await get_jid_node( with pytest.raises(exceptions.PermissionError):
"___urn.3axmpp.3amicroblog.3a0@pubsub.test.example" await get_jid_node("___urn.3axmpp.3amicroblog.3a0@pubsub.test.example")
) == (jid.JID("pubsub.test.example"), "urn:xmpp:microblog:0")
@ed @ed
async def test_ap_to_pubsub_conversion(self, ap_gateway, monkeypatch): async def test_ap_to_pubsub_conversion(self, ap_gateway, monkeypatch):