mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
Accept Follow requests from remote actors to local actors
This commit is contained in:
parent
76d228f5fa
commit
f7cfd1f92b
3 changed files with 126 additions and 3 deletions
|
@ -6,7 +6,7 @@ from app import db, constants, cache, celery
|
|||
from app.activitypub import bp
|
||||
from flask import request, current_app, abort, jsonify, json, g, url_for, redirect, make_response
|
||||
|
||||
from app.activitypub.signature import HttpSignature, post_request
|
||||
from app.activitypub.signature import HttpSignature, post_request, VerificationError
|
||||
from app.community.routes import show_community
|
||||
from app.community.util import send_to_remote_instance
|
||||
from app.post.routes import continue_discussion, show_post
|
||||
|
@ -14,7 +14,7 @@ from app.user.routes import show_profile
|
|||
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, SUBSCRIPTION_MEMBER
|
||||
from app.models import User, Community, CommunityJoinRequest, CommunityMember, CommunityBan, ActivityPubLog, Post, \
|
||||
PostReply, Instance, PostVote, PostReplyVote, File, AllowedInstances, BannedInstances, utcnow, Site, Notification, \
|
||||
ChatMessage, Conversation
|
||||
ChatMessage, Conversation, UserFollower
|
||||
from app.activitypub.util import public_key, users_total, active_half_year, active_month, local_posts, local_comments, \
|
||||
post_to_activity, find_actor_or_create, default_context, instance_blocked, find_reply_parent, find_liked_object, \
|
||||
lemmy_site_data, instance_weight, is_activitypub_request, downvote_post_reply, downvote_post, upvote_post_reply, \
|
||||
|
@ -1186,13 +1186,91 @@ def community_moderators_route(actor):
|
|||
return jsonify(community_data)
|
||||
|
||||
|
||||
@bp.route('/u/<actor>/inbox', methods=['GET', 'POST'])
|
||||
@bp.route('/u/<actor>/inbox', methods=['POST'])
|
||||
def user_inbox(actor):
|
||||
site = Site.query.get(1)
|
||||
activity_log = ActivityPubLog(direction='in', result='failure')
|
||||
activity_log.result = 'processing'
|
||||
db.session.add(activity_log)
|
||||
db.session.commit()
|
||||
|
||||
try:
|
||||
request_json = request.get_json(force=True)
|
||||
except werkzeug.exceptions.BadRequest as e:
|
||||
activity_log.exception_message = 'Unable to parse json body: ' + e.description
|
||||
activity_log.result = 'failure'
|
||||
db.session.commit()
|
||||
return '', 400
|
||||
|
||||
if 'id' in request_json:
|
||||
activity_log.activity_id = request_json['id']
|
||||
if site.log_activitypub_json:
|
||||
activity_log.activity_json = json.dumps(request_json)
|
||||
|
||||
actor = find_actor_or_create(request_json['actor']) if 'actor' in request_json else None
|
||||
if actor is not None:
|
||||
try:
|
||||
HttpSignature.verify_request(request, actor.public_key, skip_date=True)
|
||||
if 'type' in request_json and request_json['type'] == 'Follow':
|
||||
if current_app.debug:
|
||||
process_user_follow_request(request_json, activity_log.id, actor.id)
|
||||
else:
|
||||
process_user_follow_request.delay(request_json, activity_log.id, actor.id)
|
||||
return ''
|
||||
# todo: undo/follow
|
||||
except VerificationError:
|
||||
activity_log.result = 'failure'
|
||||
activity_log.exception_message = 'Could not verify signature'
|
||||
db.session.commit()
|
||||
return '', 400
|
||||
else:
|
||||
actor_name = request_json['actor'] if 'actor' in request_json else ''
|
||||
activity_log.exception_message = f'Actor could not be found: {actor_name}'
|
||||
|
||||
if activity_log.exception_message is not None:
|
||||
activity_log.result = 'failure'
|
||||
db.session.commit()
|
||||
resp = jsonify('ok')
|
||||
resp.content_type = 'application/activity+json'
|
||||
return resp
|
||||
|
||||
|
||||
def process_user_follow_request(request_json, activitypublog_id, remote_user_id):
|
||||
activity_log = ActivityPubLog.query.get(activitypublog_id)
|
||||
local_user_ap_id = request_json['object']
|
||||
follow_id = request_json['id']
|
||||
local_user = find_actor_or_create(local_user_ap_id, create_if_not_found=False)
|
||||
remote_user = User.query.get(remote_user_id)
|
||||
if local_user and local_user.is_local() and not remote_user.is_local():
|
||||
existing_follower = UserFollower.query.filter_by(local_user_id=local_user.id, remote_user_id=remote_user.id).first()
|
||||
if not existing_follower:
|
||||
auto_accept = not local_user.ap_manually_approves_followers
|
||||
new_follower = UserFollower(local_user_id=local_user.id, remote_user_id=remote_user.id, is_accepted=auto_accept)
|
||||
db.session.add(new_follower)
|
||||
accept = {
|
||||
"@context": default_context(),
|
||||
"actor": local_user.ap_profile_id,
|
||||
"to": [
|
||||
remote_user.ap_profile_id
|
||||
],
|
||||
"object": {
|
||||
"actor": remote_user.ap_profile_id,
|
||||
"to": None,
|
||||
"object": local_user.ap_profile_id,
|
||||
"type": "Follow",
|
||||
"id": follow_id
|
||||
},
|
||||
"type": "Accept",
|
||||
"id": f"https://{current_app.config['SERVER_NAME']}/activities/accept/" + gibberish(32)
|
||||
}
|
||||
if post_request(remote_user.ap_inbox_url, accept, local_user.private_key, f"https://{current_app.config['SERVER_NAME']}/u/{local_user.user_name}#main-key"):
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
activity_log.exception_message = 'Error sending Accept'
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@bp.route('/c/<actor>/inbox', methods=['GET', 'POST'])
|
||||
def community_inbox(actor):
|
||||
return shared_inbox()
|
||||
|
|
|
@ -1144,6 +1144,14 @@ class CommunityMember(db.Model):
|
|||
created_at = db.Column(db.DateTime, default=utcnow)
|
||||
|
||||
|
||||
class UserFollower(db.Model):
|
||||
local_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
||||
remote_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
||||
is_accepted = db.Column(db.Boolean, default=True) # flip to ban remote user / reject follow
|
||||
is_inward = db.Column(db.Boolean, default=True) # true = remote user is following a local one
|
||||
created_at = db.Column(db.DateTime, default=utcnow)
|
||||
|
||||
|
||||
# people banned from communities
|
||||
class CommunityBan(db.Model):
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) # person who is banned, not the banner
|
||||
|
|
37
migrations/versions/5487a1886c62_user_follower.py
Normal file
37
migrations/versions/5487a1886c62_user_follower.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
"""user_follower
|
||||
|
||||
Revision ID: 5487a1886c62
|
||||
Revises: 46c170499e71
|
||||
Create Date: 2024-04-29 15:55:33.954059
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5487a1886c62'
|
||||
down_revision = '46c170499e71'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('user_follower',
|
||||
sa.Column('local_user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('remote_user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('is_accepted', sa.Boolean(), nullable=True),
|
||||
sa.Column('is_inward', sa.Boolean(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['local_user_id'], ['user.id'], ),
|
||||
sa.ForeignKeyConstraint(['remote_user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('local_user_id', 'remote_user_id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('user_follower')
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in a new issue