comparison libervia/backend/plugins/plugin_comp_ap_gateway/http_server.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 5f2d496c633f
children 0d7bb4df2343
comparison
equal deleted inserted replaced
4258:ba28ca268f4a 4259:49019947cc76
125 request.write(json.dumps(resp).encode()) 125 request.write(json.dumps(resp).encode())
126 request.finish() 126 request.finish()
127 127
128 async def handle_undo_activity( 128 async def handle_undo_activity(
129 self, 129 self,
130 requestor_actor_id: str,
130 request: "HTTPRequest", 131 request: "HTTPRequest",
131 data: dict, 132 data: dict,
132 account_jid: jid.JID, 133 account_jid: jid.JID,
133 node: Optional[str], 134 node: Optional[str],
134 ap_account: str, 135 ap_account: str,
135 ap_url: str, 136 ap_url: str,
136 signing_actor: str 137 signing_actor: str
137 ) -> None: 138 ) -> None:
138 if node is None: 139 if node is None:
139 node = self.apg._m.namespace 140 node = self.apg._m.namespace
140 client = await self.apg.get_virtual_client(signing_actor) 141 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
141 object_ = data.get("object") 142 object_ = data.get("object")
142 if isinstance(object_, str): 143 if isinstance(object_, str):
143 # we check first if it's not a cached object 144 # we check first if it's not a cached object
144 ap_cache_key = f"{ST_AP_CACHE}{object_}" 145 ap_cache_key = f"{ST_AP_CACHE}{object_}"
145 value = await self.apg.client._ap_storage.get(ap_cache_key) 146 value = await self.apg.client._ap_storage.get(ap_cache_key)
148 if value is not None: 149 if value is not None:
149 objects = [value] 150 objects = [value]
150 # because we'll undo the activity, we can remove it from cache 151 # because we'll undo the activity, we can remove it from cache
151 await self.apg.client._ap_storage.remove(ap_cache_key) 152 await self.apg.client._ap_storage.remove(ap_cache_key)
152 else: 153 else:
153 objects = await self.apg.ap_get_list(data, "object") 154 objects = await self.apg.ap_get_list(requestor_actor_id, data, "object")
154 for obj in objects: 155 for obj in objects:
155 type_ = obj.get("type") 156 type_ = obj.get("type")
156 actor = await self.apg.ap_get_sender_actor(obj) 157 actor = await self.apg.ap_get_sender_actor(requestor_actor_id, obj)
157 if actor != signing_actor: 158 if actor != signing_actor:
158 log.warning(f"ignoring object not attributed to signing actor: {data}") 159 log.warning(f"ignoring object not attributed to signing actor: {data}")
159 continue 160 continue
160 161
161 if type_ == "Follow": 162 if type_ == "Follow":
186 else: 187 else:
187 log.warning(f"Unmanaged undo type: {type_!r}") 188 log.warning(f"Unmanaged undo type: {type_!r}")
188 189
189 async def handle_follow_activity( 190 async def handle_follow_activity(
190 self, 191 self,
192 requestor_actor_id: str,
191 request: "HTTPRequest", 193 request: "HTTPRequest",
192 data: dict, 194 data: dict,
193 account_jid: jid.JID, 195 account_jid: jid.JID,
194 node: Optional[str], 196 node: Optional[str],
195 ap_account: str, 197 ap_account: str,
196 ap_url: str, 198 ap_url: str,
197 signing_actor: str 199 signing_actor: str
198 ) -> None: 200 ) -> None:
199 if node is None: 201 if node is None:
200 node = self.apg._m.namespace 202 node = self.apg._m.namespace
201 client = await self.apg.get_virtual_client(signing_actor) 203 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
202 try: 204 try:
203 subscription = await self.apg._p.subscribe( 205 subscription = await self.apg._p.subscribe(
204 client, 206 client,
205 account_jid, 207 account_jid,
206 node, 208 node,
222 await self.apg.sign_and_post(inbox, actor_id, accept_data) 224 await self.apg.sign_and_post(inbox, actor_id, accept_data)
223 await self.apg._c.synchronise(client, account_jid, node, resync=False) 225 await self.apg._c.synchronise(client, account_jid, node, resync=False)
224 226
225 async def handle_accept_activity( 227 async def handle_accept_activity(
226 self, 228 self,
229 requestor_actor_id: str,
227 request: "HTTPRequest", 230 request: "HTTPRequest",
228 data: dict, 231 data: dict,
229 account_jid: jid.JID, 232 account_jid: jid.JID,
230 node: Optional[str], 233 node: Optional[str],
231 ap_account: str, 234 ap_account: str,
232 ap_url: str, 235 ap_url: str,
233 signing_actor: str 236 signing_actor: str
234 ) -> None: 237 ) -> None:
235 if node is None: 238 if node is None:
236 node = self.apg._m.namespace 239 node = self.apg._m.namespace
237 client = await self.apg.get_virtual_client(signing_actor) 240 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
238 objects = await self.apg.ap_get_list(data, "object") 241 objects = await self.apg.ap_get_list(requestor_actor_id, data, "object")
239 for obj in objects: 242 for obj in objects:
240 type_ = obj.get("type") 243 type_ = obj.get("type")
241 if type_ == "Follow": 244 if type_ == "Follow":
242 follow_node = await self.apg.host.memory.storage.get_pubsub_node( 245 follow_node = await self.apg.host.memory.storage.get_pubsub_node(
243 client, client.jid, node, with_subscriptions=True 246 client, client.jid, node, with_subscriptions=True
271 else: 274 else:
272 log.warning(f"Unmanaged accept type: {type_!r}") 275 log.warning(f"Unmanaged accept type: {type_!r}")
273 276
274 async def handle_delete_activity( 277 async def handle_delete_activity(
275 self, 278 self,
279 requestor_actor_id: str,
276 request: "HTTPRequest", 280 request: "HTTPRequest",
277 data: dict, 281 data: dict,
278 account_jid: Optional[jid.JID], 282 account_jid: Optional[jid.JID],
279 node: Optional[str], 283 node: Optional[str],
280 ap_account: Optional[str], 284 ap_account: Optional[str],
281 ap_url: str, 285 ap_url: str,
282 signing_actor: str 286 signing_actor: str
283 ): 287 ):
284 if node is None: 288 if node is None:
285 node = self.apg._m.namespace 289 node = self.apg._m.namespace
286 client = await self.apg.get_virtual_client(signing_actor) 290 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
287 objects = await self.apg.ap_get_list(data, "object") 291 objects = await self.apg.ap_get_list(requestor_actor_id, data, "object")
288 for obj in objects: 292 for obj in objects:
289 await self.apg.new_ap_delete_item(client, account_jid, node, obj) 293 await self.apg.new_ap_delete_item(client, account_jid, node, obj)
290 294
291 async def handle_new_ap_items( 295 async def handle_new_ap_items(
292 self, 296 self,
297 requestor_actor_id: str,
293 request: "HTTPRequest", 298 request: "HTTPRequest",
294 data: dict, 299 data: dict,
295 account_jid: Optional[jid.JID], 300 account_jid: Optional[jid.JID],
296 node: Optional[str], 301 node: Optional[str],
297 signing_actor: str, 302 signing_actor: str,
306 log.error( 311 log.error(
307 '"_repeated" field already present in given AP item, this should not ' 312 '"_repeated" field already present in given AP item, this should not '
308 f"happen. Ignoring object from {signing_actor}\n{data}" 313 f"happen. Ignoring object from {signing_actor}\n{data}"
309 ) 314 )
310 raise exceptions.DataError("unexpected field in item") 315 raise exceptions.DataError("unexpected field in item")
311 client = await self.apg.get_virtual_client(signing_actor) 316 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
312 objects = await self.apg.ap_get_list(data, "object") 317 objects = await self.apg.ap_get_list(requestor_actor_id, data, "object")
313 for obj in objects: 318 for obj in objects:
314 if node is None: 319 if node is None:
315 if obj.get("type") == TYPE_EVENT: 320 if obj.get("type") == TYPE_EVENT:
316 node = self.apg._events.namespace 321 node = self.apg._events.namespace
317 else: 322 else:
318 node = self.apg._m.namespace 323 node = self.apg._m.namespace
319 sender = await self.apg.ap_get_sender_actor(obj) 324 sender = await self.apg.ap_get_sender_actor(requestor_actor_id, obj)
320 if repeated: 325 if repeated:
321 # we don't check sender when item is repeated, as it should be different 326 # we don't check sender when item is repeated, as it should be different
322 # from post author in this case 327 # from post author in this case
323 sender_jid = await self.apg.get_jid_from_id(sender) 328 sender_jid = await self.apg.get_jid_from_id(
324 repeater_jid = await self.apg.get_jid_from_id(signing_actor) 329 requestor_actor_id,
330 sender
331 )
332 repeater_jid = await self.apg.get_jid_from_id(
333 requestor_actor_id,
334 signing_actor
335 )
325 repeated_item_id = obj["id"] 336 repeated_item_id = obj["id"]
326 if self.apg.is_local_url(repeated_item_id): 337 if self.apg.is_local_url(repeated_item_id):
327 # the repeated object is from XMPP, we need to parse the URL to find 338 # the repeated object is from XMPP, we need to parse the URL to find
328 # the right ID 339 # the right ID
329 url_type, url_args = self.apg.parse_apurl(repeated_item_id) 340 url_type, url_args = self.apg.parse_apurl(repeated_item_id)
372 383
373 await self.apg.new_ap_item(client, account_jid, node, obj) 384 await self.apg.new_ap_item(client, account_jid, node, obj)
374 385
375 async def handle_create_activity( 386 async def handle_create_activity(
376 self, 387 self,
388 requestor_actor_id: str,
377 request: "HTTPRequest", 389 request: "HTTPRequest",
378 data: dict, 390 data: dict,
379 account_jid: Optional[jid.JID], 391 account_jid: Optional[jid.JID],
380 node: Optional[str], 392 node: Optional[str],
381 ap_account: Optional[str], 393 ap_account: Optional[str],
382 ap_url: str, 394 ap_url: str,
383 signing_actor: str 395 signing_actor: str
384 ): 396 ):
385 await self.handle_new_ap_items(request, data, account_jid, node, signing_actor) 397 await self.handle_new_ap_items(
398 requestor_actor_id, request, data, account_jid, node, signing_actor
399 )
386 400
387 async def handle_update_activity( 401 async def handle_update_activity(
388 self, 402 self,
403 requestor_actor_id: str,
389 request: "HTTPRequest", 404 request: "HTTPRequest",
390 data: dict, 405 data: dict,
391 account_jid: Optional[jid.JID], 406 account_jid: Optional[jid.JID],
392 node: Optional[str], 407 node: Optional[str],
393 ap_account: Optional[str], 408 ap_account: Optional[str],
394 ap_url: str, 409 ap_url: str,
395 signing_actor: str 410 signing_actor: str
396 ): 411 ):
397 # Update is the same as create: the item ID stays the same, thus the item will be 412 # Update is the same as create: the item ID stays the same, thus the item will be
398 # overwritten 413 # overwritten
399 await self.handle_new_ap_items(request, data, account_jid, node, signing_actor) 414 await self.handle_new_ap_items(
415 requestor_actor_id, request, data, account_jid, node, signing_actor
416 )
400 417
401 async def handle_announce_activity( 418 async def handle_announce_activity(
402 self, 419 self,
420 requestor_actor_id: str,
403 request: "HTTPRequest", 421 request: "HTTPRequest",
404 data: dict, 422 data: dict,
405 account_jid: Optional[jid.JID], 423 account_jid: Optional[jid.JID],
406 node: Optional[str], 424 node: Optional[str],
407 ap_account: Optional[str], 425 ap_account: Optional[str],
408 ap_url: str, 426 ap_url: str,
409 signing_actor: str 427 signing_actor: str
410 ): 428 ):
411 # we create a new item 429 # we create a new item
412 await self.handle_new_ap_items( 430 await self.handle_new_ap_items(
431 requestor_actor_id,
413 request, 432 request,
414 data, 433 data,
415 account_jid, 434 account_jid,
416 node, 435 node,
417 signing_actor, 436 signing_actor,
514 client, author_jid, attachment_node, [item_elt] 533 client, author_jid, attachment_node, [item_elt]
515 ) 534 )
516 535
517 async def handle_like_activity( 536 async def handle_like_activity(
518 self, 537 self,
538 requestor_actor_id: str,
519 request: "HTTPRequest", 539 request: "HTTPRequest",
520 data: dict, 540 data: dict,
521 account_jid: Optional[jid.JID], 541 account_jid: Optional[jid.JID],
522 node: Optional[str], 542 node: Optional[str],
523 ap_account: Optional[str], 543 ap_account: Optional[str],
524 ap_url: str, 544 ap_url: str,
525 signing_actor: str 545 signing_actor: str
526 ) -> None: 546 ) -> None:
527 client = await self.apg.get_virtual_client(signing_actor) 547 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
528 await self.handle_attachment_item(client, data, {"noticed": True}) 548 await self.handle_attachment_item(client, data, {"noticed": True})
529 549
530 async def handle_emojireact_activity( 550 async def handle_emojireact_activity(
531 self, 551 self,
552 requestor_actor_id: str,
532 request: "HTTPRequest", 553 request: "HTTPRequest",
533 data: dict, 554 data: dict,
534 account_jid: Optional[jid.JID], 555 account_jid: Optional[jid.JID],
535 node: Optional[str], 556 node: Optional[str],
536 ap_account: Optional[str], 557 ap_account: Optional[str],
537 ap_url: str, 558 ap_url: str,
538 signing_actor: str 559 signing_actor: str
539 ) -> None: 560 ) -> None:
540 client = await self.apg.get_virtual_client(signing_actor) 561 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
541 await self.handle_attachment_item(client, data, { 562 await self.handle_attachment_item(client, data, {
542 "reactions": {"operation": "update", "add": [data["content"]]} 563 "reactions": {"operation": "update", "add": [data["content"]]}
543 }) 564 })
544 565
545 async def handle_join_activity( 566 async def handle_join_activity(
546 self, 567 self,
568 requestor_actor_id: str,
547 request: "HTTPRequest", 569 request: "HTTPRequest",
548 data: dict, 570 data: dict,
549 account_jid: Optional[jid.JID], 571 account_jid: Optional[jid.JID],
550 node: Optional[str], 572 node: Optional[str],
551 ap_account: Optional[str], 573 ap_account: Optional[str],
552 ap_url: str, 574 ap_url: str,
553 signing_actor: str 575 signing_actor: str
554 ) -> None: 576 ) -> None:
555 client = await self.apg.get_virtual_client(signing_actor) 577 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
556 await self.handle_attachment_item(client, data, {"rsvp": {"attending": "yes"}}) 578 await self.handle_attachment_item(client, data, {"rsvp": {"attending": "yes"}})
557 579
558 async def handle_leave_activity( 580 async def handle_leave_activity(
559 self, 581 self,
582 requestor_actor_id: str,
560 request: "HTTPRequest", 583 request: "HTTPRequest",
561 data: dict, 584 data: dict,
562 account_jid: Optional[jid.JID], 585 account_jid: Optional[jid.JID],
563 node: Optional[str], 586 node: Optional[str],
564 ap_account: Optional[str], 587 ap_account: Optional[str],
565 ap_url: str, 588 ap_url: str,
566 signing_actor: str 589 signing_actor: str
567 ) -> None: 590 ) -> None:
568 client = await self.apg.get_virtual_client(signing_actor) 591 client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
569 await self.handle_attachment_item(client, data, {"rsvp": {"attending": "no"}}) 592 await self.handle_attachment_item(client, data, {"rsvp": {"attending": "no"}})
570 593
571 async def ap_actor_request( 594 async def ap_actor_request(
572 self, 595 self,
573 request: "HTTPRequest", 596 request: "HTTPRequest",
812 "last": url_last_page, 835 "last": url_last_page,
813 } 836 }
814 837
815 async def ap_inbox_request( 838 async def ap_inbox_request(
816 self, 839 self,
840 requestor_actor_id: str,
817 request: "HTTPRequest", 841 request: "HTTPRequest",
818 data: Optional[dict], 842 data: Optional[dict],
819 account_jid: Optional[jid.JID], 843 account_jid: Optional[jid.JID],
820 node: Optional[str], 844 node: Optional[str],
821 ap_account: Optional[str], 845 ap_account: Optional[str],
823 signing_actor: Optional[str] 847 signing_actor: Optional[str]
824 ) -> None: 848 ) -> None:
825 assert data is not None 849 assert data is not None
826 if signing_actor is None: 850 if signing_actor is None:
827 raise exceptions.InternalError("signing_actor must be set for inbox requests") 851 raise exceptions.InternalError("signing_actor must be set for inbox requests")
828 await self.check_signing_actor(data, signing_actor) 852 await self.check_signing_actor(requestor_actor_id, data, signing_actor)
829 activity_type = (data.get("type") or "").lower() 853 activity_type = (data.get("type") or "").lower()
830 if not activity_type in ACTIVITY_TYPES_LOWER: 854 if not activity_type in ACTIVITY_TYPES_LOWER:
831 return self.response_code( 855 return self.response_code(
832 request, 856 request,
833 http.UNSUPPORTED_MEDIA_TYPE, 857 http.UNSUPPORTED_MEDIA_TYPE,
849 http.UNSUPPORTED_MEDIA_TYPE, 873 http.UNSUPPORTED_MEDIA_TYPE,
850 f"{activity_type.title()} activity is not yet supported" 874 f"{activity_type.title()} activity is not yet supported"
851 ) 875 )
852 else: 876 else:
853 await method( 877 await method(
854 request, data, account_jid, node, ap_account, ap_url, signing_actor 878 requestor_actor_id, request, data, account_jid, node, ap_account, ap_url,
879 signing_actor
855 ) 880 )
856 881
857 async def ap_followers_request( 882 async def ap_followers_request(
858 self, 883 self,
859 request: "HTTPRequest", 884 request: "HTTPRequest",
952 for k,v in request.getAllHeaders().items() 977 for k,v in request.getAllHeaders().items()
953 ) 978 )
954 to_log.append(f" headers:\n{headers}") 979 to_log.append(f" headers:\n{headers}")
955 return to_log 980 return to_log
956 981
982 def get_requestor_actor_id(
983 self,
984 data: dict|None = None,
985 uri_extra_args: list[str]|None = None
986 ) -> str:
987 """Find the actor ID of the requestor.
988
989 The requestor here is actually the local actor which will do the requests to
990 achieve the task (e.g. retrieve external actor data), not the requestor of the
991 received AP request.
992
993 It will notably be used as requestor actor ID to sign HTTP requests. We need to
994 sign GET request too to access instance checking HTTP GET signature (e.g. Mastodon
995 instances set in "secure mode").
996
997 We look for the destinee of the request and check if it's a local actor, and
998 default to a generic one if we can't find it.
999
1000 Destinee is first checked in data if any, otherwise in request URI.
1001
1002 @param data: parsed JSON data of original AP request, if any.
1003 @param uri_extra_args: arguments of the AP request as returned by
1004 [self.apg.parse_apurl]. It is most of time the destinee of the request.
1005 @return: requestor_actor_id to use to sign HTTP request needed to answer the
1006 original request.
1007 """
1008 # We first check for destinee in data.
1009 if data:
1010 try:
1011 for to_ in data["to"]:
1012 if self.apg.is_local_url(to_):
1013 url_type, url_args = self.apg.parse_apurl(to_)
1014 if url_type != TYPE_ACTOR or not url_args:
1015 continue
1016 ap_account = url_args[0]
1017 if (
1018 not ap_account.endswith(f"@{self.apg.public_url}")
1019 or ap_account.count("@") != 1
1020 ):
1021 continue
1022 return to_
1023 except KeyError:
1024 pass
1025
1026 # If nothing relevant, we try URI arguments.
1027 if uri_extra_args:
1028 ap_account = uri_extra_args[0]
1029 if (
1030 ap_account.endswith(f"@{self.apg.public_url}")
1031 and ap_account.count("@") == 1
1032 ):
1033 return self.apg.build_apurl(TYPE_ACTOR, ap_account)
1034
1035 # Still nothing, we'll have to use a generic actor.
1036 log.warning(
1037 "Can't find destinee in \"to\" field, using generic requestor for signature."
1038 )
1039 return self.apg.build_apurl(
1040 TYPE_ACTOR, f"libervia@{self.apg.public_url}"
1041 )
1042
957 async def ap_request( 1043 async def ap_request(
958 self, 1044 self,
959 request: "HTTPRequest", 1045 request: "HTTPRequest",
960 data: Optional[dict] = None, 1046 data: dict|None = None,
961 signing_actor: Optional[str] = None 1047 signing_actor: str|None = None,
1048 requestor_actor_id: str|None = None,
962 ) -> None: 1049 ) -> None:
963 if self.apg.verbose: 1050 if self.apg.verbose:
964 to_log = self._get_to_log(request, data) 1051 to_log = self._get_to_log(request, data)
965 1052
966 path = request.path.decode() 1053 path = request.path.decode()
967 ap_url = parse.urljoin( 1054 ap_url = parse.urljoin(
968 f"https://{self.apg.public_url}", 1055 f"https://{self.apg.public_url}",
969 path 1056 path
970 ) 1057 )
971 request_type, extra_args = self.apg.parse_apurl(ap_url) 1058 request_type, extra_args = self.apg.parse_apurl(ap_url)
1059
972 header_accept = request.getHeader("accept") or "" 1060 header_accept = request.getHeader("accept") or ""
973 if ((MEDIA_TYPE_AP not in header_accept 1061 if ((MEDIA_TYPE_AP not in header_accept
974 and MEDIA_TYPE_AP_ALT not in header_accept 1062 and MEDIA_TYPE_AP_ALT not in header_accept
975 and request_type in self.apg.html_redirect)): 1063 and request_type in self.apg.html_redirect)):
976 # this is not a AP request, and we have a redirections for it 1064 # this is not a AP request, and we have a redirections for it
1005 content = web_util.redirectTo(target_url.encode(), request) 1093 content = web_util.redirectTo(target_url.encode(), request)
1006 request.write(content) 1094 request.write(content)
1007 request.finish() 1095 request.finish()
1008 return 1096 return
1009 1097
1098 if requestor_actor_id is None:
1099 requestor_actor_id = self.get_requestor_actor_id(
1100 data, extra_args
1101 )
1010 if len(extra_args) == 0: 1102 if len(extra_args) == 0:
1011 if request_type != "shared_inbox": 1103 if request_type != "shared_inbox":
1012 raise exceptions.DataError(f"Invalid request type: {request_type!r}") 1104 raise exceptions.DataError(f"Invalid request type: {request_type!r}")
1013 ret_data = await self.ap_inbox_request( 1105 ret_data = await self.ap_inbox_request(
1014 request, data, None, None, None, ap_url, signing_actor 1106 requestor_actor_id, request, data, None, None, None, ap_url, signing_actor
1015 ) 1107 )
1016 elif request_type == "avatar": 1108 elif request_type == "avatar":
1017 if len(extra_args) != 1: 1109 if len(extra_args) != 1:
1018 raise exceptions.DataError("avatar argument expected in URL") 1110 raise exceptions.DataError("avatar argument expected in URL")
1019 avatar_filename = extra_args[0] 1111 avatar_filename = extra_args[0]
1032 request.method.decode().upper(), [] 1124 request.method.decode().upper(), []
1033 ): 1125 ):
1034 raise exceptions.DataError(f"Invalid request type: {request_type!r}") 1126 raise exceptions.DataError(f"Invalid request type: {request_type!r}")
1035 method = getattr(self, f"ap_{request_type}_request") 1127 method = getattr(self, f"ap_{request_type}_request")
1036 ret_data = await method( 1128 ret_data = await method(
1037 request, data, account_jid, node, ap_account, ap_url, signing_actor 1129 requestor_actor_id, request, data, account_jid, node, ap_account, ap_url, signing_actor
1038 ) 1130 )
1039 if ret_data is not None: 1131 if ret_data is not None:
1040 request.setHeader("content-type", CONTENT_TYPE_AP) 1132 request.setHeader("content-type", CONTENT_TYPE_AP)
1041 request.write(json.dumps(ret_data).encode()) 1133 request.write(json.dumps(ret_data).encode())
1042 if self.apg.verbose: 1134 if self.apg.verbose:
1070 request.finish() 1162 request.finish()
1071 return 1163 return
1072 else: 1164 else:
1073 request.content.seek(0) 1165 request.content.seek(0)
1074 1166
1167 requestor_actor_id = self.get_requestor_actor_id(data)
1168
1075 try: 1169 try:
1076 if data["type"] == "Delete" and data["actor"] == data["object"]: 1170 if data["type"] == "Delete" and data["actor"] == data["object"]:
1077 # we don't handle actor deletion 1171 # we don't handle actor deletion
1078 request.setResponseCode(http.ACCEPTED) 1172 request.setResponseCode(http.ACCEPTED)
1079 log.debug(f"ignoring actor deletion ({data['actor']})") 1173 log.debug(f"ignoring actor deletion ({data['actor']})")
1082 return 1176 return
1083 except KeyError: 1177 except KeyError:
1084 pass 1178 pass
1085 1179
1086 try: 1180 try:
1087 signing_actor = await self.check_signature(request) 1181 signing_actor = await self.check_signature(requestor_actor_id, request)
1088 except exceptions.EncryptionError as e: 1182 except exceptions.EncryptionError as e:
1089 if self.apg.verbose: 1183 if self.apg.verbose:
1090 to_log = self._get_to_log(request) 1184 to_log = self._get_to_log(request)
1091 to_log.append(f" body: {request.content.read()!r}") 1185 to_log.append(f" body: {request.content.read()!r}")
1092 request.content.seek(0) 1186 request.content.seek(0)
1116 return 1210 return
1117 self._seen_digest.append(digest) 1211 self._seen_digest.append(digest)
1118 1212
1119 # default response code, may be changed, e.g. in case of exception 1213 # default response code, may be changed, e.g. in case of exception
1120 try: 1214 try:
1121 return await self.ap_request(request, data, signing_actor) 1215 return await self.ap_request(
1216 request, data, signing_actor, requestor_actor_id=requestor_actor_id
1217 )
1122 except Exception as e: 1218 except Exception as e:
1123 self._on_request_error(failure.Failure(e), request) 1219 self._on_request_error(failure.Failure(e), request)
1124 1220
1125 async def check_signing_actor(self, data: dict, signing_actor: str) -> None: 1221 async def check_signing_actor(
1222 self,
1223 requestor_actor_id: str,
1224 data: dict,
1225 signing_actor: str
1226 ) -> None:
1126 """That that signing actor correspond to actor declared in data 1227 """That that signing actor correspond to actor declared in data
1127 1228
1229 @param requestor_actor_id: ID of the actor doing the request.
1128 @param data: request payload 1230 @param data: request payload
1129 @param signing_actor: actor ID of the signing entity, as returned by 1231 @param signing_actor: actor ID of the signing entity, as returned by
1130 check_signature 1232 check_signature
1131 @raise exceptions.NotFound: no actor found in data 1233 @raise exceptions.NotFound: no actor found in data
1132 @raise exceptions.EncryptionError: signing actor doesn't match actor in data 1234 @raise exceptions.EncryptionError: signing actor doesn't match actor in data
1133 """ 1235 """
1134 actor = await self.apg.ap_get_sender_actor(data) 1236 actor = await self.apg.ap_get_sender_actor(requestor_actor_id, data)
1135 1237
1136 if signing_actor != actor: 1238 if signing_actor != actor:
1137 raise exceptions.EncryptionError( 1239 raise exceptions.EncryptionError(
1138 f"signing actor ({signing_actor}) doesn't match actor in data ({actor})" 1240 f"signing actor ({signing_actor}) doesn't match actor in data ({actor})"
1139 ) 1241 )
1140 1242
1141 async def check_signature(self, request: "HTTPRequest") -> str: 1243 async def check_signature(
1244 self,
1245 requestor_actor_id: str,
1246 request: "HTTPRequest"
1247 ) -> str:
1142 """Check and validate HTTP signature 1248 """Check and validate HTTP signature
1143 1249
1250 @param requestor_actor_id: ID of the actor doing the request.
1144 @return: id of the signing actor 1251 @return: id of the signing actor
1145 1252
1146 @raise exceptions.EncryptionError: signature is not present or doesn't match 1253 @raise exceptions.EncryptionError: signature is not present or doesn't match
1147 """ 1254 """
1148 signature = request.getHeader("Signature") 1255 signature = request.getHeader("Signature")
1277 if created > limit_ts: 1384 if created > limit_ts:
1278 raise exceptions.EncryptionError("Signature has expired") 1385 raise exceptions.EncryptionError("Signature has expired")
1279 1386
1280 try: 1387 try:
1281 return await self.apg.check_signature( 1388 return await self.apg.check_signature(
1389 requestor_actor_id,
1282 sign_data["signature"], 1390 sign_data["signature"],
1283 key_id, 1391 key_id,
1284 headers 1392 headers
1285 ) 1393 )
1286 except exceptions.EncryptionError: 1394 except exceptions.EncryptionError:
1289 log.debug( 1397 log.debug(
1290 "Using workaround for (request-target) encoding bug in signature, " 1398 "Using workaround for (request-target) encoding bug in signature, "
1291 "see https://github.com/mastodon/mastodon/issues/18871" 1399 "see https://github.com/mastodon/mastodon/issues/18871"
1292 ) 1400 )
1293 return await self.apg.check_signature( 1401 return await self.apg.check_signature(
1402 requestor_actor_id,
1294 sign_data["signature"], 1403 sign_data["signature"],
1295 key_id, 1404 key_id,
1296 headers 1405 headers
1297 ) 1406 )
1298 1407