add Protocol.handle_to_id, implement in all protocols

pull/646/head
Ryan Barrett 2023-09-22 13:11:15 -07:00
rodzic e97270b344
commit 169f8d7a2c
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
9 zmienionych plików z 95 dodań i 2 usunięć

Wyświetl plik

@ -30,6 +30,7 @@ from common import (
)
from models import Follower, Object, PROTOCOLS, User
from protocol import Protocol
import webfinger
# TODO: remove this. we only need it to make sure Web is registered in PROTOCOLS
# before the URL route registrations below.
@ -134,6 +135,20 @@ class ActivityPub(User, Protocol):
parts = handle.lstrip('@').split('@')
return len(parts) == 2 and parts[0] and parts[1]
@classmethod
def handle_to_id(cls, handle):
"""Looks in the datastore first, then queries WebFinger."""
assert cls.owns_handle(handle)
if not handle.startswith('@'):
handle = '@' + handle
user = ActivityPub.query(ActivityPub.readable_id == handle).get()
if user:
return user.key.id()
return webfinger.fetch_actor_url(handle)
@classmethod
def target_for(cls, obj, shared=False):
"""Returns `obj`'s or its author's/actor's inbox, if available."""

Wyświetl plik

@ -99,6 +99,16 @@ class ATProto(User, Protocol):
if not re.match(DOMAIN_RE, handle):
return False
@classmethod
def handle_to_id(cls, handle):
assert cls.owns_handle(handle) is not False
user = ATProto.query(ATProto.readable_id == handle).get()
if user:
return user.key.id()
return did.resolve_handle(handle, get_fn=util.requests_get)
@classmethod
def target_for(cls, obj, shared=False):
"""Returns the PDS URL for the given object, or None.

Wyświetl plik

@ -127,6 +127,10 @@ class Protocol:
To be implemented by subclasses.
IDs are string identities that uniquely identify users, and are intended
primarily to be machine readable and usable. Compare to handles, which
are human-chosen, human-meaningful, and often but not always unique.
Some protocols' ids are more or less deterministic based on the id
format, eg AT Protocol owns at:// URIs. Others, like http(s) URLs, could
be owned by eg Web or ActivityPub.
@ -151,6 +155,10 @@ class Protocol:
To be implemented by subclasses.
Handles are string identities that are human-chosen, human-meaningful,
and often but not always unique. Compare to IDs, which uniquely identify
users, and are intended primarily to be machine readable and usable.
Some protocols' handles are more or less deterministic based on the id
format, eg ActivityPub (technically WebFinger) handles are
``@user@instance.com``. Others, like domains, could be owned by eg Web,
@ -168,6 +176,22 @@ class Protocol:
"""
return False
@classmethod
def handle_to_id(cls, handle):
"""Converts a handle to an id.
To be implemented by subclasses.
May incur network requests, eg DNS queries or HTTP requests.
Args:
handle (str)
Returns:
str: corresponding id, or None if the handle can't be found
"""
raise NotImplementedError()
@classmethod
def key_for(cls, id):
"""Returns the :class:`ndb.Key` for a given id's :class:`User`.

Wyświetl plik

@ -30,6 +30,7 @@ from web import Web
# have to import module, not attrs, to avoid circular import
from . import test_web
from .test_webfinger import WEBFINGER
ACTOR = {
'@context': 'https://www.w3.org/ns/activitystreams',
@ -1508,6 +1509,26 @@ class ActivityPubUtilsTest(TestCase):
with self.subTest(handle=handle):
self.assertFalse(ActivityPub.owns_handle(handle))
def test_handle_to_id_stored(self):
self.make_user(id='http://inst.com/@user', cls=ActivityPub)
self.assertEqual('http://inst.com/@user',
ActivityPub.handle_to_id('@user@inst.com'))
@patch('requests.get', return_value=requests_response(WEBFINGER))
def test_handle_to_id_fetch(self, mock_get):
self.assertEqual('http://localhost/user.com',
ActivityPub.handle_to_id('@user@inst.com'))
self.assert_req(
mock_get,
'https://inst.com/.well-known/webfinger?resource=acct:user@inst.com')
@patch('requests.get', return_value=requests_response({}))
def test_handle_to_id_not_found(self, mock_get):
self.assertIsNone(ActivityPub.handle_to_id('@user@inst.com'))
self.assert_req(
mock_get,
'https://inst.com/.well-known/webfinger?resource=acct:user@inst.com')
def test_postprocess_as2_multiple_in_reply_tos(self):
self.assert_equals({
'id': 'http://localhost/r/xyz',

Wyświetl plik

@ -100,6 +100,17 @@ class ATProtoTest(TestCase):
self.assertFalse(ATProto.owns_handle('@foo@bar.com'))
self.assertFalse(ATProto.owns_handle('foo@bar.com'))
def test_handle_to_id(self, *_):
self.store_object(id='did:plc:foo', raw=DID_DOC)
self.make_user('did:plc:foo', cls=ATProto)
self.assertEqual('did:plc:foo', ATProto.handle_to_id('han.dull'))
@patch('dns.resolver.resolve', side_effect=dns.resolver.NXDOMAIN())
# resolving handle, HTTPS method, not founud
@patch('requests.get', return_value=requests_response('', status=404))
def test_handle_to_id_not_found(self, *_):
self.assertIsNone(ATProto.handle_to_id('han.dull'))
def test_target_for_did_doc(self):
self.assertIsNone(ATProto.target_for(Object(id='did:plc:foo')))

Wyświetl plik

@ -1910,6 +1910,9 @@ class WebUtilTest(TestCase):
self.assertFalse(Web.owns_handle('@foo@bar.com'))
self.assertFalse(Web.owns_handle('foo@bar.com'))
def test_handle_to_id(self, *_):
self.assertEqual('foo.com', Web.handle_to_id('foo.com'))
def test_fetch(self, mock_get, __):
mock_get.return_value = REPOST

Wyświetl plik

@ -91,6 +91,10 @@ class Fake(User, protocol.Protocol):
owns_handle = owns_id
@classmethod
def handle_to_id(cls, handle):
return handle
@classmethod
def is_blocklisted(cls, url):
return url.startswith('fake:blocklisted')

5
web.py
Wyświetl plik

@ -265,6 +265,11 @@ class Web(User, Protocol):
if not re.match(DOMAIN_RE, handle):
return False
@classmethod
def handle_to_id(cls, handle):
assert cls.owns_handle(handle) is not False
return handle
@classmethod
def target_for(cls, obj, shared=False):
"""Returns `obj`'s id, as a URL webmention target."""

Wyświetl plik

@ -168,7 +168,7 @@ def host_meta_xrds():
def fetch(addr):
"""Fetches and returns an address's Webfinger data.
"""Fetches and returns an address's WebFinger data.
On failure, flashes a message and returns None.
@ -180,7 +180,7 @@ def fetch(addr):
https://x/y
Returns:
dict: fetched Webfinger data, or None on error
dict: fetched WebFinger data, or None on error
"""
addr = addr.strip().strip('@')
split = addr.split('@')