Mercurial > libervia-backend
diff libervia/backend/plugins/plugin_comp_ap_gateway/http_server.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 49019947cc76 |
children |
line wrap: on
line diff
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/http_server.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/plugins/plugin_comp_ap_gateway/http_server.py Wed Jun 19 18:44:57 2024 +0200 @@ -41,11 +41,29 @@ from libervia.backend.memory.sqla_mapping import SubscriptionState from .constants import ( - NS_AP, MEDIA_TYPE_AP, MEDIA_TYPE_AP_ALT, CONTENT_TYPE_WEBFINGER, CONTENT_TYPE_AP, - TYPE_ACTOR, TYPE_INBOX, TYPE_SHARED_INBOX, TYPE_OUTBOX, TYPE_EVENT, AP_REQUEST_TYPES, - PAGE_SIZE, ACTIVITY_TYPES_LOWER, ACTIVIY_NO_ACCOUNT_ALLOWED, SIGN_HEADERS, HS2019, - SIGN_EXP, TYPE_FOLLOWERS, TYPE_FOLLOWING, TYPE_ITEM, TYPE_LIKE, TYPE_REACTION, - ST_AP_CACHE + NS_AP, + MEDIA_TYPE_AP, + MEDIA_TYPE_AP_ALT, + CONTENT_TYPE_WEBFINGER, + CONTENT_TYPE_AP, + TYPE_ACTOR, + TYPE_INBOX, + TYPE_SHARED_INBOX, + TYPE_OUTBOX, + TYPE_EVENT, + AP_REQUEST_TYPES, + PAGE_SIZE, + ACTIVITY_TYPES_LOWER, + ACTIVIY_NO_ACCOUNT_ALLOWED, + SIGN_HEADERS, + HS2019, + SIGN_EXP, + TYPE_FOLLOWERS, + TYPE_FOLLOWING, + TYPE_ITEM, + TYPE_LIKE, + TYPE_REACTION, + ST_AP_CACHE, ) from .regex import RE_SIG_PARAM @@ -53,13 +71,13 @@ log = getLogger(__name__) VERSION = unicodedata.normalize( - 'NFKD', - f"{C.APP_NAME} ActivityPub Gateway {C.APP_VERSION}" + "NFKD", f"{C.APP_NAME} ActivityPub Gateway {C.APP_VERSION}" ) class HTTPAPGServer(web_resource.Resource): """HTTP Server handling ActivityPub S2S protocol""" + isLeaf = True def __init__(self, ap_gateway): @@ -68,30 +86,23 @@ super().__init__() def response_code( - self, - request: "HTTPRequest", - http_code: int, - msg: Optional[str] = None + self, request: "HTTPRequest", http_code: int, msg: Optional[str] = None ) -> None: """Log and set HTTP return code and associated message""" if msg is not None: log.warning(msg) request.setResponseCode(http_code, None if msg is None else msg.encode()) - def _on_request_error(self, failure_: failure.Failure, request: "HTTPRequest") -> None: + def _on_request_error( + self, failure_: failure.Failure, request: "HTTPRequest" + ) -> None: exc = failure_.value if isinstance(exc, exceptions.NotFound): - self.response_code( - request, - http.NOT_FOUND, - str(exc) - ) + self.response_code(request, http.NOT_FOUND, str(exc)) else: log.exception(f"Internal error: {failure_.value}") self.response_code( - request, - http.INTERNAL_SERVER_ERROR, - f"internal error: {failure_.value}" + request, http.INTERNAL_SERVER_ERROR, f"internal error: {failure_.value}" ) request.finish() raise failure_ @@ -105,7 +116,7 @@ account = resource[5:].strip() if not resource.startswith("acct:") or not account: return web_resource.ErrorPage( - http.BAD_REQUEST, "Bad Request" , "Invalid webfinger resource" + http.BAD_REQUEST, "Bad Request", "Invalid webfinger resource" ).render(request) actor_url = self.apg.build_apurl(TYPE_ACTOR, account) @@ -114,12 +125,8 @@ "aliases": [actor_url], "subject": resource, "links": [ - { - "rel": "self", - "type": "application/activity+json", - "href": actor_url - } - ] + {"rel": "self", "type": "application/activity+json", "href": actor_url} + ], } request.setHeader("content-type", CONTENT_TYPE_WEBFINGER) request.write(json.dumps(resp).encode()) @@ -134,7 +141,7 @@ node: Optional[str], ap_account: str, ap_url: str, - signing_actor: str + signing_actor: str, ) -> None: if node is None: node = self.apg._m.namespace @@ -181,9 +188,11 @@ elif type_ == TYPE_LIKE: await self.handle_attachment_item(client, obj, {"noticed": False}) elif type_ == TYPE_REACTION: - await self.handle_attachment_item(client, obj, { - "reactions": {"operation": "update", "remove": [obj["content"]]} - }) + await self.handle_attachment_item( + client, + obj, + {"reactions": {"operation": "update", "remove": [obj["content"]]}}, + ) else: log.warning(f"Unmanaged undo type: {type_!r}") @@ -196,7 +205,7 @@ node: Optional[str], ap_account: str, ap_url: str, - signing_actor: str + signing_actor: str, ) -> None: if node is None: node = self.apg._m.namespace @@ -207,7 +216,7 @@ account_jid, node, # subscriptions from AP are always public - options=self.apg._pps.set_public_opt() + options=self.apg._pps.set_public_opt(), ) except pubsub.SubscriptionPending: log.info(f"subscription to node {node!r} of {account_jid} is pending") @@ -218,9 +227,7 @@ raise exceptions.InternalError('"subscribed" state was expected') inbox = await self.apg.get_ap_inbox_from_id(signing_actor, use_shared=False) actor_id = self.apg.build_apurl(TYPE_ACTOR, ap_account) - accept_data = self.apg.create_activity( - "Accept", actor_id, object_=data - ) + accept_data = self.apg.create_activity("Accept", actor_id, object_=data) await self.apg.sign_and_post(inbox, actor_id, accept_data) await self.apg._c.synchronise(client, account_jid, node, resync=False) @@ -233,7 +240,7 @@ node: Optional[str], ap_account: str, ap_url: str, - signing_actor: str + signing_actor: str, ) -> None: if node is None: node = self.apg._m.namespace @@ -253,7 +260,9 @@ continue try: sub = next( - s for s in follow_node.subscriptions if s.subscriber==account_jid + s + for s in follow_node.subscriptions + if s.subscriber == account_jid ) except StopIteration: log.warning( @@ -283,7 +292,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: str + signing_actor: str, ): if node is None: node = self.apg._m.namespace @@ -325,13 +334,9 @@ if repeated: # we don't check sender when item is repeated, as it should be different # from post author in this case - sender_jid = await self.apg.get_jid_from_id( - requestor_actor_id, - sender - ) + sender_jid = await self.apg.get_jid_from_id(requestor_actor_id, sender) repeater_jid = await self.apg.get_jid_from_id( - requestor_actor_id, - signing_actor + requestor_actor_id, signing_actor ) repeated_item_id = obj["id"] if self.apg.is_local_url(repeated_item_id): @@ -347,13 +352,14 @@ if not url_account or not url_item_id: raise ValueError except (RuntimeError, ValueError): - raise exceptions.DataError( - "local URI is invalid: {repeated_id}" - ) + raise exceptions.DataError("local URI is invalid: {repeated_id}") else: url_jid, url_node = await self.apg.get_jid_and_node(url_account) - if ((url_jid != sender_jid - or url_node and url_node != self.apg._m.namespace)): + if ( + url_jid != sender_jid + or url_node + and url_node != self.apg._m.namespace + ): raise exceptions.DataError( "announced ID doesn't match sender ({sender}): " f"[repeated_item_id]" @@ -368,17 +374,15 @@ "pubsub", path=sender_jid.full(), node=self.apg._m.namespace, - item=repeated_item_id - ) + item=repeated_item_id, + ), } # we must use activity's id and targets, not the original item ones for field in ("id", "to", "bto", "cc", "bcc"): obj[field] = data.get(field) else: if sender != signing_actor: - log.warning( - "Ignoring object not attributed to signing actor: {obj}" - ) + log.warning("Ignoring object not attributed to signing actor: {obj}") continue await self.apg.new_ap_item(client, account_jid, node, obj) @@ -392,7 +396,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: str + signing_actor: str, ): await self.handle_new_ap_items( requestor_actor_id, request, data, account_jid, node, signing_actor @@ -407,7 +411,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: str + signing_actor: str, ): # Update is the same as create: the item ID stays the same, thus the item will be # overwritten @@ -424,7 +428,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: str + signing_actor: str, ): # we create a new item await self.handle_new_ap_items( @@ -434,14 +438,11 @@ account_jid, node, signing_actor, - repeated=True + repeated=True, ) async def handle_attachment_item( - self, - client: SatXMPPEntity, - data: dict, - attachment_data: dict + self, client: SatXMPPEntity, data: dict, attachment_data: dict ) -> None: target_ids = data.get("object") if not target_ids: @@ -485,11 +486,7 @@ author_jid, item_node, item_id ) cached_node = await self.apg.host.memory.storage.get_pubsub_node( - client, - author_jid, - attachment_node, - with_subscriptions=True, - create=True + client, author_jid, attachment_node, with_subscriptions=True, create=True ) found_items, __ = await self.apg.host.memory.storage.get_items( cached_node, item_ids=[client.jid.userhost()] @@ -501,19 +498,13 @@ old_item_elt = found_item.data item_elt = await self.apg._pa.apply_set_handler( - client, - {"extra": attachment_data}, - old_item_elt, - None + client, {"extra": attachment_data}, old_item_elt, None ) # we reparse the element, as there can be other attachments attachments_data = self.apg._pa.items_2_attachment_data(client, [item_elt]) # and we update the cache await self.apg.host.memory.storage.cache_pubsub_items( - client, - cached_node, - [item_elt], - attachments_data or [{}] + client, cached_node, [item_elt], attachments_data or [{}] ) if self.apg.is_virtual_jid(author_jid): @@ -525,7 +516,7 @@ self.apg.pubsub_service.notifyPublish( author_jid, attachment_node, - [(subscription.subscriber, None, [item_elt])] + [(subscription.subscriber, None, [item_elt])], ) else: # the attachment is on an XMPP item, we publish it to the attachment node @@ -542,7 +533,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: str + signing_actor: str, ) -> None: client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor) await self.handle_attachment_item(client, data, {"noticed": True}) @@ -556,12 +547,12 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: str + signing_actor: str, ) -> None: client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor) - await self.handle_attachment_item(client, data, { - "reactions": {"operation": "update", "add": [data["content"]]} - }) + await self.handle_attachment_item( + client, data, {"reactions": {"operation": "update", "add": [data["content"]]}} + ) async def handle_join_activity( self, @@ -572,7 +563,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: str + signing_actor: str, ) -> None: client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor) await self.handle_attachment_item(client, data, {"rsvp": {"attending": "yes"}}) @@ -586,7 +577,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: str + signing_actor: str, ) -> None: client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor) await self.handle_attachment_item(client, data, {"rsvp": {"attending": "no"}}) @@ -599,7 +590,7 @@ node: Optional[str], ap_account: str, ap_url: str, - signing_actor: Optional[str] + signing_actor: Optional[str], ) -> dict: inbox = self.apg.build_apurl(TYPE_INBOX, ap_account) shared_inbox = self.apg.build_apurl(TYPE_SHARED_INBOX) @@ -623,9 +614,8 @@ actor_data = { "@context": [ "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1" + "https://w3id.org/security/v1", ], - # XXX: Mastodon doesn't like percent-encode arobas, so we have to unescape it # if it is escaped "id": ap_url.replace("%40", "@"), @@ -639,7 +629,7 @@ "publicKey": { "id": f"{ap_url}#main-key", "owner": ap_url, - "publicKeyPem": self.apg.public_key_pem + "publicKeyPem": self.apg.public_key_pem, }, "endpoints": { "sharedInbox": shared_inbox, @@ -664,7 +654,7 @@ actor_data["icon"] = { "type": "Image", "url": avatar_url, - "mediaType": media_type + "mediaType": media_type, } return actor_data @@ -672,13 +662,12 @@ def get_canonical_url(self, request: "HTTPRequest") -> str: return parse.urljoin( f"https://{self.apg.public_url}", - request.path.decode().rstrip("/") - # we unescape "@" for the same reason as in [ap_actor_request] + request.path.decode().rstrip("/"), + # we unescape "@" for the same reason as in [ap_actor_request] ).replace("%40", "@") def query_data_2_rsm_request( - self, - query_data: Dict[str, List[str]] + self, query_data: Dict[str, List[str]] ) -> rsm.RSMRequest: """Get RSM kwargs to use with RSMRequest from query data""" page = query_data.get("page") @@ -690,7 +679,7 @@ else: for query_key in ("index", "before", "after"): try: - kwargs={query_key: query_data[query_key][0], "max_": PAGE_SIZE} + kwargs = {query_key: query_data[query_key][0], "max_": PAGE_SIZE} except (KeyError, IndexError, ValueError): pass else: @@ -705,7 +694,7 @@ node: Optional[str], ap_account: str, ap_url: str, - query_data: Dict[str, List[str]] + query_data: Dict[str, List[str]], ) -> dict: if node is None: node = self.apg._m.namespace @@ -719,7 +708,7 @@ service=account_jid, node=node, rsm_request=self.query_data_2_rsm_request(query_data), - extra = {C.KEY_USE_CACHE: False} + extra={C.KEY_USE_CACHE: False}, ) except error.StanzaError as e: log.warning(f"Can't get data from pubsub node {node} at {account_jid}: {e}") @@ -730,8 +719,7 @@ if node and node.startswith(self.apg._events.namespace): ordered_items = [ await self.apg.ap_events.event_data_2_ap_item( - self.apg._events.event_elt_2_event_data(item), - account_jid + self.apg._events.event_elt_2_event_data(item), account_jid ) for item in reversed(items) ] @@ -740,11 +728,8 @@ await self.apg.mb_data_2_ap_item( self.apg.client, await self.apg._m.item_2_mb_data( - self.apg.client, - item, - account_jid, - node - ) + self.apg.client, item, account_jid, node + ), ) for item in reversed(items) ] @@ -753,7 +738,7 @@ "id": url, "type": "OrderedCollectionPage", "partOf": base_url, - "orderedItems": ordered_items + "orderedItems": ordered_items, } if "rsm" not in metadata: @@ -764,13 +749,13 @@ # of what we get with RSM (at least with Libervia Pubsub) if not metadata["complete"]: try: - last= metadata["rsm"]["last"] + last = metadata["rsm"]["last"] except KeyError: last = None ret_data["prev"] = f"{base_url}?{parse.urlencode({'after': last})}" if metadata["rsm"]["index"] != 0: try: - first= metadata["rsm"]["first"] + first = metadata["rsm"]["first"] except KeyError: first = None ret_data["next"] = f"{base_url}?{parse.urlencode({'before': first})}" @@ -785,7 +770,7 @@ node: Optional[str], ap_account: str, ap_url: str, - signing_actor: Optional[str] + signing_actor: Optional[str], ) -> dict: if node is None: node = self.apg._m.namespace @@ -809,7 +794,7 @@ node=node, max_items=0, rsm_request=rsm.RSMRequest(max_=0), - extra = {C.KEY_USE_CACHE: False} + extra={C.KEY_USE_CACHE: False}, ) except error.StanzaError as e: log.warning(f"Can't get data from pubsub node {node} at {account_jid}: {e}") @@ -844,7 +829,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: Optional[str] + signing_actor: Optional[str], ) -> None: assert data is not None if signing_actor is None: @@ -855,14 +840,14 @@ return self.response_code( request, http.UNSUPPORTED_MEDIA_TYPE, - f"request is not an activity, ignoring" + f"request is not an activity, ignoring", ) if account_jid is None and activity_type not in ACTIVIY_NO_ACCOUNT_ALLOWED: return self.response_code( request, http.UNSUPPORTED_MEDIA_TYPE, - f"{activity_type.title()!r} activity must target an account" + f"{activity_type.title()!r} activity must target an account", ) try: @@ -871,12 +856,18 @@ return self.response_code( request, http.UNSUPPORTED_MEDIA_TYPE, - f"{activity_type.title()} activity is not yet supported" + f"{activity_type.title()} activity is not yet supported", ) else: await method( - requestor_actor_id, request, data, account_jid, node, ap_account, ap_url, - signing_actor + requestor_actor_id, + request, + data, + account_jid, + node, + ap_account, + ap_url, + signing_actor, ) async def ap_followers_request( @@ -887,7 +878,7 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: Optional[str] + signing_actor: Optional[str], ) -> dict: if node is None: node = self.apg._m.namespace @@ -902,20 +893,22 @@ ap_account = self.apg._e.unescape(subscriber.user) else: # regular XMPP user - ap_account = await self.apg.get_ap_account_from_jid_and_node(subscriber, node) + ap_account = await self.apg.get_ap_account_from_jid_and_node( + subscriber, node + ) followers.append(ap_account) url = self.get_canonical_url(request) return { - "@context": ["https://www.w3.org/ns/activitystreams"], - "type": "OrderedCollection", - "id": url, - "totalItems": len(subscribers), - "first": { - "type": "OrderedCollectionPage", + "@context": ["https://www.w3.org/ns/activitystreams"], + "type": "OrderedCollection", "id": url, - "orderedItems": followers - } + "totalItems": len(subscribers), + "first": { + "type": "OrderedCollectionPage", + "id": url, + "orderedItems": followers, + }, } async def ap_following_request( @@ -926,12 +919,10 @@ node: Optional[str], ap_account: Optional[str], ap_url: str, - signing_actor: Optional[str] + signing_actor: Optional[str], ) -> dict[str, Any]: client = self.apg.client - subscriptions = await self.apg._pps.subscriptions( - client, account_jid, node - ) + subscriptions = await self.apg._pps.subscriptions(client, account_jid, node) following = [] for sub_dict in subscriptions: service = jid.JID(sub_dict["service"]) @@ -947,15 +938,15 @@ url = self.get_canonical_url(request) return { - "@context": ["https://www.w3.org/ns/activitystreams"], - "type": "OrderedCollection", - "id": url, - "totalItems": len(subscriptions), - "first": { - "type": "OrderedCollectionPage", + "@context": ["https://www.w3.org/ns/activitystreams"], + "type": "OrderedCollection", "id": url, - "orderedItems": following - } + "totalItems": len(subscriptions), + "first": { + "type": "OrderedCollectionPage", + "id": url, + "orderedItems": following, + }, } def _get_to_log( @@ -965,24 +956,23 @@ ) -> List[str]: """Get base data to logs in verbose mode""" from pprint import pformat + to_log = [ "", - f"<<< got {request.method.decode()} request - {request.uri.decode()}" + f"<<< got {request.method.decode()} request - {request.uri.decode()}", ] if data is not None: to_log.append(pformat(data)) - if self.apg.verbose>=3: + if self.apg.verbose >= 3: headers = "\n".join( f" {k.decode()}: {v.decode()}" - for k,v in request.getAllHeaders().items() + for k, v in request.getAllHeaders().items() ) to_log.append(f" headers:\n{headers}") return to_log def get_requestor_actor_id( - self, - data: dict|None = None, - uri_extra_args: list[str]|None = None + self, data: dict | None = None, uri_extra_args: list[str] | None = None ) -> str: """Find the actor ID of the requestor. @@ -1034,33 +1024,30 @@ # Still nothing, we'll have to use a generic actor. log.warning( - "Can't find destinee in \"to\" field, using generic requestor for signature." + 'Can\'t find destinee in "to" field, using generic requestor for signature.' ) - return self.apg.build_apurl( - TYPE_ACTOR, f"libervia@{self.apg.public_url}" - ) + return self.apg.build_apurl(TYPE_ACTOR, f"libervia@{self.apg.public_url}") async def ap_request( self, request: "HTTPRequest", - data: dict|None = None, - signing_actor: str|None = None, - requestor_actor_id: str|None = None, + data: dict | None = None, + signing_actor: str | None = None, + requestor_actor_id: str | None = None, ) -> None: if self.apg.verbose: to_log = self._get_to_log(request, data) path = request.path.decode() - ap_url = parse.urljoin( - f"https://{self.apg.public_url}", - path - ) + ap_url = parse.urljoin(f"https://{self.apg.public_url}", path) request_type, extra_args = self.apg.parse_apurl(ap_url) header_accept = request.getHeader("accept") or "" - if ((MEDIA_TYPE_AP not in header_accept - and MEDIA_TYPE_AP_ALT not in header_accept - and request_type in self.apg.html_redirect)): + if ( + MEDIA_TYPE_AP not in header_accept + and MEDIA_TYPE_AP_ALT not in header_accept + and request_type in self.apg.html_redirect + ): # this is not a AP request, and we have a redirections for it kw = {} if extra_args: @@ -1081,14 +1068,14 @@ if not filters: break # if we have filter, they must all match - elif all(v in kw[k] for k,v in filters.items()): + elif all(v in kw[k] for k, v in filters.items()): break else: # no redirection is matching redirection = None if redirection is not None: - kw = {k: parse.quote(str(v), safe="") for k,v in kw.items()} + kw = {k: parse.quote(str(v), safe="") for k, v in kw.items()} target_url = redirection["url"].format(**kw) content = web_util.redirectTo(target_url.encode(), request) request.write(content) @@ -1096,9 +1083,7 @@ return if requestor_actor_id is None: - requestor_actor_id = self.get_requestor_actor_id( - data, extra_args - ) + requestor_actor_id = self.get_requestor_actor_id(data, extra_args) if len(extra_args) == 0: if request_type != "shared_inbox": raise exceptions.DataError(f"Invalid request type: {request_type!r}") @@ -1121,21 +1106,29 @@ ap_account = extra_args[0] account_jid, node = await self.apg.get_jid_and_node(ap_account) if request_type not in AP_REQUEST_TYPES.get( - request.method.decode().upper(), [] + request.method.decode().upper(), [] ): raise exceptions.DataError(f"Invalid request type: {request_type!r}") method = getattr(self, f"ap_{request_type}_request") ret_data = await method( - requestor_actor_id, request, data, account_jid, node, ap_account, ap_url, signing_actor + requestor_actor_id, + request, + data, + account_jid, + node, + ap_account, + ap_url, + signing_actor, ) if ret_data is not None: request.setHeader("content-type", CONTENT_TYPE_AP) request.write(json.dumps(ret_data).encode()) if self.apg.verbose: to_log.append(f"--- RET (code: {request.code})---") - if self.apg.verbose>=2: + if self.apg.verbose >= 2: if ret_data is not None: from pprint import pformat + to_log.append(f"{pformat(ret_data)}") to_log.append("---") log.info("\n".join(to_log)) @@ -1149,15 +1142,13 @@ self.response_code( request, http.BAD_REQUEST, - f"invalid body, was expecting a JSON object" + f"invalid body, was expecting a JSON object", ) request.finish() return except (json.JSONDecodeError, ValueError) as e: self.response_code( - request, - http.BAD_REQUEST, - f"invalid json in inbox request: {e}" + request, http.BAD_REQUEST, f"invalid json in inbox request: {e}" ) request.finish() return @@ -1185,18 +1176,12 @@ to_log.append(f" body: {request.content.read()!r}") request.content.seek(0) log.info("\n".join(to_log)) - self.response_code( - request, - http.FORBIDDEN, - f"invalid signature: {e}" - ) + self.response_code(request, http.FORBIDDEN, f"invalid signature: {e}") request.finish() return except Exception as e: self.response_code( - request, - http.INTERNAL_SERVER_ERROR, - f"Can't check signature: {e}" + request, http.INTERNAL_SERVER_ERROR, f"Can't check signature: {e}" ) request.finish() return @@ -1219,10 +1204,7 @@ self._on_request_error(failure.Failure(e), request) async def check_signing_actor( - self, - requestor_actor_id: str, - data: dict, - signing_actor: str + self, requestor_actor_id: str, data: dict, signing_actor: str ) -> None: """That that signing actor correspond to actor declared in data @@ -1241,9 +1223,7 @@ ) async def check_signature( - self, - requestor_actor_id: str, - request: "HTTPRequest" + self, requestor_actor_id: str, request: "HTTPRequest" ) -> str: """Check and validate HTTP signature @@ -1264,10 +1244,11 @@ except KeyError: raise exceptions.EncryptionError('"keyId" is missing from signature') algorithm = sign_data.get("algorithm", HS2019) - signed_headers = sign_data.get( - "headers", - "(created)" if algorithm==HS2019 else "date" - ).lower().split() + signed_headers = ( + sign_data.get("headers", "(created)" if algorithm == HS2019 else "date") + .lower() + .split() + ) try: headers_to_check = SIGN_HEADERS[None] + SIGN_HEADERS[request.method] except KeyError: @@ -1284,9 +1265,7 @@ f"at least one of following header must be signed: {header}" ) elif header not in signed_headers: - raise exceptions.EncryptionError( - f"the {header!r} header must be signed" - ) + raise exceptions.EncryptionError(f"the {header!r} header must be signed") body = request.content.read() request.content.seek(0) @@ -1329,7 +1308,8 @@ if forwarded is not None: try: host = [ - f[5:] for f in forwarded.split(";") + f[5:] + for f in forwarded.split(";") if f.startswith("host=") ][0] or None except IndexError: @@ -1342,7 +1322,8 @@ value = host elif to_sign == "digest": hashes = { - algo.lower(): hash_ for algo, hash_ in ( + algo.lower(): hash_ + for algo, hash_ in ( digest.split("=", 1) for digest in value.split(",") ) } @@ -1367,7 +1348,6 @@ else: created = date_utils.date_parse(headers["date"]) - try: expires = float(headers["expires"]) except KeyError: @@ -1386,23 +1366,17 @@ try: return await self.apg.check_signature( - requestor_actor_id, - sign_data["signature"], - key_id, - headers + requestor_actor_id, sign_data["signature"], key_id, headers ) except exceptions.EncryptionError: - method, url = headers["(request-target)"].rsplit(' ', 1) + method, url = headers["(request-target)"].rsplit(" ", 1) headers["(request-target)"] = f"{method} {parse.unquote(url)}" log.debug( "Using workaround for (request-target) encoding bug in signature, " "see https://github.com/mastodon/mastodon/issues/18871" ) return await self.apg.check_signature( - requestor_actor_id, - sign_data["signature"], - key_id, - headers + requestor_actor_id, sign_data["signature"], key_id, headers ) def render(self, request):