From ca64793fff8756940f7dd3b763973543fa34e84b Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Thu, 1 Jun 2023 21:37:58 -0700 Subject: [PATCH] AP users: add User.name() and label_id(), ActivityPub.address computed property for #512 --- activitypub.py | 11 +++++++++++ models.py | 23 +++++++++++++++++------ tests/test_activitypub.py | 14 ++++++++++++-- tests/test_follow.py | 2 +- tests/test_models.py | 12 ++++++++++++ tests/test_web.py | 3 +++ web.py | 4 ++++ 7 files changed, 60 insertions(+), 9 deletions(-) diff --git a/activitypub.py b/activitypub.py index dac7a51..81d43dc 100644 --- a/activitypub.py +++ b/activitypub.py @@ -6,6 +6,7 @@ import logging from urllib.parse import quote_plus from flask import abort, g, request +from google.cloud import ndb from granary import as1, as2 from httpsig import HeaderVerifier from httpsig.requests_auth import HTTPSignatureAuth @@ -51,9 +52,19 @@ class ActivityPub(User, Protocol): """ActivityPub protocol class. Key id is AP/AS2 actor id URL. (*Not* fediverse/WebFinger @-@ handle!) + + Includes extra `address` computed property for querying entities by handle. """ LABEL = 'activitypub' + @ndb.ComputedProperty + def address(self): + return self.ap_address() + + def label_id(self): + """Returns this user's human-readable unique id, eg '@me@snarfed.org'.""" + return self.address or self.key.id() + def web_url(self): """Returns this user's web URL aka web_url, eg 'https://foo.com/'.""" return util.get_url(self.actor_as2) or self.ap_actor() diff --git a/models.py b/models.py index 97ef23e..a5c6ce7 100644 --- a/models.py +++ b/models.py @@ -174,11 +174,26 @@ class User(StringIdModel, metaclass=ProtocolUserMeta): if self.actor_as2: return as2.to_as1(self.actor_as2) + def name(self): + """Returns this user's human-readable name, eg 'Ryan Barrett'.""" + if self.actor_as2: + name = self.actor_as2.get('name') + if name: + return name + + return self.label_id() + + def label_id(self): + """Returns this user's human-readable unique id, eg '@me@snarfed.org'.""" + return self.key.id() + def username(self): """Returns the user's preferred username. Uses stored representative h-card if available, falls back to id. + TODO(#512): move to Web + Returns: str """ id = self.key.id() @@ -255,7 +270,7 @@ class User(StringIdModel, metaclass=ProtocolUserMeta): def user_page_path(self, rest=None): """Returns the user's Bridgy Fed user page path.""" - path = f'/{self.LABEL}/{self.key.id()}' + path = f'/{self.LABEL}/{self.label_id()}' if rest: path += f'/{rest}' return path @@ -263,12 +278,8 @@ class User(StringIdModel, metaclass=ProtocolUserMeta): def user_page_link(self): """Returns a pretty user page link with the user's name and profile picture.""" actor = self.actor_as2 or {} - name = (actor.get('name') or - # prettify if domain, noop if username - util.domain_from_link(self.username())) img = util.get_url(actor, 'icon') or '' - - return f' {name}' + return f' {self.name()}' class Target(ndb.Model): diff --git a/tests/test_activitypub.py b/tests/test_activitypub.py index 9855024..2e8c3f7 100644 --- a/tests/test_activitypub.py +++ b/tests/test_activitypub.py @@ -1543,17 +1543,20 @@ class ActivityPubUtilsTest(TestCase): activitypub.postprocess_as2(activitypub.postprocess_as2(obj)), ignore=['to']) - def test_ap_actor(self): + def test_ap_address(self): user = ActivityPub(actor_as2={**ACTOR, 'preferredUsername': 'me'}) self.assertEqual('@me@mas.to', user.ap_address()) + self.assertEqual('@me@mas.to', user.address) user = ActivityPub(actor_as2=ACTOR) self.assertEqual('@swentel@mas.to', user.ap_address()) + self.assertEqual('@swentel@mas.to', user.address) user = ActivityPub(id='https://mas.to/users/alice') self.assertEqual('@alice@mas.to', user.ap_address()) + self.assertEqual('@alice@mas.to', user.address) - def test_ap_address(self): + def test_ap_actor(self): user = self.make_user('http://foo/actor', cls=ActivityPub) self.assertEqual('http://foo/actor', user.ap_actor()) @@ -1566,3 +1569,10 @@ class ActivityPubUtilsTest(TestCase): user.actor_as2['url'] = ['http://my/url'] self.assertEqual('http://my/url', user.web_url()) + + def test_label_id(self): + user = self.make_user('http://foo', cls=ActivityPub) + self.assertEqual('http://foo', user.label_id()) + + user.actor_as2 = ACTOR + self.assertEqual('@swentel@mas.to', user.label_id()) diff --git a/tests/test_follow.py b/tests/test_follow.py index f69b663..9c500cb 100644 --- a/tests/test_follow.py +++ b/tests/test_follow.py @@ -230,7 +230,7 @@ class FollowTest(testutil.TestCase): def test_callback_user_use_instead(self, mock_get, mock_post): user = self.make_user('www.alice.com') self.user.use_instead = user.key - user.put() + self.user.put() mock_get.side_effect = ( requests_response(''), diff --git a/tests/test_models.py b/tests/test_models.py index 42e1c79..cbc6822 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -94,6 +94,18 @@ class UserTest(TestCase): 'https://user', '://y.z'): self.assertFalse(g.user.is_web_url(url), url) + def test_name(self): + self.assertEqual('y.z', g.user.name()) + + g.user.actor_as2 = {'id': 'abc'} + self.assertEqual('y.z', g.user.name()) + + g.user.actor_as2 = {'name': 'alice'} + self.assertEqual('alice', g.user.name()) + + def test_label_id(self): + self.assertEqual('y.z', g.user.label_id()) + class ObjectTest(TestCase): def setUp(self): diff --git a/tests/test_web.py b/tests/test_web.py index 88bd5c6..a5e66d5 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -1501,6 +1501,9 @@ http://this/404s 'preferredUsername': 'user.com', }) + def test_label_id(self, _, __): + self.assertEqual('user.com', self.user.label_id()) + def test_web_url(self, _, __): self.assertEqual('https://user.com/', self.user.web_url()) diff --git a/web.py b/web.py index 31b3f1e..f473dc3 100644 --- a/web.py +++ b/web.py @@ -49,6 +49,10 @@ class Web(User, Protocol): def _get_kind(cls): return 'MagicKey' + def label_id(self): + # prettify if domain, noop if username + return util.domain_from_link(self.username(), minimize=False) + def web_url(self): """Returns this user's web URL aka web_url, eg 'https://foo.com/'.""" return f'https://{self.key.id()}/'