Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_comp_ap_gateway/pubsub_service.py @ 4259:49019947cc76
component AP Gateway: implement HTTP GET signature.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 05 Jun 2024 22:34:09 +0200 |
parents | 92551baea115 |
children | 0d7bb4df2343 |
comparison
equal
deleted
inserted
replaced
4258:ba28ca268f4a | 4259:49019947cc76 |
---|---|
142 service, | 142 service, |
143 nodeIdentifier, | 143 nodeIdentifier, |
144 [(subscription.subscriber, None, items)] | 144 [(subscription.subscriber, None, items)] |
145 ) | 145 ) |
146 | 146 |
147 async def ap_following_2_elt(self, ap_item: dict) -> domish.Element: | 147 async def ap_following_2_elt(self, requestor_actor_id: str, ap_item: dict) -> domish.Element: |
148 """Convert actor ID from following collection to XMPP item""" | 148 """Convert actor ID from following collection to XMPP item |
149 | |
150 @param requestor_actor_id: ID of the actor doing the request. | |
151 @param ap_item: AP item from which actor ID must be extracted. | |
152 """ | |
149 actor_id = ap_item["id"] | 153 actor_id = ap_item["id"] |
150 actor_jid = await self.apg.get_jid_from_id(actor_id) | 154 actor_jid = await self.apg.get_jid_from_id(requestor_actor_id, actor_id) |
151 subscription_elt = self.apg._pps.build_subscription_elt( | 155 subscription_elt = self.apg._pps.build_subscription_elt( |
152 self.apg._m.namespace, actor_jid | 156 self.apg._m.namespace, actor_jid |
153 ) | 157 ) |
154 item_elt = pubsub.Item(id=actor_id, payload=subscription_elt) | 158 item_elt = pubsub.Item(id=actor_id, payload=subscription_elt) |
155 return item_elt | 159 return item_elt |
156 | 160 |
157 async def ap_follower_2_elt(self, ap_item: dict) -> domish.Element: | 161 async def ap_follower_2_elt( |
158 """Convert actor ID from followers collection to XMPP item""" | 162 self, |
163 requestor_actor_id: str, | |
164 ap_item: dict | |
165 ) -> domish.Element: | |
166 """Convert actor ID from followers collection to XMPP item | |
167 | |
168 @param requestor_actor_id: ID of the actor doing the request. | |
169 @param ap_item: AP item from which actor ID must be extracted. | |
170 """ | |
159 actor_id = ap_item["id"] | 171 actor_id = ap_item["id"] |
160 actor_jid = await self.apg.get_jid_from_id(actor_id) | 172 actor_jid = await self.apg.get_jid_from_id(requestor_actor_id, actor_id) |
161 subscriber_elt = self.apg._pps.build_subscriber_elt(actor_jid) | 173 subscriber_elt = self.apg._pps.build_subscriber_elt(actor_jid) |
162 item_elt = pubsub.Item(id=actor_id, payload=subscriber_elt) | 174 item_elt = pubsub.Item(id=actor_id, payload=subscriber_elt) |
163 return item_elt | 175 return item_elt |
164 | 176 |
165 async def generate_v_card(self, ap_account: str) -> domish.Element: | 177 async def generate_v_card( |
166 """Generate vCard4 (XEP-0292) item element from ap_account's metadata""" | 178 self, |
167 actor_data = await self.apg.get_ap_actor_data_from_account(ap_account) | 179 requestor_actor_id: str, |
180 ap_account: str | |
181 ) -> domish.Element: | |
182 """Generate vCard4 (XEP-0292) item element from ap_account's metadata | |
183 | |
184 @param requestor_actor_id: ID of the actor doing the request. | |
185 @param ap_account: AP account from where the vcard must be retrieved. | |
186 @return: <item> with the <vcard> element | |
187 """ | |
188 actor_data = await self.apg.get_ap_actor_data_from_account( | |
189 requestor_actor_id, | |
190 ap_account | |
191 ) | |
168 identity_data = {} | 192 identity_data = {} |
169 | 193 |
170 summary = actor_data.get("summary") | 194 summary = actor_data.get("summary") |
171 # summary is HTML, we have to convert it to text | 195 # summary is HTML, we have to convert it to text |
172 if summary: | 196 if summary: |
188 return item_elt | 212 return item_elt |
189 | 213 |
190 async def get_avatar_data( | 214 async def get_avatar_data( |
191 self, | 215 self, |
192 client: SatXMPPEntity, | 216 client: SatXMPPEntity, |
217 requestor_actor_id: str, | |
193 ap_account: str | 218 ap_account: str |
194 ) -> Dict[str, Any]: | 219 ) -> dict[str, Any]: |
195 """Retrieve actor's avatar if any, cache it and file actor_data | 220 """Retrieve actor's avatar if any, cache it and file actor_data |
196 | 221 |
197 ``cache_uid``, `path``` and ``media_type`` keys are always files | 222 @param client: client to use for the request. |
198 ``base64`` key is only filled if the file was not already in cache | 223 @param requestor_actor_id: ID of the actor doing the request. |
224 @param ap_account: AP account from where the avatar data must be retrieved. | |
225 @return: Avatar data. | |
226 ``cache_uid``, `path``` and ``media_type`` keys are always filed. | |
227 ``base64`` key is only filled if the file was not already in cache. | |
199 """ | 228 """ |
200 actor_data = await self.apg.get_ap_actor_data_from_account(ap_account) | 229 actor_data = await self.apg.get_ap_actor_data_from_account(ap_account) |
201 | 230 |
202 for icon in await self.apg.ap_get_list(actor_data, "icon"): | 231 for icon in await self.apg.ap_get_list(requestor_actor_id, actor_data, "icon"): |
203 url = icon.get("url") | 232 url = icon.get("url") |
204 if icon["type"] != "Image" or not url: | 233 if icon["type"] != "Image" or not url: |
205 continue | 234 continue |
206 parsed_url = urlparse(url) | 235 parsed_url = urlparse(url) |
207 if not parsed_url.scheme in ("http", "https"): | 236 if not parsed_url.scheme in ("http", "https"): |
247 return avatar_data | 276 return avatar_data |
248 | 277 |
249 async def generate_avatar_metadata( | 278 async def generate_avatar_metadata( |
250 self, | 279 self, |
251 client: SatXMPPEntity, | 280 client: SatXMPPEntity, |
281 requestor_actor_id: str, | |
252 ap_account: str | 282 ap_account: str |
253 ) -> domish.Element: | 283 ) -> domish.Element: |
254 """Generate the metadata element for user avatar | 284 """Generate the metadata element for user avatar |
255 | 285 |
286 @param requestor_actor_id: ID of the actor doing the request. | |
256 @raise StanzaError("item-not-found"): no avatar is present in actor data (in | 287 @raise StanzaError("item-not-found"): no avatar is present in actor data (in |
257 ``icon`` field) | 288 ``icon`` field) |
258 """ | 289 """ |
259 avatar_data = await self.get_avatar_data(client, ap_account) | 290 avatar_data = await self.get_avatar_data(client, requestor_actor_id, ap_account) |
260 return self.apg._a.build_item_metadata_elt(avatar_data) | 291 return self.apg._a.build_item_metadata_elt(avatar_data) |
261 | 292 |
262 def _blocking_b_6_4_encode_avatar(self, avatar_data: Dict[str, Any]) -> None: | 293 def _blocking_b_6_4_encode_avatar(self, avatar_data: Dict[str, Any]) -> None: |
263 with avatar_data["path"].open("rb") as f: | 294 with avatar_data["path"].open("rb") as f: |
264 avatar_data["base64"] = b64encode(f.read()).decode() | 295 avatar_data["base64"] = b64encode(f.read()).decode() |
265 | 296 |
266 async def generate_avatar_data( | 297 async def generate_avatar_data( |
267 self, | 298 self, |
268 client: SatXMPPEntity, | 299 client: SatXMPPEntity, |
300 requestor_actor_id: str, | |
269 ap_account: str, | 301 ap_account: str, |
270 itemIdentifiers: Optional[List[str]], | 302 itemIdentifiers: Optional[List[str]], |
271 ) -> domish.Element: | 303 ) -> domish.Element: |
272 """Generate the data element for user avatar | 304 """Generate the data element for user avatar |
273 | 305 |
306 @param requestor_actor_id: ID of the actor doing the request. | |
274 @raise StanzaError("item-not-found"): no avatar cached with requested ID | 307 @raise StanzaError("item-not-found"): no avatar cached with requested ID |
275 """ | 308 """ |
276 if not itemIdentifiers: | 309 if not itemIdentifiers: |
277 avatar_data = await self.get_avatar_data(client, ap_account) | 310 avatar_data = await self.get_avatar_data( |
311 client, | |
312 requestor_actor_id, | |
313 ap_account | |
314 ) | |
278 if "base64" not in avatar_data: | 315 if "base64" not in avatar_data: |
279 await threads.deferToThread(self._blocking_b_6_4_encode_avatar, avatar_data) | 316 await threads.deferToThread( |
317 self._blocking_b_6_4_encode_avatar, | |
318 avatar_data | |
319 ) | |
280 else: | 320 else: |
281 if len(itemIdentifiers) > 1: | 321 if len(itemIdentifiers) > 1: |
282 # only a single item ID is supported | 322 # only a single item ID is supported |
283 raise error.StanzaError("item-not-found") | 323 raise error.StanzaError("item-not-found") |
284 item_id = itemIdentifiers[0] | 324 item_id = itemIdentifiers[0] |
310 ap_account = self.host.plugins["XEP-0106"].unescape(service.user) | 350 ap_account = self.host.plugins["XEP-0106"].unescape(service.user) |
311 if ap_account.count("@") != 1: | 351 if ap_account.count("@") != 1: |
312 log.warning(f"Invalid AP account used by {requestor}: {ap_account!r}") | 352 log.warning(f"Invalid AP account used by {requestor}: {ap_account!r}") |
313 return [], None | 353 return [], None |
314 | 354 |
355 requestor_actor_id = self.apg.build_apurl( | |
356 TYPE_ACTOR, | |
357 await self.apg.get_ap_account_from_jid_and_node(service, node) | |
358 ) | |
359 | |
315 # cached_node may be pre-filled with some nodes (e.g. attachments nodes), | 360 # cached_node may be pre-filled with some nodes (e.g. attachments nodes), |
316 # otherwise it is filled when suitable | 361 # otherwise it is filled when suitable |
317 cached_node = None | 362 cached_node = None |
318 client = self.apg.client | 363 client = self.apg.client |
319 kwargs = {} | 364 kwargs = {} |
328 parser = self.ap_follower_2_elt | 373 parser = self.ap_follower_2_elt |
329 kwargs["only_ids"] = True | 374 kwargs["only_ids"] = True |
330 use_cache = False | 375 use_cache = False |
331 elif node == self.apg._v.node: | 376 elif node == self.apg._v.node: |
332 # vCard4 request | 377 # vCard4 request |
333 item_elt = await self.generate_v_card(ap_account) | 378 item_elt = await self.generate_v_card(requestor_actor_id, ap_account) |
334 return [item_elt], None | 379 return [item_elt], None |
335 elif node == self.apg._a.namespace_metadata: | 380 elif node == self.apg._a.namespace_metadata: |
336 item_elt = await self.generate_avatar_metadata(self.apg.client, ap_account) | 381 item_elt = await self.generate_avatar_metadata( |
382 self.apg.client, | |
383 requestor_actor_id, | |
384 ap_account | |
385 ) | |
337 return [item_elt], None | 386 return [item_elt], None |
338 elif node == self.apg._a.namespace_data: | 387 elif node == self.apg._a.namespace_data: |
339 item_elt = await self.generate_avatar_data( | 388 item_elt = await self.generate_avatar_data( |
340 self.apg.client, ap_account, itemIdentifiers | 389 self.apg.client, |
390 requestor_actor_id, | |
391 ap_account, | |
392 itemIdentifiers | |
341 ) | 393 ) |
342 return [item_elt], None | 394 return [item_elt], None |
343 elif self.apg._pa.is_attachment_node(node): | 395 elif self.apg._pa.is_attachment_node(node): |
344 use_cache = True | 396 use_cache = True |
345 # we check cache here because we emit an item-not-found error if the node is | 397 # we check cache here because we emit an item-not-found error if the node is |
382 return [i.data for i in pubsub_items], rsm_resp | 434 return [i.data for i in pubsub_items], rsm_resp |
383 | 435 |
384 if itemIdentifiers: | 436 if itemIdentifiers: |
385 items = [] | 437 items = [] |
386 for item_id in itemIdentifiers: | 438 for item_id in itemIdentifiers: |
387 item_data = await self.apg.ap_get(item_id) | 439 item_data = await self.apg.ap_get(item_id, requestor_actor_id) |
388 item_elt = await parser(item_data) | 440 item_elt = await parser(requestor_actor_id, item_data) |
389 items.append(item_elt) | 441 items.append(item_elt) |
390 return items, None | 442 return items, None |
391 else: | 443 else: |
392 if rsm_req is None: | 444 if rsm_req is None: |
393 if maxItems is None: | 445 if maxItems is None: |
420 "using Collection Paging to RSM translation" | 472 "using Collection Paging to RSM translation" |
421 ) | 473 ) |
422 if self.apg._m.is_comment_node(node): | 474 if self.apg._m.is_comment_node(node): |
423 parent_item = self.apg._m.get_parent_item(node) | 475 parent_item = self.apg._m.get_parent_item(node) |
424 try: | 476 try: |
425 parent_data = await self.apg.ap_get(parent_item) | 477 parent_data = await self.apg.ap_get(parent_item, requestor_actor_id) |
426 collection = await self.apg.ap_get_object( | 478 collection = await self.apg.ap_get_object( |
479 requestor_actor_id, | |
427 parent_data.get("object", {}), | 480 parent_data.get("object", {}), |
428 "replies" | 481 "replies" |
429 ) | 482 ) |
430 except Exception as e: | 483 except Exception as e: |
431 raise error.StanzaError( | 484 raise error.StanzaError( |
432 "item-not-found", | 485 "item-not-found", |
433 text=str(e) | 486 text=str(e) |
434 ) | 487 ) |
435 else: | 488 else: |
436 actor_data = await self.apg.get_ap_actor_data_from_account(ap_account) | 489 actor_data = await self.apg.get_ap_actor_data_from_account( |
437 collection = await self.apg.ap_get_object(actor_data, collection_name) | 490 requestor_actor_id, |
491 ap_account | |
492 ) | |
493 collection = await self.apg.ap_get_object(requestor_actor_id, actor_data, collection_name) | |
438 if not collection: | 494 if not collection: |
439 raise error.StanzaError( | 495 raise error.StanzaError( |
440 "item-not-found", | 496 "item-not-found", |
441 text=f"No collection found for node {node!r} (account: {ap_account})" | 497 text=f"No collection found for node {node!r} (account: {ap_account})" |
442 ) | 498 ) |
443 | 499 |
444 kwargs["parser"] = parser | 500 kwargs["parser"] = parser |
445 return await self.apg.get_ap_items(collection, **kwargs) | 501 return await self.apg.get_ap_items(requestor_actor_id, collection, **kwargs) |
446 | 502 |
447 @ensure_deferred | 503 @ensure_deferred |
448 async def retract(self, requestor, service, nodeIdentifier, itemIdentifiers): | 504 async def retract(self, requestor, service, nodeIdentifier, itemIdentifiers): |
449 raise error.StanzaError("forbidden") | 505 raise error.StanzaError("forbidden") |
450 | 506 |