Revert "use Protocol.create_for for all protocols, not just HAS_COPIES ones"

This reverts commit 9f9ecce187.

Not 100% sure but I suspect this caused #1929. Reverting while I debug more.
pull/1933/head
Ryan Barrett 2025-05-13 13:42:09 -07:00
rodzic 3b37484b72
commit 37096e2ed2
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
6 zmienionych plików z 43 dodań i 76 usunięć

Wyświetl plik

@ -3,12 +3,10 @@ from base64 import b64encode
from hashlib import sha256 from hashlib import sha256
import itertools import itertools
import logging import logging
import random
import re import re
from urllib.parse import quote_plus, urljoin, urlparse from urllib.parse import quote_plus, urljoin, urlparse
from unittest.mock import MagicMock from unittest.mock import MagicMock
from Crypto.PublicKey import RSA
from flask import abort, g, redirect, request from flask import abort, g, redirect, request
from google.cloud import ndb from google.cloud import ndb
from google.cloud.ndb.query import FilterNode, OR, Query from google.cloud.ndb.query import FilterNode, OR, Query
@ -36,7 +34,6 @@ from common import (
error, error,
host_url, host_url,
LOCAL_DOMAINS, LOCAL_DOMAINS,
long_to_base64,
PRIMARY_DOMAIN, PRIMARY_DOMAIN,
PROTOCOL_DOMAINS, PROTOCOL_DOMAINS,
redirect_wrap, redirect_wrap,
@ -46,7 +43,7 @@ from common import (
) )
from ids import BOT_ACTOR_AP_IDS from ids import BOT_ACTOR_AP_IDS
import memcache import memcache
from models import fetch_objects, Follower, KEY_BITS, Object, PROTOCOLS, User from models import fetch_objects, Follower, Object, PROTOCOLS, User
from protocol import activity_id_memcache_key, DELETE_TASK_DELAY, Protocol from protocol import activity_id_memcache_key, DELETE_TASK_DELAY, Protocol
import webfinger import webfinger
@ -88,7 +85,7 @@ def instance_actor():
global _INSTANCE_ACTOR global _INSTANCE_ACTOR
if _INSTANCE_ACTOR is None: if _INSTANCE_ACTOR is None:
import web import web
_INSTANCE_ACTOR = web.Web.get_or_create(PRIMARY_DOMAIN, propagate=True) _INSTANCE_ACTOR = web.Web.get_or_create(PRIMARY_DOMAIN)
return _INSTANCE_ACTOR return _INSTANCE_ACTOR
@ -230,28 +227,6 @@ class ActivityPub(User, Protocol):
kwargs['prefer_id'] = False kwargs['prefer_id'] = False
return super().user_page_path(rest=rest, **kwargs) return super().user_page_path(rest=rest, **kwargs)
@classmethod
def create_for(cls, user):
"""Creates an AP keypair for a non-APProto user.
Can use urandom() and do nontrivial math, so this can take time
depending on the amount of randomness available and compute needed.
Args:
user (models.User)
"""
assert not isinstance(user, ActivityPub)
if not user.public_exponent or not user.private_exponent or not user.mod:
assert (not user.public_exponent and not user.private_exponent
and not user.mod), id
key = RSA.generate(KEY_BITS, randfunc=random.randbytes
if appengine_info.DEBUG else None)
user.mod = long_to_base64(key.n)
user.public_exponent = long_to_base64(key.e)
user.private_exponent = long_to_base64(key.d)
user.put()
@classmethod @classmethod
def target_for(cls, obj, shared=False): def target_for(cls, obj, shared=False):
"""Returns ``obj``'s or its author's/actor's inbox, if available.""" """Returns ``obj``'s or its author's/actor's inbox, if available."""
@ -1076,8 +1051,7 @@ def _load_user(handle_or_id, create=False):
assert id assert id
try: try:
user = (proto.get_or_create(id, propagate=True) if create user = proto.get_or_create(id) if create else proto.get_by_id(id)
else proto.get_by_id(id))
except ValueError as e: except ValueError as e:
logging.warning(e) logging.warning(e)
user = None user = None

Wyświetl plik

@ -5,6 +5,7 @@ from functools import lru_cache
import itertools import itertools
import json import json
import logging import logging
import random
import re import re
from threading import Lock from threading import Lock
from urllib.parse import quote, urlparse from urllib.parse import quote, urlparse
@ -29,6 +30,7 @@ import common
from common import ( from common import (
base64_to_long, base64_to_long,
DOMAIN_RE, DOMAIN_RE,
long_to_base64,
OLD_ACCOUNT_AGE, OLD_ACCOUNT_AGE,
PROTOCOL_DOMAINS, PROTOCOL_DOMAINS,
report_error, report_error,
@ -375,13 +377,30 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
proto = PROTOCOLS[label] proto = PROTOCOLS[label]
if proto == cls: if proto == cls:
continue continue
elif user.is_enabled(proto): elif proto.HAS_COPIES:
try: if not user.get_copy(proto) and user.is_enabled(proto):
proto.create_for(user) try:
except (ValueError, AssertionError): proto.create_for(user)
logger.info(f'failed creating {proto.LABEL} copy', except (ValueError, AssertionError):
exc_info=True) logger.info(f'failed creating {proto.LABEL} copy',
util.remove(user.enabled_protocols, proto.LABEL) exc_info=True)
util.remove(user.enabled_protocols, proto.LABEL)
else:
logger.debug(f'{proto.LABEL} not enabled or user copy already exists, skipping propagate')
# generate keys for all protocols _except_ our own
#
# these can use urandom() and do nontrivial math, so they can take time
# depending on the amount of randomness available and compute needed.
if cls.LABEL != 'activitypub':
if (not user.public_exponent or not user.private_exponent or not user.mod):
assert (not user.public_exponent and not user.private_exponent
and not user.mod), id
key = RSA.generate(KEY_BITS,
randfunc=random.randbytes if DEBUG else None)
user.mod = long_to_base64(key.n)
user.public_exponent = long_to_base64(key.e)
user.private_exponent = long_to_base64(key.d)
try: try:
user.put() user.put()
@ -586,9 +605,10 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
added = False added = False
# do this even if there's an existing copy since we might need to if to_proto.LABEL in ids.COPIES_PROTOCOLS:
# reactivate it, which create_for should do # do this even if there's an existing copy since we might need to
to_proto.create_for(self) # reactivate it, which create_for should do
to_proto.create_for(self)
@ndb.transactional() @ndb.transactional()
def enable(): def enable():

Wyświetl plik

@ -497,22 +497,20 @@ class Protocol:
@classmethod @classmethod
def create_for(cls, user): def create_for(cls, user):
"""Creates or re-activate a user in this protocol. """Creates or re-activate a copy user in this protocol.
If this protocol has copies, adds the new copy user to :attr:`copies`. Should add the copy user to :attr:`copies`.
If the copy user already exists and active, does nothing.
By default, does nothing. If the copy user already exists and active, should do nothing.
Args: Args:
user (models.User): original source user. Shouldn't already have a user (models.User): original source user. Shouldn't already have a
copy user for this protocol in :attr:`copies`. copy user for this protocol in :attr:`copies`.
Raises: Raises:
ValueError: if we can't create the given user in this protocol ValueError: if we can't create a copy of the given user in this protocol
""" """
if cls.HAS_COPIES: raise NotImplementedError()
raise NotImplementedError()
@classmethod @classmethod
def send(to_cls, obj, url, from_user=None, orig_obj_id=None): def send(to_cls, obj, url, from_user=None, orig_obj_id=None):

Wyświetl plik

@ -72,8 +72,7 @@ class UserTest(TestCase):
self.assertIsNone(Web.get_by_id('y.za')) self.assertIsNone(Web.get_by_id('y.za'))
def test_get_or_create(self): def test_get_or_create(self):
user = Fake.get_or_create('fake:user', enabled_protocols=['activitypub'], user = Fake.get_or_create('fake:user')
propagate=True)
assert not user.existing assert not user.existing
assert user.mod assert user.mod
@ -188,17 +187,11 @@ class UserTest(TestCase):
self.assertIsNone(Fake.get_or_create('fake:user', manual_opt_out=True)) self.assertIsNone(Fake.get_or_create('fake:user', manual_opt_out=True))
def test_public_pem(self): def test_public_pem(self):
bot = self.make_user(id='ap.brid.gy', cls=Web)
self.user.enable_protocol(ActivityPub)
pem = self.user.public_pem() pem = self.user.public_pem()
self.assertTrue(pem.decode().startswith('-----BEGIN PUBLIC KEY-----\n'), pem) self.assertTrue(pem.decode().startswith('-----BEGIN PUBLIC KEY-----\n'), pem)
self.assertTrue(pem.decode().endswith('-----END PUBLIC KEY-----'), pem) self.assertTrue(pem.decode().endswith('-----END PUBLIC KEY-----'), pem)
def test_private_pem(self): def test_private_pem(self):
bot = self.make_user(id='ap.brid.gy', cls=Web)
self.user.enable_protocol(ActivityPub)
pem = self.user.private_pem() pem = self.user.private_pem()
self.assertTrue(pem.decode().startswith('-----BEGIN RSA PRIVATE KEY-----\n'), pem) self.assertTrue(pem.decode().startswith('-----BEGIN RSA PRIVATE KEY-----\n'), pem)
self.assertTrue(pem.decode().endswith('-----END RSA PRIVATE KEY-----'), pem) self.assertTrue(pem.decode().endswith('-----END RSA PRIVATE KEY-----'), pem)
@ -452,19 +445,6 @@ class UserTest(TestCase):
self.assertIsNone(OtherFake().get_copy(Fake)) self.assertIsNone(OtherFake().get_copy(Fake))
def test_enable_protocol_calls_create_for(self):
bot = self.make_user(id='ap.brid.gy', cls=Web)
user = Fake(id='fake:user')
user.put()
self.assertIsNone(user.public_exponent)
self.assertIsNone(user.private_exponent)
user.enable_protocol(ActivityPub)
self.assertIsNotNone(user.public_exponent)
self.assertIsNotNone(user.private_exponent)
def test_count_followers(self): def test_count_followers(self):
self.assertEqual((0, 0), self.user.count_followers()) self.assertEqual((0, 0), self.user.count_followers())

Wyświetl plik

@ -295,8 +295,7 @@ Web.DEFAULT_ENABLED_PROTOCOLS += ('fake', 'other')
# expensive to generate. # expensive to generate.
requests.post(f'http://{ndb_client.host}/reset') requests.post(f'http://{ndb_client.host}/reset')
with ndb_client.context(): with ndb_client.context():
global_user = activitypub._INSTANCE_ACTOR = Fake.get_or_create( global_user = activitypub._INSTANCE_ACTOR = Fake.get_or_create('fake:user')
'fake:user', propagate=True, enabled_protocols=['activitypub'])
class TestCase(unittest.TestCase, testutil.Asserts): class TestCase(unittest.TestCase, testutil.Asserts):

10
web.py
Wyświetl plik

@ -192,7 +192,7 @@ class Web(User, Protocol):
return None return None
if verify or (verify is None and not user.existing): if verify or (verify is None and not user.existing):
user = user.verify(**kwargs) user = user.verify()
if not allow_opt_out and user.status: if not allow_opt_out and user.status:
return None return None
@ -289,13 +289,9 @@ class Web(User, Protocol):
return super().status return super().status
def verify(self, **kwargs): def verify(self):
"""Fetches site a couple ways to check for redirects and h-card. """Fetches site a couple ways to check for redirects and h-card.
Args:
**kwargs: passed through to :meth:`Web.get_or_create` if this user is a www
domain and we need to call it to create a new root domain user.
Returns: Returns:
web.Web: user that was verified. May be different than self! eg if web.Web: user that was verified. May be different than self! eg if
self's domain started with www and we switch to the root domain. self's domain started with www and we switch to the root domain.
@ -314,7 +310,7 @@ class Web(User, Protocol):
logger.info(f'{root_site} serves ok ; using {root} instead') logger.info(f'{root_site} serves ok ; using {root} instead')
root_user = Web.get_or_create( root_user = Web.get_or_create(
root, enabled_protocols=self.enabled_protocols, root, enabled_protocols=self.enabled_protocols,
allow_opt_out=True, **kwargs) allow_opt_out=True)
self.use_instead = root_user.key self.use_instead = root_user.key
self.put() self.put()
return root_user.verify() return root_user.verify()