Improve Block support

main
Thomas Sileo 2022-10-23 16:37:24 +02:00
parent 2853bf2a28
commit 3729500e3e
4 changed files with 126 additions and 10 deletions

View File

@ -25,7 +25,9 @@ from app.actor import fetch_actor
from app.actor import get_actors_metadata from app.actor import get_actors_metadata
from app.boxes import get_inbox_object_by_ap_id from app.boxes import get_inbox_object_by_ap_id
from app.boxes import get_outbox_object_by_ap_id from app.boxes import get_outbox_object_by_ap_id
from app.boxes import send_block
from app.boxes import send_follow from app.boxes import send_follow
from app.boxes import send_unblock
from app.config import EMOJIS from app.config import EMOJIS
from app.config import generate_csrf_token from app.config import generate_csrf_token
from app.config import session_serializer from app.config import session_serializer
@ -340,6 +342,7 @@ async def admin_inbox(
"Update", "Update",
"Undo", "Undo",
"Read", "Read",
"Reject",
"Add", "Add",
"Remove", "Remove",
"EmojiReact", "EmojiReact",
@ -868,10 +871,7 @@ async def admin_actions_block(
csrf_check: None = Depends(verify_csrf_token), csrf_check: None = Depends(verify_csrf_token),
db_session: AsyncSession = Depends(get_db_session), db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse: ) -> RedirectResponse:
logger.info(f"Blocking {ap_actor_id}") await send_block(db_session, ap_actor_id)
actor = await fetch_actor(db_session, ap_actor_id)
actor.is_blocked = True
await db_session.commit()
return RedirectResponse(redirect_url, status_code=302) return RedirectResponse(redirect_url, status_code=302)
@ -884,9 +884,7 @@ async def admin_actions_unblock(
db_session: AsyncSession = Depends(get_db_session), db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse: ) -> RedirectResponse:
logger.info(f"Unblocking {ap_actor_id}") logger.info(f"Unblocking {ap_actor_id}")
actor = await fetch_actor(db_session, ap_actor_id) await send_unblock(db_session, ap_actor_id)
actor.is_blocked = False
await db_session.commit()
return RedirectResponse(redirect_url, status_code=302) return RedirectResponse(redirect_url, status_code=302)

View File

@ -90,6 +90,87 @@ async def save_outbox_object(
return outbox_object return outbox_object
async def send_unblock(db_session: AsyncSession, ap_actor_id: str) -> None:
actor = await fetch_actor(db_session, ap_actor_id)
block_activity = (
await db_session.scalars(
select(models.OutboxObject).where(
models.OutboxObject.activity_object_ap_id == actor.ap_id,
models.OutboxObject.is_deleted.is_(False),
)
)
).one_or_none()
if not block_activity:
raise ValueError(f"No Block activity for {ap_actor_id}")
await _send_undo(db_session, block_activity.ap_id)
await db_session.commit()
async def send_block(db_session: AsyncSession, ap_actor_id: str) -> None:
logger.info(f"Blocking {ap_actor_id}")
actor = await fetch_actor(db_session, ap_actor_id)
actor.is_blocked = True
# 1. Unfollow the actor
following = (
await db_session.scalars(
select(models.Following)
.options(joinedload(models.Following.outbox_object))
.where(
models.Following.ap_actor_id == actor.ap_id,
)
)
).one_or_none()
if following:
await _send_undo(db_session, following.outbox_object.ap_id)
# 2. If the blocked actor is a follower, reject the follow request
follower = (
await db_session.scalars(
select(models.Follower)
.options(joinedload(models.Follower.inbox_object))
.where(
models.Follower.ap_actor_id == actor.ap_id,
)
)
).one_or_none()
if follower:
await _send_reject(db_session, actor, follower.inbox_object)
await db_session.delete(follower)
# 3. Send a block
block_id = allocate_outbox_id()
block = {
"@context": ap.AS_EXTENDED_CTX,
"id": outbox_object_id(block_id),
"type": "Block",
"actor": LOCAL_ACTOR.ap_id,
"object": actor.ap_id,
}
outbox_object = await save_outbox_object(
db_session,
block_id,
block,
)
if not outbox_object.id:
raise ValueError("Should never happen")
await new_outgoing_activity(db_session, actor.inbox_url, outbox_object.id)
# 4. Create a notification
notif = models.Notification(
notification_type=models.NotificationType.BLOCK,
actor_id=actor.id,
outbox_object_id=outbox_object.id,
)
db_session.add(notif)
await db_session.commit()
async def send_delete(db_session: AsyncSession, ap_object_id: str) -> None: async def send_delete(db_session: AsyncSession, ap_object_id: str) -> None:
outbox_object_to_delete = await get_outbox_object_by_ap_id(db_session, ap_object_id) outbox_object_to_delete = await get_outbox_object_by_ap_id(db_session, ap_object_id)
if not outbox_object_to_delete: if not outbox_object_to_delete:
@ -266,7 +347,7 @@ async def _send_undo(db_session: AsyncSession, ap_object_id: str) -> None:
if not outbox_object_to_undo: if not outbox_object_to_undo:
raise ValueError(f"{ap_object_id} not found in the outbox") raise ValueError(f"{ap_object_id} not found in the outbox")
if outbox_object_to_undo.ap_type not in ["Follow", "Like", "Announce"]: if outbox_object_to_undo.ap_type not in ["Follow", "Like", "Announce", "Block"]:
raise ValueError( raise ValueError(
f"Cannot build Undo for {outbox_object_to_undo.ap_type} activity" f"Cannot build Undo for {outbox_object_to_undo.ap_type} activity"
) )
@ -339,6 +420,30 @@ async def _send_undo(db_session: AsyncSession, ap_object_id: str) -> None:
recipients = await _compute_recipients(db_session, outbox_object.ap_object) recipients = await _compute_recipients(db_session, outbox_object.ap_object)
for rcp in recipients: for rcp in recipients:
await new_outgoing_activity(db_session, rcp, outbox_object.id) await new_outgoing_activity(db_session, rcp, outbox_object.id)
elif outbox_object_to_undo.ap_type == "Block":
if not outbox_object_to_undo.activity_object_ap_id:
raise ValueError(f"Invalid block activity {outbox_object_to_undo.ap_id}")
# Send the Undo to the blocked actor
blocked_actor = await fetch_actor(
db_session, outbox_object_to_undo.activity_object_ap_id
)
blocked_actor.is_blocked = False
await new_outgoing_activity(
db_session,
blocked_actor.inbox_url, # type: ignore
outbox_object.id,
)
notif = models.Notification(
notification_type=models.NotificationType.UNBLOCK,
actor_id=blocked_actor.id,
outbox_object_id=outbox_object.id,
)
db_session.add(notif)
else: else:
raise ValueError("Should never happen") raise ValueError("Should never happen")
@ -2034,8 +2139,10 @@ async def save_to_inbox(
await _process_transient_object(db_session, raw_object, actor) await _process_transient_object(db_session, raw_object, actor)
return None return None
if actor.is_blocked: # If we just blocked an actor, we want to process any undo sent as side
logger.warning("Actor {actor.ap_id} is blocked, ignoring object") # effects
if actor.is_blocked and ap.as_list(raw_object["type"])[0] != "Undo":
logger.warning(f"Actor {actor.ap_id} is blocked, ignoring object")
return None return None
raw_object_id = ap.get_id(raw_object) raw_object_id = ap.get_id(raw_object)

View File

@ -551,9 +551,14 @@ class NotificationType(str, enum.Enum):
UPDATED_WEBMENTION = "updated_webmention" UPDATED_WEBMENTION = "updated_webmention"
DELETED_WEBMENTION = "deleted_webmention" DELETED_WEBMENTION = "deleted_webmention"
# incoming
BLOCKED = "blocked" BLOCKED = "blocked"
UNBLOCKED = "unblocked" UNBLOCKED = "unblocked"
# outgoing
BLOCK = "block"
UNBLOCK = "unblock"
class Notification(Base): class Notification(Base):
__tablename__ = "notifications" __tablename__ = "notifications"

View File

@ -42,6 +42,12 @@
{% elif notif.notification_type.value == "unblocked" %} {% elif notif.notification_type.value == "unblocked" %}
{{ notif_actor_action(notif, "unblocked you") }} {{ notif_actor_action(notif, "unblocked you") }}
{{ utils.display_actor(notif.actor, actors_metadata) }} {{ utils.display_actor(notif.actor, actors_metadata) }}
{% elif notif.notification_type.value == "block" %}
{{ notif_actor_action(notif, "was blocked") }}
{{ utils.display_actor(notif.actor, actors_metadata) }}
{% elif notif.notification_type.value == "unblock" %}
{{ notif_actor_action(notif, "was unblocked") }}
{{ utils.display_actor(notif.actor, actors_metadata) }}
{%- elif notif.notification_type.value == "move" %} {%- elif notif.notification_type.value == "move" %}
{# for move notif, the actor is the target and the inbox object the Move activity #} {# for move notif, the actor is the target and the inbox object the Move activity #}
<div class="actor-action"> <div class="actor-action">