Improved audience handling and outbox ALC

main
Thomas Sileo 2022-06-26 18:40:25 +02:00
parent 4bf54c7040
commit 6d756fb8c4
6 changed files with 41 additions and 15 deletions

View File

@ -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
],

View File

@ -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

View File

@ -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))

View File

@ -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()

View File

@ -12,8 +12,8 @@
{{ utils.embed_redirect_url() }}
<p>
<select name="visibility">
{% for (k, v) in visibility_enum %}
<option value="{{ k }}">{{ v }}</option>
{% for (k, v) in visibility_choices %}
<option value="{{ k }}" {% if in_reply_to_object and in_reply_to_object.visibility.name == k %}selected{% endif %}>{{ v }}</option>
{% endfor %}
</select>
</p>

View File

@ -60,12 +60,12 @@
</form>
{% endmacro %}
{% macro admin_announce_button(ap_object_id) %}
{% macro admin_announce_button(ap_object_id, disabled=False) %}
<form action="{{ request.url_for("admin_actions_announce") }}" method="POST">
{{ embed_csrf_token() }}
{{ embed_redirect_url() }}
<input type="hidden" name="ap_object_id" value="{{ ap_object_id }}">
<input type="submit" value="Share">
<input type="submit" value="Share" {% if disabled %}title="Cannot share non-public content" disabled{% endif %}>
</form>
{% 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 %}
</div>
<div class="bar-item">