Skip to content

Commit

Permalink
Merge pull request #25 from kikuomax/filter-self-delete
Browse files Browse the repository at this point in the history
Filter self delete
  • Loading branch information
kikuomax authored Sep 11, 2023
2 parents 1551c15 + 99edf93 commit d9fa412
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 8 deletions.
53 changes: 45 additions & 8 deletions cdk/lambda/receive_inbound_activity/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
import json
import logging
import os
from typing import Any, Optional
from typing import Any, Optional, Tuple
import boto3
from libactivitypub.activity import Activity
from libactivitypub.activity import Activity, Delete
from libactivitypub.actor import Actor
from libactivitypub.signature import (
VerificationError,
Expand All @@ -42,6 +42,8 @@
logging.getLogger('libactivitypub').setLevel(logging.DEBUG)
logging.getLogger('libmumble').setLevel(logging.DEBUG)

PREFILTER_BODY_SIZE = 10 * 1024 # 10 KB

DOMAIN_NAME = get_domain_name(boto3.client('ssm'))

# user table
Expand Down Expand Up @@ -111,6 +113,30 @@ def quarantine(tag: str, payload: Any, options: Optional[Any]=None):
LOGGER.debug('quarantined payload: %s', res)


def prefilter_activity(body: str) -> Tuple[Optional[str], Optional[Activity]]:
"""Prefilters a given activity.
Patterns of a returned value:
* (``None``, ``None``): activity is too large to prefilter
* (``None``, non-``None``): activity is not prefiltered
* (non-``None``, non-``None``): activity is prefiltered
:raises ValueError: if ``body`` is not a valid ``Activity``.
:raises TypeError: if ``body`` is not a valid ``Activity``.
"""
if len(body) > PREFILTER_BODY_SIZE:
return None, None
LOGGER.debug('prefiltering activity')
activity = Activity.parse_object(json.loads(body))
# prefilters "Delete" activities of the actor itself
if activity.type == 'Delete':
delete = activity.cast(Delete)
if delete.actor_id == delete.object_id:
return 'Delete of the actor itself', activity
return None, activity


def lambda_handler(event, _context):
"""Runs on AWS Lambda.
Expand Down Expand Up @@ -149,6 +175,16 @@ def lambda_handler(event, _context):
username = event['username']
LOGGER.debug('processing activity sent to: %s', username)

# parses activity for prefilter unless it is too large
try:
filter_tag, activity = prefilter_activity(event['body'])
if filter_tag:
LOGGER.debug('prefiltered activity: %s', filter_tag)
return
except (TypeError, ValueError) as exc:
quarantine('invalid_activity', event)
raise BadRequestError(f'{exc}') from exc

LOGGER.debug('parsing signature')
try:
signature = parse_signature(event['signature'])
Expand Down Expand Up @@ -198,12 +234,13 @@ def lambda_handler(event, _context):
raise UnauthorizedError(f'failed to authenticate: {exc}') from exc

# once the signature is verified, we can parse the body
LOGGER.debug('parsing activity')
try:
activity = Activity.parse_object(json.loads(body))
except ValueError as exc:
quarantine('invalid_activity', event)
raise BadRequestError(f'{exc}') from exc
if activity is None:
LOGGER.debug('parsing activity')
try:
activity = Activity.parse_object(json.loads(body))
except (TypeError, ValueError) as exc:
quarantine('invalid_activity', event)
raise BadRequestError(f'{exc}') from exc
if signer.id != activity.actor_id:
quarantine('invalid_activity', event, activity.to_dict())
raise UnauthorizedError(
Expand Down
53 changes: 53 additions & 0 deletions lib/libactivitypub/src/libactivitypub/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def parse_object(obj: Dict[str, Any]) -> 'Activity':
return Announce.parse_object(obj)
if obj_type == 'Create':
return Create.parse_object(obj)
if obj_type == 'Delete':
return Delete.parse_object(obj)
if obj_type == 'Follow':
return Follow.parse_object(obj)
if obj_type == 'Like':
Expand Down Expand Up @@ -286,6 +288,52 @@ def resolve_objects(self, object_store: ObjectStore):
resolve_object(self._underlying['object'], object_store)


class Delete(Activity):
"""Wraps a "Delete" activity.
"""
def __init__(self, underlying: Dict[str, Any]):
"""Wraps a given "Follow" activity object.
:raises ValueError: if ``underlying`` does not represent an activity.
:raises TypeError: if ``underlying`` does not represent a valid object,
or if ``underlying`` does not have ``object``.
"""
super().__init__(underlying)
if 'object' not in underlying:
raise TypeError('invalid Delete activity: object is missing')

@staticmethod
def parse_object(obj: Dict[str, Any]) -> 'Delete':
"""Parses a given "Delete" activity object.
:raises ValueError: if ``obj`` does not represent an activity.
:raises TypeError: if ``obj`` does not represent a "Delete" activity
object.
"""
if obj.get('type') != 'Delete':
raise TypeError(f'type must be "Delete": {obj.get("type")}')
return Delete(obj)

@property
def object_id(self) -> str:
"""ID of the (to be) deleted object.
"""
return Reference(self._underlying['object']).id

def visit(self, visitor: 'ActivityVisitor'):
visitor.visit_delete(self)

def resolve_objects(self, object_store: ObjectStore):
"""Resolves the referenced object.
Resolves ``object``.
"""
super().resolve_objects(object_store)
resolve_object(self._underlying['object'], object_store)


class Follow(Activity):
"""Wraps a "Follow" activity.
"""
Expand Down Expand Up @@ -514,6 +562,11 @@ def visit_create(self, create: Create):
"""
LOGGER.debug('ignoring "Create": %s', create._underlying) # pylint: disable=protected-access

def visit_delete(self, delete: Delete):
"""Processes a "Delete" activity.
"""
LOGGER.debug('ignoring "Delete": %s', delete._underlying) # pylint: disable=protected-access

def visit_follow(self, follow: Follow):
"""Processes a "Follow" activity.
"""
Expand Down

0 comments on commit d9fa412

Please sign in to comment.