]> git.mxchange.org Git - friendica.git/commitdiff
We now fetch data with an automatically generated system user
authorMichael <heluecht@pirati.ca>
Sat, 22 Aug 2020 14:48:09 +0000 (14:48 +0000)
committerMichael <heluecht@pirati.ca>
Sat, 22 Aug 2020 14:48:09 +0000 (14:48 +0000)
src/Model/User.php
src/Module/Admin/Site.php
src/Module/Friendica.php
src/Module/Xrd.php
src/Protocol/ActivityPub.php
src/Protocol/ActivityPub/Transmitter.php
src/Util/HTTPSignature.php
view/templates/admin/site.tpl
view/theme/frio/templates/admin/site.tpl

index 990df65dbd30f585bf83625cff5eaa3efd35ffcb..fe20857b95b59350be649c0d9acf9fcf9eddb948 100644 (file)
@@ -99,6 +99,95 @@ class User
 
        private static $owner;
 
+       /**
+        * Fetch the system account
+        *
+        * @return return system account
+        */
+       public static function getSystemAccount()
+       {
+               $system = Contact::selectFirst([], ['self' => true, 'uid' => 0]);
+               if (!DBA::isResult($system)) {
+                       self::createSystemAccount();
+                       $system = Contact::selectFirst([], ['self' => true, 'uid' => 0]);
+                       if (!DBA::isResult($system)) {
+                               return [];
+                       }
+               }
+
+               $system['spubkey'] = $system['uprvkey'] = $system['prvkey'];
+               $system['username'] = $system['name'];
+               $system['nickname'] = $system['nick'];
+               return $system;
+       }
+
+       /**
+        * Create the system account
+        *
+        * @return void
+        */
+       private static function createSystemAccount()
+       {
+               $system_actor_name = DI::config()->get('system', 'actor_name');
+               if (empty($system_actor_name)) {
+                       $system_actor_name = self::getActorName();
+                       if (empty($system_actor_name)) {
+                               return;
+                       }
+               }
+
+               $keys = Crypto::newKeypair(4096);
+               if ($keys === false) {
+                       throw new Exception(DI::l10n()->t('SERIOUS ERROR: Generation of security keys failed.'));
+               }
+
+               $system = [];
+               $system['uid'] = 0;
+               $system['created'] = DateTimeFormat::utcNow();
+               $system['self'] = true;
+               $system['network'] = Protocol::ACTIVITYPUB;
+               $system['name'] = 'System Account';
+               $system['addr'] = $system_actor_name . '@' . DI::baseUrl()->getHostname();
+               $system['nick'] = $system_actor_name;
+               $system['avatar'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO;
+               $system['photo'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO;
+               $system['thumb'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_THUMB;
+               $system['micro'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_MICRO;
+               $system['url'] = DI::baseUrl() . '/friendica';
+               $system['nurl'] = Strings::normaliseLink($system['url']);
+               $system['pubkey'] = $keys['pubkey'];
+               $system['prvkey'] = $keys['prvkey'];
+               $system['blocked'] = 0;
+               $system['pending'] = 0;
+               $system['contact-type'] = Contact::TYPE_RELAY; // In AP this is translated to 'Application'
+               $system['name-date'] = DateTimeFormat::utcNow();
+               $system['uri-date'] = DateTimeFormat::utcNow();
+               $system['avatar-date'] = DateTimeFormat::utcNow();
+               $system['closeness'] = 0;
+               $system['baseurl'] = DI::baseUrl();
+               $system['gsid'] = GServer::getID($system['baseurl']);
+               DBA::insert('contact', $system);
+       }
+
+       /**
+        * Detect a usable actor name
+        *
+        * @return string actor account name
+        */
+       public static function getActorName()
+       {
+               // List of possible actor names
+               $possible_accounts = ['friendica', 'actor', 'system', 'internal'];
+               foreach ($possible_accounts as $name) {
+                       if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'expire']) &&
+                               !DBA::exists('userd', ['username' => $name])) {
+                               DI::config()->set('system', 'actor_name', $name);
+                               return $name;
+                       }
+               }
+               return '';
+       }
+
        /**
         * Returns true if a user record exists with the provided id
         *
@@ -588,15 +677,24 @@ class User
        public static function isNicknameBlocked($nickname)
        {
                $forbidden_nicknames = DI::config()->get('system', 'forbidden_nicknames', '');
+               if (!empty($forbidden_nicknames)) {
+                       // check if the nickname is in the list of blocked nicknames
+                       $forbidden = explode(',', $forbidden_nicknames);
+                       $forbidden = array_map('trim', $forbidden);
+               } else {
+                       $forbidden = [];
+               }
+
+               // Add the name of the internal actor to the "forbidden" list
+               $actor_name = DI::config()->get('system', 'actor_name');
+               if (!empty($actor_name)) {
+                       $forbidden[] = $actor_name;
+               }
 
-               // if the config variable is empty return false
-               if (empty($forbidden_nicknames)) {
+               if (empty($forbidden)) {
                        return false;
                }
 
-               // check if the nickname is in the list of blocked nicknames
-               $forbidden = explode(',', $forbidden_nicknames);
-               $forbidden = array_map('trim', $forbidden);
                if (in_array(strtolower($nickname), $forbidden)) {
                        return true;
                }
index 7cc17fa036bb639672cd0b4bc1e6635bf898ebfd..7033f09b9cb9ba39a9110dbe581b72541f97ffa1 100644 (file)
@@ -29,6 +29,7 @@ use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\Contact;
+use Friendica\Model\User;
 use Friendica\Module\BaseAdmin;
 use Friendica\Module\Register;
 use Friendica\Util\BasePath;
@@ -148,6 +149,7 @@ class Site extends BaseAdmin
                $allowed_sites          = (!empty($_POST['allowed_sites'])           ? Strings::escapeTags(trim($_POST['allowed_sites']))  : '');
                $allowed_email          = (!empty($_POST['allowed_email'])           ? Strings::escapeTags(trim($_POST['allowed_email']))  : '');
                $forbidden_nicknames    = (!empty($_POST['forbidden_nicknames'])     ? strtolower(Strings::escapeTags(trim($_POST['forbidden_nicknames']))) : '');
+               $system_actor_name      = (!empty($_POST['system_actor_name'])       ? Strings::escapeTags(trim($_POST['system_actor_name'])) : '');
                $no_oembed_rich_content = !empty($_POST['no_oembed_rich_content']);
                $allowed_oembed         = (!empty($_POST['allowed_oembed'])          ? Strings::escapeTags(trim($_POST['allowed_oembed'])) : '');
                $block_public           = !empty($_POST['block_public']);
@@ -355,6 +357,7 @@ class Site extends BaseAdmin
                DI::config()->set('system', 'allowed_sites'          , $allowed_sites);
                DI::config()->set('system', 'allowed_email'          , $allowed_email);
                DI::config()->set('system', 'forbidden_nicknames'    , $forbidden_nicknames);
+               DI::config()->set('system', 'system_actor_name'      , $system_actor_name);
                DI::config()->set('system', 'no_oembed_rich_content' , $no_oembed_rich_content);
                DI::config()->set('system', 'allowed_oembed'         , $allowed_oembed);
                DI::config()->set('system', 'block_public'           , $block_public);
@@ -510,6 +513,11 @@ class Site extends BaseAdmin
                get_temppath();
                get_itemcachepath();
 
+               $system_actor_name = DI::config()->get('system', 'actor_name');
+               if (empty($system_actor_name)) {
+                       $system_actor_name = User::getActorName();
+               }
+
                /* Register policy */
                $register_choices = [
                        Register::CLOSED => DI::l10n()->t('Closed'),
@@ -600,6 +608,7 @@ class Site extends BaseAdmin
                        // name, label, value, help string, extra data...
                        '$sitename'         => ['sitename', DI::l10n()->t('Site name'), DI::config()->get('config', 'sitename'), ''],
                        '$sender_email'     => ['sender_email', DI::l10n()->t('Sender Email'), DI::config()->get('config', 'sender_email'), DI::l10n()->t('The email address your server shall use to send notification emails from.'), '', '', 'email'],
+                       '$system_actor_name' => ['system_actor_name', DI::l10n()->t('Name of the system actor'), $system_actor_name, DI::l10n()->t("Name of the internal system account that is used to perform ActivityPub requests. This must be an unused username. If set, this shouldn't be changed again.")],
                        '$banner'           => ['banner', DI::l10n()->t('Banner/Logo'), $banner, ''],
                        '$email_banner'     => ['email_banner', DI::l10n()->t('Email Banner/Logo'), $email_banner, ''],
                        '$shortcut_icon'    => ['shortcut_icon', DI::l10n()->t('Shortcut icon'), DI::config()->get('system', 'shortcut_icon'), DI::l10n()->t('Link to an icon that will be used for browsers.')],
index ea693ce2752be74f9ae4f4538b40aa59296e327c..2d76f80d04a7185b95a69ad2ead8cc20da8c65bc 100644 (file)
@@ -25,8 +25,10 @@ use Friendica\BaseModule;
 use Friendica\Core\Addon;
 use Friendica\Core\Hook;
 use Friendica\Core\Renderer;
+use Friendica\Core\System;
 use Friendica\DI;
 use Friendica\Model\User;
+use Friendica\Protocol\ActivityPub;
 
 /**
  * Prints information about the current node
@@ -108,6 +110,15 @@ class Friendica extends BaseModule
 
        public static function rawContent(array $parameters = [])
        {
+               if (ActivityPub::isRequest()) {
+                       $data = ActivityPub\Transmitter::getProfile(0);
+                       if (!empty($data)) {
+                               header('Access-Control-Allow-Origin: *');
+                               header('Cache-Control: max-age=23200, stale-while-revalidate=23200');
+                               System::jsonExit($data, 'application/activity+json');
+                       }
+               }
+
                $app = DI::app();
 
                // @TODO: Replace with parameter from router
index 249c143ffbd7168fd2ee901e8c67e66b4d9aba90..87adef5cfc9e4736958cfcee225fafccb712a397 100644 (file)
@@ -24,6 +24,7 @@ namespace Friendica\Module;
 use Friendica\BaseModule;
 use Friendica\Core\Hook;
 use Friendica\Core\Renderer;
+use Friendica\Core\System;
 use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\Photo;
@@ -77,24 +78,30 @@ class Xrd extends BaseModule
                        $name = substr($local, 0, strpos($local, '@'));
                }
 
-               $user = User::getByNickname($name);
+               if ($name == DI::config()->get('system', 'actor_name')) {
+                       $owner = User::getSystemAccount();
+                       if (empty($owner)) {
+                               throw new \Friendica\Network\HTTPException\NotFoundException();
+                       }
+                       self::printSystemJSON($owner);
+               } else {
+                       $user = User::getByNickname($name);
+                       if (empty($user)) {
+                               throw new \Friendica\Network\HTTPException\NotFoundException();
+                       }
 
-               if (empty($user)) {
-                       throw new \Friendica\Network\HTTPException\NotFoundException();
-               }
+                       $owner = User::getOwnerDataById($user['uid']);
+                       if (empty($owner)) {
+                               DI::logger()->warning('No owner data for user id', ['uri' => $uri, 'name' => $name, 'user' => $user]);
+                               throw new \Friendica\Network\HTTPException\NotFoundException();
+                       }
 
-               $owner = User::getOwnerDataById($user['uid']);
+                       $alias = str_replace('/profile/', '/~', $owner['url']);
 
-               if (empty($owner)) {
-                       DI::logger()->warning('No owner data for user id', ['uri' => $uri, 'name' => $name, 'user' => $user]);
-                       throw new \Friendica\Network\HTTPException\NotFoundException();
+                       $avatar = Photo::selectFirst(['type'], ['uid' => $owner['uid'], 'profile' => true]);
                }
 
-               $alias = str_replace('/profile/', '/~', $owner['url']);
-
-               $avatar = Photo::selectFirst(['type'], ['uid' => $owner['uid'], 'profile' => true]);
-
-               if (!DBA::isResult($avatar)) {
+               if (empty($avatar)) {
                        $avatar = ['type' => 'image/jpeg'];
                }
 
@@ -105,6 +112,32 @@ class Xrd extends BaseModule
                }
        }
 
+       private static function printSystemJSON(array $owner)
+       {
+               $json = [
+                       'subject' => 'acct:' . $owner['addr'],
+                       'aliases' => [$owner['url']],
+                       'links'   => [
+                               [
+                                       'rel'  => 'http://webfinger.net/rel/profile-page',
+                                       'type' => 'text/html',
+                                       'href' => $owner['url'],
+                               ],
+                               [
+                                       'rel'  => 'self',
+                                       'type' => 'application/activity+json',
+                                       'href' => $owner['url'],
+                               ],
+                               [
+                                       'rel'      => 'http://ostatus.org/schema/1.0/subscribe',
+                                       'template' => DI::baseUrl()->get() . '/follow?url={uri}',
+                               ],
+                       ]
+               ];
+               header('Access-Control-Allow-Origin: *');
+               System::jsonExit($json, 'application/jrd+json; charset=utf-8');
+       }
+
        private static function printJSON($alias, $baseURL, $owner, $avatar)
        {
                $salmon_key = Salmon::salmonKey($owner['spubkey']);
index c04b9e592debc183f60930959c7e5b7b46086c24..359d361b0186f58050b63b2488f42d6f5cd91bc5 100644 (file)
@@ -90,20 +90,6 @@ class ActivityPub
         */
        public static function fetchContent(string $url, int $uid = 0)
        {
-               if (empty($uid)) {
-                       $user = User::getFirstAdmin(['uid']);
-               
-                       if (empty($user['uid'])) {
-                               // When the system setup is missing an admin we just take the first user
-                               $condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
-                               $user = DBA::selectFirst('user', ['uid'], $condition);
-                       }
-
-                       if (!empty($user['uid'])) {
-                               $uid = $user['uid'];
-                       }
-               }
-
                return HTTPSignature::fetch($url, $uid);
        }
 
index f1b2d3acc772046166896f52916df4638154add2..259c8c225181a5f29cc67ee18f4c09747557bad9 100644 (file)
@@ -214,39 +214,63 @@ class Transmitter
         */
        public static function getProfile($uid)
        {
-               $condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
-                       'account_removed' => false, 'verified' => true];
-               $fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
-               $user = DBA::selectFirst('user', $fields, $condition);
-               if (!DBA::isResult($user)) {
-                       return [];
-               }
+               if ($uid != 0) {
+                       $condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
+                               'account_removed' => false, 'verified' => true];
+                       $fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
+                       $user = DBA::selectFirst('user', $fields, $condition);
+                       if (!DBA::isResult($user)) {
+                               return [];
+                       }
 
-               $fields = ['locality', 'region', 'country-name'];
-               $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
-               if (!DBA::isResult($profile)) {
-                       return [];
-               }
+                       $fields = ['locality', 'region', 'country-name'];
+                       $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
+                       if (!DBA::isResult($profile)) {
+                               return [];
+                       }
 
-               $fields = ['name', 'url', 'location', 'about', 'avatar', 'photo'];
-               $contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
-               if (!DBA::isResult($contact)) {
-                       return [];
+                       $fields = ['name', 'url', 'location', 'about', 'avatar', 'photo'];
+                       $contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
+                       if (!DBA::isResult($contact)) {
+                               return [];
+                       }
+               } else {
+                       $contact = User::getSystemAccount();
+                       $user = ['guid' => '', 'nickname' => $contact['nick'], 'pubkey' => $contact['pubkey'],
+                               'account-type' => $contact['contact-type'], 'page-flags' => User::PAGE_FLAGS_NORMAL];
+                       $profile = ['locality' => '', 'region' => '', 'country-name' => ''];
                }
 
                $data = ['@context' => ActivityPub::CONTEXT];
                $data['id'] = $contact['url'];
-               $data['diaspora:guid'] = $user['guid'];
+
+               if (!empty($user['guid'])) {
+                       $data['diaspora:guid'] = $user['guid'];
+               }
+
                $data['type'] = ActivityPub::ACCOUNT_TYPES[$user['account-type']];
-               $data['following'] = DI::baseUrl() . '/following/' . $user['nickname'];
-               $data['followers'] = DI::baseUrl() . '/followers/' . $user['nickname'];
-               $data['inbox'] = DI::baseUrl() . '/inbox/' . $user['nickname'];
-               $data['outbox'] = DI::baseUrl() . '/outbox/' . $user['nickname'];
+               
+               if ($uid != 0) {
+                       $data['following'] = DI::baseUrl() . '/following/' . $user['nickname'];
+                       $data['followers'] = DI::baseUrl() . '/followers/' . $user['nickname'];
+                       $data['inbox'] = DI::baseUrl() . '/inbox/' . $user['nickname'];
+                       $data['outbox'] = DI::baseUrl() . '/outbox/' . $user['nickname'];
+               } else {
+                       $data['inbox'] = DI::baseUrl() . '/friendica/inbox';
+               }
+
                $data['preferredUsername'] = $user['nickname'];
                $data['name'] = $contact['name'];
-               $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
-                       'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
-               $data['summary'] = BBCode::convert($contact['about'], false);
+
+               if (!empty($profile['country-name'] . $profile['region'] . $profile['locality'])) {
+                       $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
+                               'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
+               }
+
+               if (!empty($contact['about'])) {
+                       $data['summary'] = BBCode::convert($contact['about'], false);
+               }
+
                $data['url'] = $contact['url'];
                $data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
                $data['publicKey'] = ['id' => $contact['url'] . '#main-key',
@@ -652,6 +676,10 @@ class Transmitter
                        $item_profile = APContact::getByURL($item['owner-link'], false);
                }
 
+               if (empty($item_profile)) {
+                       return [];
+               }
+
                $profile_uid = User::getIdForURL($item_profile['url']);
 
                foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
index bbd5cd9b1585edcc46f486f00cabefd3bd8a4f71..5165f600fc1c340b9de16adc28a683ef8fbc6c00 100644 (file)
@@ -420,21 +420,26 @@ class HTTPSignature
                        if (!$owner) {
                                return;
                        }
+               } else {
+                       $owner = User::getSystemAccount();
+                       if (!$owner) {
+                               return;
+                       }
+               }
 
-                       if (!empty($owner['uprvkey'])) {
-                               // Header data that is about to be signed.
-                               $host = parse_url($request, PHP_URL_HOST);
-                               $path = parse_url($request, PHP_URL_PATH);
-                               $date = DateTimeFormat::utcNow(DateTimeFormat::HTTP);
+               if (!empty($owner['uprvkey'])) {
+                       // Header data that is about to be signed.
+                       $host = parse_url($request, PHP_URL_HOST);
+                       $path = parse_url($request, PHP_URL_PATH);
+                       $date = DateTimeFormat::utcNow(DateTimeFormat::HTTP);
 
-                               $headers = ['Date: ' . $date, 'Host: ' . $host];
+                       $headers = ['Date: ' . $date, 'Host: ' . $host];
 
-                               $signed_data = "(request-target): get " . $path . "\ndate: ". $date . "\nhost: " . $host;
+                       $signed_data = "(request-target): get " . $path . "\ndate: ". $date . "\nhost: " . $host;
 
-                               $signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256'));
+                       $signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256'));
 
-                               $headers[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) date host",signature="' . $signature . '"';
-                       }
+                       $headers[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) date host",signature="' . $signature . '"';
                }
 
                if (!empty($opts['accept_content'])) {
index 5c63970e9f3d4e1ff4efde930447e8e181e3bad9..0c097413433927fe82091071e17243fc52aa9113 100644 (file)
@@ -14,6 +14,7 @@
 
                {{include file="field_input.tpl" field=$sitename}}
                {{include file="field_input.tpl" field=$sender_email}}
+               {{include file="field_input.tpl" field=$system_actor_name}}
                {{include file="field_textarea.tpl" field=$banner}}
                {{include file="field_input.tpl" field=$email_banner}}
                {{include file="field_input.tpl" field=$shortcut_icon}}
index 0136b1cd44aab407cae023291441543bba71d133..a60cb06b63c9bb551e9e6c473bc0e35dfc781237 100644 (file)
@@ -41,6 +41,7 @@
                                        <div class="panel-body">
                                                {{include file="field_input.tpl" field=$sitename}}
                                                {{include file="field_input.tpl" field=$sender_email}}
+                                               {{include file="field_input.tpl" field=$system_actor_name}}
                                                {{include file="field_textarea.tpl" field=$banner}}
                                                {{include file="field_input.tpl" field=$shortcut_icon}}
                                                {{include file="field_input.tpl" field=$touch_icon}}