comparison sat/plugins/plugin_comp_ap_gateway/http_server.py @ 3981:acc9dfc8ba8d

component AP gateway: parse body immediately on `POST` request: the body is parsed immediately during a `POST` request: this avoids duplication of code, and allows to check the body data before continuing (will be used to filter some requests in a future patch).
author Goffi <goffi@goffi.org>
date Tue, 15 Nov 2022 18:07:34 +0100
parents 9b5092225e46
children 74f7c10a48bc
comparison
equal deleted inserted replaced
3980:9b5092225e46 3981:acc9dfc8ba8d
558 await self.handleAttachmentItem(client, data, {"rsvp": {"attending": "no"}}) 558 await self.handleAttachmentItem(client, data, {"rsvp": {"attending": "no"}})
559 559
560 async def APActorRequest( 560 async def APActorRequest(
561 self, 561 self,
562 request: "HTTPRequest", 562 request: "HTTPRequest",
563 data: Optional[dict],
563 account_jid: jid.JID, 564 account_jid: jid.JID,
564 node: Optional[str], 565 node: Optional[str],
565 ap_account: str, 566 ap_account: str,
566 ap_url: str, 567 ap_url: str,
567 signing_actor: Optional[str] 568 signing_actor: Optional[str]
660 raise ValueError(f"Invalid query data: {query_data!r}") 661 raise ValueError(f"Invalid query data: {query_data!r}")
661 662
662 async def APOutboxPageRequest( 663 async def APOutboxPageRequest(
663 self, 664 self,
664 request: "HTTPRequest", 665 request: "HTTPRequest",
666 data: Optional[dict],
665 account_jid: jid.JID, 667 account_jid: jid.JID,
666 node: Optional[str], 668 node: Optional[str],
667 ap_account: str, 669 ap_account: str,
668 ap_url: str, 670 ap_url: str,
669 query_data: Dict[str, List[str]] 671 query_data: Dict[str, List[str]]
707 node 709 node
708 ) 710 )
709 ) 711 )
710 for item in reversed(items) 712 for item in reversed(items)
711 ] 713 ]
712 data = { 714 ret_data = {
713 "@context": ["https://www.w3.org/ns/activitystreams"], 715 "@context": ["https://www.w3.org/ns/activitystreams"],
714 "id": url, 716 "id": url,
715 "type": "OrderedCollectionPage", 717 "type": "OrderedCollectionPage",
716 "partOf": base_url, 718 "partOf": base_url,
717 "orderedItems": ordered_items 719 "orderedItems": ordered_items
722 if not metadata["complete"]: 724 if not metadata["complete"]:
723 try: 725 try:
724 last= metadata["rsm"]["last"] 726 last= metadata["rsm"]["last"]
725 except KeyError: 727 except KeyError:
726 last = None 728 last = None
727 data["prev"] = f"{base_url}?{parse.urlencode({'after': last})}" 729 ret_data["prev"] = f"{base_url}?{parse.urlencode({'after': last})}"
728 if metadata["rsm"]["index"] != 0: 730 if metadata["rsm"]["index"] != 0:
729 try: 731 try:
730 first= metadata["rsm"]["first"] 732 first= metadata["rsm"]["first"]
731 except KeyError: 733 except KeyError:
732 first = None 734 first = None
733 data["next"] = f"{base_url}?{parse.urlencode({'before': first})}" 735 ret_data["next"] = f"{base_url}?{parse.urlencode({'before': first})}"
734 736
735 return data 737 return ret_data
736 738
737 async def APOutboxRequest( 739 async def APOutboxRequest(
738 self, 740 self,
739 request: "HTTPRequest", 741 request: "HTTPRequest",
742 data: Optional[dict],
740 account_jid: jid.JID, 743 account_jid: jid.JID,
741 node: Optional[str], 744 node: Optional[str],
742 ap_account: str, 745 ap_account: str,
743 ap_url: str, 746 ap_url: str,
744 signing_actor: Optional[str] 747 signing_actor: Optional[str]
792 } 795 }
793 796
794 async def APInboxRequest( 797 async def APInboxRequest(
795 self, 798 self,
796 request: "HTTPRequest", 799 request: "HTTPRequest",
800 data: Optional[dict],
797 account_jid: Optional[jid.JID], 801 account_jid: Optional[jid.JID],
798 node: Optional[str], 802 node: Optional[str],
799 ap_account: Optional[str], 803 ap_account: Optional[str],
800 ap_url: str, 804 ap_url: str,
801 signing_actor: Optional[str] 805 signing_actor: Optional[str]
802 ) -> None: 806 ) -> None:
807 assert data is not None
803 if signing_actor is None: 808 if signing_actor is None:
804 raise exceptions.InternalError("signing_actor must be set for inbox requests") 809 raise exceptions.InternalError("signing_actor must be set for inbox requests")
805 try:
806 data = json.load(request.content)
807 if not isinstance(data, dict):
808 raise ValueError("data should be an object")
809 except (json.JSONDecodeError, ValueError) as e:
810 return self.responseCode(
811 request,
812 http.BAD_REQUEST,
813 f"invalid json in inbox request: {e}"
814 )
815 await self.checkSigningActor(data, signing_actor) 810 await self.checkSigningActor(data, signing_actor)
816 activity_type = (data.get("type") or "").lower() 811 activity_type = (data.get("type") or "").lower()
817 if not activity_type in ACTIVITY_TYPES_LOWER: 812 if not activity_type in ACTIVITY_TYPES_LOWER:
818 return self.responseCode( 813 return self.responseCode(
819 request, 814 request,
842 ) 837 )
843 838
844 async def APFollowersRequest( 839 async def APFollowersRequest(
845 self, 840 self,
846 request: "HTTPRequest", 841 request: "HTTPRequest",
842 data: Optional[dict],
847 account_jid: jid.JID, 843 account_jid: jid.JID,
848 node: Optional[str], 844 node: Optional[str],
849 ap_account: Optional[str], 845 ap_account: Optional[str],
850 ap_url: str, 846 ap_url: str,
851 signing_actor: Optional[str] 847 signing_actor: Optional[str]
880 } 876 }
881 877
882 async def APFollowingRequest( 878 async def APFollowingRequest(
883 self, 879 self,
884 request: "HTTPRequest", 880 request: "HTTPRequest",
881 data: Optional[dict],
885 account_jid: jid.JID, 882 account_jid: jid.JID,
886 node: Optional[str], 883 node: Optional[str],
887 ap_account: Optional[str], 884 ap_account: Optional[str],
888 ap_url: str, 885 ap_url: str,
889 signing_actor: Optional[str] 886 signing_actor: Optional[str]
919 } 916 }
920 917
921 async def APRequest( 918 async def APRequest(
922 self, 919 self,
923 request: "HTTPRequest", 920 request: "HTTPRequest",
921 data: Optional[dict] = None,
924 signing_actor: Optional[str] = None 922 signing_actor: Optional[str] = None
925 ) -> None: 923 ) -> None:
926 if self.apg.verbose: 924 if self.apg.verbose:
927 from pprint import pformat 925 from pprint import pformat
928 to_log = [ 926 to_log = [
929 "", 927 "",
930 f"<<< got {request.method.decode()} request - {request.uri.decode()}" 928 f"<<< got {request.method.decode()} request - {request.uri.decode()}"
931 ] 929 ]
932 try: 930 if data is not None:
933 data = json.load(request.content)
934 except (json.JSONDecodeError, ValueError):
935 pass
936 else:
937 to_log.append(pformat(data)) 931 to_log.append(pformat(data))
938 finally:
939 request.content.seek(0)
940 if self.apg.verbose>=3: 932 if self.apg.verbose>=3:
941 headers = "\n".join( 933 headers = "\n".join(
942 f" {k.decode()}: {v.decode()}" 934 f" {k.decode()}: {v.decode()}"
943 for k,v in request.getAllHeaders().items() 935 for k,v in request.getAllHeaders().items()
944 ) 936 )
988 980
989 if len(extra_args) == 0: 981 if len(extra_args) == 0:
990 if request_type != "shared_inbox": 982 if request_type != "shared_inbox":
991 raise exceptions.DataError(f"Invalid request type: {request_type!r}") 983 raise exceptions.DataError(f"Invalid request type: {request_type!r}")
992 ret_data = await self.APInboxRequest( 984 ret_data = await self.APInboxRequest(
993 request, None, None, None, ap_url, signing_actor 985 request, data, None, None, None, ap_url, signing_actor
994 ) 986 )
995 elif request_type == "avatar": 987 elif request_type == "avatar":
996 if len(extra_args) != 1: 988 if len(extra_args) != 1:
997 raise exceptions.DataError("avatar argument expected in URL") 989 raise exceptions.DataError("avatar argument expected in URL")
998 avatar_filename = extra_args[0] 990 avatar_filename = extra_args[0]
1011 request.method.decode().upper(), [] 1003 request.method.decode().upper(), []
1012 ): 1004 ):
1013 raise exceptions.DataError(f"Invalid request type: {request_type!r}") 1005 raise exceptions.DataError(f"Invalid request type: {request_type!r}")
1014 method = getattr(self, f"AP{request_type.title()}Request") 1006 method = getattr(self, f"AP{request_type.title()}Request")
1015 ret_data = await method( 1007 ret_data = await method(
1016 request, account_jid, node, ap_account, ap_url, signing_actor 1008 request, data, account_jid, node, ap_account, ap_url, signing_actor
1017 ) 1009 )
1018 if ret_data is not None: 1010 if ret_data is not None:
1019 request.setHeader("content-type", CONTENT_TYPE_AP) 1011 request.setHeader("content-type", CONTENT_TYPE_AP)
1020 request.write(json.dumps(ret_data).encode()) 1012 request.write(json.dumps(ret_data).encode())
1021 if self.apg.verbose: 1013 if self.apg.verbose:
1025 to_log.append(f"{pformat(ret_data)}") 1017 to_log.append(f"{pformat(ret_data)}")
1026 to_log.append("---") 1018 to_log.append("---")
1027 log.info("\n".join(to_log)) 1019 log.info("\n".join(to_log))
1028 request.finish() 1020 request.finish()
1029 1021
1030 async def APPostRequest(self, request: "HTTPRequest"): 1022 async def APPostRequest(self, request: "HTTPRequest") -> None:
1023 try:
1024 data = json.load(request.content)
1025 if not isinstance(data, dict):
1026 log.warning(f"JSON data should be an object (uri={request.uri.decode()})")
1027 self.responseCode(
1028 request,
1029 http.BAD_REQUEST,
1030 f"invalid body, was expecting a JSON object"
1031 )
1032 request.finish()
1033 return
1034 except (json.JSONDecodeError, ValueError) as e:
1035 self.responseCode(
1036 request,
1037 http.BAD_REQUEST,
1038 f"invalid json in inbox request: {e}"
1039 )
1040 request.finish()
1041 return
1031 try: 1042 try:
1032 signing_actor = await self.checkSignature(request) 1043 signing_actor = await self.checkSignature(request)
1033 except exceptions.EncryptionError as e: 1044 except exceptions.EncryptionError as e:
1034 self.responseCode( 1045 self.responseCode(
1035 request, 1046 request,
1048 return 1059 return
1049 self._seen_digest.append(digest) 1060 self._seen_digest.append(digest)
1050 1061
1051 # default response code, may be changed, e.g. in case of exception 1062 # default response code, may be changed, e.g. in case of exception
1052 try: 1063 try:
1053 return await self.APRequest(request, signing_actor) 1064 return await self.APRequest(request, data, signing_actor)
1054 except Exception as e: 1065 except Exception as e:
1055 self._onRequestError(failure.Failure(e), request) 1066 self._onRequestError(failure.Failure(e), request)
1056 1067
1057 async def checkSigningActor(self, data: dict, signing_actor: str) -> None: 1068 async def checkSigningActor(self, data: dict, signing_actor: str) -> None:
1058 """That that signing actor correspond to actor declared in data 1069 """That that signing actor correspond to actor declared in data