Commit c1ce211b authored by Mario's avatar Mario
Browse files

Merge branch 'dev' into 6.2RC

parents 3d40ea7f 1aa78263
Hubzilla 6.2 (2021-09-??)
- Check post_mail permission when receiving a direct message
- Refactor actor store and cache mechanism
- Add optional strict flag (false) to base64url_decode() which is passed on to base64_decode()
- Update russian translations
- Add optional force flag (false) to Activity::actor_store()
- Improved icon lookup for actor objects
- Improved desktop notifications
- Make Hubzilla installable as PWA in supported browsers
- Capitalized widget titles (redbasic)
- Make images loaded counter showing % loaded instead of actual image count
- Deprecate optional channel menu in favor of extended app bin
- Implement infrastructure to provide an app install widget for modules
- Implement app descriptions
- Implement app drag and drop sorting
- Implement app drag and drop pinning/starring
- Update polish translations
- Update to bootstrap 5 and adjust templates
Bugfixes
- Fix anonymous comments for cards and articles
- Resolve regex interference between contact and channel autocomplete
- Fix files stored in wrong directory when uploaded in the photo module
- Fix records with empty public key selected for OWA
- Fix import_author_zot failing if primary is both dead and unknown - issue #1599
- Fix regression with bookmarks
- Fix pubstream notifications link redirecting to mod hq when not local
- Cleanup legacy mail leftovers - issue #1595
- Fix getimagesize() called with empty path
- Fix display issue caused by redundant closing div tag
Addons
- Pubcrawl: fix post to forum - issue #159
- Diaspora: implement post_mail permissions for direct messages
- Pubcrawl: implement post_mail permissions for direct messages
- Pubcrawl: deprecate asencode_person() in favor of Activity::encode_person()
- Pubcrawl: implement Activity::get_actor_collections() to reflect core actor store changes
- Diaspora: fix use of deprecated function
- Twitter: prevent re-retweeting of a post from a clone if it has already been sent
- Totp: use platform name instead of banner text
- Pubcrawl: use force flag when updating an actor
- Sse: do not process possible empty hashes
- Implement app descriptions for addons that already support the apps infrastructure
Hubzilla 6.0.1 (2021-07-27)
- Fix regression in notification handling introduced in 6.0
- Fix regression in regard to unified session page load times introduced in 6.0
......
......@@ -65,9 +65,9 @@ class Permissions {
'write_wiki' => t('Can write to my wiki pages'),
'post_wall' => t('Can post on my channel (wall) page'),
'post_comments' => t('Can comment on or like my posts'),
'post_mail' => t('Can send me private mail messages'),
'post_mail' => t('Can send me direct messages'),
'post_like' => t('Can like/dislike profiles and profile things'),
'tag_deliver' => t('Can forward to all my channel connections via ! mentions in posts'),
'tag_deliver' => t('Can forward direct messages to all my channel connections (forum)'),
'chat' => t('Can chat with me'),
'republish' => t('Can source my public posts in derived channels'),
'delegate' => t('Can administer my channel')
......
......@@ -126,7 +126,10 @@ class Onepoll {
$max = intval(get_config('system', 'max_imported_posts', 30));
if (intval($max)) {
$cl = get_xconfig($contact['abook_xchan'], 'activitypub', 'collections');
$cl = Activity::get_actor_collections($contact['abook_xchan']);
if(empty($cl)) {
$cl = get_xconfig($contact['abook_xchan'], 'activitypub', 'collections');
}
if (is_array($cl) && array_key_exists('outbox', $cl)) {
$url = $cl['outbox'];
......
......@@ -8,6 +8,8 @@ use Zotlabs\Access\PermissionRoles;
use Zotlabs\Access\Permissions;
use Zotlabs\Daemon\Master;
use Zotlabs\Web\HTTPSig;
use Zotlabs\Lib\XConfig;
use Zotlabs\Lib\Libzot;
require_once('include/event.php');
require_once('include/html2plain.php');
......@@ -1109,6 +1111,7 @@ class Activity {
call_hooks('encode_person', $arr);
$ret = $arr['encoded'];
return $ret;
}
......@@ -1537,6 +1540,49 @@ class Activity {
return;
}
/* not implemented
if (array_key_exists('movedTo',$person_obj) && $person_obj['movedTo'] && ! is_array($person_obj['movedTo'])) {
$tgt = self::fetch($person_obj['movedTo']);
if (is_array($tgt)) {
self::actor_store($person_obj['movedTo'],$tgt);
ActivityPub::move($person_obj['id'],$tgt);
}
return;
}
*/
$ap_hubloc = null;
$hublocs = self::get_actor_hublocs($url);
if ($hublocs) {
foreach ($hublocs as $hub) {
if ($hub['hubloc_network'] === 'activitypub') {
$ap_hubloc = $hub;
}
if ($hub['hubloc_network'] === 'zot6') {
Libzot::update_cached_hubloc($hub);
}
}
}
if ($ap_hubloc) {
// we already have a stored record. Determine if it needs updating.
if ($ap_hubloc['hubloc_updated'] < datetime_convert('UTC','UTC',' now - 3 days') || $force) {
$person_obj = self::fetch($url);
}
else {
return;
}
}
if (isset($person_obj['id'])) {
$url = $person_obj['id'];
}
if (! $url) {
return;
}
$inbox = $person_obj['inbox'];
// invalid identity
......@@ -1545,6 +1591,9 @@ class Activity {
return;
}
// store the actor record in XConfig
XConfig::Set($url, 'system', 'actor_record', $person_obj);
$name = $person_obj['name'];
if (!$name) {
$name = $person_obj['preferredUsername'];
......@@ -1603,20 +1652,6 @@ class Activity {
$profile = $url;
}
$collections = [];
if ($inbox) {
$collections['inbox'] = $inbox;
if (array_key_exists('outbox', $person_obj))
$collections['outbox'] = $person_obj['outbox'];
if (array_key_exists('followers', $person_obj))
$collections['followers'] = $person_obj['followers'];
if (array_key_exists('following', $person_obj))
$collections['following'] = $person_obj['following'];
if (array_key_exists('endpoints', $person_obj) && array_key_exists('sharedInbox', $person_obj['endpoints']))
$collections['sharedInbox'] = $person_obj['endpoints']['sharedInbox'];
}
if (array_key_exists('publicKey', $person_obj) && array_key_exists('publicKeyPem', $person_obj['publicKey'])) {
if ($person_obj['id'] === $person_obj['publicKey']['owner']) {
$pubkey = $person_obj['publicKey']['publicKeyPem'];
......@@ -1702,10 +1737,6 @@ class Activity {
);
}
if ($collections) {
set_xconfig($url, 'activitypub', 'collections', $collections);
}
$photos = import_xchan_photo($icon, $url);
q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'",
dbescdate(datetime_convert('UTC', 'UTC', $photos[5])),
......@@ -1784,12 +1815,27 @@ class Activity {
}
else {
if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) {
logger('no permission');
logger('no send_stream permission');
return;
}
$s['owner_xchan'] = $s['author_xchan'] = $observer_hash;
}
if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips)))
$s['item_private'] = 1;
if (array_key_exists('directMessage', $act->obj) && intval($act->obj['directMessage'])) {
$s['item_private'] = 2;
}
if (intval($s['item_private']) === 2) {
if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) {
logger('no post_mail permission');
return;
}
}
$abook = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1",
dbesc($observer_hash),
intval($channel['channel_id'])
......@@ -1938,14 +1984,6 @@ class Activity {
}
}
if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips)))
$s['item_private'] = 1;
if (array_key_exists('directMessage', $act->obj) && intval($act->obj['directMessage'])) {
$s['item_private'] = 2;
}
set_iconfig($s, 'activitypub', 'recips', $act->raw_recips);
if ($parent) {
set_iconfig($s, 'activitypub', 'rawmsg', $act->raw, 1);
......@@ -2641,6 +2679,11 @@ class Activity {
$allowed = true;
}
if (intval($item['item_private']) === 2) {
if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) {
$allowed = false;
}
}
if ($is_sys_channel) {
......@@ -3504,4 +3547,53 @@ class Activity {
}
static function get_cached_actor($id) {
return (XConfig::Get($id,'system','actor_record'));
}
static function get_actor_hublocs($url, $options = 'all') {
$hublocs = false;
switch ($options) {
case 'activitypub':
$hublocs = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_hash = '%s' and hubloc_deleted = 0 ",
dbesc($url)
);
break;
case 'zot6':
$hublocs = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_id_url = '%s' and hubloc_deleted = 0 ",
dbesc($url)
);
break;
case 'all':
default:
$hublocs = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where ( hubloc_id_url = '%s' OR hubloc_hash = '%s' ) and hubloc_deleted = 0 ",
dbesc($url),
dbesc($url)
);
break;
}
return $hublocs;
}
static function get_actor_collections($url) {
$ret = [];
$actor_record = XConfig::Get($url,'system','actor_record');
if (! $actor_record) {
return $ret;
}
foreach ( [ 'inbox','outbox','followers','following' ] as $collection) {
if (isset($actor_record[$collection]) && $actor_record[$collection]) {
$ret[$collection] = $actor_record[$collection];
}
}
if (array_path_exists('endpoints/sharedInbox',$actor_record) && $actor_record['endpoints']['sharedInbox']) {
$ret['sharedInbox'] = $actor_record['endpoints']['sharedInbox'];
}
return $ret;
}
}
......@@ -300,17 +300,8 @@ class ActivityStreams {
function get_actor($property, $base = '', $namespace = '') {
$x = $this->get_property_obj($property, $base, $namespace);
if ($this->is_url($x)) {
// SECURITY: If we have already stored the actor profile, re-generate it
// from cached data - don't refetch it from the network
$r = q("select * from xchan join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s'",
dbesc($x)
);
if ($r) {
$r = Libzot::zot_record_preferred($r);
$y = Activity::encode_person($r);
$y['cached'] = true;
$y = Activity::get_cached_actor($x);
if ($y) {
return $y;
}
}
......
......@@ -854,8 +854,8 @@ class Enotify {
'photo' => $item[$who]['xchan_photo_s'],
'when' => (($edit) ? datetime_convert('UTC', date_default_timezone_get(), $item['edited']) : datetime_convert('UTC', date_default_timezone_get(), $item['created'])),
'class' => (intval($item['item_unseen']) ? 'notify-unseen' : 'notify-seen'),
'b64mid' => (($item['mid']) ? 'b64.' . base64url_encode($item['mid']) : ''),
//'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? 'b64.' . base64url_encode($item['thr_parent']) : 'b64.' . base64url_encode($item['mid'])),
'b64mid' => (($item['mid']) ? gen_link_id($item['mid']) : ''),
//'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? gen_link_id($item['thr_parent']) : gen_link_id($item['mid'])),
'thread_top' => (($item['item_thread_top']) ? true : false),
'message' => bbcode(escape_tags($itemem_text)),
'body' => htmlentities(html2plain(bbcode($item['body'], ['drop_media', true]), 75, true), ENT_QUOTES, 'UTF-8', false),
......@@ -882,7 +882,7 @@ class Enotify {
$mid = basename($tt['link']);
$b64mid = ((strpos($mid, 'b64.') === 0) ? $mid : 'b64.' . base64url_encode($mid));
$b64mid = gen_link_id($mid);
$x = [
'notify_link' => (($tt['ntype'] === NOTIFY_MAIL) ? $tt['link'] : z_root() . '/notify/view/' . $tt['id']),
'name' => $tt['xname'],
......@@ -910,7 +910,7 @@ class Enotify {
'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['abook_created']),
'hclass' => ('notify-unseen'),
'message' => t('added your channel')
];
];
}
......
......@@ -1601,6 +1601,12 @@ class Libzot {
$friendofriend = true;
}
if (intval($arr['item_private']) === 2) {
if (!perm_is_allowed($channel['channel_id'], $sender, 'post_mail')) {
$allowed = false;
}
}
if (!$allowed) {
logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}");
$DR->update('permission denied');
......@@ -3154,4 +3160,10 @@ class Libzot {
}
static function update_cached_hubloc($hubloc) {
if ($hubloc['hubloc_updated'] > datetime_convert('UTC','UTC','now - 1 week') || $hubloc['hubloc_url'] === z_root()) {
return;
}
self::refresh( [ 'hubloc_id_url' => $hubloc['hubloc_id_url'] ] );
}
}
......@@ -503,7 +503,7 @@ class ThreadItem {
'wait' => t('Please wait'),
'thread_level' => $thread_level,
'settings' => $settings,
'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? 'b64.' . base64url_encode($item['thr_parent']) : '')
'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? gen_link_id($item['thr_parent']) : '')
);
$arr = array('item' => $item, 'output' => $tmp_item);
......@@ -842,7 +842,7 @@ class ThreadItem {
'$cipher' => $conv->get_cipher(),
'$sourceapp' => \App::$sourcename,
'$observer' => get_observer_hash(),
'$anoncomments' => ((($conv->get_mode() === 'channel' || $conv->get_mode() === 'display') && perm_is_allowed($conv->get_profile_owner(),'','post_comments')) ? true : false),
'$anoncomments' => ((in_array($conv->get_mode(), ['channel', 'display', 'cards', 'articles']) && perm_is_allowed($conv->get_profile_owner(),'','post_comments')) ? true : false),
'$anonname' => [ 'anonname', t('Your full name (required)') ],
'$anonmail' => [ 'anonmail', t('Your email address (required)') ],
'$anonurl' => [ 'anonurl', t('Your website URL (optional)') ]
......
......@@ -107,13 +107,11 @@ class Channel extends Controller {
// Somebody may attempt an ActivityStreams fetch on one of our message permalinks
// Make it do the right thing.
$mid = ((x($_REQUEST, 'mid')) ? $_REQUEST['mid'] : '');
if ($mid && strpos($mid, 'b64.') === 0) {
$decoded = @base64url_decode(substr($mid, 4));
if ($decoded) {
$mid = $decoded;
}
$mid = ((x($_REQUEST, 'mid')) ? unpack_link_id($_REQUEST['mid']) : '');
if ($mid === false) {
http_status_exit(404, 'Not found');
}
if ($mid) {
$obj = null;
if (strpos($mid, z_root() . '/item/') === 0) {
......@@ -158,15 +156,19 @@ class Channel extends Controller {
profile_load($which, $profile);
// Add Opengraph markup
$mid = ((x($_REQUEST, 'mid')) ? $_REQUEST['mid'] : '');
if (strpos($mid, 'b64.') === 0)
$mid = @base64url_decode(substr($mid, 4));
$mid = ((x($_REQUEST, 'mid')) ? unpack_link_id($_REQUEST['mid']) : '');
if ($mid)
if ($mid === false) {
notice(t('Malformed message id.') . EOL);
return;
}
if ($mid) {
$r = q("SELECT * FROM item WHERE mid = '%s' AND uid = %d AND item_private = 0 LIMIT 1",
dbesc($mid),
intval($channel['channel_id'])
);
}
opengraph_add_meta((isset($r) && count($r) ? $r[0] : []), $channel);
}
......@@ -177,12 +179,11 @@ class Channel extends Controller {
$category = $datequery = $datequery2 = '';
$mid = ((x($_REQUEST, 'mid')) ? $_REQUEST['mid'] : '');
if (strpos($mid, 'b64.') === 0)
$decoded = @base64url_decode(substr($mid, 4));
if (isset($decoded))
$mid = $decoded;
$mid = ((x($_REQUEST, 'mid')) ? unpack_link_id($_REQUEST['mid']) : '');
if ($mid === false) {
notice(t('Malformed message id.') . EOL);
return;
}
$datequery = ((x($_GET, 'dend') && is_a_date_arg($_GET['dend'])) ? notags($_GET['dend']) : '');
$datequery2 = ((x($_GET, 'dbegin') && is_a_date_arg($_GET['dbegin'])) ? notags($_GET['dbegin']) : '');
......@@ -433,8 +434,8 @@ class Channel extends Controller {
if ((!$update) && (!$load)) {
if (isset($decoded))
$mid = 'b64.' . base64url_encode($mid);
//if we got a decoded hash we must encode it again before handing to javascript
$mid = gen_link_id($mid);
// This is ugly, but we can't pass the profile_uid through the session to the ajax updater,
// because browser prefetching might change it on us. We have to deliver it with the page.
......
<?php
namespace Zotlabs\Module;
use App;
require_once("include/bbcode.php");
require_once('include/security.php');
require_once('include/conversation.php');
......@@ -34,11 +36,12 @@ class Display extends \Zotlabs\Web\Controller {
}
}
if($_REQUEST['mid'])
$item_hash = $_REQUEST['mid'];
if($_REQUEST['mid']) {
$item_hash = unpack_link_id($_REQUEST['mid']);
}
if(! $item_hash) {
\App::$error = 404;
if(!$item_hash) {
App::$error = 404;
notice( t('Item not found.') . EOL);
return;
}
......@@ -47,7 +50,7 @@ class Display extends \Zotlabs\Web\Controller {
if(local_channel() && (! $update)) {
$channel = \App::get_channel();
$channel = App::get_channel();
$channel_acl = array(
'allow_cid' => $channel['channel_allow_cid'],
......@@ -92,10 +95,10 @@ class Display extends \Zotlabs\Web\Controller {
$target_item = null;
if(strpos($item_hash,'b64.') === 0)
$decoded = @base64url_decode(substr($item_hash,4));
if($decoded)
$item_hash = $decoded;
if ($item_hash === false) {
notice(t('Malformed message id.') . EOL);
return;
}
$r = q("select id, uid, mid, parent, parent_mid, thr_parent, verb, item_type, item_deleted, author_xchan, item_blocked from item where mid = '%s' limit 1",
dbesc($item_hash)
......@@ -110,7 +113,7 @@ class Display extends \Zotlabs\Web\Controller {
);
if($x) {
// not yet ready for prime time
// \App::$poi = $x[0];
// App::$poi = $x[0];
}
//if the item is to be moderated redirect to /moderate
......@@ -189,17 +192,15 @@ class Display extends \Zotlabs\Web\Controller {
// if the target item is not a post (eg a like) we want to address its thread parent
//$mid = ((($target_item['verb'] == ACTIVITY_LIKE) || ($target_item['verb'] == ACTIVITY_DISLIKE)) ? $target_item['thr_parent'] : $target_item['mid']);
$mid = $target_item['mid'];
// if we got a decoded hash we must encode it again before handing to javascript
if($decoded)
$mid = 'b64.' . base64url_encode($mid);
$mid = gen_link_id($target_item['mid']);
$o .= '<div id="live-display"></div>' . "\r\n";
$o .= "<script> var profile_uid = " . ((intval(local_channel())) ? local_channel() : (-1))
. "; var netargs = '?f='; var profile_page = " . \App::$pager['page'] . "; </script>\r\n";
. "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n";
\App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array(
App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array(
'$baseurl' => z_root(),
'$pgtype' => 'display',
'$uid' => '0',
......@@ -215,7 +216,7 @@ class Display extends \Zotlabs\Web\Controller {
'$dm' => '0',
'$nouveau' => '0',
'$wall' => '0',
'$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1),
'$page' => ((App::$pager['page'] != 1) ? App::$pager['page'] : 1),
'$list' => ((x($_REQUEST,'list')) ? intval($_REQUEST['list']) : 0),
'$search' => '',
'$xchan' => '',
......@@ -233,7 +234,7 @@ class Display extends \Zotlabs\Web\Controller {
head_add_link([
'rel' => 'alternate',
'type' => 'application/json+oembed',
'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . \App::$query_string),
'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . App::$query_string),
'title' => 'oembed'
]);
......@@ -355,7 +356,7 @@ class Display extends \Zotlabs\Web\Controller {
}
$o .= '</noscript>';
\App::$page['title'] = (($items[0]['title']) ? $items[0]['title'] . " - " . \App::$page['title'] : \App::$page['title']);
App::$page['title'] = (($items[0]['title']) ? $items[0]['title'] . " - " . App::$page['title'] : App::$page['title']);
$o .= conversation($items, 'display', $update, 'client');
}
......@@ -368,7 +369,7 @@ class Display extends \Zotlabs\Web\Controller {
'$version' => xmlify(\Zotlabs\Lib\System::get_project_version()),
'$generator' => xmlify(\Zotlabs\Lib\System::get_platform_name()),
'$generator_uri' => 'https://hubzilla.org',
'$feed_id' => xmlify(\App::$cmd),
'$feed_id' => xmlify(App::$cmd),
'$feed_title' => xmlify(t('Article')),
'$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now', ATOM_TIME)),
'$author' => '',
......
......@@ -12,24 +12,12 @@ class Dreport extends \Zotlabs\Web\Controller {
}
$table = 'item';
$channel = \App::get_channel();
$mid = ((argc() > 1) ? unpack_link_id(argv(1)) : '');
$mid = ((argc() > 1) ? argv(1) : '');
$encoded_mid = '';
if(strpos($mid,'b64.') === 0) {
$encoded_mid = $mid;
$mid = @base64url_decode(substr($mid,4));
}
if($mid === 'push') {
$table = 'push';
$mid = ((argc() > 2) ? argv(2) : '');
if(strpos($mid,'b64.') === 0) {
$encoded_mid = $mid;
$mid = @base64url_decode(substr($mid,4));
}
$mid = ((argc() > 2) ? unpack_link_id(argv(2)) : '');
if($mid) {
$i = q("select id from item where mid = '%s' and uid = %d and ( author_xchan = '%s' or ( owner_xchan = '%s' and item_wall = 1 )) ",
......@@ -43,7 +31,7 @@ class Dreport extends \Zotlabs\Web\Controller {
}
}
sleep(3);
goaway(z_root() . '/dreport/' . (($encoded_mid) ? $encoded_mid : $mid));
goaway(z_root() . '/dreport/' . gen_link_id($mid));
}
if(! $mid) {
......
......@@ -23,15 +23,21 @@ class Hq extends \Zotlabs\Web\Controller {
function get($update = 0, $load = false) {
if(!local_channel())
if(!local_channel()) {
return;
}