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).
This commit is contained in:
Goffi 2022-11-15 18:07:34 +01:00
commit 4d14a623fc
2 changed files with 41 additions and 32 deletions

View file

@ -560,6 +560,7 @@ class HTTPAPGServer(web_resource.Resource):
async def APActorRequest(
self,
request: "HTTPRequest",
data: Optional[dict],
account_jid: jid.JID,
node: Optional[str],
ap_account: str,
@ -662,6 +663,7 @@ class HTTPAPGServer(web_resource.Resource):
async def APOutboxPageRequest(
self,
request: "HTTPRequest",
data: Optional[dict],
account_jid: jid.JID,
node: Optional[str],
ap_account: str,
@ -709,7 +711,7 @@ class HTTPAPGServer(web_resource.Resource):
)
for item in reversed(items)
]
data = {
ret_data = {
"@context": ["https://www.w3.org/ns/activitystreams"],
"id": url,
"type": "OrderedCollectionPage",
@ -724,19 +726,20 @@ class HTTPAPGServer(web_resource.Resource):
last= metadata["rsm"]["last"]
except KeyError:
last = None
data["prev"] = f"{base_url}?{parse.urlencode({'after': last})}"
ret_data["prev"] = f"{base_url}?{parse.urlencode({'after': last})}"
if metadata["rsm"]["index"] != 0:
try:
first= metadata["rsm"]["first"]
except KeyError:
first = None
data["next"] = f"{base_url}?{parse.urlencode({'before': first})}"
ret_data["next"] = f"{base_url}?{parse.urlencode({'before': first})}"
return data
return ret_data
async def APOutboxRequest(
self,
request: "HTTPRequest",
data: Optional[dict],
account_jid: jid.JID,
node: Optional[str],
ap_account: str,
@ -794,24 +797,16 @@ class HTTPAPGServer(web_resource.Resource):
async def APInboxRequest(
self,
request: "HTTPRequest",
data: Optional[dict],
account_jid: Optional[jid.JID],
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
signing_actor: Optional[str]
) -> None:
assert data is not None
if signing_actor is None:
raise exceptions.InternalError("signing_actor must be set for inbox requests")
try:
data = json.load(request.content)
if not isinstance(data, dict):
raise ValueError("data should be an object")
except (json.JSONDecodeError, ValueError) as e:
return self.responseCode(
request,
http.BAD_REQUEST,
f"invalid json in inbox request: {e}"
)
await self.checkSigningActor(data, signing_actor)
activity_type = (data.get("type") or "").lower()
if not activity_type in ACTIVITY_TYPES_LOWER:
@ -844,6 +839,7 @@ class HTTPAPGServer(web_resource.Resource):
async def APFollowersRequest(
self,
request: "HTTPRequest",
data: Optional[dict],
account_jid: jid.JID,
node: Optional[str],
ap_account: Optional[str],
@ -882,6 +878,7 @@ class HTTPAPGServer(web_resource.Resource):
async def APFollowingRequest(
self,
request: "HTTPRequest",
data: Optional[dict],
account_jid: jid.JID,
node: Optional[str],
ap_account: Optional[str],
@ -921,6 +918,7 @@ class HTTPAPGServer(web_resource.Resource):
async def APRequest(
self,
request: "HTTPRequest",
data: Optional[dict] = None,
signing_actor: Optional[str] = None
) -> None:
if self.apg.verbose:
@ -929,14 +927,8 @@ class HTTPAPGServer(web_resource.Resource):
"",
f"<<< got {request.method.decode()} request - {request.uri.decode()}"
]
try:
data = json.load(request.content)
except (json.JSONDecodeError, ValueError):
pass
else:
if data is not None:
to_log.append(pformat(data))
finally:
request.content.seek(0)
if self.apg.verbose>=3:
headers = "\n".join(
f" {k.decode()}: {v.decode()}"
@ -990,7 +982,7 @@ class HTTPAPGServer(web_resource.Resource):
if request_type != "shared_inbox":
raise exceptions.DataError(f"Invalid request type: {request_type!r}")
ret_data = await self.APInboxRequest(
request, None, None, None, ap_url, signing_actor
request, data, None, None, None, ap_url, signing_actor
)
elif request_type == "avatar":
if len(extra_args) != 1:
@ -1013,7 +1005,7 @@ class HTTPAPGServer(web_resource.Resource):
raise exceptions.DataError(f"Invalid request type: {request_type!r}")
method = getattr(self, f"AP{request_type.title()}Request")
ret_data = await method(
request, account_jid, node, ap_account, ap_url, signing_actor
request, data, account_jid, node, ap_account, ap_url, signing_actor
)
if ret_data is not None:
request.setHeader("content-type", CONTENT_TYPE_AP)
@ -1027,7 +1019,26 @@ class HTTPAPGServer(web_resource.Resource):
log.info("\n".join(to_log))
request.finish()
async def APPostRequest(self, request: "HTTPRequest"):
async def APPostRequest(self, request: "HTTPRequest") -> None:
try:
data = json.load(request.content)
if not isinstance(data, dict):
log.warning(f"JSON data should be an object (uri={request.uri.decode()})")
self.responseCode(
request,
http.BAD_REQUEST,
f"invalid body, was expecting a JSON object"
)
request.finish()
return
except (json.JSONDecodeError, ValueError) as e:
self.responseCode(
request,
http.BAD_REQUEST,
f"invalid json in inbox request: {e}"
)
request.finish()
return
try:
signing_actor = await self.checkSignature(request)
except exceptions.EncryptionError as e:
@ -1050,7 +1061,7 @@ class HTTPAPGServer(web_resource.Resource):
# default response code, may be changed, e.g. in case of exception
try:
return await self.APRequest(request, signing_actor)
return await self.APRequest(request, data, signing_actor)
except Exception as e:
self._onRequestError(failure.Failure(e), request)

View file

@ -629,7 +629,7 @@ class TestActivityPubGateway:
ap_gateway,
type_: Optional[str] = None,
url: Optional[str] = None,
doc: Optional[Any] = None,
data: Optional[dict] = None,
query_data: Optional[dict] = None,
signing_actor: Optional[str] = None,
) -> Dict[str, Any]:
@ -637,8 +637,7 @@ class TestActivityPubGateway:
@param type_: one of the AP query type (e.g. "outbox")
@param url: URL to query (mutually exclusif with type_)
@param doc: if set, a document to embed in content of the request (will be
converted to JSON)
@param data: object used as body of the request
@param query_data: query data as returned by parse.parse_qs
@return dict with kw params to use
"""
@ -661,12 +660,11 @@ class TestActivityPubGateway:
request = Request(MagicMock())
request.path = path.encode()
request.uri = uri.encode()
if doc is not None:
request.content = io.BytesIO(json.dumps(doc).encode())
ap_url = parse.urljoin(f"https://{ap_gateway.public_url}", path)
kwargs = {
"request": request,
"data": data,
"account_jid": test_jid,
"node": None,
"ap_account": test_jid.full(),
@ -1510,7 +1508,7 @@ class TestActivityPubGateway:
with patch.object(ap_gateway._p, "sendItems") as sendItems:
await ap_gateway.server.resource.APInboxRequest(
**self.ap_request_params(
ap_gateway, "inbox", doc=like, signing_actor=TEST_AP_ACTOR_ID
ap_gateway, "inbox", data=like, signing_actor=TEST_AP_ACTOR_ID
)
)
@ -1614,7 +1612,7 @@ class TestActivityPubGateway:
with patch.object(ap_gateway._p, "sendItems") as sendItems:
await ap_gateway.server.resource.APInboxRequest(
**self.ap_request_params(
ap_gateway, "inbox", doc=like, signing_actor=TEST_AP_ACTOR_ID
ap_gateway, "inbox", data=like, signing_actor=TEST_AP_ACTOR_ID
)
)