From 0ba43561fa7784d72d577d1471a9b50f7d853992 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Wed, 5 Apr 2023 07:16:31 -0700 Subject: [PATCH] /render: hydrate author/actor if necessary fixes semi-blank authors in outgoing webmentions --- render.py | 17 ++++++++++++++--- tests/test_render.py | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/render.py b/render.py index 029ea9c..86ebd40 100644 --- a/render.py +++ b/render.py @@ -9,6 +9,7 @@ from oauth_dropins.webutil import flask_util from oauth_dropins.webutil.flask_util import error from oauth_dropins.webutil import util +import activitypub from app import app, cache import common from models import Object @@ -27,9 +28,9 @@ def render(): elif not obj.as1: error(f'Stored object for {id} has no AS1', status=404) + # redirect creates, updates, etc to inner object type = as1.object_type(obj.as1) if type in ('post', 'update', 'delete'): - # redirect to inner object obj_id = as1.get_object(obj.as1).get('id') if obj_id: obj_obj = Object.get_by_id(obj_id) @@ -41,11 +42,21 @@ def render(): if obj.deleted or type == 'delete': return '', 410 + # fill in author/actor if available + obj_as1 = obj.as1 + for field in 'author', 'actor': + val = as1.get_object(obj.as1, field) + if val.keys() == set(['id']) and val['id']: + # TODO: abstract on obj.source_protocol + loaded = activitypub.ActivityPub.load(val['id']) + if loaded and loaded.as1: + obj_as1 = {**obj_as1, field: loaded.as1} + # add HTML meta redirect to source page. should trigger for end users in # browsers but not for webmention receivers (hopefully). - html = microformats2.activities_to_html([obj.as1]) + html = microformats2.activities_to_html([obj_as1]) utf8 = '' - url = util.get_url(obj.as1) + url = util.get_url(obj_as1) if url: refresh = f'' html = html.replace(utf8, utf8 + '\n' + refresh) diff --git a/tests/test_render.py b/tests/test_render.py index 189a1a9..a67897f 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -2,7 +2,7 @@ import copy from granary import as2 -from granary.tests.test_as1 import COMMENT, DELETE_OF_ID, UPDATE +from granary.tests.test_as1 import ACTOR, COMMENT, DELETE_OF_ID, UPDATE from app import app import common @@ -28,6 +28,29 @@ EXPECTED_HTML = """\ """ +EXPECTED_AUTHOR_HTML = """\ + + + + + +
+ tag:fake.com:123456 + + + + Bob + + + fake.com/123456 +
+ A ☕ reply +
+ +
+ + +""" class RenderTest(testutil.TestCase): @@ -49,6 +72,15 @@ class RenderTest(testutil.TestCase): self.assertEqual(200, resp.status_code) self.assert_multiline_equals(EXPECTED_HTML, resp.get_data(as_text=True), ignore_blanks=True) + def test_render_with_author(self): + with app.test_request_context('/'): + Object(id='abc', as2=as2.from_as1({**COMMENT, 'author': 'def'})).put() + Object(id='def', as2=as2.from_as1(ACTOR)).put() + resp = self.client.get('/render?id=abc') + self.assertEqual(200, resp.status_code) + self.assert_multiline_equals( + EXPECTED_AUTHOR_HTML, resp.get_data(as_text=True), ignore_blanks=True) + def test_render_no_url(self): comment = copy.deepcopy(COMMENT) del comment['url'] @@ -113,7 +145,6 @@ A ☕ reply """, resp.get_data(as_text=True), ignore_blanks=True) - def test_render_update_inner_obj_too_minimal_serve_as_is(self): with app.test_request_context('/'): # UPDATE's object field is a full object