From 0ab24510efcaa521952313d32e358d450a3fd660 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 15 Jun 2021 11:12:44 +0000 Subject: [PATCH 1/8] Centzralized functionality to update and publish profile changes --- include/api.php | 14 +---- mod/settings.php | 31 ++--------- src/Model/Contact.php | 8 ++- src/Model/Profile.php | 65 ++++++++++++++++++++++ src/Model/User.php | 36 ++++++++++++ src/Module/Settings/Profile/Index.php | 20 +------ src/Module/Settings/Profile/Photo/Crop.php | 12 ++-- update.php | 8 ++- 8 files changed, 125 insertions(+), 69 deletions(-) diff --git a/include/api.php b/include/api.php index 7da32f36dd..492d3d1cb7 100644 --- a/include/api.php +++ b/include/api.php @@ -42,6 +42,7 @@ use Friendica\Model\Mail; use Friendica\Model\Notification; use Friendica\Model\Photo; use Friendica\Model\Post; +use Friendica\Model\Profile; use Friendica\Model\User; use Friendica\Model\Verb; use Friendica\Network\HTTPException; @@ -4552,12 +4553,7 @@ function api_account_update_profile_image($type) Contact::updateSelfFromUserID(api_user(), true); // Update global directory in background - $url = DI::baseUrl() . '/profile/' . DI::app()->user['nickname']; - if ($url && strlen(DI::config()->get('system', 'directory'))) { - Worker::add(PRIORITY_LOW, "Directory", $url); - } - - Worker::add(PRIORITY_LOW, 'ProfileUpdate', api_user()); + Profile::publishUpdate(api_user()); // output for client if ($data) { @@ -4608,11 +4604,7 @@ function api_account_update_profile($type) DBA::update('contact', ['about' => $_POST['description']], ['id' => $api_user['id']]); } - Worker::add(PRIORITY_LOW, 'ProfileUpdate', $local_user); - // Update global directory in background - if ($api_user['url'] && strlen(DI::config()->get('system', 'directory'))) { - Worker::add(PRIORITY_LOW, "Directory", $api_user['url']); - } + Profile::publishUpdate($local_user); return api_account_verify_credentials($type); } diff --git a/mod/settings.php b/mod/settings.php index ec81339848..b248f017e6 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -30,9 +30,9 @@ use Friendica\Core\Renderer; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; -use Friendica\Model\Contact; use Friendica\Model\Group; use Friendica\Model\Notification; +use Friendica\Model\Profile; use Friendica\Model\User; use Friendica\Module\BaseSettings; use Friendica\Module\Security\Login; @@ -447,38 +447,15 @@ function settings_post(App $a) $fields['openidserver'] = ''; } - if (!DBA::update('user', $fields, ['uid' => local_user()])) { + $profile_fields = ['publish' => $publish, 'net-publish' => $net_publish, 'hide-friends' => $hide_friends]; + + if (!User::update($fields, local_user()) || !Profile::update($profile_fields, local_user())) { notice(DI::l10n()->t('Settings were not updated.')); } // clear session language unset($_SESSION['language']); - q("UPDATE `profile` - SET `publish` = %d, - `name` = '%s', - `net-publish` = %d, - `hide-friends` = %d - WHERE `uid` = %d", - intval($publish), - DBA::escape($username), - intval($net_publish), - intval($hide_friends), - intval(local_user()) - ); - - Contact::updateSelfFromUserID(local_user()); - - if (($old_visibility != $net_publish) || ($page_flags != $old_page_flags)) { - // Update global directory in background - $url = $_SESSION['my_url']; - if ($url && strlen(DI::config()->get('system', 'directory'))) { - Worker::add(PRIORITY_LOW, "Directory", $url); - } - } - - Worker::add(PRIORITY_LOW, 'ProfileUpdate', local_user()); - DI::baseUrl()->redirect('settings'); return; // NOTREACHED } diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 246a2967b2..1b2dd3c9c1 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -623,6 +623,7 @@ class Contact * * @param int $uid * @param boolean $update_avatar Force the avatar update + * @return bool "true" if updated * @throws HTTPException\InternalServerErrorException */ public static function updateSelfFromUserID($uid, $update_avatar = false) @@ -632,20 +633,20 @@ class Contact 'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco']; $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]); if (!DBA::isResult($self)) { - return; + return false; } $fields = ['nickname', 'page-flags', 'account-type', 'prvkey', 'pubkey']; $user = DBA::selectFirst('user', $fields, ['uid' => $uid, 'account_expired' => false]); if (!DBA::isResult($user)) { - return; + return false; } $fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region', 'country-name', 'pub_keywords', 'xmpp', 'net-publish']; $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]); if (!DBA::isResult($profile)) { - return; + return false; } $file_suffix = 'jpg'; @@ -724,6 +725,7 @@ class Contact 'thumb' => DI::baseUrl() . '/photo/avatar/' . $uid .'.' . $file_suffix]; DBA::update('profile', $fields, ['uid' => $uid]); } + return $update; } /** diff --git a/src/Model/Profile.php b/src/Model/Profile.php index 1943861856..63d83a8dab 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -29,8 +29,10 @@ use Friendica\Core\Hook; use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Core\Renderer; +use Friendica\Core\Search; use Friendica\Core\Session; use Friendica\Core\System; +use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Protocol\Activity; @@ -84,6 +86,69 @@ class Profile return DBA::selectToArray('profile', $fields, ['uid' => $uid]); } + /** + * Update a profile entry and distribute the changes if needed + * + * @param array $fields + * @param integer $uid + * @return boolean + */ + public static function update(array $fields, int $uid): bool + { + $old_owner = User::getOwnerDataById($uid); + if (empty($old_owner)) { + return false; + } + + if (!DBA::update('profile', $fields, ['uid' => $uid])) { + return false; + } + + $update = Contact::updateSelfFromUserID($uid); + + $owner = User::getOwnerDataById($uid); + if (empty($owner)) { + return false; + } + + if ($old_owner['name'] != $owner['name']) { + User::update(['username' => $owner['name']], $uid); + } + + $profile_fields = ['postal-code', 'dob', 'prv_keywords', 'homepage']; + foreach ($profile_fields as $field) { + if ($old_owner[$field] != $owner[$field]) { + $update = true; + } + } + + if ($update) { + self::publishUpdate($uid); + } + + return true; + } + + /** + * Publish a changed profile + * @param int $uid + */ + public static function publishUpdate(int $uid) + { + $owner = User::getOwnerDataById($uid); + if (empty($owner)) { + return; + } + if ($owner['net-publish']) { + // Update global directory in background + if (Search::getGlobalDirectory()) { + Worker::add(PRIORITY_LOW, 'Directory', $owner['url']); + } + } + + Worker::add(PRIORITY_LOW, 'ProfileUpdate', $uid); + } + /** * Returns a formatted location string from the given profile array * diff --git a/src/Model/User.php b/src/Model/User.php index 029613b17b..062537330c 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -1133,6 +1133,42 @@ class User return $return; } + /** + * Update a user entry and distribute the changes if needed + * + * @param array $fields + * @param integer $uid + * @return boolean + */ + public static function update(array $fields, int $uid): bool + { + $old_owner = self::getOwnerDataById($uid); + if (empty($old_owner)) { + return false; + } + + if (!DBA::update('user', $fields, ['uid' => $uid])) { + return false; + } + + $update = Contact::updateSelfFromUserID($uid); + + $owner = self::getOwnerDataById($uid); + if (empty($owner)) { + return false; + } + + if ($old_owner['name'] != $owner['name']) { + Profile::update(['name' => $owner['name']], $uid); + } + + if ($update) { + Profile::publishUpdate($uid); + } + + return true; + } + /** * Sets block state for a given user * diff --git a/src/Module/Settings/Profile/Index.php b/src/Module/Settings/Profile/Index.php index 93d2bbd23c..35740a0a7e 100644 --- a/src/Module/Settings/Profile/Index.php +++ b/src/Module/Settings/Profile/Index.php @@ -86,8 +86,6 @@ class Index extends BaseSettings return; } - $namechanged = $profile['name'] != $name; - $about = Strings::escapeTags(trim($_POST['about'])); $address = Strings::escapeTags(trim($_POST['address'])); $locality = Strings::escapeTags(trim($_POST['locality'])); @@ -114,8 +112,7 @@ class Index extends BaseSettings DI::profileField()->saveCollection($profileFields); - $result = DBA::update( - 'profile', + $result = Profile::update( [ 'name' => $name, 'about' => $about, @@ -130,26 +127,13 @@ class Index extends BaseSettings 'pub_keywords' => $pub_keywords, 'prv_keywords' => $prv_keywords, ], - ['uid' => local_user()] + local_user() ); if (!$result) { notice(DI::l10n()->t('Profile couldn\'t be updated.')); return; } - - if ($namechanged) { - DBA::update('user', ['username' => $name], ['uid' => local_user()]); - } - - Contact::updateSelfFromUserID(local_user()); - - // Update global directory in background - if (Session::get('my_url') && strlen(DI::config()->get('system', 'directory'))) { - Worker::add(PRIORITY_LOW, 'Directory', Session::get('my_url')); - } - - Worker::add(PRIORITY_LOW, 'ProfileUpdate', local_user()); } public static function content(array $parameters = []) diff --git a/src/Module/Settings/Profile/Photo/Crop.php b/src/Module/Settings/Profile/Photo/Crop.php index c3663e59ca..adbf83311a 100644 --- a/src/Module/Settings/Profile/Photo/Crop.php +++ b/src/Module/Settings/Profile/Photo/Crop.php @@ -28,6 +28,7 @@ use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Photo; +use Friendica\Model\Profile; use Friendica\Module\BaseSettings; use Friendica\Network\HTTPException; @@ -137,12 +138,9 @@ class Crop extends BaseSettings Contact::updateSelfFromUserID(local_user(), true); info(DI::l10n()->t('Shift-reload the page or clear browser cache if the new photo does not display immediately.')); - // Update global directory in background - if ($path && strlen(DI::config()->get('system', 'directory'))) { - Worker::add(PRIORITY_LOW, 'Directory', DI::baseUrl()->get() . '/' . $path); - } - Worker::add(PRIORITY_LOW, 'ProfileUpdate', local_user()); + // Update global directory in background + Profile::publishUpdate(local_user()); } else { notice(DI::l10n()->t('Unable to process image')); } @@ -183,9 +181,7 @@ class Crop extends BaseSettings Contact::updateSelfFromUserID(local_user(), true); // Update global directory in background - if (Session::get('my_url') && strlen(DI::config()->get('system', 'directory'))) { - Worker::add(PRIORITY_LOW, 'Directory', Session::get('my_url')); - } + Profile::publishUpdate(local_user()); info(DI::l10n()->t('Profile picture successfully updated.')); diff --git a/update.php b/update.php index 551d208fe4..aa043468ca 100644 --- a/update.php +++ b/update.php @@ -53,6 +53,7 @@ use Friendica\Model\ItemURI; use Friendica\Model\Notification; use Friendica\Model\Photo; use Friendica\Model\Post; +use Friendica\Model\Profile; use Friendica\Model\Storage; use Friendica\Worker\Delivery; @@ -98,8 +99,9 @@ function update_1298() DBA::update('profile', [$translateKey => $key], ['id' => $data['id']]); Logger::notice('Updated contact', ['action' => 'update', 'contact' => $data['id'], "$translateKey" => $key, 'was' => $data[$translateKey]]); - Worker::add(PRIORITY_LOW, 'ProfileUpdate', $data['id']); + Contact::updateSelfFromUserID($data['id']); + Profile::publishUpdate($data['id']); $success++; } } @@ -153,7 +155,9 @@ function update_1323() { $users = DBA::select('user', ['uid']); while ($user = DBA::fetch($users)) { - Contact::updateSelfFromUserID($user['uid']); + if (Contact::updateSelfFromUserID($user['uid'])) { + Profile::publishUpdate($user['uid']); + } } DBA::close($users); From 3796e1314281be7798243be8b20bddf730237a1a Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 16 Jun 2021 05:23:43 +0000 Subject: [PATCH 2/8] Force a directory update if previously published --- src/Model/Contact.php | 1 + src/Model/Profile.php | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 1b2dd3c9c1..f196f6f6a7 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -725,6 +725,7 @@ class Contact 'thumb' => DI::baseUrl() . '/photo/avatar/' . $uid .'.' . $file_suffix]; DBA::update('profile', $fields, ['uid' => $uid]); } + return $update; } diff --git a/src/Model/Profile.php b/src/Model/Profile.php index 63d83a8dab..03e35c02b0 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -123,7 +123,7 @@ class Profile } if ($update) { - self::publishUpdate($uid); + self::publishUpdate($uid, ($old_owner['net-publish'] != $owner['net-publish'])); } return true; @@ -131,15 +131,17 @@ class Profile /** * Publish a changed profile - * @param int $uid + * @param int $uid + * @param bool $force Force publishing to the directory */ - public static function publishUpdate(int $uid) + public static function publishUpdate(int $uid, bool $force = false) { $owner = User::getOwnerDataById($uid); if (empty($owner)) { return; } - if ($owner['net-publish']) { + + if ($owner['net-publish'] || $force) { // Update global directory in background if (Search::getGlobalDirectory()) { Worker::add(PRIORITY_LOW, 'Directory', $owner['url']); From ae5f57233e33c5672ee32b195684717ee9ae5b40 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 16 Jun 2021 13:23:07 +0000 Subject: [PATCH 3/8] Issue 10392: Avoid "Friendica can't display this page at the moment" --- src/App/BaseURL.php | 2 +- src/Core/Console.php | 2 ++ src/Database/DBStructure.php | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/App/BaseURL.php b/src/App/BaseURL.php index 7e1ab1c2b9..a7bfce2b18 100644 --- a/src/App/BaseURL.php +++ b/src/App/BaseURL.php @@ -232,7 +232,7 @@ class BaseURL { $parsed = @parse_url($url); - if (empty($parsed)) { + if (empty($parsed) || empty($parsed['host'])) { return false; } diff --git a/src/Core/Console.php b/src/Core/Console.php index 56a294062b..d1374a1b8f 100644 --- a/src/Core/Console.php +++ b/src/Core/Console.php @@ -172,6 +172,8 @@ HELP; Friendica\DI::init($this->dice); + Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine'); + /** @var Console $subconsole */ $subconsole = $this->dice->create($className, [$subargs]); diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index 9af6100a0a..4779487283 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -162,8 +162,6 @@ class DBStructure public static function writeStructure() { - Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine'); - $tables = []; foreach (self::definition(null) as $name => $definition) { $indexes = [[ From 78d4aff8780931ae65be34fa150eea4d65fff5e7 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 16 Jun 2021 15:02:33 +0000 Subject: [PATCH 4/8] Set the "Link" header --- .../Api/Mastodon/Accounts/Followers.php | 2 + .../Api/Mastodon/Accounts/Following.php | 2 + src/Module/Api/Mastodon/Accounts/Statuses.php | 2 + src/Module/Api/Mastodon/Blocks.php | 2 + src/Module/Api/Mastodon/Bookmarks.php | 2 + src/Module/Api/Mastodon/Conversations.php | 2 + src/Module/Api/Mastodon/Favourited.php | 2 + src/Module/Api/Mastodon/FollowRequests.php | 20 +----- src/Module/Api/Mastodon/Lists/Accounts.php | 2 + src/Module/Api/Mastodon/Mutes.php | 2 + src/Module/Api/Mastodon/Notifications.php | 2 + src/Module/Api/Mastodon/Search.php | 2 + src/Module/Api/Mastodon/Timelines/Direct.php | 2 + src/Module/Api/Mastodon/Timelines/Home.php | 2 + .../Api/Mastodon/Timelines/ListTimeline.php | 2 + .../Api/Mastodon/Timelines/PublicTimeline.php | 2 + src/Module/Api/Mastodon/Timelines/Tag.php | 2 + src/Module/BaseApi.php | 64 +++++++++++++++++++ 18 files changed, 98 insertions(+), 18 deletions(-) diff --git a/src/Module/Api/Mastodon/Accounts/Followers.php b/src/Module/Api/Mastodon/Accounts/Followers.php index 666cafc420..c495d6ca1e 100644 --- a/src/Module/Api/Mastodon/Accounts/Followers.php +++ b/src/Module/Api/Mastodon/Accounts/Followers.php @@ -77,6 +77,7 @@ class Followers extends BaseApi $followers = DBA::select('contact-relation', ['relation-cid'], $condition, $parameters); while ($follower = DBA::fetch($followers)) { + self::setBoundaries($follower['relation-cid']); $accounts[] = DI::mstdnAccount()->createFromContactId($follower['relation-cid'], $uid); } DBA::close($followers); @@ -85,6 +86,7 @@ class Followers extends BaseApi array_reverse($accounts); } + self::setLinkHeader(); System::jsonExit($accounts); } } diff --git a/src/Module/Api/Mastodon/Accounts/Following.php b/src/Module/Api/Mastodon/Accounts/Following.php index c967715dfc..b6a8f7f75c 100644 --- a/src/Module/Api/Mastodon/Accounts/Following.php +++ b/src/Module/Api/Mastodon/Accounts/Following.php @@ -77,6 +77,7 @@ class Following extends BaseApi $followers = DBA::select('contact-relation', ['cid'], $condition, $parameters); while ($follower = DBA::fetch($followers)) { + self::setBoundaries($follower['cid']); $accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid); } DBA::close($followers); @@ -85,6 +86,7 @@ class Following extends BaseApi array_reverse($accounts); } + self::setLinkHeader(); System::jsonExit($accounts); } } diff --git a/src/Module/Api/Mastodon/Accounts/Statuses.php b/src/Module/Api/Mastodon/Accounts/Statuses.php index 3b281392bf..0e7a7fb337 100644 --- a/src/Module/Api/Mastodon/Accounts/Statuses.php +++ b/src/Module/Api/Mastodon/Accounts/Statuses.php @@ -108,6 +108,7 @@ class Statuses extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { + self::setBoundaries($item['uri-id']); $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); } DBA::close($items); @@ -116,6 +117,7 @@ class Statuses extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); System::jsonExit($statuses); } } diff --git a/src/Module/Api/Mastodon/Blocks.php b/src/Module/Api/Mastodon/Blocks.php index d92b6059d3..37ab61af52 100644 --- a/src/Module/Api/Mastodon/Blocks.php +++ b/src/Module/Api/Mastodon/Blocks.php @@ -77,6 +77,7 @@ class Blocks extends BaseApi $followers = DBA::select('user-contact', ['cid'], $condition, $parameters); while ($follower = DBA::fetch($followers)) { + self::setBoundaries($follower['cid']); $accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid); } DBA::close($followers); @@ -85,6 +86,7 @@ class Blocks extends BaseApi array_reverse($accounts); } + self::setLinkHeader(); System::jsonExit($accounts); } } diff --git a/src/Module/Api/Mastodon/Bookmarks.php b/src/Module/Api/Mastodon/Bookmarks.php index 7e298d120f..a7141e3dcb 100644 --- a/src/Module/Api/Mastodon/Bookmarks.php +++ b/src/Module/Api/Mastodon/Bookmarks.php @@ -72,6 +72,7 @@ class Bookmarks extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { + self::setBoundaries($item['uri-id']); $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); } DBA::close($items); @@ -80,6 +81,7 @@ class Bookmarks extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); System::jsonExit($statuses); } } diff --git a/src/Module/Api/Mastodon/Conversations.php b/src/Module/Api/Mastodon/Conversations.php index 0340478c6b..22774b57ce 100644 --- a/src/Module/Api/Mastodon/Conversations.php +++ b/src/Module/Api/Mastodon/Conversations.php @@ -85,6 +85,7 @@ class Conversations extends BaseApi $conversations = []; while ($conv = DBA::fetch($convs)) { + self::setBoundaries($conv['id']); $conversations[] = DI::mstdnConversation()->CreateFromConvId($conv['id']); } @@ -94,6 +95,7 @@ class Conversations extends BaseApi array_reverse($conversations); } + self::setLinkHeader(); System::jsonExit($conversations); } } diff --git a/src/Module/Api/Mastodon/Favourited.php b/src/Module/Api/Mastodon/Favourited.php index 69b102a276..239257e833 100644 --- a/src/Module/Api/Mastodon/Favourited.php +++ b/src/Module/Api/Mastodon/Favourited.php @@ -70,6 +70,7 @@ class Favourited extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { + self::setBoundaries($item['thr-parent-id']); $statuses[] = DI::mstdnStatus()->createFromUriId($item['thr-parent-id'], $uid); } DBA::close($items); @@ -78,6 +79,7 @@ class Favourited extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); System::jsonExit($statuses); } } diff --git a/src/Module/Api/Mastodon/FollowRequests.php b/src/Module/Api/Mastodon/FollowRequests.php index 4628738456..cb7d5da220 100644 --- a/src/Module/Api/Mastodon/FollowRequests.php +++ b/src/Module/Api/Mastodon/FollowRequests.php @@ -92,8 +92,6 @@ class FollowRequests extends BaseApi 'limit' => 40, // Maximum number of results to return. Defaults to 40. Paginate using the HTTP Link header. ]); - $baseUrl = DI::baseUrl(); - $introductions = DI::intro()->selectByBoundaries( ['`uid` = ? AND NOT `ignore`', $uid], ['order' => ['id' => 'DESC']], @@ -106,6 +104,7 @@ class FollowRequests extends BaseApi foreach ($introductions as $key => $introduction) { try { + self::setBoundaries($introduction->id); $return[] = DI::mstdnFollowRequest()->createFromIntroduction($introduction); } catch (HTTPException\InternalServerErrorException $exception) { DI::intro()->delete($introduction); @@ -113,22 +112,7 @@ class FollowRequests extends BaseApi } } - $base_query = []; - if (isset($_GET['limit'])) { - $base_query['limit'] = $request['limit']; - } - - $links = []; - if ($introductions->getTotalCount() > $request['limit']) { - $links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $introductions[count($introductions) - 1]->id]) . '>; rel="next"'; - } - - if (count($introductions)) { - $links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['min_id' => $introductions[0]->id]) . '>; rel="prev"'; - } - - header('Link: ' . implode(', ', $links)); - + self::setLinkHeader(); System::jsonExit($return); } } diff --git a/src/Module/Api/Mastodon/Lists/Accounts.php b/src/Module/Api/Mastodon/Lists/Accounts.php index 21f85f3160..6e10ad6bfa 100644 --- a/src/Module/Api/Mastodon/Lists/Accounts.php +++ b/src/Module/Api/Mastodon/Lists/Accounts.php @@ -95,6 +95,7 @@ class Accounts extends BaseApi $members = DBA::select('group_member', ['contact-id'], $condition, $params); while ($member = DBA::fetch($members)) { + self::setBoundaries($member['contact-id']); $accounts[] = DI::mstdnAccount()->createFromContactId($member['contact-id'], $uid); } DBA::close($members); @@ -103,6 +104,7 @@ class Accounts extends BaseApi array_reverse($accounts); } + self::setLinkHeader(); System::jsonExit($accounts); } } diff --git a/src/Module/Api/Mastodon/Mutes.php b/src/Module/Api/Mastodon/Mutes.php index 469f3e0e61..ea890d9fdb 100644 --- a/src/Module/Api/Mastodon/Mutes.php +++ b/src/Module/Api/Mastodon/Mutes.php @@ -77,6 +77,7 @@ class Mutes extends BaseApi $followers = DBA::select('user-contact', ['cid'], $condition, $parameters); while ($follower = DBA::fetch($followers)) { + self::setBoundaries($follower['cid']); $accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid); } DBA::close($followers); @@ -85,6 +86,7 @@ class Mutes extends BaseApi array_reverse($accounts); } + self::setLinkHeader(); System::jsonExit($accounts); } } diff --git a/src/Module/Api/Mastodon/Notifications.php b/src/Module/Api/Mastodon/Notifications.php index cf2a9827c2..e02823dea5 100644 --- a/src/Module/Api/Mastodon/Notifications.php +++ b/src/Module/Api/Mastodon/Notifications.php @@ -128,6 +128,7 @@ class Notifications extends BaseApi $notify = DBA::select('notification', ['id'], $condition, $params); while ($notification = DBA::fetch($notify)) { + self::setBoundaries($notification['id']); $entry = DI::mstdnNotification()->createFromNotificationId($notification['id']); if (!empty($entry)) { $notifications[] = $entry; @@ -138,6 +139,7 @@ class Notifications extends BaseApi array_reverse($notifications); } + self::setLinkHeader(); System::jsonExit($notifications); } } diff --git a/src/Module/Api/Mastodon/Search.php b/src/Module/Api/Mastodon/Search.php index 4ef4292f8b..f6b95ae923 100644 --- a/src/Module/Api/Mastodon/Search.php +++ b/src/Module/Api/Mastodon/Search.php @@ -162,6 +162,7 @@ class Search extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { + self::setBoundaries($item['uri-id']); $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); } DBA::close($items); @@ -170,6 +171,7 @@ class Search extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); return $statuses; } diff --git a/src/Module/Api/Mastodon/Timelines/Direct.php b/src/Module/Api/Mastodon/Timelines/Direct.php index adf677c72f..c0fef79c54 100644 --- a/src/Module/Api/Mastodon/Timelines/Direct.php +++ b/src/Module/Api/Mastodon/Timelines/Direct.php @@ -71,6 +71,7 @@ class Direct extends BaseApi $statuses = []; while ($mail = DBA::fetch($mails)) { + self::setBoundaries($mail['uri-id']); $statuses[] = DI::mstdnStatus()->createFromMailId($mail['id']); } @@ -78,6 +79,7 @@ class Direct extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); System::jsonExit($statuses); } } diff --git a/src/Module/Api/Mastodon/Timelines/Home.php b/src/Module/Api/Mastodon/Timelines/Home.php index 63d40e8440..2b556d7ce7 100644 --- a/src/Module/Api/Mastodon/Timelines/Home.php +++ b/src/Module/Api/Mastodon/Timelines/Home.php @@ -93,6 +93,7 @@ class Home extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { + self::setBoundaries($item['uri-id']); $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); } DBA::close($items); @@ -101,6 +102,7 @@ class Home extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); System::jsonExit($statuses); } } diff --git a/src/Module/Api/Mastodon/Timelines/ListTimeline.php b/src/Module/Api/Mastodon/Timelines/ListTimeline.php index 22b2be3bc8..8d4a432c57 100644 --- a/src/Module/Api/Mastodon/Timelines/ListTimeline.php +++ b/src/Module/Api/Mastodon/Timelines/ListTimeline.php @@ -98,6 +98,7 @@ class ListTimeline extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { + self::setBoundaries($item['uri-id']); $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); } DBA::close($items); @@ -106,6 +107,7 @@ class ListTimeline extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); System::jsonExit($statuses); } } diff --git a/src/Module/Api/Mastodon/Timelines/PublicTimeline.php b/src/Module/Api/Mastodon/Timelines/PublicTimeline.php index 1efeabba80..bc617b65a8 100644 --- a/src/Module/Api/Mastodon/Timelines/PublicTimeline.php +++ b/src/Module/Api/Mastodon/Timelines/PublicTimeline.php @@ -99,6 +99,7 @@ class PublicTimeline extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { + self::setBoundaries($item['uri-id']); $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $item['uid']); } DBA::close($items); @@ -107,6 +108,7 @@ class PublicTimeline extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); System::jsonExit($statuses); } } diff --git a/src/Module/Api/Mastodon/Timelines/Tag.php b/src/Module/Api/Mastodon/Timelines/Tag.php index 56697f2f58..0437be2771 100644 --- a/src/Module/Api/Mastodon/Timelines/Tag.php +++ b/src/Module/Api/Mastodon/Timelines/Tag.php @@ -107,6 +107,7 @@ class Tag extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { + self::setBoundaries($item['uri-id']); $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); } DBA::close($items); @@ -115,6 +116,7 @@ class Tag extends BaseApi array_reverse($statuses); } + self::setLinkHeader(); System::jsonExit($statuses); } } diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index bae466c905..76acd4d889 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -44,6 +44,16 @@ class BaseApi extends BaseModule */ protected static $format = 'json'; + /** + * @var array + */ + protected static $boundaries = []; + + /** + * @var array + */ + protected static $request = []; + public static function init(array $parameters = []) { $arguments = DI::args(); @@ -129,6 +139,11 @@ class BaseApi extends BaseModule $httpinput = HTTPInputData::process(); $input = array_merge($httpinput['variables'], $httpinput['files'], $_REQUEST); + self::$request = $input; + self::$boundaries = []; + + unset(self::$request['pagename']); + $request = []; foreach ($defaults as $parameter => $defaultvalue) { @@ -160,6 +175,55 @@ class BaseApi extends BaseModule return $request; } + /** + * Set boundaries for the "link" header + * @param array $boundaries + * @param int $id + * @return array + */ + protected static function setBoundaries(int $id) + { + if (!isset(self::$boundaries['min'])) { + self::$boundaries['min'] = $id; + } + + if (!isset(self::$boundaries['max'])) { + self::$boundaries['max'] = $id; + } + + self::$boundaries['min'] = min(self::$boundaries['min'], $id); + self::$boundaries['max'] = max(self::$boundaries['max'], $id); + } + + /** + * Set the "link" header with "next" and "prev" links + * @return void + */ + protected static function setLinkHeader() + { + if (empty(self::$boundaries)) { + return; + } + + $request = self::$request; + + unset($request['min_id']); + unset($request['max_id']); + unset($request['since_id']); + + $prev_request = $next_request = $request; + + $prev_request['min_id'] = self::$boundaries['max'] + 1; + $next_request['max_id'] = self::$boundaries['min'] - 1; + + $command = DI::baseUrl() . '/' . DI::args()->getCommand(); + + $prev = $command . '?' . http_build_query($prev_request); + $next = $command . '?' . http_build_query($next_request); + + header('Link: <' . $next . '>; rel="next", <' . $prev . '>; rel="prev"'); + } + /** * Get current application token * From 6e789ec19af3b7d2de33dfa5d18e1765600175f6 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 16 Jun 2021 17:57:01 +0000 Subject: [PATCH 5/8] No need for +1 and -1 --- src/Module/BaseApi.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 76acd4d889..f5a16da765 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -213,8 +213,8 @@ class BaseApi extends BaseModule $prev_request = $next_request = $request; - $prev_request['min_id'] = self::$boundaries['max'] + 1; - $next_request['max_id'] = self::$boundaries['min'] - 1; + $prev_request['min_id'] = self::$boundaries['max']; + $next_request['max_id'] = self::$boundaries['min']; $command = DI::baseUrl() . '/' . DI::args()->getCommand(); From 5e22704f7e59c054cf1f0fca0a459dbbc07fc79e Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 16 Jun 2021 19:24:44 +0000 Subject: [PATCH 6/8] API: Added OAuth revoke, adding documentation to parameters --- src/Module/OAuth/Authorize.php | 9 +++++---- src/Module/OAuth/Revoke.php | 20 +++++++++++++++++++- src/Module/OAuth/Token.php | 11 ++++++----- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/Module/OAuth/Authorize.php b/src/Module/OAuth/Authorize.php index b2792c598f..cf5187d947 100644 --- a/src/Module/OAuth/Authorize.php +++ b/src/Module/OAuth/Authorize.php @@ -41,11 +41,12 @@ class Authorize extends BaseApi public static function rawContent(array $parameters = []) { $request = self::getRequest([ - 'response_type' => '', - 'client_id' => '', + 'force_login' => '', // Forces the user to re-login, which is necessary for authorizing with multiple accounts from the same instance. + 'response_type' => '', // Should be set equal to "code". + 'client_id' => '', // Client ID, obtained during app registration. 'client_secret' => '', // Isn't normally provided. We will use it if present. - 'redirect_uri' => '', - 'scope' => 'read', + 'redirect_uri' => '', // Set a URI to redirect the user to. If this parameter is set to "urn:ietf:wg:oauth:2.0:oob" then the authorization code will be shown instead. Must match one of the redirect URIs declared during app registration. + 'scope' => 'read', // List of requested OAuth scopes, separated by spaces (or by pluses, if using query parameters). Must be a subset of scopes declared during app registration. If not provided, defaults to "read". 'state' => '', ]); diff --git a/src/Module/OAuth/Revoke.php b/src/Module/OAuth/Revoke.php index c641932437..519e79db01 100644 --- a/src/Module/OAuth/Revoke.php +++ b/src/Module/OAuth/Revoke.php @@ -21,6 +21,10 @@ namespace Friendica\Module\OAuth; +use Friendica\Core\Logger; +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; use Friendica\Module\BaseApi; /** @@ -30,6 +34,20 @@ class Revoke extends BaseApi { public static function post(array $parameters = []) { - self::unsupported('post'); + $request = self::getRequest([ + 'client_id' => '', // Client ID, obtained during app registration + 'client_secret' => '', // Client secret, obtained during app registration + 'token' => '', // The previously obtained token, to be invalidated + ]); + + $condition = ['client_id' => $request['client_id'], 'client_secret' => $request['client_secret'], 'access_token' => $request['token']]; + $token = DBA::selectFirst('application-view', ['id'], $condition); + if (empty($token['id'])) { + Logger::warning('Token not found', $condition); + DI::mstdnError()->Unauthorized(); + } + + DBA::delete('application-token', ['application-id' => $token['id']]); + System::jsonExit([]); } } diff --git a/src/Module/OAuth/Token.php b/src/Module/OAuth/Token.php index 3a8be825ff..715cabeaf2 100644 --- a/src/Module/OAuth/Token.php +++ b/src/Module/OAuth/Token.php @@ -37,11 +37,12 @@ class Token extends BaseApi public static function post(array $parameters = []) { $request = self::getRequest([ - 'grant_type' => '', - 'code' => '', - 'redirect_uri' => '', - 'client_id' => '', - 'client_secret' => '', + 'client_id' => '', // Client ID, obtained during app registration + 'client_secret' => '', // Client secret, obtained during app registration + 'redirect_uri' => '', // Set a URI to redirect the user to. If this parameter is set to "urn:ietf:wg:oauth:2.0:oob" then the token will be shown instead. Must match one of the redirect URIs declared during app registration. + 'scope' => 'read', // List of requested OAuth scopes, separated by spaces. Must be a subset of scopes declared during app registration. If not provided, defaults to "read". + 'code' => '', // A user authorization code, obtained via /oauth/authorize + 'grant_type' => '', // Set equal to "authorization_code" if code is provided in order to gain user-level access. Otherwise, set equal to "client_credentials" to obtain app-level access only. ]); // AndStatus transmits the client data in the AUTHORIZATION header field, see https://github.com/andstatus/andstatus/issues/530 From 6b3476409aa2efd8d4e2f41c2d039ee97fe7915a Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 16 Jun 2021 19:39:51 +0000 Subject: [PATCH 7/8] Check for REDIRECT_REMOTE_USER as well --- src/Module/OAuth/Token.php | 10 ++++++++-- src/Security/BasicAuth.php | 2 +- src/Security/OAuth.php | 5 +++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Module/OAuth/Token.php b/src/Module/OAuth/Token.php index 715cabeaf2..1a2fff5254 100644 --- a/src/Module/OAuth/Token.php +++ b/src/Module/OAuth/Token.php @@ -46,8 +46,14 @@ class Token extends BaseApi ]); // AndStatus transmits the client data in the AUTHORIZATION header field, see https://github.com/andstatus/andstatus/issues/530 - if (empty($request['client_id']) && !empty($_SERVER['HTTP_AUTHORIZATION']) && (substr($_SERVER['HTTP_AUTHORIZATION'], 0, 6) == 'Basic ')) { - $datapair = explode(':', base64_decode(trim(substr($_SERVER['HTTP_AUTHORIZATION'], 6)))); + $authorization = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; + if (empty($authorization)) { + // workaround for HTTP-auth in CGI mode + $authorization = $_SERVER['REDIRECT_REMOTE_USER'] ?? ''; + } + + if (empty($request['client_id']) && !empty($authorization) && (substr($authorization, 0, 6) == 'Basic ')) { + $datapair = explode(':', base64_decode(trim(substr($authorization, 6)))); if (count($datapair) == 2) { $request['client_id'] = $datapair[0]; $request['client_secret'] = $datapair[1]; diff --git a/src/Security/BasicAuth.php b/src/Security/BasicAuth.php index b76073e8b3..070c6500d3 100644 --- a/src/Security/BasicAuth.php +++ b/src/Security/BasicAuth.php @@ -124,7 +124,7 @@ class BasicAuth // workaround for HTTP-auth in CGI mode if (!empty($_SERVER['REDIRECT_REMOTE_USER'])) { $userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6)); - if (strlen($userpass)) { + if (!empty($userpass) && strpos($userpass, ':')) { list($name, $password) = explode(':', $userpass); $_SERVER['PHP_AUTH_USER'] = $name; $_SERVER['PHP_AUTH_PW'] = $password; diff --git a/src/Security/OAuth.php b/src/Security/OAuth.php index 7210df8c2e..2f5dd39641 100644 --- a/src/Security/OAuth.php +++ b/src/Security/OAuth.php @@ -83,6 +83,11 @@ class OAuth { $authorization = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; + if (empty($authorization)) { + // workaround for HTTP-auth in CGI mode + $authorization = $_SERVER['REDIRECT_REMOTE_USER'] ?? ''; + } + if (substr($authorization, 0, 7) != 'Bearer ') { return []; } From a256f18159aa58c182487cc12462ce2fdb4a0c3e Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Wed, 16 Jun 2021 22:19:26 +0200 Subject: [PATCH 8/8] Update src/Module/OAuth/Token.php Co-authored-by: Hypolite Petovan --- src/Module/OAuth/Token.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/OAuth/Token.php b/src/Module/OAuth/Token.php index 1a2fff5254..f104e96721 100644 --- a/src/Module/OAuth/Token.php +++ b/src/Module/OAuth/Token.php @@ -52,7 +52,7 @@ class Token extends BaseApi $authorization = $_SERVER['REDIRECT_REMOTE_USER'] ?? ''; } - if (empty($request['client_id']) && !empty($authorization) && (substr($authorization, 0, 6) == 'Basic ')) { + if (empty($request['client_id']) && substr($authorization, 0, 6) == 'Basic ') { $datapair = explode(':', base64_decode(trim(substr($authorization, 6)))); if (count($datapair) == 2) { $request['client_id'] = $datapair[0];