diff --git a/app/admin.py b/app/admin.py index 01f3bd9..84ecf8c 100644 --- a/app/admin.py +++ b/app/admin.py @@ -119,7 +119,7 @@ def admin_new( { "in_reply_to_object": in_reply_to_object, "content": content, - "visibility_enum": [ + "visibility_choices": [ (v.name, ap.VisibilityEnum.get_display_name(v)) for v in ap.VisibilityEnum ], diff --git a/app/boxes.py b/app/boxes.py index aab782b..afdb6e5 100644 --- a/app/boxes.py +++ b/app/boxes.py @@ -15,6 +15,7 @@ from app import activitypub as ap from app import config from app import models from app.actor import LOCAL_ACTOR +from app.actor import Actor from app.actor import RemoteActor from app.actor import fetch_actor from app.actor import save_actor @@ -270,6 +271,8 @@ def send_create( elif visibility == ap.VisibilityEnum.DIRECT: to = mentioned_actors cc = [] + else: + raise ValueError(f"Unhandled visibility {visibility}") note = { "@context": ap.AS_CTX, @@ -329,8 +332,7 @@ def _compute_recipients(db: Session, ap_object: ap.RawObject) -> set[str]: # If we got a local collection, assume it's a collection of actors if r.startswith(BASE_URL): - for raw_actor in fetch_collection(db, r): - actor = RemoteActor(raw_actor) + for actor in fetch_actor_collection(db, r): recipients.add(actor.shared_inbox_url or actor.inbox_url) continue @@ -757,15 +759,15 @@ def public_outbox_objects_count(db: Session) -> int: ) -def fetch_collection(db: Session, url: str) -> list[ap.RawObject]: +def fetch_actor_collection(db: Session, url: str) -> list[Actor]: if url.startswith(config.BASE_URL): if url == config.BASE_URL + "/followers": q = db.query(models.Follower).options(joinedload(models.Follower.actor)) - return [follower.actor.ap_actor for follower in q.all()] + return [follower.actor for follower in q.all()] else: raise ValueError(f"internal collection for {url}) not supported") - return ap.parse_collection(url) + return [RemoteActor(actor) for actor in ap.parse_collection(url)] @dataclass diff --git a/app/main.py b/app/main.py index 4f33fc6..995a2c1 100644 --- a/app/main.py +++ b/app/main.py @@ -408,14 +408,33 @@ def featured( ) +def _check_outbox_object_acl( + db: Session, ap_object: models.OutboxObject, httpsig_info: httpsig.HTTPSigInfo +) -> None: + if ap_object.visibility in [ + ap.VisibilityEnum.PUBLIC, + ap.VisibilityEnum.UNLISTED, + ]: + return None + elif ap_object.visibility == ap.VisibilityEnum.FOLLOWERS_ONLY: + followers = boxes.fetch_actor_collection(db, BASE_URL + "/followers") + if httpsig_info.signed_by_ap_actor_id in [actor.ap_id for actor in followers]: + return None + elif ap_object.visibility == ap.VisibilityEnum.DIRECT: + audience = ap_object.ap_object.get("to", []) + ap_object.ap_object.get("cc", []) + if httpsig_info.signed_by_ap_actor_id in audience: + return None + + raise HTTPException(status_code=404) + + @app.get("/o/{public_id}") def outbox_by_public_id( public_id: str, request: Request, db: Session = Depends(get_db), - _: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker), + httpsig_info: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker), ) -> ActivityPubResponse | templates.TemplateResponse: - # TODO: ACL? maybe_object = ( db.query(models.OutboxObject) .options( @@ -432,6 +451,8 @@ def outbox_by_public_id( if not maybe_object: raise HTTPException(status_code=404) + _check_outbox_object_acl(db, maybe_object, httpsig_info) + if is_activitypub_requested(request): return ActivityPubResponse(maybe_object.ap_object) @@ -452,9 +473,8 @@ def outbox_by_public_id( def outbox_activity_by_public_id( public_id: str, db: Session = Depends(get_db), - _: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker), + httpsig_info: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker), ) -> ActivityPubResponse: - # TODO: ACL? maybe_object = ( db.query(models.OutboxObject) .filter( @@ -466,6 +486,8 @@ def outbox_activity_by_public_id( if not maybe_object: raise HTTPException(status_code=404) + _check_outbox_object_acl(db, maybe_object, httpsig_info) + return ActivityPubResponse(ap.wrap_object(maybe_object.ap_object)) diff --git a/app/templates.py b/app/templates.py index ea82c52..0b4512c 100644 --- a/app/templates.py +++ b/app/templates.py @@ -14,6 +14,7 @@ from loguru import logger from sqlalchemy.orm import Session from starlette.templating import _TemplateResponse as TemplateResponse +from app import activitypub as ap from app import models from app.actor import LOCAL_ACTOR from app.ap_object import Attachment @@ -84,6 +85,7 @@ def render_template( "is_admin": is_admin, "csrf_token": generate_csrf_token() if is_admin else None, "highlight_css": HIGHLIGHT_CSS, + "visibility_enum": ap.VisibilityEnum, "notifications_count": db.query(models.Notification) .filter(models.Notification.is_new.is_(True)) .count() diff --git a/app/templates/admin_new.html b/app/templates/admin_new.html index 4992960..689bb89 100644 --- a/app/templates/admin_new.html +++ b/app/templates/admin_new.html @@ -12,8 +12,8 @@ {{ utils.embed_redirect_url() }}

diff --git a/app/templates/utils.html b/app/templates/utils.html index 564141b..8f7ff86 100644 --- a/app/templates/utils.html +++ b/app/templates/utils.html @@ -60,12 +60,12 @@ {% endmacro %} -{% macro admin_announce_button(ap_object_id) %} +{% macro admin_announce_button(ap_object_id, disabled=False) %}
{{ embed_csrf_token() }} {{ embed_redirect_url() }} - +
{% endmacro %} @@ -253,7 +253,7 @@ {% if object.announced_via_outbox_object_ap_id %} {{ admin_undo_button(object.liked_via_outbox_object_ap_id, "Unshare") }} {% else %} - {{ admin_announce_button(object.ap_id) }} + {{ admin_announce_button(object.ap_id, disabled=object.visibility not in [visibility_enum.PUBLIC, visibility_enum.UNLISTED]) }} {% endif %}