Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_comp_email_gateway/pubsub_service.py @ 4338:7c0b7ecb816f
component email gateway: Add a pubsub service:
a pubsub service is implemented to retrieve and manage attachments using XEP-0498.
rel 453
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 03 Dec 2024 00:13:23 +0100 |
parents | |
children | 699aa8788d98 |
comparison
equal
deleted
inserted
replaced
4337:95792a1f26c7 | 4338:7c0b7ecb816f |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # Libervia ActivityPub Gateway | |
4 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) | |
5 | |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
19 from pathlib import Path | |
20 from typing import TYPE_CHECKING | |
21 from twisted.internet import defer | |
22 from twisted.words.protocols.jabber import jid, error | |
23 from twisted.words.xish import domish | |
24 from wokkel import data_form, disco, pubsub, rsm | |
25 | |
26 from libervia.backend.core.i18n import _ | |
27 from libervia.backend.core.constants import Const as C | |
28 from libervia.backend.core.log import getLogger | |
29 from libervia.backend.plugins.plugin_xep_0498 import NodeData | |
30 from libervia.backend.tools.utils import ensure_deferred | |
31 | |
32 if TYPE_CHECKING: | |
33 from . import EmailGatewayComponent | |
34 | |
35 | |
36 log = getLogger(__name__) | |
37 | |
38 # all nodes have the same config | |
39 NODE_CONFIG = [ | |
40 {"var": "pubsub#persist_items", "type": "boolean", "value": True}, | |
41 {"var": "pubsub#max_items", "value": "max"}, | |
42 {"var": "pubsub#access_model", "type": "list-single", "value": "open"}, | |
43 {"var": "pubsub#publish_model", "type": "list-single", "value": "open"}, | |
44 ] | |
45 | |
46 NODE_CONFIG_VALUES = {c["var"]: c["value"] for c in NODE_CONFIG} | |
47 NODE_OPTIONS = {c["var"]: {} for c in NODE_CONFIG} | |
48 for c in NODE_CONFIG: | |
49 NODE_OPTIONS[c["var"]].update( | |
50 {k: v for k, v in c.items() if k not in ("var", "value")} | |
51 ) | |
52 | |
53 | |
54 class EmailGWPubsubResource(pubsub.PubSubResource): | |
55 | |
56 def __init__(self, service: "EmailGWPubsubService") -> None: | |
57 self.gateway = service.gateway | |
58 self.host = self.gateway.host | |
59 self.service = service | |
60 self._pfs = service._pfs | |
61 super().__init__() | |
62 | |
63 def getNodes( | |
64 self, requestor: jid.JID, service: jid.JID, nodeIdentifier: str | |
65 ) -> defer.Deferred[list[str]]: | |
66 return defer.succeed([self._pfs.namespace]) | |
67 | |
68 @ensure_deferred | |
69 async def items( | |
70 self, | |
71 request: rsm.PubSubRequest, | |
72 ) -> tuple[list[domish.Element], rsm.RSMResponse | None]: | |
73 client = self.gateway.client | |
74 assert client is not None | |
75 sender = request.sender.userhostJID() | |
76 if not client.is_local(sender): | |
77 raise error.StanzaError("forbidden") | |
78 | |
79 if request.nodeIdentifier != self._pfs.namespace: | |
80 return [], None | |
81 | |
82 files = await self.host.memory.get_files(client, sender) | |
83 node_data = NodeData.from_files_data(client.jid, files) | |
84 return node_data.to_elements(), None | |
85 | |
86 @ensure_deferred | |
87 async def retract(self, request: rsm.PubSubRequest) -> None: | |
88 client = self.gateway.client | |
89 assert client is not None | |
90 sender = request.sender.userhostJID() | |
91 if not client.is_local(sender): | |
92 raise error.StanzaError("forbidden") | |
93 if request.nodeIdentifier != self._pfs.namespace: | |
94 raise error.StanzaError("bad-request") | |
95 | |
96 for item_id in request.itemIdentifiers: | |
97 try: | |
98 # FIXME: item ID naming convention must be hanlded using dedicated methods | |
99 # in XEP-0498. | |
100 file_id = item_id.rsplit("_", 1)[1] | |
101 except IndexError: | |
102 file_id = "" | |
103 if not file_id: | |
104 raise error.StanzaError("bad-request") | |
105 # Ownership is checked by ``file_delete``, and payload deletion is done there | |
106 # too. | |
107 await self.host.memory.file_delete(client, sender.userhostJID(), file_id) | |
108 | |
109 @ensure_deferred | |
110 async def subscribe(self, request: rsm.PubSubRequest): | |
111 raise rsm.Unsupported("subscribe") | |
112 | |
113 @ensure_deferred | |
114 async def unsubscribe(self, request: rsm.PubSubRequest): | |
115 raise rsm.Unsupported("unsubscribe") | |
116 | |
117 def getConfigurationOptions(self): | |
118 return NODE_OPTIONS | |
119 | |
120 def getConfiguration( | |
121 self, requestor: jid.JID, service: jid.JID, nodeIdentifier: str | |
122 ) -> defer.Deferred: | |
123 return defer.succeed(NODE_CONFIG_VALUES) | |
124 | |
125 def getNodeInfo( | |
126 self, | |
127 requestor: jid.JID, | |
128 service: jid.JID, | |
129 nodeIdentifier: str, | |
130 pep: bool = False, | |
131 recipient: jid.JID | None = None, | |
132 ) -> dict | None: | |
133 if not nodeIdentifier: | |
134 return None | |
135 info = {"type": "leaf", "meta-data": NODE_CONFIG} | |
136 return info | |
137 | |
138 | |
139 class EmailGWPubsubService(rsm.PubSubService): | |
140 """Pubsub service for XMPP requests""" | |
141 | |
142 def __init__(self, gateway: "EmailGatewayComponent"): | |
143 self.gateway = gateway | |
144 self._pfs = gateway._pfs | |
145 resource = EmailGWPubsubResource(self) | |
146 super().__init__(resource) | |
147 self.host = gateway.host | |
148 self.discoIdentity = { | |
149 "category": "pubsub", | |
150 "type": "service", | |
151 "name": "Libervia Email Gateway", | |
152 } | |
153 | |
154 @ensure_deferred | |
155 async def getDiscoInfo( | |
156 self, requestor: jid.JID, target: jid.JID, nodeIdentifier: str = "" | |
157 ) -> list[disco.DiscoFeature | disco.DiscoIdentity | data_form.Form]: | |
158 infos = await super().getDiscoInfo(requestor, target, nodeIdentifier) | |
159 infos.append(disco.DiscoFeature(self._pfs.namespace)) | |
160 return infos |