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,
headers=request.headers)
try:
return verify_request_signature(req, required=False)
return verify_request_signature(req)
except ValueError:
return None

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -88,7 +88,8 @@ class Protocol:
if not skip_author_verification:
try:
# 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:
logger.warning('HTTP signature verification failed: %s', exc)
return self.actor, {}

Wyświetl plik

@ -13,6 +13,7 @@ from httpsig.sign_algorithms import PSS
from httpsig.requests_auth import HTTPSignatureAuth
from httpsig.verify import HeaderVerifier
from federation.entities.utils import get_profile
from federation.types import RequestType
from federation.utils.network import parse_http_date
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.
"""
@ -43,23 +44,23 @@ def verify_request_signature(request: RequestType, required: bool=True):
sig_struct = request.headers.get("Signature", None)
if not sig_struct:
if required:
raise ValueError("A signature is required but was not provided")
else:
return None
raise ValueError("A signature is required but was not provided")
# this should return a dict populated with the following keys:
# keyId, algorithm, headers and signature
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:
raise ValueError(f"Failed to retrieve keyId for {sig.get('keyId')}")
if not getattr(signer, 'public_key_dict', None):
raise ValueError(f"Failed to retrieve public key for {sig.get('keyId')}")
key = encode_if_text(signer.public_key_dict['publicKeyPem'])
signer = retrieve_and_parse_document(sig.get('keyId'))
key = getattr(signer, 'public_key', None)
if not key and pubkey:
# 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 = pubkey
else:
raise ValueError(f"No public key for {sig.get('keyId')}")
key = encode_if_text(key)
date_header = request.headers.get("Date")
if not date_header:
raise ValueError("Request Date header is missing")