From 1c7f4e3c6331f8ad7b1987f33a17074908ad41eb Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Mon, 18 Jun 2018 23:05:44 +0200 Subject: [PATCH 01/49] port hubzillas OpenWebAuth - remote authentification --- boot.php | 2 +- database.sql | 17 +- doc/Addons.md | 8 + index.php | 42 ++-- mod/xrd.php | 6 +- src/Core/System.php | 10 +- src/Database/DBStructure.php | 14 ++ src/Model/Profile.php | 156 +++++++++++++-- src/Model/Verify.php | 73 +++++++ src/Module/Magic.php | 121 ++++++++++++ src/Module/Owa.php | 94 +++++++++ src/Network/Probe.php | 17 +- src/Util/Crypto.php | 218 +++++++++++++++++++++ src/Util/HTTPHeaders.php | 59 ++++++ src/Util/HTTPSig.php | 352 ++++++++++++++++++++++++++++++++++ view/templates/xrd_person.tpl | 3 + 16 files changed, 1151 insertions(+), 41 deletions(-) create mode 100644 src/Model/Verify.php create mode 100644 src/Module/Magic.php create mode 100644 src/Module/Owa.php create mode 100644 src/Util/HTTPHeaders.php create mode 100644 src/Util/HTTPSig.php diff --git a/boot.php b/boot.php index 79ec53abf..46bb2a3f8 100644 --- a/boot.php +++ b/boot.php @@ -41,7 +41,7 @@ define('FRIENDICA_PLATFORM', 'Friendica'); define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily'); define('FRIENDICA_VERSION', '2018.08-dev'); define('DFRN_PROTOCOL_VERSION', '2.23'); -define('DB_UPDATE_VERSION', 1268); +define('DB_UPDATE_VERSION', 1269); define('NEW_UPDATE_ROUTINE_VERSION', 1170); /** diff --git a/database.sql b/database.sql index b186c0c0a..b871ce2de 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2018.08-dev (The Tazmans Flax-lily) --- DB_UPDATE_VERSION 1268 +-- DB_UPDATE_VERSION 1269 -- ------------------------------------------ @@ -375,7 +375,7 @@ CREATE TABLE IF NOT EXISTS `group` ( CREATE TABLE IF NOT EXISTS `group_member` ( `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', `gid` int unsigned NOT NULL DEFAULT 0 COMMENT 'groups.id of the associated group', - `contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id of the member assigned to the associated group', + `contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id of the member assigned to the associated group', PRIMARY KEY(`id`), INDEX `contactid` (`contact-id`), UNIQUE INDEX `gid_contactid` (`gid`,`contact-id`) @@ -1084,6 +1084,19 @@ CREATE TABLE IF NOT EXISTS `user-item` ( PRIMARY KEY(`uid`,`iid`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data'; +-- +-- TABLE verify +-- +CREATE TABLE IF NOT EXISTS `verify` ( + `id` int(10) NOT NULL auto_increment COMMENT 'sequential ID', + `uid` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'User id', + `type` varchar(32) DEFAULT '' COMMENT 'Verify type', + `token` varchar(255) DEFAULT '' COMMENT 'A generated token', + `meta` varchar(255) DEFAULT '' COMMENT '', + `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime of creation', + PRIMARY KEY(`id`) +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Store token to verify contacts'; + -- -- TABLE worker-ipc -- diff --git a/doc/Addons.md b/doc/Addons.md index 22b34fa62..090d5a9d7 100644 --- a/doc/Addons.md +++ b/doc/Addons.md @@ -357,6 +357,13 @@ Hook data: 'item' => item array (input) 'html' => converted item body (input/output) +### 'magic_auth_success' +Called when a magic-auth was successful. +Hook data: + 'visitor' => array with the contact record of the visitor + 'url' => the query string + 'session' => $_SESSION array + Current JavaScript hooks ------------- @@ -557,6 +564,7 @@ Here is a complete list of all hook callbacks with file locations (as of 01-Apr- Addon::callHooks('profile_sidebar', $arr); Addon::callHooks('profile_tabs', $arr); Addon::callHooks('zrl_init', $arr); + Addon::callHooks('magic_auth_success', $arr); ### src/Model/Event.php diff --git a/index.php b/index.php index aeda99982..c0290dff4 100644 --- a/index.php +++ b/index.php @@ -121,25 +121,35 @@ if ((x($_SESSION, 'language')) && ($_SESSION['language'] !== $lang)) { L10n::loadTranslationTable($lang); } -if ((x($_GET, 'zrl')) && $a->mode == App::MODE_NORMAL) { - // Only continue when the given profile link seems valid - // Valid profile links contain a path with "/profile/" and no query parameters - if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == "") - && strstr(parse_url($_GET['zrl'], PHP_URL_PATH), "/profile/") - ) { - $_SESSION['my_url'] = $_GET['zrl']; - $a->query_string = preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $a->query_string); - Profile::zrlInit($a); - } else { - // Someone came with an invalid parameter, maybe as a DDoS attempt - // We simply stop processing here - logger("Invalid ZRL parameter ".$_GET['zrl'], LOGGER_DEBUG); - header('HTTP/1.1 403 Forbidden'); - echo "

403 Forbidden

"; - killme(); +if ((x($_GET,'zrl')) && $a->mode == App::MODE_NORMAL) { + $a->query_string = Profile::stripZrls($a->query_string); + if (!local_user()) { + // Only continue when the given profile link seems valid + // Valid profile links contain a path with "/profile/" and no query parameters + if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == "") && + strstr(parse_url($_GET['zrl'], PHP_URL_PATH), "/profile/")) { + if ($_SESSION["visitor_home"] != $_GET["zrl"]) { + $_SESSION['my_url'] = $_GET['zrl']; + $_SESSION['authenticated'] = 0; + } + Profile::zrlInit($a); + } else { + // Someone came with an invalid parameter, maybe as a DDoS attempt + // We simply stop processing here + logger("Invalid ZRL parameter " . $_GET['zrl'], LOGGER_DEBUG); + header('HTTP/1.1 403 Forbidden'); + echo "

403 Forbidden

"; + killme(); + } } } +if ((x($_GET,'owt')) && $a->mode == App::MODE_NORMAL) { + $token = $_GET['owt']; + $a->query_string = Profile::stripQueryParam($a->query_string, 'owt'); + Profile::owtInit($token); +} + /** * For Mozilla auth manager - still needs sorting, and this might conflict with LRDD header. * Apache/PHP lumps the Link: headers into one - and other services might not be able to parse it diff --git a/mod/xrd.php b/mod/xrd.php index bbfd7ce64..2d19bb3b7 100644 --- a/mod/xrd.php +++ b/mod/xrd.php @@ -78,7 +78,8 @@ function xrd_json($a, $uri, $alias, $profile_url, $r) ['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], ['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'], ['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'], - ['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key] + ['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key], + array('rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa') ]]; echo json_encode($json); killme(); @@ -102,10 +103,11 @@ function xrd_xml($a, $uri, $alias, $profile_url, $r) '$atom' => System::baseUrl() . '/dfrn_poll/' . $r['nickname'], '$poco_url' => System::baseUrl() . '/poco/' . $r['nickname'], '$photo' => System::baseUrl() . '/photo/profile/' . $r['uid'] . '.jpg', - '$baseurl' => System::baseUrl(), + '$baseurl' => System::baseUrl(), '$salmon' => System::baseUrl() . '/salmon/' . $r['nickname'], '$salmen' => System::baseUrl() . '/salmon/' . $r['nickname'] . '/mention', '$subscribe' => System::baseUrl() . '/follow?url={uri}', + '$openwebauth' => System::baseUrl() .'/owa', '$modexp' => 'data:application/magic-public-key,' . $salmon_key] ); diff --git a/src/Core/System.php b/src/Core/System.php index 1db417eb8..ded781da8 100644 --- a/src/Core/System.php +++ b/src/Core/System.php @@ -163,17 +163,17 @@ EOT; } /** - * @brief Encodes content to json + * @brief Encodes content to json. * * This function encodes an array to json format * and adds an application/json HTTP header to the output. * After finishing the process is getting killed. * - * @param array $x The input content + * @param array $x The input content. + * @param string $content_type Type of the input (Default: 'application/json'). */ - public static function jsonExit($x) - { - header("content-type: application/json"); + public static function jsonExit($x, $content_type = 'application/json') { + header("Content-type: $content_type"); echo json_encode($x); killme(); } diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index d4419553c..33babded9 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -1818,6 +1818,20 @@ class DBStructure "PRIMARY" => ["uid", "iid"], ] ]; + $database["verify"] = [ + "comment" => "Store token to verify contacts", + "fields" => [ + "id" => ["type" => "int(10)", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], + "uid" => ["type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"], + "type" => ["type" => "varchar(32)", "not_null", "default" => "", "comment" => "Verify type"], + "token" => ["type" => "varchar(255)", "not_null" => "1", "default" => "", "comment" => "A generated token"], + "meta" => ["type" => "varchar(255)", "not_null" => "1", "default" => "", "comment" => ""], + "created" => ["type" => "datetime", "not null" => "1", "default" => NULL_DATE, "comment" => "datetime of creation"], + ], + "indexes" => [ + "PRIMARY" => ["id"], + ] + ]; $database["worker-ipc"] = [ "comment" => "Inter process communication between the frontend and the worker", "fields" => [ diff --git a/src/Model/Profile.php b/src/Model/Profile.php index 39a89694a..cb1a15afd 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -17,7 +17,9 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Model\Contact; +use Friendica\Model\Verify; use Friendica\Protocol\Diaspora; +use Friendica\Network\Probe; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; use Friendica\Util\Temporal; @@ -978,27 +980,137 @@ class Profile return null; } + /** + * Process the 'zrl' parameter and initiate the remote authentication. + * + * This method checks if the visitor has a public contact entry and + * redirects the visitor to his/her instance to start the magic auth (Authentication) + * process. + * + * @param App $a Application instance. + */ public static function zrlInit(App $a) { $my_url = self::getMyURL(); $my_url = Network::isUrlValid($my_url); + if ($my_url) { - // Is it a DDoS attempt? - // The check fetches the cached value from gprobe to reduce the load for this system - $urlparts = parse_url($my_url); + if (!local_user()) { + // Is it a DDoS attempt? + // The check fetches the cached value from gprobe to reduce the load for this system + $urlparts = parse_url($my_url); - $result = Cache::get('gprobe:' . $urlparts['host']); - if ((!is_null($result)) && (in_array($result['network'], [NETWORK_FEED, NETWORK_PHANTOM]))) { - logger('DDoS attempt detected for ' . $urlparts['host'] . ' by ' . $_SERVER['REMOTE_ADDR'] . '. server data: ' . print_r($_SERVER, true), LOGGER_DEBUG); - return; + $result = Cache::get('gprobe:' . $urlparts['host']); + if ((!is_null($result)) && (in_array($result['network'], [NETWORK_FEED, NETWORK_PHANTOM]))) { + logger('DDoS attempt detected for ' . $urlparts['host'] . ' by ' . $_SERVER['REMOTE_ADDR'] . '. server data: ' . print_r($_SERVER, true), LOGGER_DEBUG); + return; + } + + Worker::add(PRIORITY_LOW, 'GProbe', $my_url); + $arr = ['zrl' => $my_url, 'url' => $a->cmd]; + Addon::callHooks('zrl_init', $arr); + + // Try to find the public contact entry of the visitor. + $fields = ["id", "url"]; + $condition = ['uid' => 0, 'nurl' => normalise_link($my_url)]; + + $contact = dba::selectFirst('contact',$fields, $condition); + + // Not found? Try to probe the visitor. + if (!DBM::is_result($contact)) { + Probe::uri($my_url, '', -1, true, true); + $contact = dba::selectFirst('contact',$fields, $condition); + } + + if (!DBM::is_result($contact)) { + logger('No contact record found for ' . $my_url, LOGGER_DEBUG); + return; + } + + if (DBM::is_result($contact) && remote_user() && remote_user() === $contact['id']) { + // The visitor is already authenticated. + return; + } + + logger('Not authenticated. Invoking reverse magic-auth for ' . $my_url, LOGGER_DEBUG); + + // Try to avoid recursion - but send them home to do a proper magic auth. + $query = str_replace(array('?zrl=', '&zid='), array('?rzrl=', '&rzrl='), $a->query_string); + // The other instance needs to know where to redirect. + $dest = urlencode(System::baseUrl() . "/" . $query); + + // We need to extract the basebath from the profile url + // to redirect the visitors '/magic' module. + // Note: We should have the basepath of a contact also in the contact table. + $urlarr = explode("/profile/", $contact['url']); + $basepath = $urlarr[0]; + + if ($basepath != System::baseUrl() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) { + goaway($basepath . '/magic' . '?f=&owa=1&dest=' . $dest); + } } - - Worker::add(PRIORITY_LOW, 'GProbe', $my_url); - $arr = ['zrl' => $my_url, 'url' => $a->cmd]; - Addon::callHooks('zrl_init', $arr); } } + /** + * OpenWebAuth authentication. + * + * @param string $token + */ + public static function owtInit($token) + { + $a = get_app(); + + // Clean old verify entries. + Verify::purge('owt', '3 MINUTE'); + + // Check if the token we got is the same one + // we have stored in the database. + $visitor_handle = Verify::getMeta('owt', 0, $token); + + if($visitor_handle === false) { + return; + } + + // Try to find the public contact entry of the visitor. + $condition = ["uid" => 0, "addr" => $visitor_handle]; + $visitor = dba::selectFirst("contact", [], $condition); + + if (!DBM::is_result($visitor)) { + Probe::uri($visitor_handle, '', -1, true, true); + $visitor = dba::selectFirst("contact", [], $condition); + } + if(!DBM::is_result($visitor)) { + logger('owt: unable to finger ' . $visitor_handle, LOGGER_DEBUG); + return; + } + + // Authenticate the visitor. + $_SESSION['authenticated'] = 1; + $_SESSION['visitor_id'] = $visitor['id']; + $_SESSION['visitor_handle'] = $visitor['addr']; + $_SESSION['visitor_home'] = $visitor['url']; + + $arr = [ + 'visitor' => $visitor, + 'url' => $a->query_string, + 'session' => $_SESSION + ]; + /** + * @hooks magic_auth_success + * Called when a magic-auth was successful. + * * \e array \b visitor + * * \e string \b url + * * \e array \b session + */ + Addon::callHooks('magic_auth_success', $arr); + $a->contact = $visitor; + + info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->get_hostname(), $visitor['name'])); + + logger('OpenWebAuth: auth success from ' . $visitor['addr'], LOGGER_DEBUG); + } + public static function zrl($s, $force = false) { if (!strlen($s)) { @@ -1042,4 +1154,26 @@ class Profile return $uid; } + + /** + * Stip zrl parameter from a string. + * + * @param string $s The input string. + * @return string The zrl. + */ + public static function stripZrls($s) + { + return preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $s); + } + + /** + * Stip query parameter from a string. + * + * @param string $s The input string. + * @return string The query parameter. + */ + public static function stripQueryParam($s, $param) + { + return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism', '$2', $s); + } } diff --git a/src/Model/Verify.php b/src/Model/Verify.php new file mode 100644 index 000000000..92dbafd3a --- /dev/null +++ b/src/Model/Verify.php @@ -0,0 +1,73 @@ + $type, + "uid" => $uid, + "token" => $token, + "meta" => $meta, + "created" => DateTimeFormat::utcNow() + ]; + return dba::insert("verify", $fields); + } + + /** + * Get the "meta" field of an entry in the verify table. + * + * @param string $type Verify type. + * @param int $uid The user ID. + * @param string $token + * + * @return string|boolean The meta enry or false if not found. + */ + public static function getMeta($type, $uid, $token) + { + $condition = ["type" => $type, "uid" => $uid, "token" => $token]; + + $entry = dba::selectFirst("verify", ["id", "meta"], $condition); + if (DBM::is_result($entry)) { + dba::delete("verify", ["id" => $entry["id"]]); + + return $entry["meta"]; + } + return false; + } + + /** + * Purge entries of a verify-type older than interval. + * + * @param string $type Verify type. + * @param string $interval SQL compatible time interval + */ + public static function purge($type, $interval) + { + $condition = ["`type` = ? AND `created` < ?", $type, DateTimeFormat::utcNow() . " - INTERVAL " . $interval]; + dba::delete("verify", $condition); + } + +} diff --git a/src/Module/Magic.php b/src/Module/Magic.php new file mode 100644 index 000000000..fef970da1 --- /dev/null +++ b/src/Module/Magic.php @@ -0,0 +1,121 @@ + false, 'url' => '', 'message' => '']; + logger('magic mdule: invoked', LOGGER_DEBUG); + + logger('args: ' . print_r($_REQUEST, true), LOGGER_DATA); + + $addr = ((x($_REQUEST, 'addr')) ? $_REQUEST['addr'] : ''); + $dest = ((x($_REQUEST, 'dest')) ? $_REQUEST['dest'] : ''); + $test = ((x($_REQUEST, 'test')) ? intval($_REQUEST['test']) : 0); + $owa = ((x($_REQUEST, 'owa')) ? intval($_REQUEST['owa']) : 0); + + // NOTE: I guess $dest isn't just the profile url (could be also + // other profile pages e.g. photo). We need to find a solution + // to be able to redirct to other pages than the contact profile. + $fields = ["id", "nurl", "url"]; + $condition = ["nurl" => normalise_link($dest)]; + + $contact = dba::selectFirst("contact", $fields, $condition); + + if (!DBM::is_result($contact)) { + // If we don't have a contact record, try to probe it. + /// @todo: Also check against the $addr. + Probe::uri($dest, '', -1, true, true); + $contact = dba::selectFirst("contact", $fields, $condition); + } + + if (!DBM::is_result($contact)) { + logger("No contact record found: " . print_r($_REQUEST, true), LOGGER_DEBUG); + goaway($dest); + } + + // Redirect if the contact is already authenticated on this site. + if (array_key_exists("id", $a->contact) && strpos($contact['nurl'], normalise_link(self::getApp()->get_baseurl())) !== false) { + if($test) { + $ret['success'] = true; + $ret['message'] .= 'Local site - you are already authenticated.' . EOL; + return $ret; + } + + logger("Contact is already authenticated", LOGGER_DEBUG); + goaway($dest); + } + + if (local_user()) { + $user = $a->user; + + // OpenWebAuth + if ($owa) { + // Extract the basepath + // NOTE: we need another solution because this does only work + // for friendica contacts :-/ . We should have the basepath + // of a contact also in the contact table. + $exp = explode("/profile/", $contact['url']); + $basepath = $exp[0]; + + $headers = []; + $headers['Accept'] = 'application/x-dfrn+json'; + $headers['X-Open-Web-Auth'] = random_string(); + + // Create a header that is signed with the local users private key. + $headers = HTTPSig::createSig( + '', + $headers, + $user['prvkey'], + 'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->path ? '/' . $a->path : ''), + false, + true, + 'sha512' + ); + + // Try to get an authentication token from the other instance. + $x = Network::curl($basepath . '/owa', false, $redirects, ['headers' => $headers]); + + if ($x['success']) { + $j = json_decode($x['body'], true); + + if ($j['success']) { + $token = ''; + if ($j['encrypted_token']) { + // The token is encrypted. If the local user is really the one the other instance + // thinks he/she is, the token can be decrypted with the local users public key. + openssl_private_decrypt(base64url_decode($j['encrypted_token']), $token, $user['prvkey']); + } else { + $token = $j['token']; + } + $x = strpbrk($dest, '?&'); + $args = (($x) ? '&owt=' . $token : '?f=&owt=' . $token); + + goaway($dest . $args); + } + } + goaway($dest); + } + } + + if($test) { + $ret['message'] = 'Not authenticated or invalid arguments' . EOL; + return $ret; + } + + goaway($dest); + } +} diff --git a/src/Module/Owa.php b/src/Module/Owa.php new file mode 100644 index 000000000..27c863e1b --- /dev/null +++ b/src/Module/Owa.php @@ -0,0 +1,94 @@ + false ]; + + foreach (['REDIRECT_REMOTE_USER', 'HTTP_AUTHORIZATION'] as $head) { + if (array_key_exists($head, $_SERVER) && substr(trim($_SERVER[$head]), 0, 9) === 'Signature') { + if ($head !== 'HTTP_AUTHORIZATION') { + $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER[$head]; + continue; + } + + $sigblock = HTTPSig::parseSigheader($_SERVER[$head]); + if ($sigblock) { + $keyId = $sigblock['keyId']; + + if ($keyId) { + // Try to find the public contact entry of the handle. + $handle = str_replace("acct:", "", $keyId); + $fields = ["id", "url", "addr", "pubkey"]; + $condition = ["addr" => $handle, "uid" => 0]; + + $contact = dba::selectFirst("contact", $fields, $condition); + + // Not found? Try to probe with the handle. + if(!DBM::is_result($contact)) { + Probe::uri($handle, '', -1, true, true); + $contact = dba::selectFirst("contact", $fields, $condition); + } + + if (DBM::is_result($contact)) { + // Try to verify the signed header with the public key of the contact record + // we have found. + $verified = HTTPSig::verify('', $contact['pubkey']); + + if ($verified && $verified['header_signed'] && $verified['header_valid']) { + logger('OWA header: ' . print_r($verified, true), LOGGER_DATA); + logger('OWA success: ' . $contact['addr'], LOGGER_DATA); + + $ret['success'] = true; + $token = random_string(32); + + // Store the generated token in the databe. + Verify::create('owt', 0, $token, $contact['addr']); + + $result = ''; + + // Encrypt the token with the public contacts publik key. + // Only the specific public contact will be able to encrypt it. + // At a later time, we will compare weather the token we're getting + // is really the same token we have stored in the database. + openssl_public_encrypt($token, $result, $contact['pubkey']); + $ret['encrypted_token'] = base64url_encode($result); + } else { + logger('OWA fail: ' . $contact['id'] . ' ' . $contact['addr'] . ' ' . $contact['url'], LOGGER_DEBUG); + } + } else { + logger('Contact not found: ' . $handle, LOGGER_DEBUG); + } + } + } + } + } + System::jsonExit($ret, 'application/x-dfrn+json'); + } +} diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 5f665814b..7f41b2304 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -311,10 +311,11 @@ class Probe * @param string $network Test for this specific network * @param integer $uid User ID for the probe (only used for mails) * @param boolean $cache Use cached values? + * @param boolean $insert Insert the contact into the contact table. * * @return array uri data */ - public static function uri($uri, $network = "", $uid = -1, $cache = true) + public static function uri($uri, $network = "", $uid = -1, $cache = true, $insert = false) { if ($cache) { $result = Cache::get("Probe::uri:".$network.":".$uri); @@ -463,11 +464,19 @@ class Probe $condition = ['nurl' => normalise_link($data["url"]), 'self' => false, 'uid' => 0]; // "$old_fields" will return a "false" when the contact doesn't exist. - // This won't trigger an insert. This is intended, since we only need - // public contacts for everyone we store items from. - // We don't need to store every contact on the planet. + // This won't trigger an insert except $insert is set to true. + // This is intended, since we only need public contacts + // for everyone we store items from. We don't need to store + // every contact on the planet. $old_fields = dba::selectFirst('contact', $fieldnames, $condition); + // When the contact doesn't exist, the value "true" will trigger an insert + if (!$old_fields && $insert) { + $old_fields = true; + $fields['blocked'] = false; + $fields['pending'] = false; + } + $fields['name-date'] = DateTimeFormat::utcNow(); $fields['uri-date'] = DateTimeFormat::utcNow(); $fields['success_update'] = DateTimeFormat::utcNow(); diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index b2fad9970..2dc978362 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -4,6 +4,7 @@ */ namespace Friendica\Util; +use Friendica\Core\Addon; use Friendica\Core\Config; use ASN_BASE; use ASNValue; @@ -246,4 +247,221 @@ class Crypto return $response; } + + /** + * Encrypt a string with 'aes-256-cbc' cipher method. + * + * @param string $data + * @param string $key The key used for encryption. + * @param string $iv A non-NULL Initialization Vector. + * + * @return string|boolean Encrypted string or false on failure. + */ + private static function encryptAES256CBC($data, $key, $iv) + { + return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); + } + + /** + * Decrypt a string with 'aes-256-cbc' cipher method. + * + * @param string $data + * @param string $key The key used for decryption. + * @param string $iv A non-NULL Initialization Vector. + * + * @return string|boolean Decrypted string or false on failure. + */ + private static function decryptAES256CBC($data, $key, $iv) + { + return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); + } + + /** + * Encrypt a string with 'aes-256-ctr' cipher method. + * + * @param string $data + * @param string $key The key used for encryption. + * @param string $iv A non-NULL Initialization Vector. + * + * @return string|boolean Encrypted string or false on failure. + */ + private static function encryptAES256CTR($data, $key, $iv) + { + $key = substr($key, 0, 32); + $iv = substr($iv, 0, 16); + return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); + } + + /** + * Decrypt a string with 'aes-256-cbc' cipher method. + * + * @param string $data + * @param string $key The key used for decryption. + * @param string $iv A non-NULL Initialization Vector. + * + * @return string|boolean Decrypted string or false on failure. + */ + private static function decryptAES256CTR($data, $key, $iv) + { + $key = substr($key, 0, 32); + $iv = substr($iv, 0, 16); + return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); + } + + /** + * + * @param string $data + * @param string $pubkey The public key. + * @param string $alg The algorithm used for encryption. + * + * @return array + */ + public static function encapsulate($data, $pubkey, $alg = 'aes256cbc') + { + if ($alg === 'aes256cbc') { + return self::encapsulateAes($data, $pubkey); + } + return self::encapsulateOther($data, $pubkey, $alg); + } + + /** + * + * @param type $data + * @param type $pubkey The public key. + * @param type $alg The algorithm used for encryption. + * + * @return array + */ + private static function encapsulateOther($data, $pubkey, $alg) + { + if (!$pubkey) { + logger('no key. data: '.$data); + } + $fn = 'encrypt' . strtoupper($alg); + if (method_exists(__CLASS__, $fn)) { + // A bit hesitant to use openssl_random_pseudo_bytes() as we know + // it has been historically targeted by US agencies for 'weakening'. + // It is still arguably better than trying to come up with an + // alternative cryptographically secure random generator. + // There is little point in using the optional second arg to flag the + // assurance of security since it is meaningless if the source algorithms + // have been compromised. Also none of this matters if RSA has been + // compromised by state actors and evidence is mounting that this has + // already happened. + $result = ['encrypted' => true]; + $key = openssl_random_pseudo_bytes(256); + $iv = openssl_random_pseudo_bytes(256); + $result['data'] = base64url_encode(self::$fn($data, $key, $iv), true); + + // log the offending call so we can track it down + if (!openssl_public_encrypt($key, $k, $pubkey)) { + $x = debug_backtrace(); + logger('RSA failed. ' . print_r($x[0], true)); + } + + $result['alg'] = $alg; + $result['key'] = base64url_encode($k, true); + openssl_public_encrypt($iv, $i, $pubkey); + $result['iv'] = base64url_encode($i, true); + + return $result; + } else { + $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data]; + Addon::callHooks('other_encapsulate', $x); + + return $x['result']; + } + } + + /** + * + * @param string $data + * @param string $pubkey + * + * @return array + */ + private static function encapsulateAes($data, $pubkey) + { + if (!$pubkey) { + logger('aes_encapsulate: no key. data: ' . $data); + } + + $key = openssl_random_pseudo_bytes(32); + $iv = openssl_random_pseudo_bytes(16); + $result = ['encrypted' => true]; + $result['data'] = base64url_encode(AES256CBC_encrypt($data, $key, $iv), true); + + // log the offending call so we can track it down + if (!openssl_public_encrypt($key, $k, $pubkey)) { + $x = debug_backtrace(); + logger('aes_encapsulate: RSA failed. ' . print_r($x[0], true)); + } + + $result['alg'] = 'aes256cbc'; + $result['key'] = base64url_encode($k, true); + openssl_public_encrypt($iv, $i, $pubkey); + $result['iv'] = base64url_encode($i, true); + + return $result; + } + + /** + * + * @param string $data + * @param string $prvkey The private key used for decryption. + * + * @return string|boolean The decrypted string or false on failure. + */ + public static function unencapsulate($data, $prvkey) + { + if (!$data) { + return; + } + + $alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc'); + if ($alg === 'aes256cbc') { + return self::encapsulateAes($data, $prvkey); + } + return self::encapsulateOther($data, $prvkey, $alg); + } + + /** + * + * @param string $data + * @param string $prvkey The private key used for decryption. + * @param string $alg + * + * @return string|boolean The decrypted string or false on failure. + */ + private static function unencapsulateOther($data, $prvkey, $alg) + { + $fn = 'decrypt' . strtoupper($alg); + + if (method_exists(__CLASS__, $fn)) { + openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey); + openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey); + + return self::$fn(base64url_decode($data['data']), $k, $i); + } else { + $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data]; + Addon::callHooks('other_unencapsulate', $x); + + return $x['result']; + } + } + + /** + * + * @param array $data + * @param string $prvkey The private key used for decryption. + * + * @return string|boolean The decrypted string or false on failure. + */ + private static function unencapsulateAes($data, $prvkey) + { + openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey); + openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey); + + return self::decryptAES256CBC(base64url_decode($data['data']), $k, $i); + } } diff --git a/src/Util/HTTPHeaders.php b/src/Util/HTTPHeaders.php new file mode 100644 index 000000000..a6c270d13 --- /dev/null +++ b/src/Util/HTTPHeaders.php @@ -0,0 +1,59 @@ +in_progress['k']) { + $this->in_progress['v'] .= ' ' . ltrim($line); + continue; + } + } else { + if ($this->in_progress['k']) { + $this->parsed[] = [$this->in_progress['k'] => $this->in_progress['v']]; + $this->in_progress = []; + } + + $this->in_progress['k'] = strtolower(substr($line, 0, strpos($line, ':'))); + $this->in_progress['v'] = ltrim(substr($line, strpos($line, ':') + 1)); + } + } + + if ($this->in_progress['k']) { + $this->parsed[] = [$this->in_progress['k'] => $this->in_progress['v']]; + $this->in_progress = []; + } + } + } + + function fetch() + { + return $this->parsed; + } + + function fetcharr() + { + $ret = []; + + if ($this->parsed) { + foreach ($this->parsed as $x) { + foreach ($x as $y => $z) { + $ret[$y] = $z; + } + } + } + return $ret; + } +} diff --git a/src/Util/HTTPSig.php b/src/Util/HTTPSig.php new file mode 100644 index 000000000..a7c9f2336 --- /dev/null +++ b/src/Util/HTTPSig.php @@ -0,0 +1,352 @@ + '', + 'header_signed' => false, + 'header_valid' => false, + 'content_signed' => false, + 'content_valid' => false + ]; + + // Decide if $data arrived via controller submission or curl. + if (is_array($data) && $data['header']) { + if (!$data['success']) { + return $result; + } + + $h = new HTTPHeaders($data['header']); + $headers = $h->fetcharr(); + $body = $data['body']; + } else { + $headers = []; + $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']).' '.$_SERVER['REQUEST_URI']; + + foreach ($_SERVER as $k => $v) { + if (strpos($k, 'HTTP_') === 0) { + $field = str_replace('_', '-', strtolower(substr($k, 5))); + $headers[$field] = $v; + } + } + } + + $sig_block = null; + + if (array_key_exists('signature', $headers)) { + $sig_block = self::parseSigheader($headers['signature']); + } elseif (array_key_exists('authorization', $headers)) { + $sig_block = self::parseSigheader($headers['authorization']); + } + + if (!$sig_block) { + logger('no signature provided.'); + return $result; + } + + // Warning: This log statement includes binary data + // logger('sig_block: ' . print_r($sig_block,true), LOGGER_DATA); + + $result['header_signed'] = true; + + $signed_headers = $sig_block['headers']; + if (!$signed_headers) { + $signed_headers = ['date']; + } + + $signed_data = ''; + foreach ($signed_headers as $h) { + if (array_key_exists($h, $headers)) { + $signed_data .= $h . ': ' . $headers[$h] . "\n"; + } + if (strpos($h, '.')) { + $spoofable = true; + } + } + + $signed_data = rtrim($signed_data, "\n"); + + $algorithm = null; + if ($sig_block['algorithm'] === 'rsa-sha256') { + $algorithm = 'sha256'; + } + if ($sig_block['algorithm'] === 'rsa-sha512') { + $algorithm = 'sha512'; + } + + if ($key && function_exists($key)) { /// @todo What function do we check for - maybe we check now for a method !!! + $result['signer'] = $sig_block['keyId']; + $key = $key($sig_block['keyId']); + } + + if (!$key) { + return $result; + } + + $x = Crypto::rsaVerify($signed_data, $sig_block['signature'], $key, $algorithm); + + logger('verified: ' . $x, LOGGER_DEBUG); + + if (!$x) { + return $result; + } + + if (!$spoofable) { + $result['header_valid'] = true; + } + + if (in_array('digest', $signed_headers)) { + $result['content_signed'] = true; + $digest = explode('=', $headers['digest']); + + if ($digest[0] === 'SHA-256') { + $hashalg = 'sha256'; + } + if ($digest[0] === 'SHA-512') { + $hashalg = 'sha512'; + } + + // The explode operation will have stripped the '=' padding, so compare against unpadded base64. + if (rtrim(base64_encode(hash($hashalg, $body, true)), '=') === $digest[1]) { + $result['content_valid'] = true; + } + } + + logger('Content_Valid: ' . $result['content_valid']); + + return $result; + } + + /** + * @brief + * + * @param string $request + * @param array $head + * @param string $prvkey + * @param string $keyid (optional, default 'Key') + * @param boolean $send_headers (optional, default false) + * If set send a HTTP header + * @param boolean $auth (optional, default false) + * @param string $alg (optional, default 'sha256') + * @param string $crypt_key (optional, default null) + * @param string $crypt_algo (optional, default 'aes256ctr') + * + * @return array + */ + public static function createSig($request, $head, $prvkey, $keyid = 'Key', $send_headers = false, $auth = false, $alg = 'sha256', $crypt_key = null, $crypt_algo = 'aes256ctr') + { + $return_headers = []; + + if ($alg === 'sha256') { + $algorithm = 'rsa-sha256'; + } + + if ($alg === 'sha512') { + $algorithm = 'rsa-sha512'; + } + + $x = self::sign($request, $head, $prvkey, $alg); + + $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm + . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; + + if ($crypt_key) { + $x = Crypto::encapsulate($headerval, $crypt_key, $crypt_algo); + $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; + } + + if ($auth) { + $sighead = 'Authorization: Signature ' . $headerval; + } else { + $sighead = 'Signature: ' . $headerval; + } + + if ($head) { + foreach ($head as $k => $v) { + if ($send_headers) { + header($k . ': ' . $v); + } else { + $return_headers[] = $k . ': ' . $v; + } + } + } + + if ($send_headers) { + header($sighead); + } else { + $return_headers[] = $sighead; + } + + return $return_headers; + } + + /** + * @brief + * + * @param string $request + * @param array $head + * @param string $prvkey + * @param string $alg (optional) default 'sha256' + * + * @return array + */ + private static function sign($request, $head, $prvkey, $alg = 'sha256') + { + $ret = []; + $headers = ''; + $fields = ''; + + if ($request) { + $headers = '(request-target)' . ': ' . trim($request) . "\n"; + $fields = '(request-target)'; + } + + if ($head) { + foreach ($head as $k => $v) { + $headers .= strtolower($k) . ': ' . trim($v) . "\n"; + if ($fields) { + $fields .= ' '; + } + $fields .= strtolower($k); + } + // strip the trailing linefeed + $headers = rtrim($headers, "\n"); + } + + $sig = base64_encode(Crypto::rsaSign($headers, $prvkey, $alg)); + + $ret['headers'] = $fields; + $ret['signature'] = $sig; + + return $ret; + } + + /** + * @brief + * + * @param string $header + * @return array associate array with + * - \e string \b keyID + * - \e string \b algorithm + * - \e array \b headers + * - \e string \b signature + */ + public static function parseSigheader($header) + { + $ret = []; + $matches = []; + + // if the header is encrypted, decrypt with (default) site private key and continue + if (preg_match('/iv="(.*?)"/ism', $header, $matches)) { + $header = self::decryptSigheader($header); + } + + if (preg_match('/keyId="(.*?)"/ism', $header, $matches)) { + $ret['keyId'] = $matches[1]; + } + + if (preg_match('/algorithm="(.*?)"/ism', $header, $matches)) { + $ret['algorithm'] = $matches[1]; + } + + if (preg_match('/headers="(.*?)"/ism', $header, $matches)) { + $ret['headers'] = explode(' ', $matches[1]); + } + + if (preg_match('/signature="(.*?)"/ism', $header, $matches)) { + $ret['signature'] = base64_decode(preg_replace('/\s+/', '', $matches[1])); + } + + if (($ret['signature']) && ($ret['algorithm']) && (!$ret['headers'])) { + $ret['headers'] = ['date']; + } + + return $ret; + } + + /** + * @brief + * + * @param string $header + * @param string $prvkey (optional), if not set use site private key + * + * @return array|string associative array, empty string if failue + * - \e string \b iv + * - \e string \b key + * - \e string \b alg + * - \e string \b data + */ + private static function decryptSigheader($header, $prvkey = null) + { + $iv = $key = $alg = $data = null; + + if (!$prvkey) { + $prvkey = Config::get('system', 'prvkey'); + } + + $matches = []; + + if (preg_match('/iv="(.*?)"/ism', $header, $matches)) { + $iv = $matches[1]; + } + + if (preg_match('/key="(.*?)"/ism', $header, $matches)) { + $key = $matches[1]; + } + + if (preg_match('/alg="(.*?)"/ism', $header, $matches)) { + $alg = $matches[1]; + } + + if (preg_match('/data="(.*?)"/ism', $header, $matches)) { + $data = $matches[1]; + } + + if ($iv && $key && $alg && $data) { + return Crypto::unencapsulate(['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data], $prvkey); + } + + return ''; + } +} diff --git a/view/templates/xrd_person.tpl b/view/templates/xrd_person.tpl index 360489b87..aa402b1a8 100644 --- a/view/templates/xrd_person.tpl +++ b/view/templates/xrd_person.tpl @@ -33,4 +33,7 @@ template="{{$subscribe}}" /> + From 35480fe4f9625fd311c924838419ab4c87ced1a6 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Mon, 18 Jun 2018 23:15:52 +0200 Subject: [PATCH 02/49] xrd.php - fix coding style --- mod/xrd.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mod/xrd.php b/mod/xrd.php index 2d19bb3b7..6788d0ebe 100644 --- a/mod/xrd.php +++ b/mod/xrd.php @@ -67,7 +67,8 @@ function xrd_json($a, $uri, $alias, $profile_url, $r) $json = ['subject' => $uri, 'aliases' => [$alias, $profile_url], - 'links' => [['rel' => NAMESPACE_DFRN, 'href' => $profile_url], + 'links' => [ + ['rel' => NAMESPACE_DFRN, 'href' => $profile_url], ['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']], ['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url], ['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']], @@ -79,8 +80,9 @@ function xrd_json($a, $uri, $alias, $profile_url, $r) ['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'], ['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'], ['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key], - array('rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa') - ]]; + ['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa'] + ] + ]; echo json_encode($json); killme(); } From af9116635bd176bd04a98df9c24f85fe6e593111 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Tue, 19 Jun 2018 13:18:35 +0200 Subject: [PATCH 03/49] port hubzillas OpenWebAuth - readd some ActivityPup code to HTTPSig --- src/Util/HTTPSig.php | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/Util/HTTPSig.php b/src/Util/HTTPSig.php index a7c9f2336..18f382121 100644 --- a/src/Util/HTTPSig.php +++ b/src/Util/HTTPSig.php @@ -6,8 +6,10 @@ namespace Friendica\Util; use Friendica\Core\Config; +use Friendica\Database\DBM; use Friendica\Util\Crypto; use Friendica\Util\HTTPHeaders; +use dba; /** * @brief Implements HTTP Signatures per draft-cavage-http-signatures-07. @@ -120,6 +122,12 @@ class HTTPSig $key = $key($sig_block['keyId']); } + // We don't use Activity Pub at the moment. +// if (!$key) { +// $result['signer'] = $sig_block['keyId']; +// $key = self::getActivitypubKey($sig_block['keyId']); +// } + if (!$key) { return $result; } @@ -158,6 +166,43 @@ class HTTPSig return $result; } + /** + * Fetch the public key for Activity Pub contact. + * + * @param string|int The identifier (contact addr or contact ID). + * @return string|boolean The public key or false on failure. + */ + private static function getActivitypubKey($id) + { + if (strpos($id, 'acct:') === 0) { + $x = dba::selectFirst('contact', ['pubkey'], ['uid' => 0, 'addr' => str_replace('acct:', '', $id)]); + } else { + $x = dba::selectFirst('contact', ['pubkey'], ['id' => $id, 'network' => 'activitypub']); + } + + if (DBM::is_result($x)) { + return $x['pubkey']; + } + + if(function_exists('as_fetch')) { + $r = as_fetch($id); + } + + if ($r) { + $j = json_decode($r, true); + + if (array_key_exists('publicKey', $j) && array_key_exists('publicKeyPem', $j['publicKey'])) { + if ((array_key_exists('id', $j['publicKey']) && $j['publicKey']['id'] !== $id) && $j['id'] !== $id) { + return false; + } + + return $j['publicKey']['publicKeyPem']; + } + } + + return false; + } + /** * @brief * From 9195ea26b1551c57f5686547954db6a8b03d61b9 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Tue, 19 Jun 2018 13:30:55 +0200 Subject: [PATCH 04/49] port hubzillas OpenWebAuth - add some notes to the doxygen to refer to hubzilla's source code --- src/Model/Profile.php | 4 ++++ src/Module/Magic.php | 5 +++++ src/Module/Owa.php | 2 ++ src/Util/Crypto.php | 20 ++++++++++++++++++++ src/Util/HTTPHeaders.php | 3 +++ src/Util/HTTPSig.php | 2 ++ 6 files changed, 36 insertions(+) diff --git a/src/Model/Profile.php b/src/Model/Profile.php index cb1a15afd..0064a75c3 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -987,6 +987,8 @@ class Profile * redirects the visitor to his/her instance to start the magic auth (Authentication) * process. * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/channel.php + * * @param App $a Application instance. */ public static function zrlInit(App $a) @@ -1055,6 +1057,8 @@ class Profile /** * OpenWebAuth authentication. * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/zid.php + * * @param string $token */ public static function owtInit($token) diff --git a/src/Module/Magic.php b/src/Module/Magic.php index fef970da1..1b364a331 100644 --- a/src/Module/Magic.php +++ b/src/Module/Magic.php @@ -12,6 +12,11 @@ use Friendica\Util\Network; use dba; +/** + * Magic Auth (remote authentication) module. + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Module/Magic.php + */ class Magic extends BaseModule { public static function init() diff --git a/src/Module/Owa.php b/src/Module/Owa.php index 27c863e1b..337c0554f 100644 --- a/src/Module/Owa.php +++ b/src/Module/Owa.php @@ -23,6 +23,8 @@ use dba; * If the signature verifies a token is returned. * * This token may be exchanged for an authenticated cookie. + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Module/Owa.php */ class Owa extends BaseModule { diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index 2dc978362..d1344fc22 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -251,6 +251,8 @@ class Crypto /** * Encrypt a string with 'aes-256-cbc' cipher method. * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php + * * @param string $data * @param string $key The key used for encryption. * @param string $iv A non-NULL Initialization Vector. @@ -265,6 +267,8 @@ class Crypto /** * Decrypt a string with 'aes-256-cbc' cipher method. * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php + * * @param string $data * @param string $key The key used for decryption. * @param string $iv A non-NULL Initialization Vector. @@ -279,6 +283,8 @@ class Crypto /** * Encrypt a string with 'aes-256-ctr' cipher method. * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php + * * @param string $data * @param string $key The key used for encryption. * @param string $iv A non-NULL Initialization Vector. @@ -295,6 +301,8 @@ class Crypto /** * Decrypt a string with 'aes-256-cbc' cipher method. * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php + * * @param string $data * @param string $key The key used for decryption. * @param string $iv A non-NULL Initialization Vector. @@ -309,6 +317,8 @@ class Crypto } /** + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php * * @param string $data * @param string $pubkey The public key. @@ -325,6 +335,8 @@ class Crypto } /** + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php * * @param type $data * @param type $pubkey The public key. @@ -374,6 +386,8 @@ class Crypto } /** + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php * * @param string $data * @param string $pubkey @@ -406,6 +420,8 @@ class Crypto } /** + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php * * @param string $data * @param string $prvkey The private key used for decryption. @@ -426,6 +442,8 @@ class Crypto } /** + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php * * @param string $data * @param string $prvkey The private key used for decryption. @@ -451,6 +469,8 @@ class Crypto } /** + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php * * @param array $data * @param string $prvkey The private key used for decryption. diff --git a/src/Util/HTTPHeaders.php b/src/Util/HTTPHeaders.php index a6c270d13..ac2d41be4 100644 --- a/src/Util/HTTPHeaders.php +++ b/src/Util/HTTPHeaders.php @@ -4,6 +4,9 @@ */ namespace Friendica\Util; +/** + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Web/HTTPHeaders.php + */ class HTTPHeaders { private $in_progress = []; diff --git a/src/Util/HTTPSig.php b/src/Util/HTTPSig.php index 18f382121..96ea2afb5 100644 --- a/src/Util/HTTPSig.php +++ b/src/Util/HTTPSig.php @@ -14,6 +14,8 @@ use dba; /** * @brief Implements HTTP Signatures per draft-cavage-http-signatures-07. * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Web/HTTPSig.php + * * @see https://tools.ietf.org/html/draft-cavage-http-signatures-07 */ From b65e4b278b1d1c1bdf152d36664a254bf4a5bfe1 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Tue, 19 Jun 2018 16:15:28 +0200 Subject: [PATCH 05/49] port hubzillas OpenWebAuth - use Contact::getIdForURL to query for contact entry --- src/Model/Profile.php | 34 +++++++++++----------------------- src/Module/Magic.php | 27 +++++++++++---------------- src/Module/Owa.php | 17 ++++++----------- src/Network/Probe.php | 17 ++++------------- src/Util/HTTPSig.php | 7 ++++--- 5 files changed, 36 insertions(+), 66 deletions(-) diff --git a/src/Model/Profile.php b/src/Model/Profile.php index 0064a75c3..9741f2a74 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -19,7 +19,6 @@ use Friendica\Database\DBM; use Friendica\Model\Contact; use Friendica\Model\Verify; use Friendica\Protocol\Diaspora; -use Friendica\Network\Probe; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; use Friendica\Util\Temporal; @@ -1013,22 +1012,14 @@ class Profile Addon::callHooks('zrl_init', $arr); // Try to find the public contact entry of the visitor. - $fields = ["id", "url"]; - $condition = ['uid' => 0, 'nurl' => normalise_link($my_url)]; - - $contact = dba::selectFirst('contact',$fields, $condition); - - // Not found? Try to probe the visitor. - if (!DBM::is_result($contact)) { - Probe::uri($my_url, '', -1, true, true); - $contact = dba::selectFirst('contact',$fields, $condition); - } - - if (!DBM::is_result($contact)) { + $cid = Contact::getIdForURL($my_url); + if (!$cid) { logger('No contact record found for ' . $my_url, LOGGER_DEBUG); return; } + $contact = dba::selectFirst('contact',['id', 'url'], ['id' => $cid]); + if (DBM::is_result($contact) && remote_user() && remote_user() === $contact['id']) { // The visitor is already authenticated. return; @@ -1039,12 +1030,12 @@ class Profile // Try to avoid recursion - but send them home to do a proper magic auth. $query = str_replace(array('?zrl=', '&zid='), array('?rzrl=', '&rzrl='), $a->query_string); // The other instance needs to know where to redirect. - $dest = urlencode(System::baseUrl() . "/" . $query); + $dest = urlencode(System::baseUrl() . '/' . $query); // We need to extract the basebath from the profile url // to redirect the visitors '/magic' module. // Note: We should have the basepath of a contact also in the contact table. - $urlarr = explode("/profile/", $contact['url']); + $urlarr = explode('/profile/', $contact['url']); $basepath = $urlarr[0]; if ($basepath != System::baseUrl() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) { @@ -1077,18 +1068,14 @@ class Profile } // Try to find the public contact entry of the visitor. - $condition = ["uid" => 0, "addr" => $visitor_handle]; - $visitor = dba::selectFirst("contact", [], $condition); - - if (!DBM::is_result($visitor)) { - Probe::uri($visitor_handle, '', -1, true, true); - $visitor = dba::selectFirst("contact", [], $condition); - } - if(!DBM::is_result($visitor)) { + $cid = Contact::getIdForURL($visitor_handle); + if(!$cid) { logger('owt: unable to finger ' . $visitor_handle, LOGGER_DEBUG); return; } + $visitor = dba::selectFirst('contact', [], ['id' => $cid]); + // Authenticate the visitor. $_SESSION['authenticated'] = 1; $_SESSION['visitor_id'] = $visitor['id']; @@ -1108,6 +1095,7 @@ class Profile * * \e array \b session */ Addon::callHooks('magic_auth_success', $arr); + $a->contact = $visitor; info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->get_hostname(), $visitor['name'])); diff --git a/src/Module/Magic.php b/src/Module/Magic.php index 1b364a331..86e9e44d8 100644 --- a/src/Module/Magic.php +++ b/src/Module/Magic.php @@ -5,8 +5,7 @@ namespace Friendica\Module; use Friendica\BaseModule; -use Friendica\Database\DBM; -use Friendica\Network\Probe; +use Friendica\Model\Contact; use Friendica\Util\HTTPSig; use Friendica\Util\Network; @@ -35,32 +34,28 @@ class Magic extends BaseModule // NOTE: I guess $dest isn't just the profile url (could be also // other profile pages e.g. photo). We need to find a solution // to be able to redirct to other pages than the contact profile. - $fields = ["id", "nurl", "url"]; - $condition = ["nurl" => normalise_link($dest)]; + $cid = Contact::getIdForURL($dest); - $contact = dba::selectFirst("contact", $fields, $condition); - - if (!DBM::is_result($contact)) { - // If we don't have a contact record, try to probe it. - /// @todo: Also check against the $addr. - Probe::uri($dest, '', -1, true, true); - $contact = dba::selectFirst("contact", $fields, $condition); + if (!$cid && !empty($addr)) { + $cid = Contact::getIdForURL($addr); } - if (!DBM::is_result($contact)) { - logger("No contact record found: " . print_r($_REQUEST, true), LOGGER_DEBUG); + if (!$cid) { + logger('No contact record found: ' . print_r($_REQUEST, true), LOGGER_DEBUG); goaway($dest); } + $contact = dba::selectFirst('contact', ['id', 'nurl', 'url'], ['id' => $cid]); + // Redirect if the contact is already authenticated on this site. - if (array_key_exists("id", $a->contact) && strpos($contact['nurl'], normalise_link(self::getApp()->get_baseurl())) !== false) { + if (array_key_exists('id', $a->contact) && strpos($contact['nurl'], normalise_link(self::getApp()->get_baseurl())) !== false) { if($test) { $ret['success'] = true; $ret['message'] .= 'Local site - you are already authenticated.' . EOL; return $ret; } - logger("Contact is already authenticated", LOGGER_DEBUG); + logger('Contact is already authenticated', LOGGER_DEBUG); goaway($dest); } @@ -73,7 +68,7 @@ class Magic extends BaseModule // NOTE: we need another solution because this does only work // for friendica contacts :-/ . We should have the basepath // of a contact also in the contact table. - $exp = explode("/profile/", $contact['url']); + $exp = explode('/profile/', $contact['url']); $basepath = $exp[0]; $headers = []; diff --git a/src/Module/Owa.php b/src/Module/Owa.php index 337c0554f..6c580704f 100644 --- a/src/Module/Owa.php +++ b/src/Module/Owa.php @@ -7,9 +7,8 @@ namespace Friendica\Module; use Friendica\BaseModule; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Model\Contact; use Friendica\Model\Verify; -use Friendica\Network\Probe; -use Friendica\Util\DateTimeFormat; use Friendica\Util\HTTPSig; use dba; @@ -46,17 +45,13 @@ class Owa extends BaseModule if ($keyId) { // Try to find the public contact entry of the handle. - $handle = str_replace("acct:", "", $keyId); - $fields = ["id", "url", "addr", "pubkey"]; - $condition = ["addr" => $handle, "uid" => 0]; + $handle = str_replace('acct:', '', $keyId); - $contact = dba::selectFirst("contact", $fields, $condition); + $cid = Contact::getIdForURL($handle); + $fields = ['id', 'url', 'addr', 'pubkey']; + $condition = ['id' => $cid]; - // Not found? Try to probe with the handle. - if(!DBM::is_result($contact)) { - Probe::uri($handle, '', -1, true, true); - $contact = dba::selectFirst("contact", $fields, $condition); - } + $contact = dba::selectFirst('contact', $fields, $condition); if (DBM::is_result($contact)) { // Try to verify the signed header with the public key of the contact record diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 7f41b2304..5f665814b 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -311,11 +311,10 @@ class Probe * @param string $network Test for this specific network * @param integer $uid User ID for the probe (only used for mails) * @param boolean $cache Use cached values? - * @param boolean $insert Insert the contact into the contact table. * * @return array uri data */ - public static function uri($uri, $network = "", $uid = -1, $cache = true, $insert = false) + public static function uri($uri, $network = "", $uid = -1, $cache = true) { if ($cache) { $result = Cache::get("Probe::uri:".$network.":".$uri); @@ -464,19 +463,11 @@ class Probe $condition = ['nurl' => normalise_link($data["url"]), 'self' => false, 'uid' => 0]; // "$old_fields" will return a "false" when the contact doesn't exist. - // This won't trigger an insert except $insert is set to true. - // This is intended, since we only need public contacts - // for everyone we store items from. We don't need to store - // every contact on the planet. + // This won't trigger an insert. This is intended, since we only need + // public contacts for everyone we store items from. + // We don't need to store every contact on the planet. $old_fields = dba::selectFirst('contact', $fieldnames, $condition); - // When the contact doesn't exist, the value "true" will trigger an insert - if (!$old_fields && $insert) { - $old_fields = true; - $fields['blocked'] = false; - $fields['pending'] = false; - } - $fields['name-date'] = DateTimeFormat::utcNow(); $fields['uri-date'] = DateTimeFormat::utcNow(); $fields['success_update'] = DateTimeFormat::utcNow(); diff --git a/src/Util/HTTPSig.php b/src/Util/HTTPSig.php index 96ea2afb5..b78cca896 100644 --- a/src/Util/HTTPSig.php +++ b/src/Util/HTTPSig.php @@ -26,9 +26,10 @@ class HTTPSig * * @see https://tools.ietf.org/html/rfc5843 * - * @param string $body The value to create the digest for - * @param boolean $set (optional, default true) + * @param string $body The value to create the digest for + * @param boolean $set (optional, default true) * If set send a Digest HTTP header + * * @return string The generated digest of $body */ public static function generateDigest($body, $set = true) @@ -119,7 +120,7 @@ class HTTPSig $algorithm = 'sha512'; } - if ($key && function_exists($key)) { /// @todo What function do we check for - maybe we check now for a method !!! + if ($key && function_exists($key)) { $result['signer'] = $sig_block['keyId']; $key = $key($sig_block['keyId']); } From f0235c4a98e3f75afe6f4eb23b12d3b9a1c150ae Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Wed, 20 Jun 2018 18:38:23 +0200 Subject: [PATCH 06/49] port hubzillas OpenWebAuth - rename some methods and classes --- doc/Addons.md | 1 - index.php | 2 +- src/Model/Profile.php | 8 +++----- src/Module/Magic.php | 4 ++-- src/Module/Owa.php | 4 ++-- src/Util/{HTTPSig.php => HTTPSignature.php} | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) rename src/Util/{HTTPSig.php => HTTPSignature.php} (99%) diff --git a/doc/Addons.md b/doc/Addons.md index 090d5a9d7..3241ddc0d 100644 --- a/doc/Addons.md +++ b/doc/Addons.md @@ -362,7 +362,6 @@ Called when a magic-auth was successful. Hook data: 'visitor' => array with the contact record of the visitor 'url' => the query string - 'session' => $_SESSION array Current JavaScript hooks ------------- diff --git a/index.php b/index.php index c0290dff4..f65867feb 100644 --- a/index.php +++ b/index.php @@ -147,7 +147,7 @@ if ((x($_GET,'zrl')) && $a->mode == App::MODE_NORMAL) { if ((x($_GET,'owt')) && $a->mode == App::MODE_NORMAL) { $token = $_GET['owt']; $a->query_string = Profile::stripQueryParam($a->query_string, 'owt'); - Profile::owtInit($token); + Profile::openWebAuthInit($token); } /** diff --git a/src/Model/Profile.php b/src/Model/Profile.php index 9741f2a74..8d2045221 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -1052,7 +1052,7 @@ class Profile * * @param string $token */ - public static function owtInit($token) + public static function openWebAuthInit($token) { $a = get_app(); @@ -1084,19 +1084,17 @@ class Profile $arr = [ 'visitor' => $visitor, - 'url' => $a->query_string, - 'session' => $_SESSION + 'url' => $a->query_string ]; /** * @hooks magic_auth_success * Called when a magic-auth was successful. * * \e array \b visitor * * \e string \b url - * * \e array \b session */ Addon::callHooks('magic_auth_success', $arr); - $a->contact = $visitor; + $a->contact = $arr['visitor']; info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->get_hostname(), $visitor['name'])); diff --git a/src/Module/Magic.php b/src/Module/Magic.php index 86e9e44d8..aaf0348ed 100644 --- a/src/Module/Magic.php +++ b/src/Module/Magic.php @@ -6,7 +6,7 @@ namespace Friendica\Module; use Friendica\BaseModule; use Friendica\Model\Contact; -use Friendica\Util\HTTPSig; +use Friendica\Util\HTTPSignature; use Friendica\Util\Network; use dba; @@ -76,7 +76,7 @@ class Magic extends BaseModule $headers['X-Open-Web-Auth'] = random_string(); // Create a header that is signed with the local users private key. - $headers = HTTPSig::createSig( + $headers = HTTPSignature::createSig( '', $headers, $user['prvkey'], diff --git a/src/Module/Owa.php b/src/Module/Owa.php index 6c580704f..13594a3ea 100644 --- a/src/Module/Owa.php +++ b/src/Module/Owa.php @@ -9,7 +9,7 @@ use Friendica\Core\System; use Friendica\Database\DBM; use Friendica\Model\Contact; use Friendica\Model\Verify; -use Friendica\Util\HTTPSig; +use Friendica\Util\HTTPSignature; use dba; @@ -39,7 +39,7 @@ class Owa extends BaseModule continue; } - $sigblock = HTTPSig::parseSigheader($_SERVER[$head]); + $sigblock = HTTPSignature::parseSigheader($_SERVER[$head]); if ($sigblock) { $keyId = $sigblock['keyId']; diff --git a/src/Util/HTTPSig.php b/src/Util/HTTPSignature.php similarity index 99% rename from src/Util/HTTPSig.php rename to src/Util/HTTPSignature.php index b78cca896..d6152075e 100644 --- a/src/Util/HTTPSig.php +++ b/src/Util/HTTPSignature.php @@ -19,7 +19,7 @@ use dba; * @see https://tools.ietf.org/html/draft-cavage-http-signatures-07 */ -class HTTPSig +class HTTPSignature { /** * @brief RFC5843 From 1148c29916800aceb7f847f84753f77e3ffe0043 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Wed, 20 Jun 2018 18:45:37 +0200 Subject: [PATCH 07/49] port hubzillas OpenWebAuth - use random_bytes() in crypto class + bugfixes --- src/Util/Crypto.php | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index d1344fc22..ad2a6fb2c 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -299,7 +299,7 @@ class Crypto } /** - * Decrypt a string with 'aes-256-cbc' cipher method. + * Decrypt a string with 'aes-256-ctr' cipher method. * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php * @@ -351,18 +351,9 @@ class Crypto } $fn = 'encrypt' . strtoupper($alg); if (method_exists(__CLASS__, $fn)) { - // A bit hesitant to use openssl_random_pseudo_bytes() as we know - // it has been historically targeted by US agencies for 'weakening'. - // It is still arguably better than trying to come up with an - // alternative cryptographically secure random generator. - // There is little point in using the optional second arg to flag the - // assurance of security since it is meaningless if the source algorithms - // have been compromised. Also none of this matters if RSA has been - // compromised by state actors and evidence is mounting that this has - // already happened. $result = ['encrypted' => true]; - $key = openssl_random_pseudo_bytes(256); - $iv = openssl_random_pseudo_bytes(256); + $key = random_bytes(256); + $iv = random_bytes(256); $result['data'] = base64url_encode(self::$fn($data, $key, $iv), true); // log the offending call so we can track it down @@ -400,10 +391,10 @@ class Crypto logger('aes_encapsulate: no key. data: ' . $data); } - $key = openssl_random_pseudo_bytes(32); - $iv = openssl_random_pseudo_bytes(16); + $key = random_bytes(32); + $iv = random_bytes(16); $result = ['encrypted' => true]; - $result['data'] = base64url_encode(AES256CBC_encrypt($data, $key, $iv), true); + $result['data'] = base64url_encode(self::AES256CBC_encrypt($data, $key, $iv), true); // log the offending call so we can track it down if (!openssl_public_encrypt($key, $k, $pubkey)) { From 863a49d8e7dbb392b9db9912ba7d42146b233562 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Wed, 20 Jun 2018 18:52:37 +0200 Subject: [PATCH 08/49] port hubzillas OpenWebAuth - rework the HTTPHeaders class --- src/Util/HTTPHeaders.php | 22 ++++------------------ src/Util/HTTPSignature.php | 2 +- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/Util/HTTPHeaders.php b/src/Util/HTTPHeaders.php index ac2d41be4..9b0c4529d 100644 --- a/src/Util/HTTPHeaders.php +++ b/src/Util/HTTPHeaders.php @@ -19,12 +19,12 @@ class HTTPHeaders if ($lines) { foreach ($lines as $line) { if (preg_match('/^\s+/', $line, $matches) && trim($line)) { - if ($this->in_progress['k']) { + if (!empty($this->in_progress['k'])) { $this->in_progress['v'] .= ' ' . ltrim($line); continue; } } else { - if ($this->in_progress['k']) { + if (!empty($this->in_progress['k'])) { $this->parsed[] = [$this->in_progress['k'] => $this->in_progress['v']]; $this->in_progress = []; } @@ -34,8 +34,8 @@ class HTTPHeaders } } - if ($this->in_progress['k']) { - $this->parsed[] = [$this->in_progress['k'] => $this->in_progress['v']]; + if (!empty($this->in_progress['k'])) { + $this->parsed[$this->in_progress['k']] = $this->in_progress['v']; $this->in_progress = []; } } @@ -45,18 +45,4 @@ class HTTPHeaders { return $this->parsed; } - - function fetcharr() - { - $ret = []; - - if ($this->parsed) { - foreach ($this->parsed as $x) { - foreach ($x as $y => $z) { - $ret[$y] = $z; - } - } - } - return $ret; - } } diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index d6152075e..731357f75 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -63,7 +63,7 @@ class HTTPSignature } $h = new HTTPHeaders($data['header']); - $headers = $h->fetcharr(); + $headers = $h->fetch(); $body = $data['body']; } else { $headers = []; From 1ab3f7bfc828ea2fdb2fd9df091224e8a5ab07c9 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Wed, 20 Jun 2018 19:05:33 +0200 Subject: [PATCH 09/49] port hubzillas OpenWebAuth - HTTPSignature - comment out some ActivityPub parts --- src/Util/HTTPSignature.php | 39 +++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index 731357f75..1ed0e1e8a 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -24,6 +24,9 @@ class HTTPSignature /** * @brief RFC5843 * + * Disabled until Friendica's ActivityPub implementation + * is ready. + * * @see https://tools.ietf.org/html/rfc5843 * * @param string $body The value to create the digest for @@ -32,15 +35,15 @@ class HTTPSignature * * @return string The generated digest of $body */ - public static function generateDigest($body, $set = true) - { - $digest = base64_encode(hash('sha256', $body, true)); - - if($set) { - header('Digest: SHA-256=' . $digest); - } - return $digest; - } +// public static function generateDigest($body, $set = true) +// { +// $digest = base64_encode(hash('sha256', $body, true)); +// +// if($set) { +// header('Digest: SHA-256=' . $digest); +// } +// return $digest; +// } // See draft-cavage-http-signatures-08 public static function verify($data, $key = '') @@ -178,13 +181,13 @@ class HTTPSignature private static function getActivitypubKey($id) { if (strpos($id, 'acct:') === 0) { - $x = dba::selectFirst('contact', ['pubkey'], ['uid' => 0, 'addr' => str_replace('acct:', '', $id)]); + $contact = dba::selectFirst('contact', ['pubkey'], ['uid' => 0, 'addr' => str_replace('acct:', '', $id)]); } else { - $x = dba::selectFirst('contact', ['pubkey'], ['id' => $id, 'network' => 'activitypub']); + $contact = dba::selectFirst('contact', ['pubkey'], ['id' => $id, 'network' => 'activitypub']); } - if (DBM::is_result($x)) { - return $x['pubkey']; + if (DBM::is_result($contact)) { + return $contact['pubkey']; } if(function_exists('as_fetch')) { @@ -253,7 +256,10 @@ class HTTPSignature if ($head) { foreach ($head as $k => $v) { if ($send_headers) { - header($k . ': ' . $v); + // This is for ActivityPub implementation. + // Since the Activity Pub implementation isn't + // ready at the moment, we comment it out. + // header($k . ': ' . $v); } else { $return_headers[] = $k . ': ' . $v; } @@ -261,7 +267,10 @@ class HTTPSignature } if ($send_headers) { - header($sighead); + // This is for ActivityPub implementation. + // Since the Activity Pub implementation isn't + // ready at the moment, we comment it out. + // header($sighead); } else { $return_headers[] = $sighead; } From d5afbd3357e00294b466be618d262a8e4c4e2ba9 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Wed, 20 Jun 2018 19:18:41 +0200 Subject: [PATCH 10/49] port hubzillas OpenWebAuth - rename verify table to openwebauth-token --- database.sql | 6 +++--- src/Database/DBStructure.php | 4 ++-- src/Model/Verify.php | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/database.sql b/database.sql index b871ce2de..d084ba519 100644 --- a/database.sql +++ b/database.sql @@ -1085,9 +1085,9 @@ CREATE TABLE IF NOT EXISTS `user-item` ( ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data'; -- --- TABLE verify +-- TABLE openwebauth-token -- -CREATE TABLE IF NOT EXISTS `verify` ( +CREATE TABLE IF NOT EXISTS `openwebauth-token` ( `id` int(10) NOT NULL auto_increment COMMENT 'sequential ID', `uid` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'User id', `type` varchar(32) DEFAULT '' COMMENT 'Verify type', @@ -1095,7 +1095,7 @@ CREATE TABLE IF NOT EXISTS `verify` ( `meta` varchar(255) DEFAULT '' COMMENT '', `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime of creation', PRIMARY KEY(`id`) -) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Store token to verify contacts'; +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Store OpenWebAuth token to verify contacts'; -- -- TABLE worker-ipc diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index 33babded9..0d7ba49e4 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -1818,8 +1818,8 @@ class DBStructure "PRIMARY" => ["uid", "iid"], ] ]; - $database["verify"] = [ - "comment" => "Store token to verify contacts", + $database["openwebauth-token"] = [ + "comment" => "Store OpenWebAuth token to verify contacts", "fields" => [ "id" => ["type" => "int(10)", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], "uid" => ["type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"], diff --git a/src/Model/Verify.php b/src/Model/Verify.php index 92dbafd3a..18fbb2eb5 100644 --- a/src/Model/Verify.php +++ b/src/Model/Verify.php @@ -10,12 +10,12 @@ use Friendica\Util\DateTimeFormat; use dba; /** - * Methods to deal with entries of the 'verify' table. + * Methods to deal with entries of the 'openwebauth_token' table. */ class Verify { /** - * Create an entry in the 'verify' table. + * Create an entry in the 'openwebauth_token' table. * * @param string $type Verify type. * @param int $uid The user ID. @@ -33,11 +33,11 @@ class Verify "meta" => $meta, "created" => DateTimeFormat::utcNow() ]; - return dba::insert("verify", $fields); + return dba::insert("openwebauth_token", $fields); } /** - * Get the "meta" field of an entry in the verify table. + * Get the "meta" field of an entry in the openwebauth_token table. * * @param string $type Verify type. * @param int $uid The user ID. @@ -49,9 +49,9 @@ class Verify { $condition = ["type" => $type, "uid" => $uid, "token" => $token]; - $entry = dba::selectFirst("verify", ["id", "meta"], $condition); + $entry = dba::selectFirst("openwebauth_token", ["id", "meta"], $condition); if (DBM::is_result($entry)) { - dba::delete("verify", ["id" => $entry["id"]]); + dba::delete("openwebauth_token", ["id" => $entry["id"]]); return $entry["meta"]; } @@ -67,7 +67,7 @@ class Verify public static function purge($type, $interval) { $condition = ["`type` = ? AND `created` < ?", $type, DateTimeFormat::utcNow() . " - INTERVAL " . $interval]; - dba::delete("verify", $condition); + dba::delete("openwebauth_token", $condition); } } From 149142b4bc7cfd9a40bfa04d767d3e7149a69b22 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Wed, 20 Jun 2018 19:24:02 +0200 Subject: [PATCH 11/49] port hubzillas OpenWebAuth - rename Verify class to OpenWebAuthToken --- src/Model/{Verify.php => OpenWebAuthToken.php} | 4 ++-- src/Model/Profile.php | 8 ++++---- src/Module/Owa.php | 4 ++-- src/Util/HTTPSignature.php | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/Model/{Verify.php => OpenWebAuthToken.php} (96%) diff --git a/src/Model/Verify.php b/src/Model/OpenWebAuthToken.php similarity index 96% rename from src/Model/Verify.php rename to src/Model/OpenWebAuthToken.php index 18fbb2eb5..f295fc370 100644 --- a/src/Model/Verify.php +++ b/src/Model/OpenWebAuthToken.php @@ -1,7 +1,7 @@ Date: Wed, 20 Jun 2018 19:32:26 +0200 Subject: [PATCH 12/49] port hubzillas OpenWebAuth - fix some code standards violations --- mod/xrd.php | 30 +++++++++++++++--------------- src/Module/Magic.php | 14 +++++++------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mod/xrd.php b/mod/xrd.php index 6788d0ebe..24cb27c28 100644 --- a/mod/xrd.php +++ b/mod/xrd.php @@ -68,20 +68,20 @@ function xrd_json($a, $uri, $alias, $profile_url, $r) $json = ['subject' => $uri, 'aliases' => [$alias, $profile_url], 'links' => [ - ['rel' => NAMESPACE_DFRN, 'href' => $profile_url], - ['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']], - ['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url], - ['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']], - ['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']], - ['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'], - ['rel' => 'http://joindiaspora.com/seed_location', 'type' => 'text/html', 'href' => System::baseUrl()], - ['rel' => 'salmon', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], - ['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], - ['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'], - ['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'], - ['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key], - ['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa'] - ] + ['rel' => NAMESPACE_DFRN, 'href' => $profile_url], + ['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']], + ['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url], + ['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']], + ['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']], + ['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'], + ['rel' => 'http://joindiaspora.com/seed_location', 'type' => 'text/html', 'href' => System::baseUrl()], + ['rel' => 'salmon', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], + ['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], + ['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'], + ['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'], + ['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key], + ['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa'] + ] ]; echo json_encode($json); killme(); @@ -109,7 +109,7 @@ function xrd_xml($a, $uri, $alias, $profile_url, $r) '$salmon' => System::baseUrl() . '/salmon/' . $r['nickname'], '$salmen' => System::baseUrl() . '/salmon/' . $r['nickname'] . '/mention', '$subscribe' => System::baseUrl() . '/follow?url={uri}', - '$openwebauth' => System::baseUrl() .'/owa', + '$openwebauth' => System::baseUrl() . '/owa', '$modexp' => 'data:application/magic-public-key,' . $salmon_key] ); diff --git a/src/Module/Magic.php b/src/Module/Magic.php index aaf0348ed..ce41f228d 100644 --- a/src/Module/Magic.php +++ b/src/Module/Magic.php @@ -77,13 +77,13 @@ class Magic extends BaseModule // Create a header that is signed with the local users private key. $headers = HTTPSignature::createSig( - '', - $headers, - $user['prvkey'], - 'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->path ? '/' . $a->path : ''), - false, - true, - 'sha512' + '', + $headers, + $user['prvkey'], + 'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->path ? '/' . $a->path : ''), + false, + true, + 'sha512' ); // Try to get an authentication token from the other instance. From 9f493357f4b8507012bb1ad03c98f51bb667352b Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Wed, 20 Jun 2018 20:11:26 +0200 Subject: [PATCH 13/49] port hubzillas OpenWebAuth - fix wrong table name + fix wrong method name --- src/Model/OpenWebAuthToken.php | 14 +++++++------- src/Util/Crypto.php | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Model/OpenWebAuthToken.php b/src/Model/OpenWebAuthToken.php index f295fc370..5c405b27d 100644 --- a/src/Model/OpenWebAuthToken.php +++ b/src/Model/OpenWebAuthToken.php @@ -10,12 +10,12 @@ use Friendica\Util\DateTimeFormat; use dba; /** - * Methods to deal with entries of the 'openwebauth_token' table. + * Methods to deal with entries of the 'openwebauth-token' table. */ class OpenWebAuthToken { /** - * Create an entry in the 'openwebauth_token' table. + * Create an entry in the 'openwebauth-token' table. * * @param string $type Verify type. * @param int $uid The user ID. @@ -33,11 +33,11 @@ class OpenWebAuthToken "meta" => $meta, "created" => DateTimeFormat::utcNow() ]; - return dba::insert("openwebauth_token", $fields); + return dba::insert("openwebauth-token", $fields); } /** - * Get the "meta" field of an entry in the openwebauth_token table. + * Get the "meta" field of an entry in the openwebauth-token table. * * @param string $type Verify type. * @param int $uid The user ID. @@ -49,9 +49,9 @@ class OpenWebAuthToken { $condition = ["type" => $type, "uid" => $uid, "token" => $token]; - $entry = dba::selectFirst("openwebauth_token", ["id", "meta"], $condition); + $entry = dba::selectFirst("openwebauth-token", ["id", "meta"], $condition); if (DBM::is_result($entry)) { - dba::delete("openwebauth_token", ["id" => $entry["id"]]); + dba::delete("openwebauth-token", ["id" => $entry["id"]]); return $entry["meta"]; } @@ -67,7 +67,7 @@ class OpenWebAuthToken public static function purge($type, $interval) { $condition = ["`type` = ? AND `created` < ?", $type, DateTimeFormat::utcNow() . " - INTERVAL " . $interval]; - dba::delete("openwebauth_token", $condition); + dba::delete("openwebauth-token", $condition); } } diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index ad2a6fb2c..6a49626bd 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -394,7 +394,7 @@ class Crypto $key = random_bytes(32); $iv = random_bytes(16); $result = ['encrypted' => true]; - $result['data'] = base64url_encode(self::AES256CBC_encrypt($data, $key, $iv), true); + $result['data'] = base64url_encode(self::encryptAES256CBC($data, $key, $iv), true); // log the offending call so we can track it down if (!openssl_public_encrypt($key, $k, $pubkey)) { From 3d1822996ac1ddc6935ca40cfdaec90ebc9f42b7 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Wed, 20 Jun 2018 20:16:36 +0200 Subject: [PATCH 14/49] port hubzillas OpenWebAuth - fix another code standards violation --- mod/xrd.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/mod/xrd.php b/mod/xrd.php index 24cb27c28..d251d41fe 100644 --- a/mod/xrd.php +++ b/mod/xrd.php @@ -66,22 +66,22 @@ function xrd_json($a, $uri, $alias, $profile_url, $r) header("Content-type: application/json; charset=utf-8"); $json = ['subject' => $uri, - 'aliases' => [$alias, $profile_url], - 'links' => [ - ['rel' => NAMESPACE_DFRN, 'href' => $profile_url], - ['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']], - ['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url], - ['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']], - ['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']], - ['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'], - ['rel' => 'http://joindiaspora.com/seed_location', 'type' => 'text/html', 'href' => System::baseUrl()], - ['rel' => 'salmon', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], - ['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], - ['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'], - ['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'], - ['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key], - ['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa'] - ] + 'aliases' => [$alias, $profile_url], + 'links' => [ + ['rel' => NAMESPACE_DFRN, 'href' => $profile_url], + ['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']], + ['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url], + ['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']], + ['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']], + ['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'], + ['rel' => 'http://joindiaspora.com/seed_location', 'type' => 'text/html', 'href' => System::baseUrl()], + ['rel' => 'salmon', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], + ['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], + ['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'], + ['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'], + ['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key], + ['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa'] + ] ]; echo json_encode($json); killme(); From 9cc0d5479bce29ec7a9a41c8e6643ec813175c5f Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 22:49:05 +0100 Subject: [PATCH 15/49] old behaviour restored Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index a9e836499..09fb0dacd 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -2357,7 +2357,11 @@ class DFRN ); if (!DBM::is_result($r)) { + /* + * @TODO maybe one day: logger("Query failed to execute, no result returned in " . __FUNCTION__); + killme(); + */ return false; } From f2f12d6f50893d0a35d0db4ca3adbf8a3f8caa9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20H=C3=A4der?= Date: Thu, 20 Jul 2017 20:04:32 +0200 Subject: [PATCH 16/49] Minor improvements: - added spaces and curly braces - added more TODOs as there really more to be done - removed redundant braces from "return" keyword (no function) - merged 2 nested if() into one single MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Roland Häder --- include/text.php | 1 + 1 file changed, 1 insertion(+) diff --git a/include/text.php b/include/text.php index 10f626458..f1bc51232 100644 --- a/include/text.php +++ b/include/text.php @@ -1970,6 +1970,7 @@ function is_a_date_arg($s) { */ function deindent($text, $chr = "[\t ]", $count = NULL) { $lines = explode("\n", $text); + if (is_null($count)) { $m = []; $k = 0; From 63f55987668b429c5b72e69dbde0d1c2ef3acc7d Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 22:26:07 +0100 Subject: [PATCH 17/49] better comment added by @Hypolite Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 09fb0dacd..3b94842e2 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -1973,11 +1973,9 @@ class DFRN */ if (!DBM::is_result($r)) { // database record did not get created. Quietly give up. - killme(); + return false; } - $fid = $r[0]["id"]; - $hash = random_string(); $r = q( From a95db714ca758bbc45373e855c470f5a182df149 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 22:33:12 +0100 Subject: [PATCH 18/49] added curely branches + reverted back to old behaviour (may come back one day) Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 3b94842e2..1b760b064 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -2056,7 +2056,11 @@ class DFRN ); if (!DBM::is_result($r)) { + /* + * @TODO maybe one day: logger("Query failed to execute, no result returned in " . __FUNCTION__); + killme(); + */ return false; } From 22bbfdadd058d2bddc780f220d77918cf872cb64 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 23:05:53 +0100 Subject: [PATCH 19/49] added more curly braces + spaces for #3254 Signed-off-by: Roland Haeder --- mod/profiles.php | 1 + 1 file changed, 1 insertion(+) diff --git a/mod/profiles.php b/mod/profiles.php index 9c925924f..3ceb8c664 100644 --- a/mod/profiles.php +++ b/mod/profiles.php @@ -479,6 +479,7 @@ function profiles_post(App $a) { intval(local_user()) ); + /// @TODO decide to use dbm::is_result() here and check $r if ($r) { info(L10n::t('Profile updated.') . EOL); } From f0b05838cb1ee23dde1c1b1a875c247530f12d38 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 23:37:58 +0100 Subject: [PATCH 20/49] fixed comment and explained one Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 1b760b064..6a64a8295 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -1973,9 +1973,11 @@ class DFRN */ if (!DBM::is_result($r)) { // database record did not get created. Quietly give up. - return false; + killme(); } + $fid = $r[0]["id"]; + $hash = random_string(); $r = q( From f89b99965974854ef27b5c54cdda8935cf8d976b Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Sat, 25 Mar 2017 13:46:28 +0100 Subject: [PATCH 21/49] was a bit confusing for me or I was not sleeping to much ... Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 6a64a8295..a9e836499 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -2058,11 +2058,7 @@ class DFRN ); if (!DBM::is_result($r)) { - /* - * @TODO maybe one day: logger("Query failed to execute, no result returned in " . __FUNCTION__); - killme(); - */ return false; } @@ -2361,11 +2357,7 @@ class DFRN ); if (!DBM::is_result($r)) { - /* - * @TODO maybe one day: logger("Query failed to execute, no result returned in " . __FUNCTION__); - killme(); - */ return false; } From fe77e1b5384fca3efd11c9280a6e2c1bc5a2d481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20H=C3=A4der?= Date: Wed, 25 Jan 2017 15:59:27 +0100 Subject: [PATCH 22/49] added more curly braces + a bit more usage of dbm::is_result() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Roland Häder --- src/Protocol/DFRN.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index a9e836499..31d94e77e 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -96,6 +96,7 @@ class DFRN * @param boolean $onlyheader Output only the header without content? (Default is "no") * * @return string DFRN feed entries + * @todo Find proper type-hints */ public static function feed($dfrn_id, $owner_nick, $last_update, $direction = 0, $onlyheader = false) { From 852ef4021b30c56185818971e17a50eec0a7a5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20H=C3=A4der?= Date: Thu, 26 Jan 2017 16:07:30 +0100 Subject: [PATCH 23/49] more spaces + some curly spaces added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Roland Häder --- include/text.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/text.php b/include/text.php index f1bc51232..9d8ba2604 100644 --- a/include/text.php +++ b/include/text.php @@ -1883,8 +1883,15 @@ function file_tag_save_file($uid, $item, $file) return true; } +<<<<<<< HEAD function file_tag_unsave_file($uid, $item, $file, $cat = false) { +======= +function file_tag_unsave_file($uid, $item, $file, $cat = false) { + require_once "include/files.php"; + + $result = false; +>>>>>>> more spaces + some curly spaces added if (! intval($uid)) { return false; } From ad9bd7bb72cf7fa22d4d87b690e379056e42df5b Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 22:26:07 +0100 Subject: [PATCH 24/49] better comment added by @Hypolite Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 31d94e77e..d1870254b 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -1974,11 +1974,9 @@ class DFRN */ if (!DBM::is_result($r)) { // database record did not get created. Quietly give up. - killme(); + return false; } - $fid = $r[0]["id"]; - $hash = random_string(); $r = q( From 0e5daa86df145481e8464b5b1d6342d623d8676b Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 22:33:12 +0100 Subject: [PATCH 25/49] added curely branches + reverted back to old behaviour (may come back one day) Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index d1870254b..20c59fc2f 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -2057,7 +2057,11 @@ class DFRN ); if (!DBM::is_result($r)) { + /* + * @TODO maybe one day: logger("Query failed to execute, no result returned in " . __FUNCTION__); + killme(); + */ return false; } From a5dba628c44fe64d2f4138dce7ef1cec13148770 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 22:49:05 +0100 Subject: [PATCH 26/49] old behaviour restored Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 20c59fc2f..897f6a8cc 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -2360,7 +2360,11 @@ class DFRN ); if (!DBM::is_result($r)) { + /* + * @TODO maybe one day: logger("Query failed to execute, no result returned in " . __FUNCTION__); + killme(); + */ return false; } From 1567eb0791d61b5718d30ecc84e249347d71b1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20H=C3=A4der?= Date: Tue, 15 May 2018 23:25:30 +0200 Subject: [PATCH 27/49] fixed another left-over ... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Roland Häder --- include/text.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/text.php b/include/text.php index 9d8ba2604..f1bc51232 100644 --- a/include/text.php +++ b/include/text.php @@ -1883,15 +1883,8 @@ function file_tag_save_file($uid, $item, $file) return true; } -<<<<<<< HEAD function file_tag_unsave_file($uid, $item, $file, $cat = false) { -======= -function file_tag_unsave_file($uid, $item, $file, $cat = false) { - require_once "include/files.php"; - - $result = false; ->>>>>>> more spaces + some curly spaces added if (! intval($uid)) { return false; } From 4ca26fd3bc60817752e0c45679283a1217ac1e68 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Thu, 23 Mar 2017 23:37:58 +0100 Subject: [PATCH 28/49] fixed comment and explained one Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 897f6a8cc..93cc8f2ab 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -1974,9 +1974,11 @@ class DFRN */ if (!DBM::is_result($r)) { // database record did not get created. Quietly give up. - return false; + killme(); } + $fid = $r[0]["id"]; + $hash = random_string(); $r = q( From 506657ec424d78e368da99e6ba31d41c3c5c8297 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Sat, 25 Mar 2017 13:01:41 +0100 Subject: [PATCH 29/49] removed TODO, one day this all needs refacturizing ... Signed-off-by: Roland Haeder --- mod/profiles.php | 1 - 1 file changed, 1 deletion(-) diff --git a/mod/profiles.php b/mod/profiles.php index 3ceb8c664..9c925924f 100644 --- a/mod/profiles.php +++ b/mod/profiles.php @@ -479,7 +479,6 @@ function profiles_post(App $a) { intval(local_user()) ); - /// @TODO decide to use dbm::is_result() here and check $r if ($r) { info(L10n::t('Profile updated.') . EOL); } From 2d744fa18978fa83135197c4b400099c09754b2a Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Sat, 25 Mar 2017 13:37:15 +0100 Subject: [PATCH 30/49] PHP5 does not support native type-hints, except `array` + used dbm::is_result() Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 93cc8f2ab..6a64a8295 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -96,7 +96,6 @@ class DFRN * @param boolean $onlyheader Output only the header without content? (Default is "no") * * @return string DFRN feed entries - * @todo Find proper type-hints */ public static function feed($dfrn_id, $owner_nick, $last_update, $direction = 0, $onlyheader = false) { From 593d1feca621cf48e8c4afe140c6f8ed476e5c7a Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Sat, 25 Mar 2017 13:46:28 +0100 Subject: [PATCH 31/49] was a bit confusing for me or I was not sleeping to much ... Signed-off-by: Roland Haeder --- src/Protocol/DFRN.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 6a64a8295..a9e836499 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -2058,11 +2058,7 @@ class DFRN ); if (!DBM::is_result($r)) { - /* - * @TODO maybe one day: logger("Query failed to execute, no result returned in " . __FUNCTION__); - killme(); - */ return false; } @@ -2361,11 +2357,7 @@ class DFRN ); if (!DBM::is_result($r)) { - /* - * @TODO maybe one day: logger("Query failed to execute, no result returned in " . __FUNCTION__); - killme(); - */ return false; } From 7c048b50778ac0ad8a8be7b8219847732067f205 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Sat, 25 Mar 2017 15:06:54 +0100 Subject: [PATCH 32/49] Opps, cutted this out by accident ... Signed-off-by: Roland Haeder --- mod/photos.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mod/photos.php b/mod/photos.php index e823da59f..92fdfb048 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -1232,6 +1232,8 @@ function photos_content(App $a) if (!Config::get('system', 'no_count', false)) { $order_field = defaults($_GET, 'order', ''); if ($order_field === 'posted') { + if (!Config::get('system', 'no_count', false)) { + if ($_GET['order'] === 'posted') { $order = 'ASC'; } else { $order = 'DESC'; From 6cb26f884e4622ead2469663b8afd055971379b9 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Sun, 2 Apr 2017 21:29:06 +0200 Subject: [PATCH 33/49] Fixes for composer: - ./vendor/ is being prepared by executing `utils/composer.phar install`, no need to have it around - converted .gitignore to LF line-ending (more common over the whole project) - no need to have them around ... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Roland Häder --- mod/photos.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/mod/photos.php b/mod/photos.php index 92fdfb048..727ecb3cc 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -1229,9 +1229,6 @@ function photos_content(App $a) * The query leads to a really intense used index. * By now we hide it if someone wants to. */ - if (!Config::get('system', 'no_count', false)) { - $order_field = defaults($_GET, 'order', ''); - if ($order_field === 'posted') { if (!Config::get('system', 'no_count', false)) { if ($_GET['order'] === 'posted') { $order = 'ASC'; From f806fa91b1cc0726395986a6a696a9ab68a39433 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 2 May 2018 19:26:15 +0000 Subject: [PATCH 34/49] Replace old database queries with the new ones --- mod/message.php | 2 +- mod/settings.php | 2 +- src/Protocol/DFRN.php | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mod/message.php b/mod/message.php index 987babf74..1e49311d3 100644 --- a/mod/message.php +++ b/mod/message.php @@ -148,7 +148,7 @@ function message_content(App $a) $cmd = $a->argv[1]; if ($cmd === 'drop') { - if (dba::delete('mail', ['id' => $a->argv[2], 'uid' => local_user()])) { + if (dba::delete('mail', ['id' => $a->argv[2]])) { info(L10n::t('Message deleted.') . EOL); } //goaway(System::baseUrl(true) . '/message' ); diff --git a/mod/settings.php b/mod/settings.php index cd45cc507..a869269d5 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -149,7 +149,7 @@ function settings_post(App $a) check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth'); $key = $_POST['remove']; - dba::delete('tokens', ['id' => $key, 'uid' => local_user()]); + dba::delete('tokens', ['id' => $key]); goaway(System::baseUrl(true)."/settings/oauth/"); return; } diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index a9e836499..ea14aa6ff 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -2080,7 +2080,6 @@ class DFRN 'confirm' => $relocate["confirm"], 'notify' => $relocate["notify"], 'poll' => $relocate["poll"], 'site-pubkey' => $relocate["sitepubkey"]]; $condition = ["(`id` = ?) OR (`nurl` = ?)", $importer["id"], normalise_link($old["url"])]; - dba::update('contact', $fields, $condition); // @TODO No dba:update here? dba::update('contact', $fields, $condition); From 8ad523fbc8e346cd21f60e69ef27d3ab039abbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20H=C3=A4der?= Date: Wed, 20 Jun 2018 22:12:59 +0200 Subject: [PATCH 35/49] Continued a bit: - removed/fixed whitespaces and mixture of spaces/tabs (some) - added new-line character at end of files (POSIX-compilant) - reverted some code which I had messed up (compared to upstream/develop) - removed duplicate dba::update() invocation in src/Protocol/DFRN.php - also removed no longer valid TODO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Roland Häder --- doc/Addons.md | 8 ++++---- doc/Tags-and-Mentions.md | 6 +++--- mod/message.php | 4 +++- mod/photos.php | 4 +++- mod/settings.php | 2 +- mods/sample-nginx.config | 5 ++--- src/Protocol/DFRN.php | 1 - view/theme/frio/js/mod_notifications.js | 2 +- view/theme/quattro/templates/nav.tpl | 14 +++++++------- 9 files changed, 24 insertions(+), 22 deletions(-) diff --git a/doc/Addons.md b/doc/Addons.md index 22b34fa62..062f90795 100644 --- a/doc/Addons.md +++ b/doc/Addons.md @@ -72,12 +72,12 @@ JavaScript addon hooks --- #### PHP part -Make sure your JavaScript addon file (addon/*addon_name*/*addon_name*.js) is listed in the document response. +Make sure your JavaScript addon file (addon/*addon_name*/*addon_name*.js) is listed in the document response. In your addon install function, add: Addon::registerHook('template_vars', 'addon//.php', '_template_vars'); - + In your addon uninstall function, add: Addon::unregisterHook('template_vars', 'addon//.php', '_template_vars'); @@ -104,7 +104,7 @@ Register your addon hooks in file 'addon/*addon_name*/*addon_name*.js'. No arguments are provided to your JavaScript callback function. Example: function myhook_function() { - + } Modules @@ -668,4 +668,4 @@ Here is a complete list of all hook callbacks with file locations (as of 01-Apr- ### view/js/main.js - callAddonHooks("postprocess_liveupdate"); \ No newline at end of file + callAddonHooks("postprocess_liveupdate"); diff --git a/doc/Tags-and-Mentions.md b/doc/Tags-and-Mentions.md index 5b046228f..020214457 100644 --- a/doc/Tags-and-Mentions.md +++ b/doc/Tags-and-Mentions.md @@ -18,7 +18,7 @@ You can tag **persons who are in your social circle** by adding the "@"-sign in * @mike+151 - this form is used by the drop-down tag completion tool. It indicates the contact whose nickname is mike and whose contact identifier number is 151. The drop-down tool may be used to resolve people with duplicate nicknames. You can tag a person on a different network or one that is **not in your social circle** by using the following notation: - + * @mike@macgirvin.com - This is called a "remote mention" and can only be an email-style locator, not a web URL. Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts. @@ -27,7 +27,7 @@ The exception is an ongoing conversation started from a contact of both you and This is a spam prevention measure. Remote mentions are delivered using the OStatus protocol. -This protocol is used by Friendica and GNU Social and several other systems like Mastodon, but is not currently implemented in Diaspora. +This protocol is used by Friendica and GNU Social and several other systems like Mastodon, but is not currently implemented in Diaspora. As the OStatus protocol allows this Friendica user can be @-mentioned by users from platforms using this protocol in conversations if the "Enable OStatus support" is activated on the Friendica node. These @-mentions wont be blocked, even if there is no relationship between the sender and the receiver of the message. @@ -52,5 +52,5 @@ The same rules apply as with names that spaces within tags are represented by th It is therefore not possible to create a tag whose target contains an underscore. Topical tags are also not linked if they are purely numeric, e.g. #1. -If you wish to use a numerica hashtag, please add some descriptive text such as #2012-elections. +If you wish to use a numerica hashtag, please add some descriptive text such as #2012-elections. diff --git a/mod/message.php b/mod/message.php index 1e49311d3..ddd5d03d6 100644 --- a/mod/message.php +++ b/mod/message.php @@ -141,6 +141,7 @@ function message_content(App $a) '$cancel' => L10n::t('Cancel'), ]); } + // Now check how the user responded to the confirmation query if ($_REQUEST['canceled']) { goaway($_SESSION['return_url']); @@ -148,9 +149,10 @@ function message_content(App $a) $cmd = $a->argv[1]; if ($cmd === 'drop') { - if (dba::delete('mail', ['id' => $a->argv[2]])) { + if (dba::delete('mail', ['id' => $a->argv[2], 'uid' => local_user()])) { info(L10n::t('Message deleted.') . EOL); } + //goaway(System::baseUrl(true) . '/message' ); goaway($_SESSION['return_url']); } else { diff --git a/mod/photos.php b/mod/photos.php index 727ecb3cc..7bf857e0e 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -1230,7 +1230,9 @@ function photos_content(App $a) * By now we hide it if someone wants to. */ if (!Config::get('system', 'no_count', false)) { - if ($_GET['order'] === 'posted') { + $order_field = defaults($_GET, 'order', ''); + + if ($order_field === 'posted') { $order = 'ASC'; } else { $order = 'DESC'; diff --git a/mod/settings.php b/mod/settings.php index a869269d5..cd45cc507 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -149,7 +149,7 @@ function settings_post(App $a) check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth'); $key = $_POST['remove']; - dba::delete('tokens', ['id' => $key]); + dba::delete('tokens', ['id' => $key, 'uid' => local_user()]); goaway(System::baseUrl(true)."/settings/oauth/"); return; } diff --git a/mods/sample-nginx.config b/mods/sample-nginx.config index eb4ae4577..829bfc70a 100644 --- a/mods/sample-nginx.config +++ b/mods/sample-nginx.config @@ -83,10 +83,9 @@ server { # rewrite to front controller as default rule location / { if (!-e $request_filename) { - rewrite ^(.*)$ /index.php?pagename=$1; + rewrite ^(.*)$ /index.php?pagename=$1; } } - # make sure webfinger and other well known services aren't blocked # by denying dot files and rewrite request to the front controller @@ -96,7 +95,7 @@ server { rewrite ^(.*)$ /index.php?pagename=$1; } } - + include mime.types; # block these file types diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index ea14aa6ff..511cd0444 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -2081,7 +2081,6 @@ class DFRN 'poll' => $relocate["poll"], 'site-pubkey' => $relocate["sitepubkey"]]; $condition = ["(`id` = ?) OR (`nurl` = ?)", $importer["id"], normalise_link($old["url"])]; - // @TODO No dba:update here? dba::update('contact', $fields, $condition); Contact::updateAvatar($relocate["avatar"], $importer["importer_uid"], $importer["id"], true); diff --git a/view/theme/frio/js/mod_notifications.js b/view/theme/frio/js/mod_notifications.js index 9574ea02b..9db3eabf0 100644 --- a/view/theme/frio/js/mod_notifications.js +++ b/view/theme/frio/js/mod_notifications.js @@ -7,7 +7,7 @@ var introID = location.pathname.split("/").pop(); $(document).ready(function(){ - // Since only the DIV's inside the notification-list are marked + // Since only the DIV's inside the notification-list are marked // with the class "unseen", we need some js to transfer this class // to the parent li list-elements. if($(".notif-item").hasClass("unseen")) { diff --git a/view/theme/quattro/templates/nav.tpl b/view/theme/quattro/templates/nav.tpl index b5ae5c2d0..c021f977a 100644 --- a/view/theme/quattro/templates/nav.tpl +++ b/view/theme/quattro/templates/nav.tpl @@ -66,22 +66,22 @@
  • {{$emptynotifications}}
  • - - {{/if}} - + + {{/if}} +
  • {{$nav.tos.1}}
  • {{/if}} + - + {{if $nav.help}}