kopia lustrzana https://gitlab.com/jaywink/federation
Implement automatic remote profile updates. Reduce remote requests frequency. WIP: implement profile retractions.
rodzic
a0c4e7fb6e
commit
6b9c74b793
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, {}
|
||||
|
|
|
@ -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")
|
||||
|
|
Ładowanie…
Reference in New Issue