From 470aa951e8bfad537f5e18878933547768a39f7a Mon Sep 17 00:00:00 2001 From: nobody Date: Tue, 3 Aug 2021 15:28:53 -0700 Subject: [PATCH] refactor Zotlabs\Lib\Activity::fetch() to ignore fragments and remove the corresponding fragment ignoring code from the HTTPSig key fetching functions. This primarily affects funkwhale/django but has shown up occasionally in other HTTP/CGI server platforms which don't pass fragments to the application, resulting in signature failures on those platforms. It isn't our fault because we're just fetching and signing the URL we were given; but it also isn't theirs because they're just verifying the request-uri their webserver provided. It just isn't the same one we fetched. --- Zotlabs/Lib/Activity.php | 50 ++++++++++++++++++++----------------- Zotlabs/Web/HTTPSig.php | 54 ++++++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 47 deletions(-) diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index a83e827cc..79725f2ab 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -80,22 +80,23 @@ class Activity { $channel = get_sys_channel(); } - $idn = parse_url($url); - if ($idn['host'] !== punify($idn['host'])) { - $url = str_replace($idn['host'],punify($idn['host']),$url); + $parsed = parse_url($url); + + // perform IDN substitution + + if ($parsed['host'] !== punify($parsed['host'])) { + $url = str_replace($parsed['host'],punify($parsed['host']),$url); } logger('fetch: ' . $url, LOGGER_DEBUG); - if (strpos($url,'x-zot:') === 0) { + if (isset($parsed['scheme']) && $parsed['scheme'] === 'x-zot') { $x = ZotURL::fetch($url,$channel,$hub); } else { - $m = parse_url($url); - // handle bearcaps - if ($m['scheme'] === 'bear' && $m['query']) { - $params = explode('&',$m['query']); + if (isset($parsed['scheme']) && isset($parsed['query']) && $parsed['scheme'] === 'bear' && $parsed['query'] !== EMPTY_STR) { + $params = explode('&', $parsed['query']); if ($params) { foreach ($params as $p) { if (substr($p,0,2) === 'u=') { @@ -105,18 +106,26 @@ class Activity { $token = substr($p,2); } } - // re-parse the URL because it changed and we need the host in the next section - $m = parse_url($url); + // the entire URL just changed so parse it again + $parsed = parse_url($url); } - } + + // Ignore fragments; as we are not in a browser and some platforms (e.g. Django or at least funkwhale) don't handle them well + unset($parsed['fragment']); + + // rebuild the url + $url = unparse_url($parsed); + + logger('fetch_actual: ' . $url, LOGGER_DEBUG); $headers = [ 'Accept' => 'application/activity+json, application/x-zot-activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"', - 'Host' => $m['host'], + 'Host' => $parsed['host'], 'Date' => datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'), '(request-target)' => 'get ' . get_request_string($url) ]; + if (isset($token)) { $headers['Authorization'] = 'Bearer ' . $token; } @@ -129,15 +138,12 @@ class Activity { $y = json_decode($x['body'],true); logger('returned: ' . json_encode($y,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); - $m = parse_url($url); - if ($m) { - $site_url = unparse_url( ['scheme' => $m['scheme'], 'host' => $m['host'], 'port' => ((array_key_exists('port',$m) && intval($m['port'])) ? $m['port'] : 0) ] ); - q("update site set site_update = '%s' where site_url = '%s' and site_update < %s - INTERVAL %s", - dbesc(datetime_convert()), - dbesc($site_url), - db_utcnow(), db_quoteinterval('1 DAY') - ); - } + $site_url = unparse_url( ['scheme' => $parsed['scheme'], 'host' => $parsed['host'], 'port' => ((array_key_exists('port',$parsed) && intval($parsed['port'])) ? $parsed['port'] : 0) ] ); + q("update site set site_update = '%s' where site_url = '%s' and site_update < %s - INTERVAL %s", + dbesc(datetime_convert()), + dbesc($site_url), + db_utcnow(), db_quoteinterval('1 DAY') + ); // check for a valid signature, but only if this is not an actor object. If it is signed, it must be valid. // Ignore actors because of the potential for infinite recursion if we perform this step while @@ -153,8 +159,6 @@ class Activity { } return json_decode($x['body'], true); - - } else { logger('fetch failed: ' . $url); diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index cf29edc4f..8f9e5bfa8 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -285,24 +285,28 @@ class HTTPSig { static function get_activitystreams_key($id,$force = false) { - // remove fragment + // Check the local cache first, but remove any fragments like #main-key since these won't be present in our cached data - $url = ((strpos($id,'#')) ? substr($id,0,strpos($id,'#')) : $id); + $cache_url = ((strpos($id,'#')) ? substr($id,0,strpos($id,'#')) : $id); + // $force is used to ignore the local cache and only use the remote data; for instance the cached key might be stale + if (! $force) { $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' order by hubloc_id desc", - dbesc(str_replace('acct:','',$url)), - dbesc($url) + dbesc(str_replace('acct:','',$cache_url)), + dbesc($cache_url) ); + + if ($x) { + $best = Libzot::zot_record_preferred($x); + } + + if ($best && $best['xchan_pubkey']) { + return [ 'portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'] , 'hubloc' => $best ]; + } } - if ($x) { - $best = Libzot::zot_record_preferred($x); - } - - if ($best && $best['xchan_pubkey']) { - return [ 'portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'] , 'hubloc' => $best ]; - } + // The record wasn't in cache. Fetch it now. $r = Activity::fetch($id); @@ -314,6 +318,9 @@ class HTTPSig { } } } + + // No key was found + return false; } @@ -325,14 +332,14 @@ class HTTPSig { dbesc(str_replace('acct:','',$id)), dbesc($id) ); - } - if ($x) { - $best = Libzot::zot_record_preferred($x); - } + if ($x) { + $best = Libzot::zot_record_preferred($x); + } - if ($best && $best['xchan_pubkey']) { - return [ 'portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'] , 'hubloc' => $best ]; + if ($best && $best['xchan_pubkey']) { + return [ 'portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'] , 'hubloc' => $best ]; + } } $wf = Webfinger::exec($id); @@ -365,17 +372,16 @@ class HTTPSig { dbesc(str_replace('acct:','',$id)), dbesc($id) ); - } - if ($x) { - $best = Libzot::zot_record_preferred($x); - } + if ($x) { + $best = Libzot::zot_record_preferred($x); + } - if ($best && $best['xchan_pubkey']) { - return [ 'portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'] , 'hubloc' => $best ]; + if ($best && $best['xchan_pubkey']) { + return [ 'portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'] , 'hubloc' => $best ]; + } } - $wf = Webfinger::exec($id); $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ];