diff --git a/activities/models/post.py b/activities/models/post.py index f3777b6..69aac6d 100644 --- a/activities/models/post.py +++ b/activities/models/post.py @@ -1,4 +1,5 @@ import hashlib +import json import mimetypes import re from collections.abc import Iterable @@ -872,12 +873,15 @@ class Post(StatorModel): f"Error fetching post from {object_uri}: {response.status_code}", {response.content}, ) - post = cls.by_ap( - canonicalise(response.json(), include_security=True), - create=True, - update=True, - fetch_author=True, - ) + try: + post = cls.by_ap( + canonicalise(response.json(), include_security=True), + create=True, + update=True, + fetch_author=True, + ) + except (json.JSONDecodeError, ValueError): + raise cls.DoesNotExist(f"Invalid ld+json response for {object_uri}") # We may need to fetch the author too if post.author.state == IdentityStates.outdated: async_to_sync(post.author.fetch_actor)() diff --git a/users/models/domain.py b/users/models/domain.py index 7dd1a7d..e7557f0 100644 --- a/users/models/domain.py +++ b/users/models/domain.py @@ -1,4 +1,5 @@ import json +import ssl from typing import Optional import httpx @@ -7,6 +8,7 @@ from asgiref.sync import sync_to_async from django.conf import settings from django.db import models +from core.exceptions import capture_message from stator.models import State, StateField, StateGraph, StatorModel from users.schemas import NodeInfo @@ -164,6 +166,8 @@ class Domain(StatorModel): ) except httpx.HTTPError: pass + except ssl.SSLCertVerificationError: + return None else: try: for link in response.json().get("links", []): @@ -183,7 +187,7 @@ class Domain(StatorModel): headers={"Accept": "application/json"}, ) response.raise_for_status() - except httpx.HTTPError as ex: + except (httpx.HTTPError, ssl.SSLCertVerificationError) as ex: response = getattr(ex, "response", None) if ( response @@ -199,9 +203,10 @@ class Domain(StatorModel): try: info = NodeInfo(**response.json()) except json.JSONDecodeError as ex: - raise ValueError( + capture_message( f"Client error decoding nodeinfo: domain={self.domain}, error={str(ex)}" ) + return None return info @property diff --git a/users/models/identity.py b/users/models/identity.py index ada35d7..c3333c0 100644 --- a/users/models/identity.py +++ b/users/models/identity.py @@ -1,3 +1,4 @@ +import ssl from functools import cached_property, partial from typing import Literal from urllib.parse import urlparse @@ -642,7 +643,10 @@ class Identity(StatorModel): (actor uri, canonical handle) or None, None if it does not resolve. """ domain = handle.split("@")[1].lower() - webfinger_url = await cls.fetch_webfinger_url(domain) + try: + webfinger_url = await cls.fetch_webfinger_url(domain) + except ssl.SSLCertVerificationError: + return None, None # Go make a Webfinger request async with httpx.AsyncClient( @@ -656,7 +660,7 @@ class Identity(StatorModel): headers={"Accept": "application/json"}, ) response.raise_for_status() - except httpx.RequestError as ex: + except (httpx.HTTPError, ssl.SSLCertVerificationError) as ex: response = getattr(ex, "response", None) if ( response @@ -703,25 +707,24 @@ class Identity(StatorModel): method="get", uri=self.actor_uri, ) - except httpx.RequestError: + except (httpx.RequestError, ssl.SSLCertVerificationError): return False content_type = response.headers.get("content-type") if content_type and "html" in content_type: # Some servers don't properly handle "application/activity+json" return False - if response.status_code == 410: - # Their account got deleted, so let's do the same. - if self.pk: + status_code = response.status_code + if status_code >= 400: + if status_code == 410 and self.pk: + # Their account got deleted, so let's do the same. await Identity.objects.filter(pk=self.pk).adelete() - return False - if response.status_code == 404: - # We don't trust this as much as 410 Gone, but skip for now - return False - if response.status_code >= 500: - return False - if response.status_code >= 400: + + if status_code >= 500 or status_code in [403, 404, 410]: + # Common errors with other server, not worth reporting + return False + raise ValueError( - f"Client error fetching actor at {self.actor_uri}: {response.status_code}", + f"Client error fetching actor at {self.actor_uri}: {status_code}", response.content, ) document = canonicalise(response.json(), include_security=True)