kopia lustrzana https://github.com/snarfed/bridgy-fed
add Protocol.handle_to_id, implement in all protocols
rodzic
e97270b344
commit
169f8d7a2c
|
@ -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."""
|
||||
|
|
10
atproto.py
10
atproto.py
|
@ -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.
|
||||
|
|
24
protocol.py
24
protocol.py
|
@ -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`.
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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')))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
5
web.py
|
@ -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."""
|
||||
|
|
|
@ -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('@')
|
||||
|
|
Ładowanie…
Reference in New Issue