noop, rename Domain => User

pull/287/head
Ryan Barrett 2022-11-15 22:00:28 -08:00
rodzic 46f039af63
commit 50956c02b0
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
14 zmienionych plików z 77 dodań i 77 usunięć

Wyświetl plik

@ -17,7 +17,7 @@ from oauth_dropins.webutil.util import json_dumps, json_loads
from app import app, cache
import common
from common import redirect_unwrap, redirect_wrap
from models import Follower, Domain
from models import Follower, User
from httpsig.requests_auth import HTTPSignatureAuth
logger = logging.getLogger(__name__)
@ -58,8 +58,8 @@ def send(activity, inbox_url, user_domain):
# https://tools.ietf.org/html/draft-cavage-http-signatures-07
# https://github.com/tootsuite/mastodon/issues/4906#issuecomment-328844846
key_id = request.host_url + user_domain
domain = Domain.get_or_create(user_domain)
auth = HTTPSignatureAuth(secret=domain.private_pem(), key_id=key_id,
user = User.get_or_create(user_domain)
auth = HTTPSignatureAuth(secret=user.private_pem(), key_id=key_id,
algorithm='rsa-sha256', sign_header='signature',
headers=('Date', 'Digest', 'Host'))
@ -94,9 +94,9 @@ def actor(domain):
if not hcard:
error(f"Couldn't find a representative h-card (http://microformats.org/wiki/representative-hcard-parsing) on {mf2['url']}")
entity = Domain.get_or_create(domain)
user = User.get_or_create(domain)
obj = common.postprocess_as2(
as2.from_as1(microformats2.json_to_object(hcard)), domain=entity)
as2.from_as1(microformats2.json_to_object(hcard)), user=user)
obj.update({
'preferredUsername': domain,
'inbox': f'{request.host_url}{domain}/inbox',

Wyświetl plik

@ -14,7 +14,7 @@ from oauth_dropins.webutil.flask_util import error
import requests
from werkzeug.exceptions import BadGateway
from models import Activity, Domain
from models import Activity, User
logger = logging.getLogger(__name__)
@ -242,22 +242,22 @@ def send_webmentions(activity_wrapped, proxy=None, **activity_props):
error(msg, status=int(errors[0][0] or 502))
def postprocess_as2(activity, domain=None, target=None):
def postprocess_as2(activity, user=None, target=None):
"""Prepare an AS2 object to be served or sent via ActivityPub.
Args:
activity: dict, AS2 object or activity
domain: :class:`Domain`, required. populated into actor.id and
user: :class:`User`, required. populated into actor.id and
publicKey fields if needed.
target: dict, AS2 object, optional. The target of activity's inReplyTo or
Like/Announce/etc object, if any.
"""
assert domain
assert user
type = activity.get('type')
# actor objects
if type == 'Person':
postprocess_as2_actor(activity, domain)
postprocess_as2_actor(activity, user)
if not activity.get('publicKey'):
# underspecified, inferred from this issue and Mastodon's implementation:
# https://github.com/w3c/activitypub/issues/203#issuecomment-297553229
@ -267,7 +267,7 @@ def postprocess_as2(activity, domain=None, target=None):
'publicKey': {
'id': actor_url,
'owner': actor_url,
'publicKeyPem': domain.public_pem().decode(),
'publicKeyPem': user.public_pem().decode(),
},
'@context': (util.get_list(activity, '@context') +
['https://w3id.org/security/v1']),
@ -276,7 +276,7 @@ def postprocess_as2(activity, domain=None, target=None):
for actor in (util.get_list(activity, 'attributedTo') +
util.get_list(activity, 'actor')):
postprocess_as2_actor(actor, domain)
postprocess_as2_actor(actor, user)
# inReplyTo: singly valued, prefer id over url
target_id = target.get('id') if target else None
@ -355,32 +355,32 @@ def postprocess_as2(activity, domain=None, target=None):
'@context': as2.CONTEXT,
'type': 'Create',
'id': f'{activity["id"]}#bridgy-fed-create',
'actor': postprocess_as2_actor({}, domain),
'actor': postprocess_as2_actor({}, user),
'object': activity,
}
return util.trim_nulls(activity)
def postprocess_as2_actor(actor, domain=None):
def postprocess_as2_actor(actor, user=None):
"""Prepare an AS2 actor object to be served or sent via ActivityPub.
Modifies actor in place.
Args:
actor: dict, AS2 actor object
domain: :class:`Domain`
user: :class:`User`
Returns:
actor dict
"""
url = actor.get('url') or f'https://{domain.key.id()}/'
domain_str = urllib.parse.urlparse(url).netloc
url = actor.get('url') or f'https://{user.key.id()}/'
domain = urllib.parse.urlparse(url).netloc
actor.setdefault('id', request.host_url + domain_str)
actor.setdefault('id', request.host_url + domain)
actor.update({
'url': redirect_wrap(url),
'preferredUsername': domain_str,
'preferredUsername': domain,
})
# required by pixelfed. https://github.com/snarfed/bridgy-fed/issues/39

Wyświetl plik

@ -11,7 +11,7 @@ from oauth_dropins.webutil.models import StringIdModel
logger = logging.getLogger(__name__)
class Domain(StringIdModel):
class User(StringIdModel):
"""Stores a user's public/private key pair used for Magic Signatures.
The key name is the domain.
@ -35,18 +35,18 @@ class Domain(StringIdModel):
@staticmethod
@ndb.transactional()
def get_or_create(domain):
"""Loads and returns a Domain. Creates it if necessary."""
entity = Domain.get_by_id(domain)
"""Loads and returns a User. Creates it if necessary."""
user = User.get_by_id(domain)
if not entity:
if not user:
# this uses urandom(), and does nontrivial math, so it can take a
# while depending on the amount of randomness available.
pubexp, mod, privexp = magicsigs.generate()
entity = Domain(id=domain, mod=mod, public_exponent=pubexp,
user = User(id=domain, mod=mod, public_exponent=pubexp,
private_exponent=privexp)
entity.put()
user.put()
return entity
return user
def href(self):
return 'data:application/magic-public-key,RSA.%s.%s' % (

Wyświetl plik

@ -13,7 +13,7 @@ from oauth_dropins.webutil.util import json_dumps, json_loads
from app import app, cache
import common
from models import Follower, Domain, Activity
from models import Follower, User, Activity
PAGE_SIZE = 20
FOLLOWERS_UI_LIMIT = 999
@ -33,7 +33,7 @@ def user_deprecated(domain):
@app.get(f'/user/<regex("{common.DOMAIN_RE}"):domain>')
def user(domain):
if not Domain.get_by_id(domain):
if not User.get_by_id(domain):
return render_template('user_not_found.html', domain=domain), 404
query = Activity.query(
@ -63,7 +63,7 @@ def followers(domain):
# TODO:
# pull more info from last_follow, eg name, profile picture, url
# unify with following
if not Domain.get_by_id(domain):
if not User.get_by_id(domain):
return render_template('user_not_found.html', domain=domain), 404
query = Follower.query(
@ -88,7 +88,7 @@ def followers(domain):
@app.get(f'/user/<regex("{common.DOMAIN_RE}"):domain>/following')
def following(domain):
if not Domain.get_by_id(domain):
if not User.get_by_id(domain):
return render_template('user_not_found.html', domain=domain), 404
query = Follower.query(
@ -192,7 +192,7 @@ def fetch_page(query, model_class):
def stats():
return render_template(
'stats.html',
users=KindStat.query(KindStat.kind_name == 'Domain').get().count,
users=KindStat.query(KindStat.kind_name == 'User').get().count,
activities=KindStat.query(KindStat.kind_name == 'Activity').get().count,
followers=KindStat.query(KindStat.kind_name == 'Follower').get().count,
)

Wyświetl plik

@ -23,7 +23,7 @@ from werkzeug.exceptions import abort
from app import app, cache
import common
from models import Domain
from models import User
logger = logging.getLogger(__name__)
@ -52,9 +52,9 @@ def redir(to):
urllib.parse.urlparse(to).hostname))
for domain in domains:
if domain:
entity = Domain.get_by_id(domain)
if entity:
logger.info(f'Found Domain for domain {domain}')
user = User.get_by_id(domain)
if user:
logger.info(f'Found User for domain {domain}')
break
else:
logger.info(f'No user found for any of {domains}; returning 404')
@ -64,7 +64,7 @@ def redir(to):
# priorities.
if request.headers.get('Accept') in (common.CONTENT_TYPE_AS2,
common.CONTENT_TYPE_AS2_LD):
return convert_to_as2(to, entity)
return convert_to_as2(to, user)
# redirect
logger.info(f'redirecting to {to}')
@ -79,7 +79,7 @@ def convert_to_as2(url, domain):
Args:
url: str
domain: :class:`Domain`
domain: :class:`User`
"""
mf2 = util.fetch_mf2(url)
entry = mf2util.find_first_entry(mf2, ['h-entry'])

Wyświetl plik

@ -14,7 +14,7 @@ from urllib3.exceptions import ReadTimeoutError
import activitypub
import common
from models import Follower, Domain, Activity
from models import Follower, User, Activity
from . import testutil
REPLY_OBJECT = {
@ -180,7 +180,7 @@ class ActivityPubTest(testutil.TestCase):
'publicKey': {
'id': 'http://localhost/foo.com',
'owner': 'http://localhost/foo.com',
'publicKeyPem': Domain.get_by_id('foo.com').public_pem().decode(),
'publicKeyPem': User.get_by_id('foo.com').public_pem().decode(),
},
}, got.json)

Wyświetl plik

@ -9,7 +9,7 @@ from werkzeug.exceptions import BadGateway
from app import app
import common
from models import Domain
from models import User
from . import testutil
HTML = requests_response('<html></html>', headers={
@ -75,7 +75,7 @@ class CommonTest(testutil.TestCase):
}, common.postprocess_as2({
'id': 'xyz',
'inReplyTo': ['foo', 'bar'],
}, domain=Domain(id='foo.com')))
}, user=User(id='foo.com')))
def test_postprocess_as2_actor_attributedTo(self):
with app.test_request_context('/'):
@ -98,7 +98,7 @@ class CommonTest(testutil.TestCase):
}, common.postprocess_as2({
'attributedTo': [{'id': 'bar'}, {'id': 'baz'}],
'actor': {'id': 'baj'},
}, domain=Domain(id='foo.com')))
}, user=User(id='foo.com')))
def test_postprocess_as2_note(self):
with app.test_request_context('/'):
@ -119,5 +119,5 @@ class CommonTest(testutil.TestCase):
}, common.postprocess_as2({
'id': 'xyz',
'type': 'Note',
}, domain=Domain(id='foo.com')))
}, user=User(id='foo.com')))

Wyświetl plik

@ -1,37 +1,37 @@
# coding=utf-8
"""Unit tests for models.py."""
from app import app
from models import Domain, Activity
from models import User, Activity
from . import testutil
class DomainTest(testutil.TestCase):
class UserTest(testutil.TestCase):
def setUp(self):
super(DomainTest, self).setUp()
self.domain = Domain.get_or_create('y.z')
super(UserTest, self).setUp()
self.user = User.get_or_create('y.z')
def test_magic_key_get_or_create(self):
assert self.domain.mod
assert self.domain.public_exponent
assert self.domain.private_exponent
assert self.user.mod
assert self.user.public_exponent
assert self.user.private_exponent
same = Domain.get_or_create('y.z')
self.assertEqual(same, self.domain)
same = User.get_or_create('y.z')
self.assertEqual(same, self.user)
def test_href(self):
href = self.domain.href()
href = self.user.href()
self.assertTrue(href.startswith('data:application/magic-public-key,RSA.'), href)
self.assertIn(self.domain.mod, href)
self.assertIn(self.domain.public_exponent, href)
self.assertIn(self.user.mod, href)
self.assertIn(self.user.public_exponent, href)
def test_public_pem(self):
pem = self.domain.public_pem()
pem = self.user.public_pem()
self.assertTrue(pem.decode().startswith('-----BEGIN PUBLIC KEY-----\n'), pem)
self.assertTrue(pem.decode().endswith('-----END PUBLIC KEY-----'), pem)
def test_private_pem(self):
pem = self.domain.private_pem()
pem = self.user.private_pem()
self.assertTrue(pem.decode().startswith('-----BEGIN RSA PRIVATE KEY-----\n'), pem)
self.assertTrue(pem.decode().endswith('-----END RSA PRIVATE KEY-----'), pem)

Wyświetl plik

@ -6,7 +6,7 @@ from unittest.mock import patch
from oauth_dropins.webutil.testutil import requests_response
import common
from models import Domain
from models import User
from .test_webmention import REPOST_HTML, REPOST_AS2
from . import testutil
@ -15,7 +15,7 @@ class RedirectTest(testutil.TestCase):
def setUp(self):
super().setUp()
Domain.get_or_create('foo.com')
User.get_or_create('foo.com')
def test_redirect(self):
got = self.client.get('/r/https://foo.com/bar?baz=baj&biff')

Wyświetl plik

@ -12,7 +12,7 @@ from oauth_dropins.webutil.testutil import requests_response, UrlopenResult
import requests
import common
from models import Domain, Activity
from models import User, Activity
from . import testutil
@ -24,7 +24,7 @@ class SalmonTest(testutil.TestCase):
def setUp(self):
super().setUp()
self.key = Domain.get_or_create('alice')
self.key = User.get_or_create('alice')
def send_slap(self, mock_urlopen, mock_head, mock_get, mock_post, atom_slap):
# salmon magic key discovery. first host-meta, then webfinger

Wyświetl plik

@ -32,7 +32,7 @@ class WebfingerTest(testutil.TestCase):
</a>
</body>
"""
self.key = models.Domain.get_or_create('foo.com')
self.key = models.User.get_or_create('foo.com')
self.expected_webfinger = {
'subject': 'acct:foo.com@foo.com',
'aliases': [

Wyświetl plik

@ -26,7 +26,7 @@ from common import (
CONTENT_TYPE_HTML,
CONTENT_TYPE_MAGIC_ENVELOPE,
)
from models import Follower, Domain, Activity
from models import Follower, User, Activity
import webmention
from . import testutil
@ -69,7 +69,7 @@ REPOST_AS2 = {
class WebmentionTest(testutil.TestCase):
def setUp(self):
super().setUp()
self.key = Domain.get_or_create('a')
self.key = User.get_or_create('a')
self.orig_html_as2 = requests_response("""\
<html>

Wyświetl plik

@ -17,7 +17,7 @@ from oauth_dropins.webutil.util import json_dumps
from app import app, cache
import common
import models
from models import User
CACHE_TIME = datetime.timedelta(seconds=15)
NON_TLDS = frozenset(('html', 'json', 'php', 'xml'))
@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
# CACHE_TIME.total_seconds(),
# make_cache_key=lambda domain: f'{request.path} {request.headers.get("Accept")}')
class User(flask_util.XrdOrJrd):
class Actor(flask_util.XrdOrJrd):
"""Fetches a site's home page, converts its mf2 to WebFinger, and serves."""
def template_prefix(self):
return 'webfinger_user'
@ -59,7 +59,7 @@ class User(flask_util.XrdOrJrd):
error(f"didn't find a representative h-card (http://microformats.org/wiki/representative-hcard-parsing) on {resp.url}")
logger.info(f'Generating WebFinger data for {domain}')
entity = models.Domain.get_or_create(domain)
user = User.get_or_create(domain)
props = hcard.get('properties', {})
urls = util.dedupe_urls(props.get('url', []) + [resp.url])
canonical_url = urls[0]
@ -97,7 +97,7 @@ class User(flask_util.XrdOrJrd):
data = util.trim_nulls({
'subject': 'acct:' + acct,
'aliases': urls,
'magic_keys': [{'value': entity.href()}],
'magic_keys': [{'value': user.href()}],
'links': sum(([{
'rel': 'http://webfinger.net/rel/profile-page',
'type': 'text/html',
@ -135,7 +135,7 @@ class User(flask_util.XrdOrJrd):
'href': hub,
}, {
'rel': 'magic-public-key',
'href': entity.href(),
'href': user.href(),
}, {
'rel': 'salmon',
'href': f'{request.host_url}{domain}/salmon',
@ -145,7 +145,7 @@ class User(flask_util.XrdOrJrd):
return data
class Webfinger(User):
class Webfinger(Actor):
"""Handles Webfinger requests.
https://webfinger.net/
@ -192,7 +192,7 @@ def host_meta_xrds():
app.add_url_rule(f'/acct:<regex("{common.DOMAIN_RE}"):domain>',
view_func=User.as_view('actor_acct'))
view_func=Actor.as_view('actor_acct'))
app.add_url_rule('/.well-known/webfinger', view_func=Webfinger.as_view('webfinger'))
app.add_url_rule('/.well-known/host-meta', view_func=HostMeta.as_view('hostmeta'))
app.add_url_rule('/.well-known/host-meta.json', view_func=HostMeta.as_view('hostmeta-json'))

Wyświetl plik

@ -25,7 +25,7 @@ from werkzeug.exceptions import BadGateway
import activitypub
from app import app
import common
from models import Follower, Domain, Activity
from models import Follower, User, Activity
logger = logging.getLogger(__name__)
@ -91,7 +91,7 @@ class Webmention(View):
if not targets:
return None
domain = Domain.get_or_create(self.source_domain)
user = User.get_or_create(self.source_domain)
error = None
last_success = None
@ -106,7 +106,7 @@ class Webmention(View):
for resp, inbox in targets:
target_obj = json_loads(resp.target_as2) if resp.target_as2 else None
source_activity = common.postprocess_as2(
as2.from_as1(self.source_obj), target=target_obj, domain=domain)
as2.from_as1(self.source_obj), target=target_obj, user=user)
if resp.status == 'complete':
if resp.source_mf2:
@ -361,10 +361,10 @@ class Webmention(View):
# sign reply and wrap in magic envelope
domain = urllib.parse.urlparse(self.source_url).netloc
entity = Domain.get_or_create(domain)
logger.info(f'Using key for {domain}: {entity}')
user = User.get_or_create(domain)
logger.info(f'Using key for {domain}: {user}')
magic_envelope = magicsigs.magic_envelope(
entry, common.CONTENT_TYPE_ATOM, entity).decode()
entry, common.CONTENT_TYPE_ATOM, user).decode()
logger.info(f'Sending Salmon slap to {endpoint}')
common.requests_post(