Mercurial > libervia-backend
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 |