mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
remote admins can delete remote posts (not just moderators)
This commit is contained in:
parent
77a0ee9b5d
commit
fef3a1e995
5 changed files with 168 additions and 37 deletions
|
@ -124,7 +124,7 @@ def lemmy_site():
|
|||
@bp.route('/api/v3/federated_instances')
|
||||
@cache.cached(timeout=600)
|
||||
def lemmy_federated_instances():
|
||||
instances = Instance.query.all()
|
||||
instances = Instance.query.filter(Instance.id != 1).all()
|
||||
linked = []
|
||||
allowed = []
|
||||
blocked = []
|
||||
|
|
|
@ -10,7 +10,7 @@ from flask_babel import _
|
|||
from sqlalchemy import text, func
|
||||
from app import db, cache, constants, celery
|
||||
from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, \
|
||||
PostVote, PostReplyVote, ActivityPubLog, Notification, Site, CommunityMember
|
||||
PostVote, PostReplyVote, ActivityPubLog, Notification, Site, CommunityMember, InstanceRole
|
||||
import time
|
||||
import base64
|
||||
import requests
|
||||
|
@ -211,14 +211,18 @@ def find_actor_or_create(actor: str) -> Union[User, Community, None]:
|
|||
user = Community.query.filter(Community.ap_profile_id == actor).first()
|
||||
|
||||
if user is not None:
|
||||
if not user.is_local() and user.ap_fetched_at < utcnow() - timedelta(days=7):
|
||||
if not user.is_local() and (user.ap_fetched_at is None or user.ap_fetched_at < utcnow() - timedelta(days=7)):
|
||||
# To reduce load on remote servers, refreshing the user profile happens after a delay of 1 to 10 seconds. Meanwhile, subsequent calls to
|
||||
# find_actor_or_create() which happen to be for the same actor might queue up refreshes of the same user. To avoid this, set a flag to
|
||||
# indicate that user is currently being refreshed.
|
||||
refresh_in_progress = cache.get(f'refreshing_{user.id}')
|
||||
if not refresh_in_progress:
|
||||
cache.set(f'refreshing_{user.id}', True, timeout=300)
|
||||
if isinstance(user, User):
|
||||
refresh_user_profile(user.id)
|
||||
elif isinstance(user, Community):
|
||||
# todo: refresh community profile also, not just instance_profile
|
||||
refresh_instance_profile(user.instance_id)
|
||||
return user
|
||||
else: # User does not exist in the DB, it's going to need to be created from it's remote home instance
|
||||
if actor.startswith('https://'):
|
||||
|
@ -687,6 +691,7 @@ def find_instance_id(server):
|
|||
|
||||
|
||||
def refresh_instance_profile(instance_id: int):
|
||||
if instance_id:
|
||||
if current_app.debug:
|
||||
refresh_instance_profile_task(instance_id)
|
||||
else:
|
||||
|
@ -696,6 +701,7 @@ def refresh_instance_profile(instance_id: int):
|
|||
@celery.task
|
||||
def refresh_instance_profile_task(instance_id: int):
|
||||
instance = Instance.query.get(instance_id)
|
||||
if instance.updated_at < utcnow() - timedelta(days=7):
|
||||
try:
|
||||
instance_data = get_request(f"https://{instance.domain}", headers={'Accept': 'application/activity+json'})
|
||||
except:
|
||||
|
@ -723,6 +729,39 @@ def refresh_instance_profile_task(instance_id: int):
|
|||
instance.updated_at = utcnow()
|
||||
db.session.commit()
|
||||
|
||||
# retrieve list of Admins from /api/v3/site, update InstanceRole
|
||||
try:
|
||||
response = get_request(f'https://{instance.domain}/api/v3/site')
|
||||
except:
|
||||
response = None
|
||||
|
||||
if response and response.status_code == 200:
|
||||
try:
|
||||
instance_data = response.json()
|
||||
except:
|
||||
instance_data = None
|
||||
finally:
|
||||
response.close()
|
||||
|
||||
if instance_data:
|
||||
if 'admins' in instance_data:
|
||||
admin_profile_ids = []
|
||||
for admin in instance_data['admins']:
|
||||
admin_profile_ids.append(admin['person']['actor_id'].lower())
|
||||
user = find_actor_or_create(admin['person']['actor_id'])
|
||||
if user and not instance.user_is_admin(user.id):
|
||||
new_instance_role = InstanceRole(instance_id=instance.id, user_id=user.id, role='admin')
|
||||
db.session.add(new_instance_role)
|
||||
db.session.commit()
|
||||
# remove any InstanceRoles that are no longer part of instance-data['admins']
|
||||
for instance_admin in InstanceRole.query.filter_by(instance_id=instance.id):
|
||||
if instance_admin.user.profile_id() not in admin_profile_ids:
|
||||
db.session.query(InstanceRole).filter(
|
||||
InstanceRole.user_id == instance_admin.user.id,
|
||||
InstanceRole.instance_id == instance.id,
|
||||
InstanceRole.role == 'admin').delete()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
# alter the effect of upvotes based on their instance. Default to 1.0
|
||||
@cache.memoize(timeout=50)
|
||||
|
@ -897,7 +936,7 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id
|
|||
to_delete = find_liked_object(to_be_deleted_ap_id)
|
||||
|
||||
if deletor and community and to_delete:
|
||||
if deletor.is_admin() or community.is_moderator(deletor) or to_delete.author.id == deletor.id:
|
||||
if deletor.is_admin() or community.is_moderator(deletor) or community.is_instance_admin(deletor) or to_delete.author.id == deletor.id:
|
||||
if isinstance(to_delete, Post):
|
||||
to_delete.delete_dependencies()
|
||||
to_delete.flush_cache()
|
||||
|
|
|
@ -6,7 +6,7 @@ from random import randint
|
|||
from sqlalchemy.sql.operators import or_
|
||||
|
||||
from app import db, cache
|
||||
from app.activitypub.util import default_context, make_image_sizes_async, refresh_user_profile
|
||||
from app.activitypub.util import default_context, make_image_sizes_async, refresh_user_profile, find_actor_or_create
|
||||
from app.constants import SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER, POST_TYPE_IMAGE, POST_TYPE_LINK, \
|
||||
SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR
|
||||
from app.inoculation import inoculation
|
||||
|
@ -19,8 +19,8 @@ from sqlalchemy import select, desc, text
|
|||
from sqlalchemy_searchable import search
|
||||
from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \
|
||||
ap_datetime, ip_address, retrieve_block_list, shorten_string, markdown_to_text, user_filters_home, \
|
||||
joined_communities, moderating_communities, parse_page, theme_list
|
||||
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic, File
|
||||
joined_communities, moderating_communities, parse_page, theme_list, get_request
|
||||
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic, File, Instance, InstanceRole
|
||||
from PIL import Image
|
||||
import pytesseract
|
||||
|
||||
|
@ -257,6 +257,42 @@ def list_files(directory):
|
|||
@bp.route('/test')
|
||||
def test():
|
||||
|
||||
instance = Instance.query.get(3)
|
||||
if instance.updated_at < utcnow() - timedelta(days=7):
|
||||
try:
|
||||
response = get_request(f'https://{instance.domain}/api/v3/site')
|
||||
except:
|
||||
response = None
|
||||
|
||||
if response and response.status_code == 200:
|
||||
try:
|
||||
instance_data = response.json()
|
||||
except:
|
||||
instance_data = None
|
||||
finally:
|
||||
response.close()
|
||||
|
||||
if instance_data:
|
||||
if 'admins' in instance_data:
|
||||
admin_profile_ids = []
|
||||
for admin in instance_data['admins']:
|
||||
admin_profile_ids.append(admin['person']['actor_id'].lower())
|
||||
user = find_actor_or_create(admin['person']['actor_id'])
|
||||
if user and not instance.user_is_admin(user.id):
|
||||
new_instance_role = InstanceRole(instance_id=instance.id, user_id=user.id, role='admin')
|
||||
db.session.add(new_instance_role)
|
||||
db.session.commit()
|
||||
# remove any InstanceRoles that are no longer part of instance-data['admins']
|
||||
for instance_admin in InstanceRole.query.filter_by(instance_id=instance.id):
|
||||
if instance_admin.user.profile_id() not in admin_profile_ids:
|
||||
db.session.query(InstanceRole).filter(
|
||||
InstanceRole.user_id == instance_admin.user.id,
|
||||
InstanceRole.instance_id == instance.id,
|
||||
InstanceRole.role == 'admin').delete()
|
||||
db.session.commit()
|
||||
|
||||
return 'Ok'
|
||||
|
||||
return ''
|
||||
retval = ''
|
||||
for user in User.query.all():
|
||||
|
|
|
@ -69,6 +69,18 @@ class Instance(db.Model):
|
|||
def online(self):
|
||||
return not self.dormant and not self.gone_forever
|
||||
|
||||
def user_is_admin(self, user_id):
|
||||
role = InstanceRole.query.filter_by(instance_id=self.id, user_id=user_id).first()
|
||||
return role and role.role == 'admin'
|
||||
|
||||
|
||||
class InstanceRole(db.Model):
|
||||
instance_id = db.Column(db.Integer, db.ForeignKey('instance.id'), primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
||||
role = db.Column(db.String(50), default='admin')
|
||||
|
||||
user = db.relationship('User', lazy='joined')
|
||||
|
||||
|
||||
class InstanceBlock(db.Model):
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
||||
|
@ -269,6 +281,15 @@ class Community(db.Model):
|
|||
else:
|
||||
return any(moderator.user_id == user.id and moderator.is_owner for moderator in self.moderators())
|
||||
|
||||
def is_instance_admin(self, user):
|
||||
if self.instance_id:
|
||||
instance_role = InstanceRole.query.filter(InstanceRole.instance_id == self.instance_id,
|
||||
InstanceRole.user_id == user.id,
|
||||
InstanceRole.role == 'admin').first()
|
||||
return instance_role is not None
|
||||
else:
|
||||
return False
|
||||
|
||||
def user_is_banned(self, user):
|
||||
membership = CommunityMember.query.filter(CommunityMember.community_id == self.id, CommunityMember.user_id == user.id).first()
|
||||
return membership.is_banned if membership else False
|
||||
|
|
35
migrations/versions/75f5b458c2f9_instance_admins.py
Normal file
35
migrations/versions/75f5b458c2f9_instance_admins.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
"""instance admins
|
||||
|
||||
Revision ID: 75f5b458c2f9
|
||||
Revises: a8fc7f7ba539
|
||||
Create Date: 2024-02-14 11:12:09.271117
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '75f5b458c2f9'
|
||||
down_revision = 'a8fc7f7ba539'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('instance_role',
|
||||
sa.Column('instance_id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('role', sa.String(length=50), nullable=True),
|
||||
sa.ForeignKeyConstraint(['instance_id'], ['instance.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('instance_id', 'user_id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('instance_role')
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in a new issue