aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMario <mario@mariovavti.com>2020-10-05 12:01:46 +0000
committerMario <mario@mariovavti.com>2020-10-05 12:01:46 +0000
commite622802c249bbd084a41ef24fe87e99e9f7d9257 (patch)
treeff70f88899684454f818e6fc53050f84bb1fb60e
parent869ce64f875dab7acc7833a9fc2d8577b705d35f (diff)
parent244c8edfca6cc7ab48c337bda37ab70bfe819413 (diff)
downloadvolse-hubzilla-e622802c249bbd084a41ef24fe87e99e9f7d9257.tar.gz
volse-hubzilla-e622802c249bbd084a41ef24fe87e99e9f7d9257.tar.bz2
volse-hubzilla-e622802c249bbd084a41ef24fe87e99e9f7d9257.zip
Merge branch 'dev' into 5.0RC
-rw-r--r--Zotlabs/Daemon/Notifier.php1
-rw-r--r--Zotlabs/Lib/Activity.php96
-rw-r--r--Zotlabs/Lib/Libzot.php47
-rw-r--r--Zotlabs/Module/Activity.php94
-rw-r--r--Zotlabs/Module/Item.php100
5 files changed, 286 insertions, 52 deletions
diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php
index ac35b4558..97889da34 100644
--- a/Zotlabs/Daemon/Notifier.php
+++ b/Zotlabs/Daemon/Notifier.php
@@ -395,7 +395,6 @@ class Notifier {
logger('target_item: ' . print_r($target_item,true), LOGGER_DEBUG);
logger('encoded: ' . print_r($activity,true), LOGGER_DEBUG);
-
// Send comments to the owner to re-deliver to everybody in the conversation
// We only do this if the item in question originated on this site. This prevents looping.
// To clarify, a site accepting a new comment is responsible for sending it to the owner for relay.
diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php
index 7cc77f124..9a31304d1 100644
--- a/Zotlabs/Lib/Activity.php
+++ b/Zotlabs/Lib/Activity.php
@@ -319,6 +319,26 @@ class Activity {
$ret = Activity::encode_object($i['obj']);
}
+ if (intval($i['item_deleted'])) {
+ $ret['type'] = 'Tombstone';
+ $ret['formerType'] = $objtype;
+ $ret['id'] = $i['mid'];
+ if($i['id'] != $i['parent'])
+ $ret['inReplyTo'] = $i['thr_parent'];
+
+ $ret['to'] = [ ACTIVITY_PUBLIC_INBOX ];
+ return $ret;
+ }
+
+ if ($i['obj']) {
+ if (is_array($i['obj'])) {
+ $ret = $i['obj'];
+ }
+ else {
+ $ret = json_decode($i['obj'],true);
+ }
+ }
+
$ret['type'] = $objtype;
if ($objtype === 'Question') {
@@ -632,7 +652,7 @@ class Activity {
- static function encode_activity($i) {
+ static function encode_activity($i, $dismiss_deleted = false) {
$ret = [];
$reply = false;
@@ -644,45 +664,45 @@ class Activity {
}
$ret['type'] = self::activity_mapper($i['verb']);
+ $fragment = '';
-
-
- if (intval($i['item_deleted'])) {
+ if (intval($i['item_deleted']) && !$dismiss_deleted) {
$is_response = false;
+
if (in_array($ret['type'], [ 'Like', 'Dislike', 'Accept', 'Reject', 'TentativeAccept', 'TentativeReject' ])) {
$ret['type'] = 'Undo';
+ $fragment = 'undo';
$is_response = true;
}
else {
$ret['type'] = 'Delete';
+ $fragment = 'delete';
}
- $ret['id'] = str_replace('/item/','/activity/',$i['mid']) . '#delete';
+
+ $ret['id'] = str_replace('/item/','/activity/',$i['mid']) . '#' . $fragment;
$actor = self::encode_person($i['author'],false);
if ($actor)
$ret['actor'] = $actor;
else
return [];
- if ($i['obj'] && !$is_response) {
- if (! is_array($i['obj'])) {
- $i['obj'] = json_decode($i['obj'],true);
- }
- $obj = self::encode_object($i['obj']);
- if ($obj)
- $ret['object'] = $obj;
- else
- return [];
- }
- else {
- $obj = self::encode_item($i);
- if ($obj)
- $ret['object'] = $obj;
- else
- return [];
+// $ret['object'] = str_replace('/item/','/activity/',$i['mid']);
+
+ $obj = (($is_response) ? self::encode_activity($i,true) : self::encode_item($i,true));
+ if ($obj) {
+ // do not leak private content in deletes
+ unset($obj['object']);
+ unset($obj['cc']);
+ $obj['to'] = [ ACTIVITY_PUBLIC_INBOX ];
+ $ret['object'] = $obj;
}
+ else
+ return [];
$ret['to'] = [ ACTIVITY_PUBLIC_INBOX ];
+
return $ret;
+
}
if($ret['type'] === 'emojiReaction') {
@@ -1166,7 +1186,9 @@ class Activity {
'Question' => 'Question',
'Document' => 'Document',
'Audio' => 'Audio',
- 'Video' => 'Video'
+ 'Video' => 'Video',
+ 'Delete' => 'Delete',
+ 'Undo' => 'Undo'
];
call_hooks('activity_obj_decode_mapper',$objs);
@@ -1203,7 +1225,9 @@ class Activity {
'Invite' => 'Invite',
'Question' => 'Question',
'Audio' => 'Audio',
- 'Video' => 'Video'
+ 'Video' => 'Video',
+ 'Delete' => 'Delete',
+ 'Undo' => 'Undo'
];
call_hooks('activity_obj_mapper',$objs);
@@ -2033,7 +2057,7 @@ class Activity {
$response_activity = true;
$s['mid'] = $act->id;
- $s['parent_mid'] = $act->obj['id'];
+ // $s['parent_mid'] = $act->obj['id'];
$s['uuid'] = $act->data['diaspora:guid'];
// over-ride the object timestamp with the activity
@@ -3115,5 +3139,29 @@ class Activity {
return $content;
}
+ // Find either an Authorization: Bearer token or 'token' request variable
+ // in the current web request and return it
+
+ static function token_from_request() {
+
+ foreach ( [ 'REDIRECT_REMOTE_USER', 'HTTP_AUTHORIZATION' ] as $s ) {
+ $auth = ((array_key_exists($s,$_SERVER) && strpos($_SERVER[$s],'Bearer ') === 0)
+ ? str_replace('Bearer ', EMPTY_STR, $_SERVER[$s])
+ : EMPTY_STR
+ );
+ if ($auth) {
+ break;
+ }
+ }
+
+ if (! $auth) {
+ if (array_key_exists('token',$_REQUEST) && $_REQUEST['token']) {
+ $auth = $_REQUEST['token'];
+ }
+ }
+
+ return $auth;
+ }
+
}
diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php
index 87da5ff7a..f16f5258a 100644
--- a/Zotlabs/Lib/Libzot.php
+++ b/Zotlabs/Lib/Libzot.php
@@ -1141,6 +1141,7 @@ class Libzot {
}
logger($AS->debug(),LOGGER_DATA);
+
}
@@ -1201,10 +1202,6 @@ class Libzot {
if(in_array($env['type'],['activity','response'])) {
- $arr = Activity::decode_note($AS);
-
- //logger($AS->debug());
-
$r = q("select hubloc_hash, hubloc_network from hubloc where hubloc_id_url = '%s' ",
dbesc($AS->actor['id'])
);
@@ -1496,13 +1493,14 @@ class Libzot {
// Try again using the delivery channel credentials.
// We will also need to re-parse the $item array,
// but preserve any values that were set during anonymous parsing.
-
+
$o = Activity::fetch($act->obj,$channel);
if($o) {
$act->obj = $o;
$arr = array_merge(Activity::decode_note($act),$arr);
}
else {
+
$DR->update('Incomplete or corrupt activity');
$result[] = $DR->get();
continue;
@@ -1725,7 +1723,7 @@ class Libzot {
$arr['aid'] = $channel['channel_account_id'];
$arr['uid'] = $channel['channel_id'];
- $item_id = self::delete_imported_item($sender,$arr,$channel['channel_id'],$relay);
+ $item_id = self::delete_imported_item($sender,$act,$arr,$channel['channel_id'],$relay);
$DR->update(($item_id) ? 'deleted' : 'delete_failed');
$result[] = $DR->get();
@@ -2108,7 +2106,7 @@ class Libzot {
* @return boolean|int post_id
*/
- static function delete_imported_item($sender, $item, $uid, $relay) {
+ static function delete_imported_item($sender, $act, $item, $uid, $relay) {
logger('invoked', LOGGER_DEBUG);
@@ -2116,42 +2114,39 @@ class Libzot {
$item_found = false;
$post_id = 0;
- // reactions such as like and dislike could have an mid with /activity/ in it.
- // Check for both forms in order to prevent duplicates.
+ if ($item['verb'] === 'Tombstone') {
+ // The id of the deleted thing is the item mid (activity id)
+ $mid = $item['mid'];
+ }
+ else {
+ // The id is the object id if the type is Undo or Delete
+ $mid = ((is_array($act->obj)) ? $act->obj['id'] : $act->obj);
+ }
+
+ // we may have stored either the object id or the activity id if it was a response activity (like, dislike, etc.)
$r = q("select * from item where ( author_xchan = '%s' or owner_xchan = '%s' or source_xchan = '%s' )
and mid IN ('%s', '%s') and uid = %d limit 1",
dbesc($sender),
dbesc($sender),
dbesc($sender),
- dbesc(str_replace('/activity/', '/item/', $item['mid'])),
- dbesc(str_replace('/item/', '/activity/', $item['mid'])),
+ dbesc($mid),
+ dbesc(str_replace('/activity/','/item/',$mid)),
intval($uid)
);
if($r) {
$stored = $r[0];
- if($stored['author_xchan'] === $sender || $stored['owner_xchan'] === $sender || $stored['source_xchan'] === $sender)
- $ownership_valid = true;
+ // we proved ownership in the sql query
+ $ownership_valid = true;
$post_id = $stored['id'];
$item_found = true;
}
else {
-
- // perhaps the item is still in transit and the delete notification got here before the actual item did. Store it with the deleted flag set.
- // item_store() won't try to deliver any notifications or start delivery chains if this flag is set.
- // This means we won't end up with potentially even more delivery threads trying to push this delete notification.
- // But this will ensure that if the (undeleted) original post comes in at a later date, we'll reject it because it will have an older timestamp.
-
- logger('delete received for non-existent item - storing item data.');
-
- if($item['author_xchan'] === $sender || $item['owner_xchan'] === $sender || $item['source_xchan'] === $sender) {
- $ownership_valid = true;
- $item_result = item_store($item);
- $post_id = $item_result['item_id'];
- }
+ // this will fail with an ownership issue, so explain the real reason
+ logger('delete received for non-existent item or not owned by sender - ignoring.');
}
if($ownership_valid === false) {
diff --git a/Zotlabs/Module/Activity.php b/Zotlabs/Module/Activity.php
index 9971ee60f..b75f0b245 100644
--- a/Zotlabs/Module/Activity.php
+++ b/Zotlabs/Module/Activity.php
@@ -21,7 +21,6 @@ class Activity extends Controller {
if (Libzot::is_zot_request()) {
$item_id = argv(1);
-
if (! $item_id)
http_status_exit(404, 'Not found');
@@ -170,6 +169,99 @@ class Activity extends Controller {
}
+ if(ActivityStreams::is_as_request()) {
+
+ $item_id = argv(1);
+
+ if (! $item_id) {
+ return;
+ }
+
+ $ob_authorise = false;
+ $item_uid = 0;
+
+ $bear = ZlibActivity::token_from_request();
+ if ($bear) {
+ logger('bear: ' . $bear, LOGGER_DEBUG);
+ $t = q("select item.uid, iconfig.v from iconfig left join item on iid = item.id where cat = 'ocap' and item.uuid = '%s'",
+ dbesc($item_id)
+ );
+ if ($t) {
+ foreach ($t as $token) {
+ if ($token['v'] === $bear) {
+ $ob_authorize = true;
+ $item_uid = $token['uid'];
+ break;
+ }
+ }
+ }
+ }
+
+ $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0
+ and item.item_delayed = 0 and item.item_blocked = 0 ";
+
+ $sigdata = HTTPSig::verify(EMPTY_STR);
+ if ($sigdata['portable_id'] && $sigdata['header_valid']) {
+ $portable_id = $sigdata['portable_id'];
+ if (! check_channelallowed($portable_id)) {
+ http_status_exit(403, 'Permission denied');
+ }
+ if (! check_siteallowed($sigdata['signer'])) {
+ http_status_exit(403, 'Permission denied');
+ }
+ observer_auth($portable_id);
+ }
+
+ // if passed an owner_id of 0 to item_permissions_sql(), we force "guest access" or observer checking
+ // Give ocap tokens priority
+
+ if ($ob_authorize) {
+ $sql_extra = " and item.uid = " . intval($token['uid']) . " ";
+ }
+ else {
+ $sql_extra = item_permissions_sql(0);
+ }
+
+ $r = q("select * from item where uuid = '%s' $item_normal $sql_extra limit 1",
+ dbesc($item_id)
+ );
+
+ if (! $r) {
+ $r = q("select * from item where uuid = '%s' $item_normal limit 1",
+ dbesc($item_id)
+ );
+ if($r) {
+ http_status_exit(403, 'Forbidden');
+ }
+ http_status_exit(404, 'Not found');
+ }
+
+ xchan_query($r,true);
+ $items = fetch_post_tags($r,false);
+
+ $channel = channelx_by_n($items[0]['uid']);
+
+ $x = array_merge( ['@context' => [
+ ACTIVITYSTREAMS_JSONLD_REV,
+ 'https://w3id.org/security/v1',
+ z_root() . ZOT_APSCHEMA_REV
+ ]], ZlibActivity::encode_activity($items[0],true));
+
+ $headers = [];
+ $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ;
+ $x['signature'] = LDSignatures::sign($x,$channel);
+ $ret = json_encode($x, JSON_UNESCAPED_SLASHES);
+ $headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T');
+ $headers['Digest'] = HTTPSig::generate_digest_header($ret);
+ $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
+
+ $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel));
+ HTTPSig::set_headers($h);
+ echo $ret;
+ killme();
+
+ }
+
goaway(z_root() . '/item/' . argv(1));
}
diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php
index d8c837522..922a2ef06 100644
--- a/Zotlabs/Module/Item.php
+++ b/Zotlabs/Module/Item.php
@@ -157,6 +157,106 @@ class Item extends Controller {
}
+ if(ActivityStreams::is_as_request()) {
+
+ $item_id = argv(1);
+ if(! $item_id)
+ http_status_exit(404, 'Not found');
+
+ $portable_id = EMPTY_STR;
+
+ $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 ";
+
+ $i = null;
+
+ // do we have the item (at all)?
+ // add preferential bias to item owners (item_wall = 1)
+
+ $r = q("select * from item where mid = '%s' or uuid = '%s' $item_normal order by item_wall desc limit 1",
+ dbesc(z_root() . '/item/' . $item_id),
+ dbesc($item_id)
+ );
+
+ if (! $r) {
+ http_status_exit(404,'Not found');
+ }
+
+ // process an authenticated fetch
+
+ $sigdata = HTTPSig::verify(EMPTY_STR);
+ if ($sigdata['portable_id'] && $sigdata['header_valid']) {
+ $portable_id = $sigdata['portable_id'];
+ if (! check_channelallowed($portable_id)) {
+ http_status_exit(403, 'Permission denied');
+ }
+ if (! check_siteallowed($sigdata['signer'])) {
+ http_status_exit(403, 'Permission denied');
+ }
+ observer_auth($portable_id);
+
+ $i = q("select id as item_id from item where mid = '%s' $item_normal and owner_xchan = '%s' limit 1 ",
+ dbesc($r[0]['parent_mid']),
+ dbesc($portable_id)
+ );
+ }
+ elseif (Config::get('system','require_authenticated_fetch',false)) {
+ http_status_exit(403,'Permission denied');
+ }
+
+ // if we don't have a parent id belonging to the signer see if we can obtain one as a visitor that we have permission to access
+ // with a bias towards those items owned by channels on this site (item_wall = 1)
+
+ $sql_extra = item_permissions_sql(0);
+
+ if (! $i) {
+ $i = q("select id as item_id from item where mid = '%s' $item_normal $sql_extra order by item_wall desc limit 1",
+ dbesc($r[0]['parent_mid'])
+ );
+ }
+
+ if(! $i) {
+ http_status_exit(403,'Forbidden');
+ }
+
+ // If we get to this point we have determined we can access the original in $r (fetched much further above), so use it.
+
+ xchan_query($r,true);
+ $items = fetch_post_tags($r,false);
+
+ $chan = channelx_by_n($items[0]['uid']);
+
+ if(! $chan)
+ http_status_exit(404, 'Not found');
+
+ if(! perm_is_allowed($chan['channel_id'],get_observer_hash(),'view_stream'))
+ http_status_exit(403, 'Forbidden');
+
+ $i = Activity::encode_item($items[0],true);
+
+ if(! $i)
+ http_status_exit(404, 'Not found');
+
+ $x = array_merge(['@context' => [
+ ACTIVITYSTREAMS_JSONLD_REV,
+ 'https://w3id.org/security/v1',
+ z_root() . ZOT_APSCHEMA_REV
+ ]], $i);
+
+ $headers = [];
+ $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ;
+ $x['signature'] = LDSignatures::sign($x,$chan);
+ $ret = json_encode($x, JSON_UNESCAPED_SLASHES);
+ $headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T');
+ $headers['Digest'] = HTTPSig::generate_digest_header($ret);
+ $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
+ $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],channel_url($chan));
+ HTTPSig::set_headers($h);
+ echo $ret;
+ killme();
+
+ }
+
+
if(argc() > 1 && argv(1) !== 'drop') {
$x = q("select uid, item_wall, llink, mid from item where mid = '%s' or mid = '%s' ",
dbesc(z_root() . '/item/' . argv(1)),