Implement automatic remote profile updates. Reduce remote requests frequency. WIP: implement profile retractions.

ap-profile-handling
Alain St-Denis 2023-04-05 19:51:30 -04:00
rodzic a0c4e7fb6e
commit 6b9c74b793
6 zmienionych plików z 46 dodań i 27 usunięć

Wyświetl plik

@ -24,7 +24,7 @@ def get_and_verify_signer(request):
method=request.method, method=request.method,
headers=request.headers) headers=request.headers)
try: try:
return verify_request_signature(req, required=False) return verify_request_signature(req)
except ValueError: except ValueError:
return None return None

Wyświetl plik

@ -11,6 +11,7 @@ from Crypto.Hash import SHA256
from Crypto.PublicKey.RSA import import_key from Crypto.PublicKey.RSA import import_key
from Crypto.Signature import pkcs1_15 from Crypto.Signature import pkcs1_15
from federation.entities.utils import get_profile
from federation.utils.activitypub import retrieve_and_parse_document from federation.utils.activitypub import retrieve_and_parse_document
@ -53,9 +54,10 @@ def verify_ld_signature(payload):
return None return None
# retrieve the author's public key # retrieve the author's public key
profile = get_profile(key_id=signature.get('creator'))
if not profile:
profile = retrieve_and_parse_document(signature.get('creator')) profile = retrieve_and_parse_document(signature.get('creator'))
if not profile: if not profile:
logger.warning('ld_signature - Failed to retrieve profile for %s', signature.get("creator")) logger.warning('ld_signature - Failed to retrieve profile for %s', signature.get("creator"))
return None return None
try: try:

Wyświetl plik

@ -617,11 +617,16 @@ class Person(Object, base.Profile):
self.inbox = value.get('private', None) self.inbox = value.get('private', None)
self.endpoints = {'sharedInbox': value.get('public', None)} self.endpoints = {'sharedInbox': value.get('public', None)}
@property
def key_id(self):
if isinstance(self.public_key_dict, dict):
return self.public_key_dict.get('id', None)
@property @property
def public_key(self): def public_key(self):
if self._cached_public_key: return self._cached_public_key if self._cached_public_key: return self._cached_public_key
if hasattr(self, 'public_key_dict') and isinstance(self.public_key_dict, dict): if isinstance(self.public_key_dict, dict):
self._cached_public_key = self.public_key_dict.get('publicKeyPem', None) self._cached_public_key = self.public_key_dict.get('publicKeyPem', None)
return self._cached_public_key return self._cached_public_key
@ -982,7 +987,7 @@ class Video(Document, base.Video):
""" """
self.__dict__.update({'schema': True}) self.__dict__.update({'schema': True})
if hasattr(self, 'content_map'): if self.content_map is not missing:
text = self.content_map['orig'] text = self.content_map['orig']
if getattr(self, 'media_type', None) == 'text/markdown': if getattr(self, 'media_type', None) == 'text/markdown':
url = "" url = ""
@ -994,7 +999,7 @@ class Video(Document, base.Video):
self.raw_content = text.strip() self.raw_content = text.strip()
self._media_type = self.media_type self._media_type = self.media_type
if hasattr(self, 'actor_id'): if self.actor_id is not missing:
act = self.actor_id act = self.actor_id
new_act = [] new_act = []
if not isinstance(act, list): act = [act] if not isinstance(act, list): act = [act]
@ -1225,7 +1230,7 @@ class Delete(Create, base.Retraction):
signable = True signable = True
def to_base(self): def to_base(self):
if hasattr(self, 'object_') and not isinstance(self.object_, Tombstone): if not isinstance(self.object_, Tombstone):
self.target_id = self.object_ self.target_id = self.object_
self.entity_type = 'Object' self.entity_type = 'Object'
return self return self
@ -1253,7 +1258,7 @@ class View(Create):
def process_followers(obj, base_url): def process_followers(obj, base_url):
pass pass
def extract_receiver(profile, receiver): def extract_receiver(author, receiver):
""" """
Transform a single receiver ID to a UserType. Transform a single receiver ID to a UserType.
""" """
@ -1267,11 +1272,18 @@ def extract_receiver(profile, receiver):
if isinstance(obj, base.Profile): if isinstance(obj, base.Profile):
return [UserType(id=receiver, receiver_variant=ReceiverVariant.ACTOR)] return [UserType(id=receiver, receiver_variant=ReceiverVariant.ACTOR)]
# This doesn't handle cases where the actor is sending to other actors
# This handles cases where the actor is sending to other actors
# followers (seen on PeerTube) # followers (seen on PeerTube)
if profile.followers == receiver: if isinstance(obj, base.Collection):
profile = get_profile(followers_fid=obj.id)
if profile:
return [UserType(id=profile.id, receiver_variant=ReceiverVariant.FOLLOWERS)] return [UserType(id=profile.id, receiver_variant=ReceiverVariant.FOLLOWERS)]
if author.followers == receiver:
return [UserType(id=author.id, receiver_variant=ReceiverVariant.FOLLOWERS)]
return []
def extract_receivers(entity): def extract_receivers(entity):
""" """
@ -1281,8 +1293,9 @@ def extract_receivers(entity):
profile = None profile = None
# don't care about receivers for payloads without an actor_id # don't care about receivers for payloads without an actor_id
if getattr(entity, 'actor_id'): if getattr(entity, 'actor_id'):
profile = retrieve_and_parse_profile(entity.actor_id) profile = get_profile_or_entity(entity.actor_id)
if not profile: return receivers if not isinstance(profile, base.Profile):
return receivers
for attr in ("to", "cc"): for attr in ("to", "cc"):
receiver = getattr(entity, attr, None) receiver = getattr(entity, attr, None)

Wyświetl plik

@ -31,7 +31,9 @@ class BaseEntity:
guid: str = "" guid: str = ""
handle: str = "" handle: str = ""
finger: str = "" finger: str = ""
followers: str = ""
id: str = "" id: str = ""
key_id: str = ""
mxid: str = "" mxid: str = ""
signature: str = "" signature: str = ""
# for AP # for AP

Wyświetl plik

@ -88,7 +88,8 @@ class Protocol:
if not skip_author_verification: if not skip_author_verification:
try: try:
# Verify the HTTP signature # Verify the HTTP signature
self.sender = verify_request_signature(self.request) pubkey = sender_key_fetcher(self.actor) if sender_key_fetcher else ''
self.sender = verify_request_signature(self.request, pubkey=pubkey)
except (ValueError, KeyError, InvalidSignature) as exc: except (ValueError, KeyError, InvalidSignature) as exc:
logger.warning('HTTP signature verification failed: %s', exc) logger.warning('HTTP signature verification failed: %s', exc)
return self.actor, {} return self.actor, {}

Wyświetl plik

@ -13,6 +13,7 @@ from httpsig.sign_algorithms import PSS
from httpsig.requests_auth import HTTPSignatureAuth from httpsig.requests_auth import HTTPSignatureAuth
from httpsig.verify import HeaderVerifier from httpsig.verify import HeaderVerifier
from federation.entities.utils import get_profile
from federation.types import RequestType from federation.types import RequestType
from federation.utils.network import parse_http_date from federation.utils.network import parse_http_date
from federation.utils.text import encode_if_text from federation.utils.text import encode_if_text
@ -35,7 +36,7 @@ def get_http_authentication(private_key: RsaKey, private_key_id: str, digest: bo
) )
def verify_request_signature(request: RequestType, required: bool=True): def verify_request_signature(request: RequestType, pubkey: str=""):
""" """
Verify HTTP signature in request against a public key. Verify HTTP signature in request against a public key.
""" """
@ -43,23 +44,23 @@ def verify_request_signature(request: RequestType, required: bool=True):
sig_struct = request.headers.get("Signature", None) sig_struct = request.headers.get("Signature", None)
if not sig_struct: if not sig_struct:
if required:
raise ValueError("A signature is required but was not provided") raise ValueError("A signature is required but was not provided")
else:
return None
# this should return a dict populated with the following keys: # this should return a dict populated with the following keys:
# keyId, algorithm, headers and signature # keyId, algorithm, headers and signature
sig = {i.split("=", 1)[0]: i.split("=", 1)[1].strip('"') for i in sig_struct.split(",")} sig = {i.split("=", 1)[0]: i.split("=", 1)[1].strip('"') for i in sig_struct.split(",")}
signer = retrieve_and_parse_document(sig.get('keyId')) signer = get_profile(key_id=sig.get('keyId'))
if not signer: if not signer:
raise ValueError(f"Failed to retrieve keyId for {sig.get('keyId')}") signer = retrieve_and_parse_document(sig.get('keyId'))
key = getattr(signer, 'public_key', None)
if not getattr(signer, 'public_key_dict', None): if not key and pubkey:
raise ValueError(f"Failed to retrieve public key for {sig.get('keyId')}") # fallback to the author's key the client app may have provided
logger.warning("Failed to retrieve keyId for %s, trying the actor's key", sig.get('keyId'))
key = encode_if_text(signer.public_key_dict['publicKeyPem']) key = pubkey
else:
raise ValueError(f"No public key for {sig.get('keyId')}")
key = encode_if_text(key)
date_header = request.headers.get("Date") date_header = request.headers.get("Date")
if not date_header: if not date_header:
raise ValueError("Request Date header is missing") raise ValueError("Request Date header is missing")