From dca93a9606cd19d149e3802c3779be94f5dda642 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 20 May 2024 19:36:40 +0000 Subject: [PATCH] Enable "magic" with Hubzilla --- src/App.php | 5 +++ src/Model/Contact.php | 14 +++--- src/Model/Profile.php | 34 +++++++------- src/Module/Contact/Redir.php | 87 +++++++++++++++++------------------- src/Module/Magic.php | 18 +++++--- src/Module/Owa.php | 1 - src/Util/Network.php | 25 +++++++++++ src/Util/Strings.php | 10 +++-- 8 files changed, 115 insertions(+), 79 deletions(-) diff --git a/src/App.php b/src/App.php index 43938676e3..96b8d8b947 100644 --- a/src/App.php +++ b/src/App.php @@ -621,6 +621,11 @@ class App Core\Hook::loadHooks(); } + // Compatibility with Hubzilla + if ($moduleName == 'rpost') { + $this->baseURL->redirect('compose'); + } + // Compatibility with the Android Diaspora client if ($moduleName == 'stream') { $this->baseURL->redirect('network?order=post'); diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 0e0dc38eb4..8d2969b72e 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -3580,15 +3580,19 @@ class Contact return $destination; } + if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local') && ($url == '')) { + return 'contact/' . $contact['id'] . '/conversations'; + } + + if (Strings::compareLink($contact['url'], $url) || Strings::compareLink($contact['alias'], $url)) { + $url = ''; + } + // Only redirections to the same host do make sense if (($url != '') && (parse_url($url, PHP_URL_HOST) != parse_url($contact['url'], PHP_URL_HOST))) { return $url; } - if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local') && ($url == '')) { - return 'contact/' . $contact['id'] . '/conversations'; - } - if (!empty($contact['network']) && $contact['network'] != Protocol::DFRN) { return $destination; } @@ -3599,7 +3603,7 @@ class Contact $redirect = 'contact/redir/' . $contact['id']; - if (($url != '') && !Strings::compareLink($contact['url'], $url)) { + if ($url != '') { $redirect .= '?url=' . $url; } diff --git a/src/Model/Profile.php b/src/Model/Profile.php index fd7b9580c9..5b8cf5bf18 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -730,16 +730,20 @@ class Profile Hook::callAll('zrl_init', $arr); // Try to find the public contact entry of the visitor. - $cid = Contact::getIdForURL($my_url); - if (!$cid) { - Logger::info('No contact record found for ' . $my_url); + $contact = Contact::getByURL($my_url, null, ['id', 'url', 'gsid']); + if (empty($contact)) { + Logger::info('No contact record found', ['url' => $my_url]); return; } - $contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]); + if (DI::userSession()->getRemoteUserId() && DI::userSession()->getRemoteUserId() == $contact['id']) { + Logger::info('The visitor is already authenticated', ['url' => $my_url]); + return; + } - if (DBA::isResult($contact) && DI::userSession()->getRemoteUserId() && DI::userSession()->getRemoteUserId() == $contact['id']) { - Logger::info('The visitor ' . $my_url . ' is already authenticated'); + $gserver = DBA::selectFirst('gserver', ['url', 'authredirect'], ['id' => $contact['gsid']]); + if (empty($gserver) || empty($gserver['authredirect'])) { + Logger::info('No server record found or magic path not defined for server', ['id' => $contact['gsid'], 'gserver' => $gserver]); return; } @@ -752,7 +756,7 @@ class Profile DI::cache()->set($cachekey, true, Duration::MINUTE); } - Logger::info('Not authenticated. Invoking reverse magic-auth for ' . $my_url); + Logger::info('Not authenticated. Invoking reverse magic-auth', ['url' => $my_url]); // Remove the "addr" parameter from the destination. It is later added as separate parameter again. $addr_request = 'addr=' . urlencode($addr); @@ -761,19 +765,11 @@ class Profile // The other instance needs to know where to redirect. $dest = urlencode(DI::baseUrl() . '/' . $query); - // We need to extract the basebath from the profile url - // to redirect the visitors '/magic' module. - $basepath = Contact::getBasepath($contact['url']); + if ($gserver['url'] != DI::baseUrl() && !strstr($dest, '/magic')) { + $magic_path = $gserver['authredirect'] . '?f=&rev=1&owa=1&dest=' . $dest . '&' . $addr_request; - if ($basepath != DI::baseUrl() && !strstr($dest, '/magic')) { - $magic_path = $basepath . '/magic' . '?owa=1&dest=' . $dest . '&' . $addr_request; - - // We have to check if the remote server does understand /magic without invoking something - $serverret = DI::httpClient()->head($basepath . '/magic', [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::HTML, HttpClientOptions::REQUEST => HttpClientRequest::MAGICAUTH]); - if ($serverret->isSuccess()) { - Logger::info('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path); - System::externalRedirect($magic_path); - } + Logger::info('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path); + System::externalRedirect($magic_path); } } diff --git a/src/Module/Contact/Redir.php b/src/Module/Contact/Redir.php index 522b0ad2b7..d9d787bec9 100644 --- a/src/Module/Contact/Redir.php +++ b/src/Module/Contact/Redir.php @@ -23,17 +23,16 @@ namespace Friendica\Module\Contact; use Friendica\Core\L10n; use Friendica\App; +use Friendica\Core\Protocol; use Friendica\Core\Session\Capability\IHandleUserSessions; +use Friendica\Core\Worker; use Friendica\Database\Database; use Friendica\Model\Contact; use Friendica\Module\Response; -use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests; -use Friendica\Network\HTTPClient\Client\HttpClientAccept; -use Friendica\Network\HTTPClient\Client\HttpClientOptions; -use Friendica\Network\HTTPClient\Client\HttpClientRequest; use Friendica\Network\HTTPException; use Friendica\Util\Profiler; use Friendica\Util\Strings; +use Friendica\Worker\UpdateContact; use Psr\Log\LoggerInterface; class Redir extends \Friendica\BaseModule @@ -44,17 +43,14 @@ class Redir extends \Friendica\BaseModule private $database; /** @var App */ private $app; - /** @var ICanSendHttpRequests */ - private $httpClient; - public function __construct(ICanSendHttpRequests $httpClient, App $app, Database $database, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + public function __construct(App $app, Database $database, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) { parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); $this->session = $session; $this->database = $database; $this->app = $app; - $this->httpClient = $httpClient; } protected function rawContent(array $request = []) @@ -89,17 +85,31 @@ class Redir extends \Friendica\BaseModule $this->logger->info('Got my url', ['visitor' => $visitor]); } - $contact = $this->database->selectFirst('contact', ['url'], ['id' => $cid]); + $contact = Contact::selectFirst(['id', 'url', 'gsid', 'alias', 'network'], ['id' => $cid]); if (!$contact) { $this->logger->info('Contact not found', ['id' => $cid]); throw new HTTPException\NotFoundException($this->t('Contact not found.')); - } else { - $contact_url = $contact['url']; - $this->checkUrl($contact_url, $url); - $target_url = $url ?: $contact_url; + } elseif (empty($contact['gsid'])) { + $this->logger->info('Contact has got no server', ['id' => $cid]); + return; } - $basepath = Contact::getBasepath($contact_url); + $contact_url = Contact::getProfileLink($contact); + $this->checkUrl($contact_url, $url); + $target_url = $url ?: $contact_url; + + $gserver = $this->database->selectFirst('gserver', ['url', 'network', 'openwebauth'], ['id' => $contact['gsid']]); + $basepath = $gserver['url']; + + // This part can be removed, when all server entries had been updated. So removing it in 2025 should be safe. + if (empty($gserver['openwebauth']) && ($gserver['network'] == Protocol::DFRN)) { + $this->logger->notice('Magic path not provided. Contact update initiated.', ['gsid' => $contact['gsid']]); + // Update contact to assign the path to the server + UpdateContact::add(Worker::PRIORITY_MEDIUM, $contact['id']); + } elseif (empty($gserver['openwebauth'])) { + $this->logger->debug('Server does not support open web auth.', ['server' => $gserver]); + return; + } // We don't use magic auth when there is no visitor, we are on the same system, or we visit our own stuff if (empty($visitor) || Strings::compareLink($basepath, $this->baseUrl) || Strings::compareLink($contact_url, $visitor)) { @@ -107,17 +117,11 @@ class Redir extends \Friendica\BaseModule $this->app->redirect($target_url); } - // Test for magic auth on the target system - $response = $this->httpClient->head($basepath . '/magic', [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::HTML, HttpClientOptions::REQUEST => HttpClientRequest::MAGICAUTH]); - if ($response->isSuccess()) { - $separator = strpos($target_url, '?') ? '&' : '?'; - $target_url .= $separator . 'zrl=' . urlencode($visitor) . '&addr=' . urlencode($contact_url); + $separator = strpos($target_url, '?') ? '&' : '?'; + $target_url .= $separator . 'zrl=' . urlencode($visitor) . '&addr=' . urlencode($contact_url); - $this->logger->info('Redirecting with magic', ['target' => $target_url, 'visitor' => $visitor, 'contact' => $contact_url]); - $this->app->redirect($target_url); - } else { - $this->logger->info('No magic for contact', ['contact' => $contact_url]); - } + $this->logger->info('Redirecting with magic', ['target' => $target_url, 'visitor' => $visitor, 'contact' => $contact_url]); + $this->app->redirect($target_url); } /** @@ -135,33 +139,32 @@ class Redir extends \Friendica\BaseModule throw new HTTPException\BadRequestException($this->t('Bad Request.')); } - $fields = ['id', 'uid', 'nurl', 'url', 'addr', 'name']; - $contact = $this->database->selectFirst('contact', $fields, ['id' => $cid, 'uid' => [0, $this->session->getLocalUserId()]]); + $fields = ['id', 'uid', 'nurl', 'url', 'addr', 'name', 'alias', 'network']; + $contact = Contact::selectFirst($fields, ['id' => $cid, 'uid' => [0, $this->session->getLocalUserId()]]); if (!$contact) { throw new HTTPException\NotFoundException($this->t('Contact not found.')); } - $contact_url = $contact['url']; + $contact_url = Contact::getProfileLink($contact); + $this->checkUrl($contact_url, $url); + $target_url = $url ?: $contact_url; if (!empty($this->app->getContactId()) && $this->app->getContactId() == $cid) { // Local user is already authenticated. - $this->checkUrl($contact_url, $url); - $this->app->redirect($url ?: $contact_url); + $this->app->redirect($target_url); } if ($contact['uid'] == 0 && $this->session->getLocalUserId()) { // Let's have a look if there is an established connection // between the public contact we have found and the local user. - $contact = $this->database->selectFirst('contact', $fields, ['nurl' => $contact['nurl'], 'uid' => $this->session->getLocalUserId()]); + $contact = Contact::selectFirst($fields, ['nurl' => $contact['nurl'], 'uid' => $this->session->getLocalUserId()]); if ($contact) { $cid = $contact['id']; } if (!empty($this->app->getContactId()) && $this->app->getContactId() == $cid) { // Local user is already authenticated. - $this->checkUrl($contact_url, $url); - $target_url = $url ?: $contact_url; - $this->logger->info($contact['name'] . " is already authenticated. Redirecting to " . $target_url); + $this->logger->info('Contact already authenticated. Redirecting to target url', ['url' => $contact['url'], 'name' => $contact['name'], 'target_url' => $target_url]); $this->app->redirect($target_url); } } @@ -176,29 +179,23 @@ class Redir extends \Friendica\BaseModule // contact. if (($host == $remotehost) && ($this->session->getRemoteContactID($this->session->get('visitor_visiting')) == $this->session->get('visitor_id'))) { // Remote user is already authenticated. - $this->checkUrl($contact_url, $url); - $target_url = $url ?: $contact_url; - $this->logger->info($contact['name'] . " is already authenticated. Redirecting to " . $target_url); + $this->logger->info('Contact already authenticated. Redirecting to target url', ['url' => $contact['url'], 'name' => $contact['name'], 'target_url' => $target_url]); $this->app->redirect($target_url); } } - if (empty($url)) { - throw new HTTPException\BadRequestException($this->t('Bad Request.')); - } - // If we don't have a connected contact, redirect with // the 'zrl' parameter. $my_profile = $this->session->getMyUrl(); - if (!empty($my_profile) && !Strings::compareLink($my_profile, $url)) { - $separator = strpos($url, '?') ? '&' : '?'; + if (!empty($my_profile) && !Strings::compareLink($my_profile, $target_url)) { + $separator = strpos($target_url, '?') ? '&' : '?'; - $url .= $separator . 'zrl=' . urlencode($my_profile); + $target_url .= $separator . 'zrl=' . urlencode($my_profile); } - $this->logger->info('redirecting to ' . $url); - $this->app->redirect($url); + $this->logger->info('redirecting to ' . $target_url); + $this->app->redirect($target_url); } diff --git a/src/Module/Magic.php b/src/Module/Magic.php index ba2896d788..99db078f96 100644 --- a/src/Module/Magic.php +++ b/src/Module/Magic.php @@ -36,6 +36,7 @@ use Friendica\Model\User; use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests; use Friendica\Network\HTTPClient\Client\HttpClientOptions; use Friendica\Util\HTTPSignature; +use Friendica\Util\Network; use Friendica\Util\Profiler; use Friendica\Util\Strings; use Friendica\Worker\UpdateContact; @@ -76,10 +77,12 @@ class Magic extends BaseModule $this->logger->debug('Invoked', ['request' => $request]); - $addr = $request['addr'] ?? ''; - $dest = $request['dest'] ?? ''; - $bdest = $request['bdest'] ?? ''; - $owa = intval($request['owa'] ?? 0); + $addr = $request['addr'] ?? ''; + $bdest = $request['bdest'] ?? ''; + $dest = $request['dest'] ?? ''; + $rev = intval($request['rev'] ?? 0); + $owa = intval($request['owa'] ?? 0); + $delegate = $request['delegate'] ?? ''; // bdest is preferred as it is hex-encoded and can survive url rewrite and argument parsing if (!empty($bdest)) { @@ -113,6 +116,9 @@ class Magic extends BaseModule $this->app->redirect($dest); } + $dest = Network::removeUrlParameter($dest, 'zid'); + $dest = Network::removeUrlParameter($dest, 'f'); + // OpenWebAuth $owner = User::getOwnerDataById($this->userSession->getLocalUserId()); @@ -150,7 +156,9 @@ class Magic extends BaseModule $header = [ 'Accept' => 'application/x-zot+json', - 'X-Open-Web-Auth' => Strings::getRandomHex() + 'Content-Type' => 'application/x-zot+json', + 'X-Open-Web-Auth' => Strings::getRandomHex(), + 'Host' => strtolower(parse_url($gserver['url'], PHP_URL_HOST)), ]; // Create a header that is signed with the local users private key. diff --git a/src/Module/Owa.php b/src/Module/Owa.php index 7b3af7b52f..cd9bfceb49 100644 --- a/src/Module/Owa.php +++ b/src/Module/Owa.php @@ -23,7 +23,6 @@ namespace Friendica\Module; use Friendica\BaseModule; use Friendica\Core\Logger; -use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\Model\Contact; use Friendica\Model\OpenWebAuthToken; diff --git a/src/Util/Network.php b/src/Util/Network.php index fe376ce5c8..d24017804d 100644 --- a/src/Util/Network.php +++ b/src/Util/Network.php @@ -29,6 +29,7 @@ use Friendica\Network\HTTPClient\Client\HttpClientAccept; use Friendica\Network\HTTPClient\Client\HttpClientOptions; use Friendica\Network\HTTPClient\Client\HttpClientRequest; use Friendica\Network\HTTPException\NotModifiedException; +use GuzzleHttp\Psr7\Exception\MalformedUriException; use GuzzleHttp\Psr7\Uri; use Psr\Http\Message\UriInterface; @@ -670,4 +671,28 @@ class Network return null; } } + + /** + * Remove an Url parameter + * + * @param string $url + * @param string $parameter + * @return string + * @throws MalformedUriException + */ + public static function removeUrlParameter(string $url, string $parameter): string + { + $parts = parse_url($url); + if (empty($parts['query'])) { + return $url; + } + + parse_str($parts['query'], $data); + + unset($data[$parameter]); + + $parts['query'] = http_build_query($data); + + return (string)Uri::fromParts($parts); + } } diff --git a/src/Util/Strings.php b/src/Util/Strings.php index 7645466139..4a68e31248 100644 --- a/src/Util/Strings.php +++ b/src/Util/Strings.php @@ -498,7 +498,8 @@ class Strings $blocks = []; - $return = preg_replace_callback($regex, + $return = preg_replace_callback( + $regex, function ($matches) use ($executionId, &$blocks) { $return = '«block-' . $executionId . '-' . count($blocks) . '»'; @@ -516,7 +517,8 @@ class Strings $text = $callback($return ?? $text) ?? ''; // Restore code blocks - $text = preg_replace_callback('/«block-' . $executionId . '-([0-9]+)»/iU', + $text = preg_replace_callback( + '/«block-' . $executionId . '-([0-9]+)»/iU', function ($matches) use ($blocks) { $return = $matches[0]; if (isset($blocks[intval($matches[1])])) { @@ -545,10 +547,10 @@ class Strings return $shorthand; } - $last = strtolower($shorthand[strlen($shorthand)-1]); + $last = strtolower($shorthand[strlen($shorthand) - 1]); $shorthand = substr($shorthand, 0, -1); - switch($last) { + switch ($last) { case 'g': $shorthand *= 1024; case 'm':