Handle Diaspora's XML webfinger
This commit is contained in:
parent
8fe99718f3
commit
13fc4b42de
|
@ -21,6 +21,7 @@ pydantic~=1.10.2
|
|||
pyld~=2.0.3
|
||||
pylibmc~=1.6.3
|
||||
pymemcache~=4.0.0
|
||||
pytest-asyncio~=0.20.3
|
||||
python-dateutil~=2.8.2
|
||||
python-dotenv~=0.21.0
|
||||
redis~=4.4.0
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import pytest
|
||||
from asgiref.sync import async_to_sync
|
||||
from pytest_httpx import HTTPXMock
|
||||
|
||||
from core.models import Config
|
||||
from users.models import Domain, Identity, User
|
||||
|
@ -176,3 +177,57 @@ def test_fetch_actor(httpx_mock, config_system):
|
|||
assert identity.image_uri == "https://example.com/image.jpg"
|
||||
assert identity.summary == "<p>A test user</p>"
|
||||
assert "ts-a-faaaake" in identity.public_key
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system):
|
||||
"""
|
||||
Ensures that we can deal with various kinds of webfinger URLs
|
||||
"""
|
||||
|
||||
# With no host-meta, it should be the default
|
||||
assert (
|
||||
await Identity.fetch_webfinger_url("example.com")
|
||||
== "https://example.com/.well-known/webfinger?resource={uri}"
|
||||
)
|
||||
|
||||
# Inject a host-meta directing it to a subdomain
|
||||
httpx_mock.add_response(
|
||||
url="https://example.com/.well-known/host-meta",
|
||||
text="""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
<Link rel="lrdd" template="https://fedi.example.com/.well-known/webfinger?resource={uri}"/>
|
||||
</XRD>""",
|
||||
)
|
||||
assert (
|
||||
await Identity.fetch_webfinger_url("example.com")
|
||||
== "https://fedi.example.com/.well-known/webfinger?resource={uri}"
|
||||
)
|
||||
|
||||
# Inject a host-meta directing it to a different URL format
|
||||
httpx_mock.add_response(
|
||||
url="https://example.com/.well-known/host-meta",
|
||||
text="""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
<Link rel="lrdd" template="https://example.com/amazing-webfinger?query={uri}"/>
|
||||
</XRD>""",
|
||||
)
|
||||
assert (
|
||||
await Identity.fetch_webfinger_url("example.com")
|
||||
== "https://example.com/amazing-webfinger?query={uri}"
|
||||
)
|
||||
|
||||
# Inject a host-meta directing it to a different url THAT SUPPORTS XML ONLY
|
||||
# (we want to ignore that one)
|
||||
httpx_mock.add_response(
|
||||
url="https://example.com/.well-known/host-meta",
|
||||
text="""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
<Link rel="lrdd" template="https://xmlfedi.example.com/webfinger?q={uri}" type="application/xrd+xml"/>
|
||||
</XRD>""",
|
||||
)
|
||||
assert (
|
||||
await Identity.fetch_webfinger_url("example.com")
|
||||
== "https://example.com/.well-known/webfinger?resource={uri}"
|
||||
)
|
||||
|
|
|
@ -601,14 +601,11 @@ class Identity(StatorModel):
|
|||
### Actor/Webfinger fetching ###
|
||||
|
||||
@classmethod
|
||||
async def fetch_webfinger(cls, handle: str) -> tuple[str | None, str | None]:
|
||||
async def fetch_webfinger_url(cls, domain: str):
|
||||
"""
|
||||
Given a username@domain handle, returns a tuple of
|
||||
(actor uri, canonical handle) or None, None if it does not resolve.
|
||||
Given a domain (hostname), returns the correct webfinger URL to use
|
||||
based on probing host-meta.
|
||||
"""
|
||||
domain = handle.split("@")[1].lower()
|
||||
webfinger_url = f"https://{domain}/.well-known/webfinger?resource={{uri}}"
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
timeout=settings.SETUP.REMOTE_TIMEOUT,
|
||||
headers={"User-Agent": settings.TAKAHE_USER_AGENT},
|
||||
|
@ -626,13 +623,29 @@ class Identity(StatorModel):
|
|||
if response.status_code == 200 and response.content.strip():
|
||||
tree = etree.fromstring(response.content)
|
||||
template = tree.xpath(
|
||||
"string(.//*[local-name() = 'Link' and @rel='lrdd']/@template)"
|
||||
"string(.//*[local-name() = 'Link' and @rel='lrdd' and (not(@type) or @type='application/jrd+json')]/@template)"
|
||||
)
|
||||
if template:
|
||||
webfinger_url = template
|
||||
return template
|
||||
except (httpx.RequestError, etree.ParseError):
|
||||
pass
|
||||
|
||||
return f"https://{domain}/.well-known/webfinger?resource={{uri}}"
|
||||
|
||||
@classmethod
|
||||
async def fetch_webfinger(cls, handle: str) -> tuple[str | None, str | None]:
|
||||
"""
|
||||
Given a username@domain handle, returns a tuple of
|
||||
(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)
|
||||
|
||||
# Go make a Webfinger request
|
||||
async with httpx.AsyncClient(
|
||||
timeout=settings.SETUP.REMOTE_TIMEOUT,
|
||||
headers={"User-Agent": settings.TAKAHE_USER_AGENT},
|
||||
) as client:
|
||||
try:
|
||||
response = await client.get(
|
||||
webfinger_url.format(uri=f"acct:{handle}"),
|
||||
|
@ -640,12 +653,12 @@ class Identity(StatorModel):
|
|||
headers={"Accept": "application/json"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
except httpx.HTTPError as ex:
|
||||
except httpx.RequestError as ex:
|
||||
response = getattr(ex, "response", None)
|
||||
if (
|
||||
response
|
||||
and response.status_code < 500
|
||||
and response.status_code not in [404, 410]
|
||||
and response.status_code not in [401, 403, 404, 410]
|
||||
):
|
||||
raise ValueError(
|
||||
f"Client error fetching webfinger: {response.status_code}",
|
||||
|
|
Loading…
Reference in New Issue