From 88ca8a676d91867488e3f9d23e17594d01e47670 Mon Sep 17 00:00:00 2001 From: Thomas Sileo Date: Sun, 17 Jul 2022 20:43:03 +0200 Subject: [PATCH] Micropub tweaks --- app/micropub.py | 74 +++++++++++++++++++++++++++---- app/templates/indieauth_flow.html | 2 +- app/utils/indieauth.py | 9 ++++ 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/app/micropub.py b/app/micropub.py index 6d96b0a..e377e02 100644 --- a/app/micropub.py +++ b/app/micropub.py @@ -5,6 +5,7 @@ from fastapi import Depends from fastapi import Request from fastapi.responses import JSONResponse from fastapi.responses import RedirectResponse +from loguru import logger from app import activitypub as ap from app.boxes import get_outbox_object_by_ap_id @@ -55,6 +56,14 @@ async def micropub_endpoint( return {} +def _prop_get(dat: dict[str, Any], key: str) -> str: + val = dat[key] + if isinstance(val, list): + return val[0] + else: + return val + + @router.post("/micropub") async def post_micropub_endpoint( request: Request, @@ -62,8 +71,17 @@ async def post_micropub_endpoint( db_session: AsyncSession = Depends(get_db_session), ) -> RedirectResponse | JSONResponse: form_data = await request.form() + is_json = False + if not form_data: + form_data = await request.json() + is_json = True + + insufficient_scope_resp = JSONResponse( + status_code=401, content={"error": "insufficient_scope"} + ) + if "action" in form_data: - if form_data["action"] == "delete": + if form_data["action"] in ["delete", "update"]: outbox_object = await get_outbox_object_by_ap_id( db_session, form_data["url"] ) @@ -75,14 +93,48 @@ async def post_micropub_endpoint( }, status_code=400, ) - await send_delete(db_session, outbox_object.ap_id) # type: ignore - return JSONResponse(content={}, status_code=200) - h = "entry" - if "h" in form_data: - h = form_data["h"] + if form_data["action"] == "delete": + if "delete" not in access_token_info.scopes: + return insufficient_scope_resp + logger.info(f"Deleting object {outbox_object.ap_id}") + await send_delete(db_session, outbox_object.ap_id) # type: ignore + return JSONResponse(content={}, status_code=200) - if h != "entry": + elif form_data["action"] == "update": + if "update" not in access_token_info.scopes: + return insufficient_scope_resp + + # TODO(ts): support update + # "replace": {"content": ["new content"]} + + logger.info(f"Updating object {outbox_object.ap_id}: {form_data}") + return JSONResponse(content={}, status_code=200) + else: + raise ValueError("Should never happen") + else: + return JSONResponse( + content={ + "error": "invalid_request", + "error_description": f'Unsupported action: {form_data["action"]}', + }, + status_code=400, + ) + + if "create" not in access_token_info.scopes: + return insufficient_scope_resp + + if is_json: + entry_type = _prop_get(form_data, "type") # type: ignore + else: + h = "entry" + if "h" in form_data: + h = form_data["h"] + entry_type = f"h-{h}" + + logger.info(f"Creating {entry_type}") + + if entry_type != "h-entry": return JSONResponse( content={ "error": "invalid_request", @@ -91,7 +143,13 @@ async def post_micropub_endpoint( status_code=400, ) - content = form_data["content"] + # TODO(ts): support creating Article (with a name) + + if is_json: + content = _prop_get(form_data["properties"], "content") # type: ignore + else: + content = form_data["content"] + public_id = await send_create( db_session, content, diff --git a/app/templates/indieauth_flow.html b/app/templates/indieauth_flow.html index a1ebe9b..6af8071 100644 --- a/app/templates/indieauth_flow.html +++ b/app/templates/indieauth_flow.html @@ -11,7 +11,7 @@
{{ client.name }} -

wants you to login as {{ me }}

+

wants you to login as {{ me }} with the following redirect URI: {{ redirect_uri }}.

diff --git a/app/utils/indieauth.py b/app/utils/indieauth.py index 9143e29..00d2714 100644 --- a/app/utils/indieauth.py +++ b/app/utils/indieauth.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from typing import Any +from urllib.parse import urlparse from app.utils import microformats from app.utils.url import make_abs @@ -22,6 +23,14 @@ def _get_prop(props: dict[str, Any], name: str, default=None) -> Any: async def get_client_id_data(url: str) -> IndieAuthClient | None: + # Don't fetch localhost URL + if urlparse(url).netloc == "localhost": + return IndieAuthClient( + logo=None, + name=url, + url=url, + ) + maybe_data_and_html = await microformats.fetch_and_parse(url) if maybe_data_and_html is not None: data: dict[str, Any] = maybe_data_and_html[0]