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