diff options
author | Mario <mario@mariovavti.com> | 2020-10-05 12:01:46 +0000 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2020-10-05 12:01:46 +0000 |
commit | e622802c249bbd084a41ef24fe87e99e9f7d9257 (patch) | |
tree | ff70f88899684454f818e6fc53050f84bb1fb60e | |
parent | 869ce64f875dab7acc7833a9fc2d8577b705d35f (diff) | |
parent | 244c8edfca6cc7ab48c337bda37ab70bfe819413 (diff) | |
download | volse-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.php | 1 | ||||
-rw-r--r-- | Zotlabs/Lib/Activity.php | 96 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzot.php | 47 | ||||
-rw-r--r-- | Zotlabs/Module/Activity.php | 94 | ||||
-rw-r--r-- | Zotlabs/Module/Item.php | 100 |
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)), |