Add new admin debug module for ActivityPub
authorHypolite Petovan <hypolite@mrpetovan.com>
Mon, 20 Jul 2020 04:39:17 +0000 (00:39 -0400)
committerHypolite Petovan <hypolite@mrpetovan.com>
Mon, 20 Jul 2020 04:39:17 +0000 (00:39 -0400)
src/Module/BaseAdmin.php
src/Module/Debug/ActivityPubConversion.php [new file with mode: 0644]
static/routes.config.php
view/templates/debug/activitypubconversion.tpl [new file with mode: 0644]

index a7b38a5..01215dc 100644 (file)
@@ -121,6 +121,7 @@ abstract class BaseAdmin extends BaseModule
                                'webfinger'    => ['webfinger'         , DI::l10n()->t('check webfinger')         , 'webfinger'],
                                'itemsource'   => ['admin/item/source' , DI::l10n()->t('Item Source')             , 'itemsource'],
                                'babel'        => ['babel'             , DI::l10n()->t('Babel')                   , 'babel'],
+                               'debug/ap'     => ['debug/ap'          , DI::l10n()->t('ActivityPub Conversion')  , 'debug/ap'],
                        ]],
                ];
 
diff --git a/src/Module/Debug/ActivityPubConversion.php b/src/Module/Debug/ActivityPubConversion.php
new file mode 100644 (file)
index 0000000..87a531d
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Module\Debug;
+
+use Friendica\BaseModule;
+use Friendica\Content\Text;
+use Friendica\Core\Logger;
+use Friendica\Core\Renderer;
+use Friendica\DI;
+use Friendica\Model\Item;
+use Friendica\Model\Tag;
+use Friendica\Protocol\ActivityPub;
+use Friendica\Util\JsonLD;
+use Friendica\Util\XML;
+
+class ActivityPubConversion extends BaseModule
+{
+       public static function content(array $parameters = [])
+       {
+               function visible_whitespace($s)
+               {
+                       return '<pre>' . htmlspecialchars($s) . '</pre>';
+               }
+
+               $results = [];
+               if (!empty($_REQUEST['source'])) {
+                       try {
+                               $source = json_decode($_REQUEST['source'], true);
+                               $trust_source = true;
+                               $uid = local_user();
+                               $push = false;
+
+                               if (!$source) {
+                                       throw new \Exception('Failed to decode source JSON');
+                               }
+
+                               $formatted = json_encode($source, JSON_PRETTY_PRINT);
+                               $results[] = [
+                                       'title'   => DI::l10n()->t('Formatted'),
+                                       'content' => visible_whitespace(trim(var_export($formatted, true), "'")),
+                               ];
+                               $results[] = [
+                                       'title'   => DI::l10n()->t('Source'),
+                                       'content' => visible_whitespace(var_export($source, true))
+                               ];
+                               $activity = JsonLD::compact($source);
+                               if (!$activity) {
+                                       throw new \Exception('Failed to compact JSON');
+                               }
+                               $results[] = [
+                                       'title'   => DI::l10n()->t('Activity'),
+                                       'content' => visible_whitespace(var_export($activity, true))
+                               ];
+
+                               $type = JsonLD::fetchElement($activity, '@type');
+
+                               if (!$type) {
+                                       throw new \Exception('Empty type');
+                               }
+
+                               if (!JsonLD::fetchElement($activity, 'as:object', '@id')) {
+                                       throw new \Exception('Empty object');
+                               }
+
+                               if (!JsonLD::fetchElement($activity, 'as:actor', '@id')) {
+                                       throw new \Exception('Empty actor');
+                               }
+
+                               // Don't trust the source if "actor" differs from "attributedTo". The content could be forged.
+                               if ($trust_source && ($type == 'as:Create') && is_array($activity['as:object'])) {
+                                       $actor = JsonLD::fetchElement($activity, 'as:actor', '@id');
+                                       $attributed_to = JsonLD::fetchElement($activity['as:object'], 'as:attributedTo', '@id');
+                                       $trust_source = ($actor == $attributed_to);
+                                       if (!$trust_source) {
+                                               throw new \Exception('Not trusting actor: ' . $actor . '. It differs from attributedTo: ' . $attributed_to);
+                                       }
+                               }
+
+                               // $trust_source is called by reference and is set to true if the content was retrieved successfully
+                               $object_data = ActivityPub\Receiver::prepareObjectData($activity, $uid, $push, $trust_source);
+                               if (empty($object_data)) {
+                                       throw new \Exception('No object data found');
+                               }
+
+                               if (!$trust_source) {
+                                       throw new \Exception('No trust for activity type "' . $type . '", so we quit now.');
+                               }
+
+                               if (!empty($body) && empty($object_data['raw'])) {
+                                       $object_data['raw'] = $body;
+                               }
+
+                               // Internal flag for thread completion. See Processor.php
+                               if (!empty($activity['thread-completion'])) {
+                                       $object_data['thread-completion'] = $activity['thread-completion'];
+                               }
+
+                               $results[] = [
+                                       'title'   => DI::l10n()->t('Object data'),
+                                       'content' => visible_whitespace(var_export($object_data, true))
+                               ];
+
+                               $item = ActivityPub\Processor::createItem($object_data);
+
+                               $results[] = [
+                                       'title'   => DI::l10n()->t('Result Item'),
+                                       'content' => visible_whitespace(var_export($item, true))
+                               ];
+                       } catch (\Throwable $e) {
+                               $results[] = [
+                                       'title'   => DI::l10n()->t('Error'),
+                                       'content' => $e->getMessage(),
+                               ];
+                       }
+               }
+
+               $tpl = Renderer::getMarkupTemplate('debug/activitypubconversion.tpl');
+               $o = Renderer::replaceMacros($tpl, [
+                       '$source'          => ['source', DI::l10n()->t('Source activity'), $_REQUEST['source'] ?? '', ''],
+                       '$results'       => $results
+               ]);
+
+               return $o;
+       }
+}
index ac78826..8c3fba9 100644 (file)
@@ -107,6 +107,7 @@ return [
        '/apps'                => [Module\Apps::class,         [R::GET]],
        '/attach/{item:\d+}'   => [Module\Attach::class,       [R::GET]],
        '/babel'               => [Module\Debug\Babel::class,  [R::GET, R::POST]],
+       '/debug/ap'            => [Module\Debug\ActivityPubConversion::class,  [R::GET, R::POST]],
        '/bookmarklet'         => [Module\Bookmarklet::class,  [R::GET]],
 
        '/community[/{content}[/{accounttype}]]' => [Module\Conversation\Community::class, [R::GET]],
diff --git a/view/templates/debug/activitypubconversion.tpl b/view/templates/debug/activitypubconversion.tpl
new file mode 100644 (file)
index 0000000..dfc6d73
--- /dev/null
@@ -0,0 +1,24 @@
+<h2>ActivityPub Conversion</h2>
+<form action="debug/ap" method="post" class="panel panel-default">
+       <div class="panel-body">
+               <div class="form-group">
+                       {{include file="field_textarea.tpl" field=$source}}
+               </div>
+               <p><button type="submit" class="btn btn-primary">Submit</button></p>
+       </div>
+</form>
+
+{{if $results}}
+<div class="babel-results">
+       {{foreach $results as $result}}
+       <div class="panel panel-default">
+               <div class="panel-heading">
+                       <h3 class="panel-title">{{$result.title}}</h3>
+               </div>
+               <div class="panel-body">
+                       {{$result.content nofilter}}
+               </div>
+       </div>
+       {{/foreach}}
+</div>
+{{/if}}
\ No newline at end of file