mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
Fetch and verify an object from its source if sent without a signature
This commit is contained in:
parent
26283a5d73
commit
af82bc7076
2 changed files with 86 additions and 8 deletions
|
@ -25,7 +25,7 @@ from app.activitypub.util import public_key, users_total, active_half_year, acti
|
|||
update_post_from_activity, undo_vote, undo_downvote, post_to_page, get_redis_connection, find_reported_object, \
|
||||
process_report, ensure_domains_match, can_edit, can_delete, remove_data_from_banned_user, resolve_remote_post, \
|
||||
inform_followers_of_post_update, comment_model_to_json, restore_post_or_comment, ban_user, unban_user, \
|
||||
log_incoming_ap, find_community, site_ban_remove_data, community_ban_remove_data
|
||||
log_incoming_ap, find_community, site_ban_remove_data, community_ban_remove_data, verify_object_from_source
|
||||
from app.utils import gibberish, get_setting, render_template, \
|
||||
community_membership, ap_datetime, ip_address, can_downvote, \
|
||||
can_upvote, can_create_post, awaken_dormant_instance, shorten_string, can_create_post_reply, sha256_digest, \
|
||||
|
@ -465,14 +465,19 @@ def shared_inbox():
|
|||
HttpSignature.verify_request(request, actor.public_key, skip_date=True)
|
||||
except VerificationError as e:
|
||||
bounced = True
|
||||
if not 'signature' in request_json:
|
||||
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify HTTP signature: ' + str(e))
|
||||
return '', 400
|
||||
# HTTP sig will fail if a.gup.pe or PeerTube have bounced a request, so check LD sig instead
|
||||
try:
|
||||
LDSignature.verify_signature(request_json, actor.public_key)
|
||||
except VerificationError as e:
|
||||
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify LD signature: ' + str(e))
|
||||
if 'signature' in request_json:
|
||||
try:
|
||||
LDSignature.verify_signature(request_json, actor.public_key)
|
||||
except VerificationError as e:
|
||||
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify LD signature: ' + str(e))
|
||||
return '', 400
|
||||
# not HTTP sig, and no LD sig, so reduce the inner object to just its remote ID, and then fetch it and check it in process_inbox_request()
|
||||
elif ((request_json['type'] == 'Create' or request_json['type'] == 'Update') and
|
||||
isinstance(request_json['object'], dict) and 'id' in request_json['object'] and isinstance(request_json['object']['id'], str)):
|
||||
request_json['object'] = request_json['object']['id']
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify HTTP signature: ' + str(e))
|
||||
return '', 400
|
||||
|
||||
actor.instance.last_seen = utcnow()
|
||||
|
@ -565,6 +570,11 @@ def replay_inbox_request(request_json):
|
|||
process_delete_request(request_json, True)
|
||||
return
|
||||
|
||||
# testing verify_object_from_source()
|
||||
if ((request_json['type'] == 'Create' or request_json['type'] == 'Update') and
|
||||
isinstance(request_json['object'], dict) and 'id' in request_json['object'] and isinstance(request_json['object']['id'], str)):
|
||||
request_json['object'] = request_json['object']['id']
|
||||
|
||||
process_inbox_request(request_json, True)
|
||||
|
||||
return
|
||||
|
@ -706,6 +716,12 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
|
||||
# Create is new content. Update is often an edit, but Updates from Lemmy can also be new content
|
||||
if request_json['type'] == 'Create' or request_json['type'] == 'Update':
|
||||
if isinstance(request_json['object'], str):
|
||||
request_json = verify_object_from_source(request_json) # change request_json['object'] from str to dict, then process normally
|
||||
if not request_json:
|
||||
log_incoming_ap(id, APLOG_CREATE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify unsigned request from source')
|
||||
return
|
||||
|
||||
if request_json['object']['type'] == 'ChatMessage':
|
||||
sender = user
|
||||
recipient_ap_id = request_json['object']['to'][0]
|
||||
|
|
|
@ -2467,6 +2467,68 @@ def resolve_remote_post_from_search(uri: str) -> Union[Post, None]:
|
|||
return None
|
||||
|
||||
|
||||
# called from activitypub/routes if something is posted to us without any kind of signature (typically from PeerTube)
|
||||
def verify_object_from_source(request_json):
|
||||
uri = request_json['object']
|
||||
uri_domain = urlparse(uri).netloc
|
||||
if not uri_domain:
|
||||
return None
|
||||
|
||||
create_domain = urlparse(request_json['actor']).netloc
|
||||
if create_domain != uri_domain:
|
||||
return None
|
||||
|
||||
try:
|
||||
object_request = get_request(uri, headers={'Accept': 'application/activity+json'})
|
||||
except httpx.HTTPError:
|
||||
time.sleep(3)
|
||||
try:
|
||||
object_request = get_request(uri, headers={'Accept': 'application/activity+json'})
|
||||
except httpx.HTTPError:
|
||||
return None
|
||||
if object_request.status_code == 200:
|
||||
try:
|
||||
object = object_request.json()
|
||||
except:
|
||||
object_request.close()
|
||||
return None
|
||||
object_request.close()
|
||||
elif object_request.status_code == 401:
|
||||
try:
|
||||
site = Site.query.get(1)
|
||||
object_request = signed_get_request(uri, site.private_key, f"https://{current_app.config['SERVER_NAME']}/actor#main-key")
|
||||
except httpx.HTTPError:
|
||||
time.sleep(3)
|
||||
try:
|
||||
object_request = signed_get_request(uri, site.private_key, f"https://{current_app.config['SERVER_NAME']}/actor#main-key")
|
||||
except httpx.HTTPError:
|
||||
return None
|
||||
try:
|
||||
object = object_request.json()
|
||||
except:
|
||||
object_request.close()
|
||||
return None
|
||||
object_request.close()
|
||||
else:
|
||||
return None
|
||||
|
||||
if not 'id' in object or not 'type' in object or not 'attributedTo' in object:
|
||||
return None
|
||||
|
||||
if isinstance(object['attributedTo'], str):
|
||||
actor_domain = urlparse(object['attributedTo']).netloc
|
||||
elif isinstance(object['attributedTo'], list) and 'id' in object['attributedTo']:
|
||||
actor_domain = urlparse(object['attributedTo']['id']).netloc
|
||||
else:
|
||||
return None
|
||||
|
||||
if uri_domain != actor_domain:
|
||||
return None
|
||||
|
||||
request_json['object'] = object
|
||||
return request_json
|
||||
|
||||
|
||||
# This is for followers on microblog apps
|
||||
# Used to let them know a Poll has been updated with a new vote
|
||||
# The plan is to also use it for activities on local user's posts that aren't understood by being Announced (anything beyond the initial Create)
|
||||
|
|
Loading…
Reference in a new issue