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, \
|
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, \
|
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, \
|
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, \
|
from app.utils import gibberish, get_setting, render_template, \
|
||||||
community_membership, ap_datetime, ip_address, can_downvote, \
|
community_membership, ap_datetime, ip_address, can_downvote, \
|
||||||
can_upvote, can_create_post, awaken_dormant_instance, shorten_string, can_create_post_reply, sha256_digest, \
|
can_upvote, can_create_post, awaken_dormant_instance, shorten_string, can_create_post_reply, sha256_digest, \
|
||||||
|
@ -465,15 +465,20 @@ def shared_inbox():
|
||||||
HttpSignature.verify_request(request, actor.public_key, skip_date=True)
|
HttpSignature.verify_request(request, actor.public_key, skip_date=True)
|
||||||
except VerificationError as e:
|
except VerificationError as e:
|
||||||
bounced = True
|
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
|
# HTTP sig will fail if a.gup.pe or PeerTube have bounced a request, so check LD sig instead
|
||||||
|
if 'signature' in request_json:
|
||||||
try:
|
try:
|
||||||
LDSignature.verify_signature(request_json, actor.public_key)
|
LDSignature.verify_signature(request_json, actor.public_key)
|
||||||
except VerificationError as e:
|
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))
|
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
|
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()
|
actor.instance.last_seen = utcnow()
|
||||||
actor.instance.dormant = False
|
actor.instance.dormant = False
|
||||||
|
@ -565,6 +570,11 @@ def replay_inbox_request(request_json):
|
||||||
process_delete_request(request_json, True)
|
process_delete_request(request_json, True)
|
||||||
return
|
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)
|
process_inbox_request(request_json, True)
|
||||||
|
|
||||||
return
|
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
|
# 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 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':
|
if request_json['object']['type'] == 'ChatMessage':
|
||||||
sender = user
|
sender = user
|
||||||
recipient_ap_id = request_json['object']['to'][0]
|
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
|
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
|
# This is for followers on microblog apps
|
||||||
# Used to let them know a Poll has been updated with a new vote
|
# 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)
|
# 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…
Add table
Reference in a new issue