commit
505350c9fb
54 changed files with 4014 additions and 1065 deletions
2
boot.php
2
boot.php
|
@ -41,7 +41,7 @@ define('FRIENDICA_PLATFORM', 'Friendica');
|
|||
define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily');
|
||||
define('FRIENDICA_VERSION', '2018.12-dev');
|
||||
define('DFRN_PROTOCOL_VERSION', '2.23');
|
||||
define('DB_UPDATE_VERSION', 1283);
|
||||
define('DB_UPDATE_VERSION', 1284);
|
||||
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,12 +37,13 @@
|
|||
"npm-asset/jgrowl": "^1.4",
|
||||
"npm-asset/fullcalendar": "^3.0.1",
|
||||
"npm-asset/cropperjs": "1.2.2",
|
||||
"npm-asset/imagesloaded": "4.1.4"
|
||||
"npm-asset/imagesloaded": "4.1.4",
|
||||
"friendica/json-ld": "^1.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/pear/Text_Highlighter"
|
||||
"url": "https://git.friendi.ca/friendica/php-json-ld"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
|
|
92
composer.lock
generated
92
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "5f6a43237dc52758484cd21cd76e8ce6",
|
||||
"content-hash": "ece71ff50417ed5fcc1a439ea554925c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asika/simple-console",
|
||||
|
@ -241,6 +241,50 @@
|
|||
],
|
||||
"time": "2015-08-05T01:03:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "friendica/json-ld",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.friendi.ca/friendica/php-json-ld",
|
||||
"reference": "a9ac64daf01cfd97e80c36a5104247d37c0ae5ef"
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"jsonld.php"
|
||||
]
|
||||
},
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Digital Bazaar, Inc.",
|
||||
"email": "support@digitalbazaar.com",
|
||||
"url": "http://digitalbazaar.com/"
|
||||
},
|
||||
{
|
||||
"name": "Friendica Team",
|
||||
"url": "https://friendi.ca/"
|
||||
}
|
||||
],
|
||||
"description": "A JSON-LD Processor and API implementation in PHP.",
|
||||
"homepage": "https://git.friendi.ca/friendica/php-json-ld",
|
||||
"keywords": [
|
||||
"JSON",
|
||||
"JSON-LD",
|
||||
"Linked Data",
|
||||
"RDF",
|
||||
"Semantic Web",
|
||||
"jsonld"
|
||||
],
|
||||
"time": "2018-09-28T00:01:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fxp/composer-asset-plugin",
|
||||
"version": "v1.4.2",
|
||||
|
@ -2000,6 +2044,52 @@
|
|||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "digitalbazaar/json-ld",
|
||||
"version": "0.4.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/digitalbazaar/php-json-ld.git",
|
||||
"reference": "dc1bd23f0ee2efd27ccf636d32d2738dabcee182"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/digitalbazaar/php-json-ld/zipball/dc1bd23f0ee2efd27ccf636d32d2738dabcee182",
|
||||
"reference": "dc1bd23f0ee2efd27ccf636d32d2738dabcee182",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"jsonld.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Digital Bazaar, Inc.",
|
||||
"email": "support@digitalbazaar.com"
|
||||
}
|
||||
],
|
||||
"description": "A JSON-LD Processor and API implementation in PHP.",
|
||||
"homepage": "https://github.com/digitalbazaar/php-json-ld",
|
||||
"keywords": [
|
||||
"JSON-LD",
|
||||
"Linked Data",
|
||||
"RDF",
|
||||
"Semantic Web",
|
||||
"json",
|
||||
"jsonld"
|
||||
],
|
||||
"time": "2016-04-25T04:17:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
"version": "1.0.5",
|
||||
|
|
|
@ -15,6 +15,34 @@
|
|||
"name": ["UNIQUE", "name"]
|
||||
}
|
||||
},
|
||||
"apcontact": {
|
||||
"comment": "ActivityPub compatible contacts - used in the ActivityPub implementation",
|
||||
"fields": {
|
||||
"url": {"type": "varbinary(255)", "not null": "1", "primary": "1", "comment": "URL of the contact"},
|
||||
"uuid": {"type": "varchar(255)", "comment": ""},
|
||||
"type": {"type": "varchar(20)", "not null": "1", "comment": ""},
|
||||
"following": {"type": "varchar(255)", "comment": ""},
|
||||
"followers": {"type": "varchar(255)", "comment": ""},
|
||||
"inbox": {"type": "varchar(255)", "not null": "1", "comment": ""},
|
||||
"outbox": {"type": "varchar(255)", "comment": ""},
|
||||
"sharedinbox": {"type": "varchar(255)", "comment": ""},
|
||||
"nick": {"type": "varchar(255)", "not null": "1", "default": "", "comment": ""},
|
||||
"name": {"type": "varchar(255)", "comment": ""},
|
||||
"about": {"type": "text", "comment": ""},
|
||||
"photo": {"type": "varchar(255)", "comment": ""},
|
||||
"addr": {"type": "varchar(255)", "comment": ""},
|
||||
"alias": {"type": "varchar(255)", "comment": ""},
|
||||
"pubkey": {"type": "text", "comment": ""},
|
||||
"baseurl": {"type": "varchar(255)", "comment": "baseurl of the ap contact"},
|
||||
"updated": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": ""}
|
||||
|
||||
},
|
||||
"indexes": {
|
||||
"PRIMARY": ["url"],
|
||||
"addr": ["addr(32)"],
|
||||
"url": ["followers(190)"]
|
||||
}
|
||||
},
|
||||
"attach": {
|
||||
"comment": "file attachments",
|
||||
"fields": {
|
||||
|
@ -215,7 +243,7 @@
|
|||
"reply-to-uri": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "URI to which this item is a reply"},
|
||||
"conversation-uri": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "GNU Social conversation URI"},
|
||||
"conversation-href": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "GNU Social conversation link"},
|
||||
"protocol": {"type": "tinyint unsigned", "not null": "1", "default": "0", "comment": "The protocol of the item"},
|
||||
"protocol": {"type": "tinyint unsigned", "not null": "1", "default": "255", "comment": "The protocol of the item"},
|
||||
"source": {"type": "mediumtext", "comment": "Original source"},
|
||||
"received": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": "Receiving date"}
|
||||
},
|
||||
|
|
|
@ -4612,7 +4612,7 @@ function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $f
|
|||
$owner_record = DBA::selectFirst('contact', [], ['uid' => api_user(), 'self' => true]);
|
||||
|
||||
$arr = [];
|
||||
$arr['guid'] = System::createGUID(32);
|
||||
$arr['guid'] = System::createUUID();
|
||||
$arr['uid'] = intval(api_user());
|
||||
$arr['uri'] = $uri;
|
||||
$arr['parent-uri'] = $uri;
|
||||
|
|
|
@ -556,7 +556,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
|
|||
if (in_array($mode, ['community', 'contacts'])) {
|
||||
$writable = true;
|
||||
} else {
|
||||
$writable = ($items[0]['uid'] == 0) && in_array($items[0]['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
|
||||
$writable = ($items[0]['uid'] == 0) && in_array($items[0]['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
|
||||
}
|
||||
|
||||
if (!local_user()) {
|
||||
|
@ -807,7 +807,7 @@ function conversation_add_children(array $parents, $block_authors, $order, $uid)
|
|||
|
||||
foreach ($items as $index => $item) {
|
||||
if ($item['uid'] == 0) {
|
||||
$items[$index]['writable'] = in_array($item['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
|
||||
$items[$index]['writable'] = in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -877,7 +877,7 @@ function item_photo_menu($item) {
|
|||
}
|
||||
|
||||
if ((($cid == 0) || ($rel == Contact::FOLLOWER)) &&
|
||||
in_array($item['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
|
||||
in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
|
||||
$menu[L10n::t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']);
|
||||
}
|
||||
} else {
|
||||
|
|
10
index.php
10
index.php
|
@ -332,15 +332,21 @@ if ($a->module_loaded) {
|
|||
$a->page['page_title'] = $a->module;
|
||||
$placeholder = '';
|
||||
|
||||
if ($a->module_class) {
|
||||
Addon::callHooks($a->module . '_mod_init', $placeholder);
|
||||
|
||||
if ($a->module_class) {
|
||||
call_user_func([$a->module_class, 'init']);
|
||||
} else if (function_exists($a->module . '_init')) {
|
||||
Addon::callHooks($a->module . '_mod_init', $placeholder);
|
||||
$func = $a->module . '_init';
|
||||
$func($a);
|
||||
}
|
||||
|
||||
// "rawContent" is especially meant for technical endpoints.
|
||||
// This endpoint doesn't need any theme initialization or other comparable stuff.
|
||||
if (!$a->error && $a->module_class) {
|
||||
call_user_func([$a->module_class, 'rawContent']);
|
||||
}
|
||||
|
||||
if (function_exists(str_replace('-', '_', $a->getCurrentTheme()) . '_init')) {
|
||||
$func = str_replace('-', '_', $a->getCurrentTheme()) . '_init';
|
||||
$func($a);
|
||||
|
|
|
@ -1488,7 +1488,7 @@ function admin_page_site(App $a)
|
|||
'$community_page_style' => ['community_page_style', L10n::t("Community pages for visitors"), Config::get('system','community_page_style'), L10n::t("Which community pages should be available for visitors. Local users always see both pages."), $community_page_style_choices],
|
||||
'$max_author_posts_community_page' => ['max_author_posts_community_page', L10n::t("Posts per user on community page"), Config::get('system','max_author_posts_community_page'), L10n::t("The maximum number of posts per user on the community page. \x28Not valid for 'Global Community'\x29")],
|
||||
'$ostatus_disabled' => ['ostatus_disabled', L10n::t("Enable OStatus support"), !Config::get('system','ostatus_disabled'), L10n::t("Provide built-in OStatus \x28StatusNet, GNU Social etc.\x29 compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed.")],
|
||||
'$ostatus_full_threads' => ['ostatus_full_threads', L10n::t("Only import OStatus threads from our contacts"), Config::get('system','ostatus_full_threads'), L10n::t("Normally we import every content from our OStatus contacts. With this option we only store threads that are started by a contact that is known on our system.")],
|
||||
'$ostatus_full_threads' => ['ostatus_full_threads', L10n::t("Only import OStatus/ActivityPub threads from our contacts"), Config::get('system','ostatus_full_threads'), L10n::t("Normally we import every content from our OStatus and ActivityPub contacts. With this option we only store threads that are started by a contact that is known on our system.")],
|
||||
'$ostatus_not_able' => L10n::t("OStatus support can only be enabled if threading is enabled."),
|
||||
'$diaspora_able' => $diaspora_able,
|
||||
'$diaspora_not_able' => L10n::t("Diaspora support can't be enabled because Friendica was installed into a sub directory."),
|
||||
|
|
|
@ -533,7 +533,7 @@ function contacts_content(App $a, $update = 0)
|
|||
$relation_text = '';
|
||||
}
|
||||
|
||||
if (!in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
|
||||
if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
|
||||
$relation_text = "";
|
||||
}
|
||||
|
||||
|
@ -555,7 +555,7 @@ function contacts_content(App $a, $update = 0)
|
|||
}
|
||||
$lblsuggest = (($contact['network'] === Protocol::DFRN) ? L10n::t('Suggest friends') : '');
|
||||
|
||||
$poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
|
||||
$poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
|
||||
|
||||
$nettype = L10n::t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact["url"]));
|
||||
|
||||
|
@ -966,7 +966,7 @@ function contact_conversations(App $a, $contact_id, $update)
|
|||
$profiledata = Contact::getDetailsByURL($contact["url"]);
|
||||
|
||||
if (local_user()) {
|
||||
if (in_array($profiledata["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
if (in_array($profiledata["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
$profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]);
|
||||
}
|
||||
}
|
||||
|
@ -990,7 +990,7 @@ function contact_posts(App $a, $contact_id)
|
|||
$profiledata = Contact::getDetailsByURL($contact["url"]);
|
||||
|
||||
if (local_user()) {
|
||||
if (in_array($profiledata["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
if (in_array($profiledata["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
$profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]);
|
||||
}
|
||||
}
|
||||
|
@ -1071,7 +1071,7 @@ function _contact_detail_for_template(array $rr)
|
|||
*/
|
||||
function contact_actions($contact)
|
||||
{
|
||||
$poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
|
||||
$poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
|
||||
$contact_actions = [];
|
||||
|
||||
// Provide friend suggestion only for Friendica contacts
|
||||
|
|
|
@ -28,6 +28,7 @@ use Friendica\Model\Group;
|
|||
use Friendica\Model\User;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
|
@ -335,10 +336,17 @@ function dfrn_confirm_post(App $a, $handsfree = null)
|
|||
intval($contact_id)
|
||||
);
|
||||
} else {
|
||||
if ($network == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub::transmitContactAccept($contact['url'], $contact['hub-verify'], $uid);
|
||||
$pending = true;
|
||||
} else {
|
||||
$pending = false;
|
||||
}
|
||||
|
||||
// $network !== Protocol::DFRN
|
||||
$network = defaults($contact, 'network', Protocol::OSTATUS);
|
||||
|
||||
$arr = Probe::uri($contact['url']);
|
||||
$arr = Probe::uri($contact['url'], $network);
|
||||
|
||||
$notify = defaults($contact, 'notify' , $arr['notify']);
|
||||
$poll = defaults($contact, 'poll' , $arr['poll']);
|
||||
|
@ -348,7 +356,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
|
|||
$new_relation = $contact['rel'];
|
||||
$writable = $contact['writable'];
|
||||
|
||||
if ($network === Protocol::DIASPORA) {
|
||||
if (in_array($network, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
||||
if ($duplex) {
|
||||
$new_relation = Contact::FRIEND;
|
||||
} else {
|
||||
|
@ -362,30 +370,12 @@ function dfrn_confirm_post(App $a, $handsfree = null)
|
|||
|
||||
DBA::delete('intro', ['id' => $intro_id]);
|
||||
|
||||
$r = q("UPDATE `contact` SET `name-date` = '%s',
|
||||
`uri-date` = '%s',
|
||||
`addr` = '%s',
|
||||
`notify` = '%s',
|
||||
`poll` = '%s',
|
||||
`blocked` = 0,
|
||||
`pending` = 0,
|
||||
`network` = '%s',
|
||||
`writable` = %d,
|
||||
`hidden` = %d,
|
||||
`rel` = %d
|
||||
WHERE `id` = %d
|
||||
",
|
||||
DBA::escape(DateTimeFormat::utcNow()),
|
||||
DBA::escape(DateTimeFormat::utcNow()),
|
||||
DBA::escape($addr),
|
||||
DBA::escape($notify),
|
||||
DBA::escape($poll),
|
||||
DBA::escape($network),
|
||||
intval($writable),
|
||||
intval($hidden),
|
||||
intval($new_relation),
|
||||
intval($contact_id)
|
||||
);
|
||||
$fields = ['name-date' => DateTimeFormat::utcNow(),
|
||||
'uri-date' => DateTimeFormat::utcNow(), 'addr' => $addr,
|
||||
'notify' => $notify, 'poll' => $poll, 'blocked' => false,
|
||||
'pending' => $pending, 'network' => $network,
|
||||
'writable' => $writable, 'hidden' => $hidden, 'rel' => $new_relation];
|
||||
DBA::update('contact', $fields, ['id' => $contact_id]);
|
||||
}
|
||||
|
||||
if (!DBA::isResult($r)) {
|
||||
|
@ -403,6 +393,10 @@ function dfrn_confirm_post(App $a, $handsfree = null)
|
|||
|
||||
Group::addMember(User::getDefaultGroup($uid, $contact["network"]), $contact['id']);
|
||||
|
||||
if ($network == Protocol::ACTIVITYPUB && $duplex) {
|
||||
ActivityPub::transmitActivity('Follow', $contact['url'], $uid);
|
||||
}
|
||||
|
||||
// Let's send our user to the contact editor in case they want to
|
||||
// do anything special with this new friend.
|
||||
if ($handsfree === null) {
|
||||
|
|
|
@ -54,7 +54,7 @@ function dirfind_content(App $a, $prefix = "") {
|
|||
if ((valid_email($search) && Network::isEmailDomainValid($search)) ||
|
||||
(substr(normalise_link($search), 0, 7) == "http://")) {
|
||||
$user_data = Probe::uri($search);
|
||||
$discover_user = (in_array($user_data["network"], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]));
|
||||
$discover_user = (in_array($user_data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ use Friendica\Model\Group;
|
|||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Protocol\DFRN;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
||||
function display_init(App $a)
|
||||
{
|
||||
|
@ -43,7 +44,7 @@ function display_init(App $a)
|
|||
|
||||
$item = null;
|
||||
|
||||
$fields = ['id', 'parent', 'author-id', 'body', 'uid'];
|
||||
$fields = ['id', 'parent', 'author-id', 'body', 'uid', 'guid'];
|
||||
|
||||
// If there is only one parameter, then check if this parameter could be a guid
|
||||
if ($a->argc == 2) {
|
||||
|
@ -76,6 +77,10 @@ function display_init(App $a)
|
|||
displayShowFeed($item["id"], false);
|
||||
}
|
||||
|
||||
if (ActivityPub::isRequest()) {
|
||||
goaway(str_replace('display/', 'object/', $a->query_string));
|
||||
}
|
||||
|
||||
if ($item["id"] != $item["parent"]) {
|
||||
$item = Item::selectFirstForUser(local_user(), $fields, ['id' => $item["parent"]]);
|
||||
}
|
||||
|
|
20
mod/item.php
20
mod/item.php
|
@ -159,7 +159,7 @@ function item_post(App $a) {
|
|||
}
|
||||
|
||||
// Allow commenting if it is an answer to a public post
|
||||
$allow_comment = local_user() && ($profile_uid == 0) && $parent && in_array($parent_item['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
|
||||
$allow_comment = local_user() && ($profile_uid == 0) && $parent && in_array($parent_item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
|
||||
|
||||
// Now check that valid personal details have been provided
|
||||
if (!can_write_wall($profile_uid) && !$allow_comment) {
|
||||
|
@ -240,7 +240,7 @@ function item_post(App $a) {
|
|||
$emailcc = notags(trim(defaults($_REQUEST, 'emailcc' , '')));
|
||||
$body = escape_tags(trim(defaults($_REQUEST, 'body' , '')));
|
||||
$network = notags(trim(defaults($_REQUEST, 'network' , Protocol::DFRN)));
|
||||
$guid = System::createGUID(32);
|
||||
$guid = System::createUUID();
|
||||
|
||||
$postopts = defaults($_REQUEST, 'postopts', '');
|
||||
|
||||
|
@ -343,23 +343,14 @@ function item_post(App $a) {
|
|||
|
||||
$tags = get_tags($body);
|
||||
|
||||
// Add a tag if the parent contact is from OStatus (This will notify them during delivery)
|
||||
if ($parent) {
|
||||
if ($thr_parent_contact['network'] == Protocol::OSTATUS) {
|
||||
// Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them)
|
||||
if ($parent && in_array($thr_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) {
|
||||
$contact = '@[url=' . $thr_parent_contact['url'] . ']' . $thr_parent_contact['nick'] . '[/url]';
|
||||
if (!stripos(implode($tags), '[url=' . $thr_parent_contact['url'] . ']')) {
|
||||
$tags[] = $contact;
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent_contact['network'] == Protocol::OSTATUS) {
|
||||
$contact = '@[url=' . $parent_contact['url'] . ']' . $parent_contact['nick'] . '[/url]';
|
||||
if (!stripos(implode($tags), '[url=' . $parent_contact['url'] . ']')) {
|
||||
$tags[] = $contact;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tagged = [];
|
||||
|
||||
$private_forum = false;
|
||||
|
@ -1026,8 +1017,7 @@ function handle_tag(App $a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $n
|
|||
$alias = $contact["alias"];
|
||||
$newname = $contact["nick"];
|
||||
|
||||
if (($newname == "") || (($contact["network"] != Protocol::OSTATUS) && ($contact["network"] != Protocol::TWITTER)
|
||||
&& ($contact["network"] != Protocol::STATUSNET))) {
|
||||
if (($newname == "") || !in_array($contact["network"], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::TWITTER, Protocol::STATUSNET])) {
|
||||
$newname = $contact["name"];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -472,7 +472,7 @@ function photos_post(App $a)
|
|||
$uri = Item::newURI($page_owner_uid);
|
||||
|
||||
$arr = [];
|
||||
$arr['guid'] = System::createGUID(32);
|
||||
$arr['guid'] = System::createUUID();
|
||||
$arr['uid'] = $page_owner_uid;
|
||||
$arr['uri'] = $uri;
|
||||
$arr['parent-uri'] = $uri;
|
||||
|
@ -651,7 +651,7 @@ function photos_post(App $a)
|
|||
$uri = Item::newURI($page_owner_uid);
|
||||
|
||||
$arr = [];
|
||||
$arr['guid'] = System::createGUID(32);
|
||||
$arr['guid'] = System::createUUID();
|
||||
$arr['uid'] = $page_owner_uid;
|
||||
$arr['uri'] = $uri;
|
||||
$arr['parent-uri'] = $uri;
|
||||
|
@ -889,7 +889,7 @@ function photos_post(App $a)
|
|||
$arr['coord'] = $lat . ' ' . $lon;
|
||||
}
|
||||
|
||||
$arr['guid'] = System::createGUID(32);
|
||||
$arr['guid'] = System::createUUID();
|
||||
$arr['uid'] = $page_owner_uid;
|
||||
$arr['uri'] = $uri;
|
||||
$arr['parent-uri'] = $uri;
|
||||
|
|
|
@ -97,7 +97,7 @@ function poke_init(App $a)
|
|||
|
||||
$arr = [];
|
||||
|
||||
$arr['guid'] = System::createGUID(32);
|
||||
$arr['guid'] = System::createUUID();
|
||||
$arr['uid'] = $uid;
|
||||
$arr['uri'] = $uri;
|
||||
$arr['parent-uri'] = (!empty($parent_uri) ? $parent_uri : $uri);
|
||||
|
|
|
@ -20,6 +20,7 @@ use Friendica\Model\Profile;
|
|||
use Friendica\Module\Login;
|
||||
use Friendica\Protocol\DFRN;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
||||
function profile_init(App $a)
|
||||
{
|
||||
|
@ -49,6 +50,16 @@ function profile_init(App $a)
|
|||
DFRN::autoRedir($a, $which);
|
||||
}
|
||||
|
||||
if (ActivityPub::isRequest()) {
|
||||
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $which]);
|
||||
if (DBA::isResult($user)) {
|
||||
$data = ActivityPub::profile($user['uid']);
|
||||
echo json_encode($data);
|
||||
header('Content-Type: application/activity+json');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
Profile::load($a, $which, $profile);
|
||||
|
||||
$blocked = !local_user() && !remote_user() && Config::get('system', 'block_public');
|
||||
|
|
|
@ -108,7 +108,7 @@ EOT;
|
|||
|
||||
$arr = [];
|
||||
|
||||
$arr['guid'] = System::createGUID(32);
|
||||
$arr['guid'] = System::createUUID();
|
||||
$arr['uri'] = $uri;
|
||||
$arr['uid'] = $owner_uid;
|
||||
$arr['contact-id'] = $contact['id'];
|
||||
|
|
|
@ -115,7 +115,7 @@ EOT;
|
|||
|
||||
$arr = [];
|
||||
|
||||
$arr['guid'] = System::createGUID(32);
|
||||
$arr['guid'] = System::createUUID();
|
||||
$arr['uri'] = $uri;
|
||||
$arr['uid'] = $owner_uid;
|
||||
$arr['contact-id'] = $contact['id'];
|
||||
|
|
|
@ -80,6 +80,7 @@ function xrd_json($a, $uri, $alias, $profile_url, $r)
|
|||
['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' => 'self', 'type' => 'application/activity+json', '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'],
|
||||
|
@ -92,6 +93,7 @@ function xrd_json($a, $uri, $alias, $profile_url, $r)
|
|||
['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa']
|
||||
]
|
||||
];
|
||||
|
||||
echo json_encode($json);
|
||||
killme();
|
||||
}
|
||||
|
|
|
@ -21,7 +21,16 @@ abstract class BaseModule extends BaseObject
|
|||
*/
|
||||
public static function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Module GET method to display raw content from technical endpoints
|
||||
*
|
||||
* Extend this method if the module is supposed to return communication data,
|
||||
* e.g. from protocol implementations.
|
||||
*/
|
||||
public static function rawContent()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -89,6 +89,7 @@ class ContactSelector
|
|||
Protocol::TWITTER => L10n::t('Twitter'),
|
||||
Protocol::DIASPORA2 => L10n::t('Diaspora Connector'),
|
||||
Protocol::STATUSNET => L10n::t('GNU Social Connector'),
|
||||
Protocol::ACTIVITYPUB => L10n::t('ActivityPub'),
|
||||
Protocol::PNUT => L10n::t('pnut'),
|
||||
];
|
||||
|
||||
|
@ -99,13 +100,17 @@ class ContactSelector
|
|||
|
||||
$networkname = str_replace($search, $replace, $s);
|
||||
|
||||
if ((in_array($s, [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) && ($profile != "")) {
|
||||
if ((in_array($s, [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) && ($profile != "")) {
|
||||
$r = DBA::fetchFirst("SELECT `gserver`.`platform` FROM `gcontact`
|
||||
INNER JOIN `gserver` ON `gserver`.`nurl` = `gcontact`.`server_url`
|
||||
WHERE `gcontact`.`nurl` = ? AND `platform` != ''", normalise_link($profile));
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
$networkname = $r['platform'];
|
||||
|
||||
if ($s == Protocol::ACTIVITYPUB) {
|
||||
$networkname .= ' (AP)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -142,11 +142,8 @@ class Widget
|
|||
|
||||
$nets = array();
|
||||
while ($rr = DBA::fetch($r)) {
|
||||
/// @TODO If 'network' is not there, this triggers an E_NOTICE
|
||||
if ($rr['network']) {
|
||||
$nets[] = array('ref' => $rr['network'], 'name' => ContactSelector::networkToName($rr['network']), 'selected' => (($selected == $rr['network']) ? 'selected' : '' ));
|
||||
}
|
||||
}
|
||||
DBA::close($r);
|
||||
|
||||
if (count($nets) < 2) {
|
||||
|
|
|
@ -161,6 +161,18 @@ class System extends BaseObject
|
|||
killme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random string in the UUID format
|
||||
*
|
||||
* @param bool|string $prefix A given prefix (default is empty)
|
||||
* @return string a generated UUID
|
||||
*/
|
||||
public static function createUUID($prefix = '')
|
||||
{
|
||||
$guid = System::createGUID(32, $prefix);
|
||||
return substr($guid, 0, 8). '-' . substr($guid, 8, 4) . '-' . substr($guid, 12, 4) . '-' . substr($guid, 16, 4) . '-' . substr($guid, 20, 12);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a GUID with the given parameters
|
||||
*
|
||||
|
|
175
src/Model/APContact.php
Normal file
175
src/Model/APContact.php
Normal file
|
@ -0,0 +1,175 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file src/Model/APContact.php
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\JsonLD;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
require_once 'boot.php';
|
||||
|
||||
class APContact extends BaseObject
|
||||
{
|
||||
/**
|
||||
* Resolves the profile url from the address by using webfinger
|
||||
*
|
||||
* @param string $addr profile address (user@domain.tld)
|
||||
* @return string url
|
||||
*/
|
||||
private static function addrToUrl($addr)
|
||||
{
|
||||
$addr_parts = explode('@', $addr);
|
||||
if (count($addr_parts) != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$webfinger = 'https://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr);
|
||||
|
||||
$ret = Network::curl($webfinger, false, $redirects, ['accept_content' => 'application/jrd+json,application/json']);
|
||||
if (!$ret['success'] || empty($ret['body'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode($ret['body'], true);
|
||||
|
||||
if (empty($data['links'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($data['links'] as $link) {
|
||||
if (empty($link['href']) || empty($link['rel']) || empty($link['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
|
||||
return $link['href'];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a profile from a given url
|
||||
*
|
||||
* @param string $url profile url
|
||||
* @param boolean $update true = always update, false = never update, null = update when not found
|
||||
* @return array profile array
|
||||
*/
|
||||
public static function getByURL($url, $update = null)
|
||||
{
|
||||
if (empty($url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($update)) {
|
||||
$apcontact = DBA::selectFirst('apcontact', [], ['url' => $url]);
|
||||
if (DBA::isResult($apcontact)) {
|
||||
return $apcontact;
|
||||
}
|
||||
|
||||
$apcontact = DBA::selectFirst('apcontact', [], ['alias' => $url]);
|
||||
if (DBA::isResult($apcontact)) {
|
||||
return $apcontact;
|
||||
}
|
||||
|
||||
$apcontact = DBA::selectFirst('apcontact', [], ['addr' => $url]);
|
||||
if (DBA::isResult($apcontact)) {
|
||||
return $apcontact;
|
||||
}
|
||||
|
||||
if (!is_null($update)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty(parse_url($url, PHP_URL_SCHEME))) {
|
||||
$url = self::addrToUrl($url);
|
||||
if (empty($url)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$data = ActivityPub::fetchContent($url);
|
||||
|
||||
if (empty($data) || empty($data['id']) || empty($data['inbox'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$apcontact = [];
|
||||
$apcontact['url'] = $data['id'];
|
||||
$apcontact['uuid'] = defaults($data, 'diaspora:guid', null);
|
||||
$apcontact['type'] = defaults($data, 'type', null);
|
||||
$apcontact['following'] = defaults($data, 'following', null);
|
||||
$apcontact['followers'] = defaults($data, 'followers', null);
|
||||
$apcontact['inbox'] = defaults($data, 'inbox', null);
|
||||
$apcontact['outbox'] = defaults($data, 'outbox', null);
|
||||
$apcontact['sharedinbox'] = JsonLD::fetchElement($data, 'endpoints', 'sharedInbox');
|
||||
$apcontact['nick'] = defaults($data, 'preferredUsername', null);
|
||||
$apcontact['name'] = defaults($data, 'name', $apcontact['nick']);
|
||||
$apcontact['about'] = defaults($data, 'summary', '');
|
||||
$apcontact['photo'] = JsonLD::fetchElement($data, 'icon', 'url');
|
||||
$apcontact['alias'] = JsonLD::fetchElement($data, 'url', 'href');
|
||||
|
||||
$parts = parse_url($apcontact['url']);
|
||||
unset($parts['scheme']);
|
||||
unset($parts['path']);
|
||||
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
|
||||
|
||||
$apcontact['pubkey'] = trim(JsonLD::fetchElement($data, 'publicKey', 'publicKeyPem'));
|
||||
|
||||
// To-Do
|
||||
// manuallyApprovesFollowers
|
||||
|
||||
// Unhandled
|
||||
// @context, tag, attachment, image, nomadicLocations, signature, following, followers, featured, movedTo, liked
|
||||
|
||||
// Unhandled from Misskey
|
||||
// sharedInbox, isCat
|
||||
|
||||
// Unhandled from Kroeg
|
||||
// kroeg:blocks, updated
|
||||
|
||||
// Check if the address is resolvable
|
||||
if (self::addrToUrl($apcontact['addr']) == $apcontact['url']) {
|
||||
$parts = parse_url($apcontact['url']);
|
||||
unset($parts['path']);
|
||||
$apcontact['baseurl'] = Network::unparseURL($parts);
|
||||
} else {
|
||||
$apcontact['addr'] = null;
|
||||
$apcontact['baseurl'] = null;
|
||||
}
|
||||
|
||||
if ($apcontact['url'] == $apcontact['alias']) {
|
||||
$apcontact['alias'] = null;
|
||||
}
|
||||
|
||||
$apcontact['updated'] = DateTimeFormat::utcNow();
|
||||
|
||||
DBA::update('apcontact', $apcontact, ['url' => $url], true);
|
||||
|
||||
// Update some data in the contact table with various ways to catch them all
|
||||
$contact_fields = ['name' => $apcontact['name'], 'about' => $apcontact['about']];
|
||||
DBA::update('contact', $contact_fields, ['nurl' => normalise_link($url)]);
|
||||
|
||||
$contacts = DBA::select('contact', ['uid', 'id'], ['nurl' => normalise_link($url)]);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
Contact::updateAvatar($apcontact['photo'], $contact['uid'], $contact['id']);
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
// Update the gcontact table
|
||||
DBA::update('gcontact', $contact_fields, ['nurl' => normalise_link($url)]);
|
||||
|
||||
logger('Updated profile for ' . $url, LOGGER_DEBUG);
|
||||
|
||||
return $apcontact;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\Model\Profile;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\DFRN;
|
||||
use Friendica\Protocol\OStatus;
|
||||
|
@ -555,6 +556,12 @@ class Contact extends BaseObject
|
|||
}
|
||||
} elseif ($contact['network'] == Protocol::DIASPORA) {
|
||||
Diaspora::sendUnshare($user, $contact);
|
||||
} elseif ($contact['network'] == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub::transmitContactUndo($contact['url'], $user['uid']);
|
||||
|
||||
if ($dissolve) {
|
||||
ActivityPub::transmitContactReject($contact['url'], $contact['hub-verify'], $user['uid']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -775,7 +782,7 @@ class Contact extends BaseObject
|
|||
}
|
||||
|
||||
if ((empty($profile["addr"]) || empty($profile["name"])) && (defaults($profile, "gid", 0) != 0)
|
||||
&& in_array($profile["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])
|
||||
&& in_array($profile["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])
|
||||
) {
|
||||
Worker::add(PRIORITY_LOW, "UpdateGContact", $profile["gid"]);
|
||||
}
|
||||
|
@ -1054,7 +1061,6 @@ class Contact extends BaseObject
|
|||
if (!x($contact, 'avatar')) {
|
||||
$update_contact = true;
|
||||
}
|
||||
|
||||
if (!$update_contact || $no_update) {
|
||||
return $contact_id;
|
||||
}
|
||||
|
@ -1088,7 +1094,7 @@ class Contact extends BaseObject
|
|||
}
|
||||
|
||||
// Last try in gcontact for unsupported networks
|
||||
if (!in_array($data["network"], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::PUMPIO, Protocol::MAIL, Protocol::FEED])) {
|
||||
if (!in_array($data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::PUMPIO, Protocol::MAIL, Protocol::FEED])) {
|
||||
if ($uid != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1316,33 +1322,27 @@ class Contact extends BaseObject
|
|||
|
||||
require_once 'include/conversation.php';
|
||||
|
||||
// There are no posts with "uid = 0" with connector networks
|
||||
// This speeds up the query a lot
|
||||
$r = q("SELECT `network`, `id` AS `author-id`, `contact-type` FROM `contact`
|
||||
WHERE `contact`.`nurl` = '%s' AND `contact`.`uid` = 0",
|
||||
DBA::escape(normalise_link($contact_url))
|
||||
);
|
||||
$cid = Self::getIdForURL($contact_url);
|
||||
|
||||
if (!DBA::isResult($r)) {
|
||||
$contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $cid]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (in_array($r[0]["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
|
||||
if (in_array($contact["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
|
||||
$sql = "(`item`.`uid` = 0 OR (`item`.`uid` = ? AND NOT `item`.`global`))";
|
||||
} else {
|
||||
$sql = "`item`.`uid` = ?";
|
||||
}
|
||||
|
||||
$author_id = intval($r[0]["author-id"]);
|
||||
|
||||
$contact = ($r[0]["contact-type"] == self::ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id');
|
||||
$contact_field = ($contact["contact-type"] == self::ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id');
|
||||
|
||||
if ($thread_mode) {
|
||||
$condition = ["`$contact` = ? AND `gravity` = ? AND " . $sql,
|
||||
$author_id, GRAVITY_PARENT, local_user()];
|
||||
$condition = ["`$contact_field` = ? AND `gravity` = ? AND " . $sql,
|
||||
$cid, GRAVITY_PARENT, local_user()];
|
||||
} else {
|
||||
$condition = ["`$contact` = ? AND `gravity` IN (?, ?) AND " . $sql,
|
||||
$author_id, GRAVITY_PARENT, GRAVITY_COMMENT, local_user()];
|
||||
$condition = ["`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql,
|
||||
$cid, GRAVITY_PARENT, GRAVITY_COMMENT, local_user()];
|
||||
}
|
||||
|
||||
$params = ['order' => ['created' => true],
|
||||
|
@ -1496,9 +1496,10 @@ class Contact extends BaseObject
|
|||
|
||||
/**
|
||||
* @param integer $id contact id
|
||||
* @param string $network Optional network we are probing for
|
||||
* @return boolean
|
||||
*/
|
||||
public static function updateFromProbe($id)
|
||||
public static function updateFromProbe($id, $network = '')
|
||||
{
|
||||
/*
|
||||
Warning: Never ever fetch the public key via Probe::uri and write it into the contacts.
|
||||
|
@ -1511,10 +1512,10 @@ class Contact extends BaseObject
|
|||
return false;
|
||||
}
|
||||
|
||||
$ret = Probe::uri($contact["url"]);
|
||||
$ret = Probe::uri($contact["url"], $network);
|
||||
|
||||
// If Probe::uri fails the network code will be different
|
||||
if ($ret["network"] != $contact["network"]) {
|
||||
if (($ret["network"] != $contact["network"]) && ($ret["network"] != $network)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1539,6 +1540,7 @@ class Contact extends BaseObject
|
|||
'contact', [
|
||||
'url' => $ret['url'],
|
||||
'nurl' => normalise_link($ret['url']),
|
||||
'network' => $ret['network'],
|
||||
'addr' => $ret['addr'],
|
||||
'alias' => $ret['alias'],
|
||||
'batch' => $ret['batch'],
|
||||
|
@ -1686,7 +1688,7 @@ class Contact extends BaseObject
|
|||
|
||||
$hidden = (($ret['network'] === Protocol::MAIL) ? 1 : 0);
|
||||
|
||||
if (in_array($ret['network'], [Protocol::MAIL, Protocol::DIASPORA])) {
|
||||
if (in_array($ret['network'], [Protocol::MAIL, Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
||||
$writeable = 1;
|
||||
}
|
||||
|
||||
|
@ -1766,6 +1768,9 @@ class Contact extends BaseObject
|
|||
} elseif ($contact['network'] == Protocol::DIASPORA) {
|
||||
$ret = Diaspora::sendShare($a->user, $contact);
|
||||
logger('share returns: ' . $ret);
|
||||
} elseif ($contact['network'] == Protocol::ACTIVITYPUB) {
|
||||
$ret = ActivityPub::transmitActivity('Follow', $contact['url'], $uid);
|
||||
logger('Follow returns: ' . $ret);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1814,7 +1819,7 @@ class Contact extends BaseObject
|
|||
return $contact;
|
||||
}
|
||||
|
||||
public static function addRelationship($importer, $contact, $datarray, $item, $sharing = false) {
|
||||
public static function addRelationship($importer, $contact, $datarray, $item = '', $sharing = false) {
|
||||
// Should always be set
|
||||
if (empty($datarray['author-id'])) {
|
||||
return;
|
||||
|
@ -1827,7 +1832,7 @@ class Contact extends BaseObject
|
|||
return;
|
||||
}
|
||||
|
||||
$url = $pub_contact['url'];
|
||||
$url = defaults($datarray, 'author-link', $pub_contact['url']);
|
||||
$name = $pub_contact['name'];
|
||||
$photo = $pub_contact['photo'];
|
||||
$nick = $pub_contact['nick'];
|
||||
|
@ -1839,13 +1844,17 @@ class Contact extends BaseObject
|
|||
DBA::update('contact', ['rel' => self::FRIEND, 'writable' => true],
|
||||
['id' => $contact['id'], 'uid' => $importer['uid']]);
|
||||
}
|
||||
|
||||
if ($contact['network'] == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub::transmitContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
|
||||
}
|
||||
|
||||
// send email notification to owner?
|
||||
} else {
|
||||
if (DBA::exists('contact', ['nurl' => normalise_link($url), 'uid' => $importer['uid'], 'pending' => true])) {
|
||||
logger('ignoring duplicated connection request from pending contact ' . $url);
|
||||
return;
|
||||
}
|
||||
|
||||
// create contact record
|
||||
q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
|
||||
`blocked`, `readonly`, `pending`, `writable`)
|
||||
|
|
|
@ -17,13 +17,14 @@ class Conversation
|
|||
* These constants represent the parcel format used to transport a conversation independently of the message protocol.
|
||||
* It currently is stored in the "protocol" field for legacy reasons.
|
||||
*/
|
||||
const PARCEL_UNKNOWN = 0;
|
||||
const PARCEL_ACTIVITYPUB = 0;
|
||||
const PARCEL_DFRN = 1;
|
||||
const PARCEL_DIASPORA = 2;
|
||||
const PARCEL_SALMON = 3;
|
||||
const PARCEL_FEED = 4; // Deprecated
|
||||
const PARCEL_SPLIT_CONVERSATION = 6;
|
||||
const PARCEL_TWITTER = 67;
|
||||
const PARCEL_UNKNOWN = 255;
|
||||
|
||||
/**
|
||||
* @brief Store the conversation data
|
||||
|
@ -34,7 +35,7 @@ class Conversation
|
|||
public static function insert(array $arr)
|
||||
{
|
||||
if (in_array(defaults($arr, 'network', Protocol::PHANTOM),
|
||||
[Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::TWITTER]) && !empty($arr['uri'])) {
|
||||
[Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::TWITTER]) && !empty($arr['uri'])) {
|
||||
$conversation = ['item-uri' => $arr['uri'], 'received' => DateTimeFormat::utcNow()];
|
||||
|
||||
if (isset($arr['parent-uri']) && ($arr['parent-uri'] != $arr['uri'])) {
|
||||
|
@ -70,7 +71,8 @@ class Conversation
|
|||
unset($old_conv['source']);
|
||||
}
|
||||
// Update structure data all the time but the source only when its from a better protocol.
|
||||
if (isset($conversation['protocol']) && isset($conversation['source']) && ($old_conv['protocol'] < $conversation['protocol']) && ($old_conv['protocol'] != 0)) {
|
||||
if (empty($conversation['source']) || (!empty($old_conv['source']) &&
|
||||
($old_conv['protocol'] < defaults($conversation, 'protocol', PARCEL_UNKNOWN)))) {
|
||||
unset($conversation['protocol']);
|
||||
unset($conversation['source']);
|
||||
}
|
||||
|
|
|
@ -314,7 +314,7 @@ class Event extends BaseObject
|
|||
|
||||
Addon::callHooks('event_updated', $event['id']);
|
||||
} else {
|
||||
$event['guid'] = defaults($arr, 'guid', System::createGUID(32));
|
||||
$event['guid'] = defaults($arr, 'guid', System::createUUID());
|
||||
|
||||
// New event. Store it.
|
||||
DBA::insert('event', $event);
|
||||
|
|
|
@ -176,7 +176,7 @@ class Item extends BaseObject
|
|||
|
||||
// We can always comment on posts from these networks
|
||||
if (array_key_exists('writable', $row) &&
|
||||
in_array($row['internal-network'], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
in_array($row['internal-network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
$row['writable'] = true;
|
||||
}
|
||||
|
||||
|
@ -1081,9 +1081,8 @@ class Item extends BaseObject
|
|||
|
||||
DBA::delete('item-delivery-data', ['iid' => $item['id']]);
|
||||
|
||||
if (!empty($item['iaid']) && !self::exists(['iaid' => $item['iaid'], 'deleted' => false])) {
|
||||
DBA::delete('item-activity', ['id' => $item['iaid']], ['cascade' => false]);
|
||||
}
|
||||
// We don't delete the item-activity here, since we need some of the data for ActivityPub
|
||||
|
||||
if (!empty($item['icid']) && !self::exists(['icid' => $item['icid'], 'deleted' => false])) {
|
||||
DBA::delete('item-content', ['id' => $item['icid']], ['cascade' => false]);
|
||||
}
|
||||
|
@ -1205,7 +1204,7 @@ class Item extends BaseObject
|
|||
} elseif (!empty($item['uri'])) {
|
||||
$guid = self::guidFromUri($item['uri'], $prefix_host);
|
||||
} else {
|
||||
$guid = System::createGUID(32, hash('crc32', $prefix_host));
|
||||
$guid = System::createUUID(hash('crc32', $prefix_host));
|
||||
}
|
||||
|
||||
return $guid;
|
||||
|
@ -1352,7 +1351,7 @@ class Item extends BaseObject
|
|||
* We have to check several networks since Friendica posts could be repeated
|
||||
* via OStatus (maybe Diasporsa as well)
|
||||
*/
|
||||
if (in_array($item['network'], [Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS, ""])) {
|
||||
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS, ""])) {
|
||||
$condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?)",
|
||||
trim($item['uri']), $item['uid'],
|
||||
Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS];
|
||||
|
@ -2054,7 +2053,7 @@ class Item extends BaseObject
|
|||
|
||||
// Only distribute public items from native networks
|
||||
$condition = ['id' => $itemid, 'uid' => 0,
|
||||
'network' => [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""],
|
||||
'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""],
|
||||
'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => false];
|
||||
$item = self::selectFirst(self::ITEM_FIELDLIST, ['id' => $itemid]);
|
||||
if (!DBA::isResult($item)) {
|
||||
|
@ -2072,14 +2071,46 @@ class Item extends BaseObject
|
|||
|
||||
$users = [];
|
||||
|
||||
$condition = ["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `id` = ?) AND `uid` != 0 AND NOT `blocked` AND `rel` IN (?, ?)",
|
||||
$parent['owner-id'], Contact::SHARING, Contact::FRIEND];
|
||||
/// @todo add a field "pcid" in the contact table that referrs to the public contact id.
|
||||
$owner = DBA::selectFirst('contact', ['url', 'nurl', 'alias'], ['id' => $parent['owner-id']]);
|
||||
if (!DBA::isResult($owner)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$condition = ['nurl' => $owner['nurl'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
|
||||
$contacts = DBA::select('contact', ['uid'], $condition);
|
||||
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
if ($contact['uid'] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$users[$contact['uid']] = $contact['uid'];
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
$condition = ['alias' => $owner['url'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
|
||||
$contacts = DBA::select('contact', ['uid'], $condition);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
if ($contact['uid'] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$users[$contact['uid']] = $contact['uid'];
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
if (!empty($owner['alias'])) {
|
||||
$condition = ['url' => $owner['alias'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
|
||||
$contacts = DBA::select('contact', ['uid'], $condition);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
if ($contact['uid'] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$users[$contact['uid']] = $contact['uid'];
|
||||
}
|
||||
DBA::close($contacts);
|
||||
}
|
||||
|
||||
$origin_uid = 0;
|
||||
|
||||
|
@ -2176,7 +2207,7 @@ class Item extends BaseObject
|
|||
}
|
||||
|
||||
// is it an entry from a connector? Only add an entry for natively connected networks
|
||||
if (!in_array($item["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
|
||||
if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2327,16 +2358,10 @@ class Item extends BaseObject
|
|||
public static function newURI($uid, $guid = "")
|
||||
{
|
||||
if ($guid == "") {
|
||||
$guid = System::createGUID(32);
|
||||
$guid = System::createUUID();
|
||||
}
|
||||
|
||||
$hostname = self::getApp()->get_hostname();
|
||||
|
||||
$user = DBA::selectFirst('user', ['nickname'], ['uid' => $uid]);
|
||||
|
||||
$uri = "urn:X-dfrn:" . $hostname . ':' . $user['nickname'] . ':' . $guid;
|
||||
|
||||
return $uri;
|
||||
return self::getApp()->get_baseurl() . '/object/' . $guid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2660,7 +2685,7 @@ class Item extends BaseObject
|
|||
}
|
||||
|
||||
if ($contact['network'] != Protocol::FEED) {
|
||||
$datarray["guid"] = System::createGUID(32);
|
||||
$datarray["guid"] = System::createUUID();
|
||||
unset($datarray["plink"]);
|
||||
$datarray["uri"] = self::newURI($contact['uid'], $datarray["guid"]);
|
||||
$datarray["parent-uri"] = $datarray["uri"];
|
||||
|
@ -2826,7 +2851,7 @@ class Item extends BaseObject
|
|||
}
|
||||
|
||||
// returns an array of contact-ids that are allowed to see this object
|
||||
private static function enumeratePermissions($obj)
|
||||
public static function enumeratePermissions($obj)
|
||||
{
|
||||
$allow_people = expand_acl($obj['allow_cid']);
|
||||
$allow_groups = Group::expand(expand_acl($obj['allow_gid']));
|
||||
|
@ -3089,7 +3114,7 @@ class Item extends BaseObject
|
|||
$objtype = $item['resource-id'] ? ACTIVITY_OBJ_IMAGE : ACTIVITY_OBJ_NOTE ;
|
||||
|
||||
$new_item = [
|
||||
'guid' => System::createGUID(32),
|
||||
'guid' => System::createUUID(),
|
||||
'uri' => self::newURI($item['uid']),
|
||||
'uid' => $item['uid'],
|
||||
'contact-id' => $item_contact_id,
|
||||
|
|
|
@ -46,7 +46,7 @@ class Mail
|
|||
return -2;
|
||||
}
|
||||
|
||||
$guid = System::createGUID(32);
|
||||
$guid = System::createUUID();
|
||||
$uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid;
|
||||
|
||||
$convid = 0;
|
||||
|
@ -73,7 +73,7 @@ class Mail
|
|||
$recip_handle = (($contact['addr']) ? $contact['addr'] : $contact['nick'] . '@' . $recip_host);
|
||||
$sender_handle = $a->user['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);
|
||||
|
||||
$conv_guid = System::createGUID(32);
|
||||
$conv_guid = System::createUUID();
|
||||
$convuri = $recip_handle . ':' . $conv_guid;
|
||||
|
||||
$handles = $recip_handle . ';' . $sender_handle;
|
||||
|
@ -171,7 +171,7 @@ class Mail
|
|||
$subject = L10n::t('[no subject]');
|
||||
}
|
||||
|
||||
$guid = System::createGUID(32);
|
||||
$guid = System::createUUID();
|
||||
$uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid;
|
||||
|
||||
$me = Probe::uri($replyto);
|
||||
|
@ -180,7 +180,7 @@ class Mail
|
|||
return -2;
|
||||
}
|
||||
|
||||
$conv_guid = System::createGUID(32);
|
||||
$conv_guid = System::createUUID();
|
||||
|
||||
$recip_handle = $recipient['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);
|
||||
|
||||
|
|
|
@ -28,6 +28,19 @@ require_once 'include/dba.php';
|
|||
|
||||
class Profile
|
||||
{
|
||||
/**
|
||||
* @brief Returns default profile for a given user id
|
||||
*
|
||||
* @param integer User ID
|
||||
*
|
||||
* @return array Profile data
|
||||
*/
|
||||
public static function getByUID($uid)
|
||||
{
|
||||
$profile = DBA::selectFirst('profile', [], ['uid' => $uid, 'is-default' => true]);
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a formatted location string from the given profile array
|
||||
*
|
||||
|
|
|
@ -33,6 +33,17 @@ class Term
|
|||
return $tag_text;
|
||||
}
|
||||
|
||||
public static function tagArrayFromItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION])
|
||||
{
|
||||
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type];
|
||||
$tags = DBA::select('term', ['type', 'term', 'url'], $condition);
|
||||
if (!DBA::isResult($tags)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return DBA::toArray($tags);
|
||||
}
|
||||
|
||||
public static function fileTextFromItemId($itemid)
|
||||
{
|
||||
$file_text = '';
|
||||
|
@ -99,6 +110,18 @@ class Term
|
|||
$pattern = '/\W([\#@])\[url\=(.*?)\](.*?)\[\/url\]/ism';
|
||||
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
|
||||
if ($match[1] == '@') {
|
||||
$contact = Contact::getDetailsByURL($match[2], 0);
|
||||
if (!empty($contact['addr'])) {
|
||||
$match[3] = $contact['addr'];
|
||||
}
|
||||
|
||||
if (!empty($contact['url'])) {
|
||||
$match[2] = $contact['url'];
|
||||
}
|
||||
}
|
||||
|
||||
$tags[$match[1] . trim($match[3], ',.:;[]/\"?!')] = $match[2];
|
||||
}
|
||||
}
|
||||
|
@ -119,12 +142,22 @@ class Term
|
|||
$term = substr($tag, 1);
|
||||
} elseif (substr(trim($tag), 0, 1) == '@') {
|
||||
$type = TERM_MENTION;
|
||||
|
||||
$contact = Contact::getDetailsByURL($link, 0);
|
||||
if (!empty($contact['name'])) {
|
||||
$term = $contact['name'];
|
||||
} else {
|
||||
$term = substr($tag, 1);
|
||||
}
|
||||
} else { // This shouldn't happen
|
||||
$type = TERM_HASHTAG;
|
||||
$term = $tag;
|
||||
}
|
||||
|
||||
if (DBA::exists('term', ['uid' => $message['uid'], 'otype' => TERM_OBJ_POST, 'oid' => $itemid, 'url' => $link])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($message['uid'] == 0) {
|
||||
$global = true;
|
||||
DBA::update('term', ['global' => true], ['otype' => TERM_OBJ_POST, 'guid' => $message['guid']]);
|
||||
|
|
|
@ -31,6 +31,23 @@ require_once 'include/text.php';
|
|||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @brief Returns the user id of a given profile url
|
||||
*
|
||||
* @param string $profile
|
||||
*
|
||||
* @return integer user id
|
||||
*/
|
||||
public static function getIdForURL($url)
|
||||
{
|
||||
$self = DBA::selectFirst('contact', ['uid'], ['nurl' => normalise_link($url), 'self' => true]);
|
||||
if (!DBA::isResult($self)) {
|
||||
return false;
|
||||
} else {
|
||||
return $self['uid'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get owner data by user id
|
||||
*
|
||||
|
@ -495,7 +512,7 @@ class User
|
|||
$spubkey = $sres['pubkey'];
|
||||
|
||||
$insert_result = DBA::insert('user', [
|
||||
'guid' => System::createGUID(32),
|
||||
'guid' => System::createUUID(),
|
||||
'username' => $username,
|
||||
'password' => $new_password_encoded,
|
||||
'email' => $email,
|
||||
|
|
38
src/Module/Followers.php
Normal file
38
src/Module/Followers.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Module/Followers.php
|
||||
*/
|
||||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\User;
|
||||
|
||||
/**
|
||||
* ActivityPub Followers
|
||||
*/
|
||||
class Followers extends BaseModule
|
||||
{
|
||||
public static function rawContent()
|
||||
{
|
||||
$a = self::getApp();
|
||||
|
||||
if (empty($a->argv[1])) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
|
||||
$owner = User::getOwnerDataByNick($a->argv[1]);
|
||||
if (empty($owner)) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
|
||||
$page = defaults($_REQUEST, 'page', null);
|
||||
|
||||
$followers = ActivityPub::getFollowers($owner, $page);
|
||||
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($followers);
|
||||
exit();
|
||||
}
|
||||
}
|
38
src/Module/Following.php
Normal file
38
src/Module/Following.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Module/Following.php
|
||||
*/
|
||||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\User;
|
||||
|
||||
/**
|
||||
* ActivityPub Following
|
||||
*/
|
||||
class Following extends BaseModule
|
||||
{
|
||||
public static function rawContent()
|
||||
{
|
||||
$a = self::getApp();
|
||||
|
||||
if (empty($a->argv[1])) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
|
||||
$owner = User::getOwnerDataByNick($a->argv[1]);
|
||||
if (empty($owner)) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
|
||||
$page = defaults($_REQUEST, 'page', null);
|
||||
|
||||
$Following = ActivityPub::getFollowing($owner, $page);
|
||||
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($Following);
|
||||
exit();
|
||||
}
|
||||
}
|
55
src/Module/Inbox.php
Normal file
55
src/Module/Inbox.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Module/Inbox.php
|
||||
*/
|
||||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
|
||||
/**
|
||||
* ActivityPub Inbox
|
||||
*/
|
||||
class Inbox extends BaseModule
|
||||
{
|
||||
public static function rawContent()
|
||||
{
|
||||
$a = self::getApp();
|
||||
|
||||
$postdata = file_get_contents('php://input');
|
||||
|
||||
if (empty($postdata)) {
|
||||
System::httpExit(400);
|
||||
}
|
||||
|
||||
// Enable for test purposes
|
||||
/*
|
||||
if (HTTPSignature::getSigner($postdata, $_SERVER)) {
|
||||
$filename = 'signed-activitypub';
|
||||
} else {
|
||||
$filename = 'failed-activitypub';
|
||||
}
|
||||
|
||||
$tempfile = tempnam(get_temppath(), $filename);
|
||||
file_put_contents($tempfile, json_encode(['argv' => $a->argv, 'header' => $_SERVER, 'body' => $postdata], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
logger('Incoming message stored under ' . $tempfile);
|
||||
*/
|
||||
if (!empty($a->argv[1])) {
|
||||
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $a->argv[1]]);
|
||||
if (!DBA::isResult($user)) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
$uid = $user['uid'];
|
||||
} else {
|
||||
$uid = 0;
|
||||
}
|
||||
|
||||
ActivityPub::processInbox($postdata, $_SERVER, $uid);
|
||||
|
||||
System::httpExit(202);
|
||||
}
|
||||
}
|
|
@ -76,13 +76,9 @@ 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->urlpath ? '/' . $a->urlpath : ''),
|
||||
false,
|
||||
true,
|
||||
'sha512'
|
||||
'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->urlpath ? '/' . $a->urlpath : '')
|
||||
);
|
||||
|
||||
// Try to get an authentication token from the other instance.
|
||||
|
|
41
src/Module/Object.php
Normal file
41
src/Module/Object.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Module/Object.php
|
||||
*/
|
||||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
/**
|
||||
* ActivityPub Object
|
||||
*/
|
||||
class Object extends BaseModule
|
||||
{
|
||||
public static function rawContent()
|
||||
{
|
||||
$a = self::getApp();
|
||||
|
||||
if (empty($a->argv[1])) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
|
||||
if (!ActivityPub::isRequest()) {
|
||||
goaway(str_replace('object/', 'display/', $a->query_string));
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['id'], ['guid' => $a->argv[1], 'wall' => true, 'private' => false]);
|
||||
if (!DBA::isResult($item)) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
|
||||
$data = ActivityPub::createObjectFromItemID($item['id']);
|
||||
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($data);
|
||||
exit();
|
||||
}
|
||||
}
|
38
src/Module/Outbox.php
Normal file
38
src/Module/Outbox.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Module/Outbox.php
|
||||
*/
|
||||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\User;
|
||||
|
||||
/**
|
||||
* ActivityPub Outbox
|
||||
*/
|
||||
class Outbox extends BaseModule
|
||||
{
|
||||
public static function rawContent()
|
||||
{
|
||||
$a = self::getApp();
|
||||
|
||||
if (empty($a->argv[1])) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
|
||||
$owner = User::getOwnerDataByNick($a->argv[1]);
|
||||
if (empty($owner)) {
|
||||
System::httpExit(404);
|
||||
}
|
||||
|
||||
$page = defaults($_REQUEST, 'page', null);
|
||||
|
||||
$outbox = ActivityPub::getOutbox($owner, $page);
|
||||
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($outbox);
|
||||
exit();
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ class Owa extends BaseModule
|
|||
if (DBA::isResult($contact)) {
|
||||
// Try to verify the signed header with the public key of the contact record
|
||||
// we have found.
|
||||
$verified = HTTPSignature::verify('', $contact['pubkey']);
|
||||
$verified = HTTPSignature::verifyMagic($contact['pubkey']);
|
||||
|
||||
if ($verified && $verified['header_signed'] && $verified['header_valid']) {
|
||||
logger('OWA header: ' . print_r($verified, true), LOGGER_DATA);
|
||||
|
|
|
@ -19,6 +19,7 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Model\Profile;
|
||||
use Friendica\Protocol\Email;
|
||||
use Friendica\Protocol\Feed;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
|
@ -328,7 +329,17 @@ class Probe
|
|||
$uid = local_user();
|
||||
}
|
||||
|
||||
if ($network != Protocol::ACTIVITYPUB) {
|
||||
$data = self::detect($uri, $network, $uid);
|
||||
} else {
|
||||
$data = null;
|
||||
}
|
||||
|
||||
$ap_profile = ActivityPub::probeProfile($uri);
|
||||
|
||||
if (!empty($ap_profile) && (defaults($data, 'network', '') != Protocol::DFRN)) {
|
||||
$data = $ap_profile;
|
||||
}
|
||||
|
||||
if (!isset($data["url"])) {
|
||||
$data["url"] = $uri;
|
||||
|
|
|
@ -324,7 +324,7 @@ class Post extends BaseObject
|
|||
$owner_name_e = $this->getOwnerName();
|
||||
|
||||
// Disable features that aren't available in several networks
|
||||
if (!in_array($item["network"], [Protocol::DFRN, Protocol::DIASPORA]) && isset($buttons["dislike"])) {
|
||||
if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA]) && isset($buttons["dislike"])) {
|
||||
unset($buttons["dislike"]);
|
||||
$isevent = false;
|
||||
$tagger = '';
|
||||
|
|
1944
src/Protocol/ActivityPub.php
Normal file
1944
src/Protocol/ActivityPub.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1592,17 +1592,13 @@ class Diaspora
|
|||
if (DBA::isResult($item)) {
|
||||
return $item["uri"];
|
||||
} elseif (!$onlyfound) {
|
||||
$contact = Contact::getDetailsByAddr($author, 0);
|
||||
if (!empty($contact['network'])) {
|
||||
$prefix = 'urn:X-' . $contact['network'] . ':';
|
||||
} else {
|
||||
// This fallback should happen most unlikely
|
||||
$prefix = 'urn:X-dspr:';
|
||||
}
|
||||
$person = self::personByHandle($author);
|
||||
|
||||
$author_parts = explode('@', $author);
|
||||
$parts = parse_url($person['url']);
|
||||
unset($parts['path']);
|
||||
$host_url = Network::unparseURL($parts);
|
||||
|
||||
return $prefix . $author_parts[1] . ':' . $author_parts[0] . ':'. $guid;
|
||||
return $host_url . '/object/' . $guid;
|
||||
}
|
||||
|
||||
return "";
|
||||
|
@ -3204,7 +3200,7 @@ class Diaspora
|
|||
$author = self::myHandle($owner);
|
||||
|
||||
$message = ["author" => $author,
|
||||
"guid" => System::createGUID(32),
|
||||
"guid" => System::createUUID(),
|
||||
"parent_type" => "Post",
|
||||
"parent_guid" => $item["guid"]];
|
||||
|
||||
|
|
|
@ -2004,8 +2004,7 @@ class OStatus
|
|||
}
|
||||
|
||||
if (intval($item["parent"]) > 0) {
|
||||
$conversation_href = System::baseUrl()."/display/".$owner["nick"]."/".$item["parent"];
|
||||
$conversation_uri = $conversation_href;
|
||||
$conversation_href = $conversation_uri = str_replace('/object/', '/context/', $item['parent-uri']);
|
||||
|
||||
if (isset($parent_item)) {
|
||||
$conversation = DBA::selectFirst('conversation', ['conversation-uri', 'conversation-href'], ['item-uri' => $parent_item]);
|
||||
|
|
|
@ -333,7 +333,7 @@ class PortableContact
|
|||
$server_url = normalise_link(self::detectServer($profile));
|
||||
}
|
||||
|
||||
if (!in_array($gcontacts[0]["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::FEED, Protocol::OSTATUS, ""])) {
|
||||
if (!in_array($gcontacts[0]["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::FEED, Protocol::OSTATUS, ""])) {
|
||||
logger("Profile ".$profile.": Network type ".$gcontacts[0]["network"]." can't be checked", LOGGER_DEBUG);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,67 +5,45 @@
|
|||
*/
|
||||
namespace Friendica\Util;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
||||
/**
|
||||
* @brief Implements HTTP Signatures per draft-cavage-http-signatures-07.
|
||||
*
|
||||
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Web/HTTPSig.php
|
||||
*
|
||||
* Other parts of the code for HTTP signing are taken from the Osada project.
|
||||
* https://framagit.org/macgirvin/osada
|
||||
*
|
||||
* @see https://tools.ietf.org/html/draft-cavage-http-signatures-07
|
||||
*/
|
||||
|
||||
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
|
||||
* @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)
|
||||
// {
|
||||
// $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 = '')
|
||||
/**
|
||||
* @brief Verifies a magic request
|
||||
*
|
||||
* @param $key
|
||||
*
|
||||
* @return array with verification data
|
||||
*/
|
||||
public static function verifyMagic($key)
|
||||
{
|
||||
$body = $data;
|
||||
$headers = null;
|
||||
$spoofable = false;
|
||||
$result = [
|
||||
'signer' => '',
|
||||
'header_signed' => false,
|
||||
'header_valid' => false,
|
||||
'content_signed' => false,
|
||||
'content_valid' => false
|
||||
'header_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->fetch();
|
||||
$body = $data['body'];
|
||||
} else {
|
||||
$headers = [];
|
||||
$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']).' '.$_SERVER['REQUEST_URI'];
|
||||
|
||||
|
@ -75,24 +53,16 @@ class HTTPSignature
|
|||
$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'];
|
||||
|
@ -112,13 +82,7 @@ class HTTPSignature
|
|||
|
||||
$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)) {
|
||||
$result['signer'] = $sig_block['keyId'];
|
||||
|
@ -127,12 +91,6 @@ class HTTPSignature
|
|||
|
||||
logger('Got keyID ' . $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;
|
||||
}
|
||||
|
@ -149,130 +107,39 @@ class HTTPSignature
|
|||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$contact = DBA::selectFirst('contact', ['pubkey'], ['uid' => 0, 'addr' => str_replace('acct:', '', $id)]);
|
||||
} else {
|
||||
$contact = DBA::selectFirst('contact', ['pubkey'], ['id' => $id, 'network' => 'activitypub']);
|
||||
}
|
||||
|
||||
if (DBA::isResult($contact)) {
|
||||
return $contact['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
|
||||
*
|
||||
* @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')
|
||||
public static function createSig($head, $prvkey, $keyid = 'Key')
|
||||
{
|
||||
$return_headers = [];
|
||||
|
||||
if ($alg === 'sha256') {
|
||||
$algorithm = 'rsa-sha256';
|
||||
}
|
||||
|
||||
if ($alg === 'sha512') {
|
||||
$alg = 'sha512';
|
||||
$algorithm = 'rsa-sha512';
|
||||
}
|
||||
|
||||
$x = self::sign($request, $head, $prvkey, $alg);
|
||||
$x = self::sign($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) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($send_headers) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
return $return_headers;
|
||||
}
|
||||
|
@ -280,25 +147,18 @@ class HTTPSignature
|
|||
/**
|
||||
* @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')
|
||||
private static function sign($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) {
|
||||
|
@ -308,7 +168,6 @@ class HTTPSignature
|
|||
}
|
||||
// strip the trailing linefeed
|
||||
$headers = rtrim($headers, "\n");
|
||||
}
|
||||
|
||||
$sig = base64_encode(Crypto::rsaSign($headers, $prvkey, $alg));
|
||||
|
||||
|
@ -405,4 +264,178 @@ class HTTPSignature
|
|||
|
||||
return '';
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for ActivityPub
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Transmit given data to a target for a user
|
||||
*
|
||||
* @param $data
|
||||
* @param $target
|
||||
* @param $uid
|
||||
*/
|
||||
public static function transmit($data, $target, $uid)
|
||||
{
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
|
||||
if (!$owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// Header data that is about to be signed.
|
||||
$host = parse_url($target, PHP_URL_HOST);
|
||||
$path = parse_url($target, PHP_URL_PATH);
|
||||
$digest = 'SHA-256=' . base64_encode(hash('sha256', $content, true));
|
||||
$content_length = strlen($content);
|
||||
|
||||
$headers = ['Content-Length: ' . $content_length, 'Digest: ' . $digest, 'Host: ' . $host];
|
||||
|
||||
$signed_data = "(request-target): post " . $path . "\ncontent-length: " . $content_length . "\ndigest: " . $digest . "\nhost: " . $host;
|
||||
|
||||
$signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256'));
|
||||
|
||||
$headers[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) content-length digest host",signature="' . $signature . '"';
|
||||
|
||||
$headers[] = 'Content-Type: application/activity+json';
|
||||
|
||||
Network::post($target, $content, $headers);
|
||||
$return_code = BaseObject::getApp()->get_curl_code();
|
||||
|
||||
logger('Transmit to ' . $target . ' returned ' . $return_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a signer from a given HTTP request
|
||||
*
|
||||
* @param $content
|
||||
* @param $http_headers
|
||||
*
|
||||
* @return signer string
|
||||
*/
|
||||
public static function getSigner($content, $http_headers)
|
||||
{
|
||||
$object = json_decode($content, true);
|
||||
|
||||
if (empty($object)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actor = JsonLD::fetchElement($object, 'actor', 'id');
|
||||
|
||||
$headers = [];
|
||||
$headers['(request-target)'] = strtolower($http_headers['REQUEST_METHOD']) . ' ' . $http_headers['REQUEST_URI'];
|
||||
|
||||
// First take every header
|
||||
foreach ($http_headers as $k => $v) {
|
||||
$field = str_replace('_', '-', strtolower($k));
|
||||
$headers[$field] = $v;
|
||||
}
|
||||
|
||||
// Now add every http header
|
||||
foreach ($http_headers as $k => $v) {
|
||||
if (strpos($k, 'HTTP_') === 0) {
|
||||
$field = str_replace('_', '-', strtolower(substr($k, 5)));
|
||||
$headers[$field] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$sig_block = self::parseSigHeader($http_headers['HTTP_SIGNATURE']);
|
||||
|
||||
if (empty($sig_block) || empty($sig_block['headers']) || empty($sig_block['keyId'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$signed_data = '';
|
||||
foreach ($sig_block['headers'] as $h) {
|
||||
if (array_key_exists($h, $headers)) {
|
||||
$signed_data .= $h . ': ' . $headers[$h] . "\n";
|
||||
}
|
||||
}
|
||||
$signed_data = rtrim($signed_data, "\n");
|
||||
|
||||
if (empty($signed_data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$algorithm = null;
|
||||
|
||||
if ($sig_block['algorithm'] === 'rsa-sha256') {
|
||||
$algorithm = 'sha256';
|
||||
}
|
||||
|
||||
if ($sig_block['algorithm'] === 'rsa-sha512') {
|
||||
$algorithm = 'sha512';
|
||||
}
|
||||
|
||||
if (empty($algorithm)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key = self::fetchKey($sig_block['keyId'], $actor);
|
||||
|
||||
if (empty($key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Crypto::rsaVerify($signed_data, $sig_block['signature'], $key['pubkey'], $algorithm)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the digest when it is part of the signed data
|
||||
if (in_array('digest', $sig_block['headers'])) {
|
||||
$digest = explode('=', $headers['digest'], 2);
|
||||
if ($digest[0] === 'SHA-256') {
|
||||
$hashalg = 'sha256';
|
||||
}
|
||||
if ($digest[0] === 'SHA-512') {
|
||||
$hashalg = 'sha512';
|
||||
}
|
||||
|
||||
/// @todo add all hashes from the rfc
|
||||
|
||||
if (!empty($hashalg) && base64_encode(hash($hashalg, $content, true)) != $digest[1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the content-length when it is part of the signed data
|
||||
if (in_array('content-length', $sig_block['headers'])) {
|
||||
if (strlen($content) != $headers['content-length']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $key['url'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief fetches a key for a given id and actor
|
||||
*
|
||||
* @param $id
|
||||
* @param $actor
|
||||
*
|
||||
* @return array with actor url and public key
|
||||
*/
|
||||
private static function fetchKey($id, $actor)
|
||||
{
|
||||
$url = (strpos($id, '#') ? substr($id, 0, strpos($id, '#')) : $id);
|
||||
|
||||
$profile = APContact::getByURL($url);
|
||||
if (!empty($profile)) {
|
||||
logger('Taking key from id ' . $id, LOGGER_DEBUG);
|
||||
return ['url' => $url, 'pubkey' => $profile['pubkey']];
|
||||
} elseif ($url != $actor) {
|
||||
$profile = APContact::getByURL($actor);
|
||||
if (!empty($profile)) {
|
||||
logger('Taking key from actor ' . $actor, LOGGER_DEBUG);
|
||||
return ['url' => $actor, 'pubkey' => $profile['pubkey']];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
143
src/Util/JsonLD.php
Normal file
143
src/Util/JsonLD.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Util/JsonLD.php
|
||||
*/
|
||||
namespace Friendica\Util;
|
||||
|
||||
use Friendica\Core\Cache;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @brief This class contain methods to work with JsonLD data
|
||||
*/
|
||||
class JsonLD
|
||||
{
|
||||
/**
|
||||
* @brief Loader for LD-JSON validation
|
||||
*
|
||||
* @param $url
|
||||
*
|
||||
* @return the loaded data
|
||||
*/
|
||||
public static function documentLoader($url)
|
||||
{
|
||||
$recursion = 0;
|
||||
|
||||
$x = debug_backtrace();
|
||||
if ($x) {
|
||||
foreach ($x as $n) {
|
||||
if ($n['function'] === __FUNCTION__) {
|
||||
$recursion ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($recursion > 5) {
|
||||
logger('jsonld bomb detected at: ' . $url);
|
||||
exit();
|
||||
}
|
||||
|
||||
$result = Cache::get('documentLoader:' . $url);
|
||||
if (!is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$data = jsonld_default_document_loader($url);
|
||||
Cache::set('documentLoader:' . $url, $data, CACHE_DAY);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalises a given JSON array
|
||||
*
|
||||
* @param array $json
|
||||
*
|
||||
* @return normalized JSON string
|
||||
*/
|
||||
public static function normalize($json)
|
||||
{
|
||||
jsonld_set_document_loader('Friendica\Util\JsonLD::documentLoader');
|
||||
|
||||
$jsonobj = json_decode(json_encode($json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
try {
|
||||
$normalized = jsonld_normalize($jsonobj, array('algorithm' => 'URDNA2015', 'format' => 'application/nquads'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$normalized = false;
|
||||
logger('normalise error:' . print_r($e, true), LOGGER_DEBUG);
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compacts a given JSON array
|
||||
*
|
||||
* @param array $json
|
||||
*
|
||||
* @return comacted JSON array
|
||||
*/
|
||||
public static function compact($json)
|
||||
{
|
||||
jsonld_set_document_loader('Friendica\Util\JsonLD::documentLoader');
|
||||
|
||||
$context = (object)['as' => 'https://www.w3.org/ns/activitystreams',
|
||||
'w3sec' => 'https://w3id.org/security',
|
||||
'ostatus' => (object)['@id' => 'http://ostatus.org#', '@type' => '@id'],
|
||||
'vcard' => (object)['@id' => 'http://www.w3.org/2006/vcard/ns#', '@type' => '@id'],
|
||||
'uuid' => (object)['@id' => 'http://schema.org/identifier', '@type' => '@id']];
|
||||
|
||||
$jsonobj = json_decode(json_encode($json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$compacted = jsonld_compact($jsonobj, $context);
|
||||
|
||||
return json_decode(json_encode($compacted, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fetches an element from a JSON array
|
||||
*
|
||||
* @param $array
|
||||
* @param $element
|
||||
* @param $key
|
||||
* @param $type
|
||||
* @param $type_value
|
||||
*
|
||||
* @return fetched element
|
||||
*/
|
||||
public static function fetchElement($array, $element, $key, $type = null, $type_value = null)
|
||||
{
|
||||
if (empty($array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($array[$element])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_string($array[$element])) {
|
||||
return $array[$element];
|
||||
}
|
||||
|
||||
if (is_null($type_value)) {
|
||||
if (!empty($array[$element][$key])) {
|
||||
return $array[$element][$key];
|
||||
}
|
||||
|
||||
if (!empty($array[$element][0][$key])) {
|
||||
return $array[$element][0][$key];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($array[$element][$key]) && !empty($array[$element][$type]) && ($array[$element][$type] == $type_value)) {
|
||||
return $array[$element][$key];
|
||||
}
|
||||
|
||||
/// @todo Add array search
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
89
src/Util/LDSignature.php
Normal file
89
src/Util/LDSignature.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util;
|
||||
|
||||
use Friendica\Util\JsonLD;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Model\APContact;
|
||||
|
||||
/**
|
||||
* @brief Implements JSON-LD signatures
|
||||
*
|
||||
* Ported from Osada: https://framagit.org/macgirvin/osada
|
||||
*/
|
||||
class LDSignature
|
||||
{
|
||||
public static function isSigned($data)
|
||||
{
|
||||
return !empty($data['signature']);
|
||||
}
|
||||
|
||||
public static function getSigner($data)
|
||||
{
|
||||
if (!self::isSigned($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actor = JsonLD::fetchElement($data, 'actor', 'id');
|
||||
if (empty($actor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$profile = APContact::getByURL($actor);
|
||||
if (empty($profile['pubkey'])) {
|
||||
return false;
|
||||
}
|
||||
$pubkey = $profile['pubkey'];
|
||||
|
||||
$ohash = self::hash(self::signableOptions($data['signature']));
|
||||
$dhash = self::hash(self::signableData($data));
|
||||
|
||||
$x = Crypto::rsaVerify($ohash . $dhash, base64_decode($data['signature']['signatureValue']), $pubkey);
|
||||
logger('LD-verify: ' . intval($x));
|
||||
|
||||
if (empty($x)) {
|
||||
return false;
|
||||
} else {
|
||||
return $actor;
|
||||
}
|
||||
}
|
||||
|
||||
public static function sign($data, $owner)
|
||||
{
|
||||
$options = [
|
||||
'type' => 'RsaSignature2017',
|
||||
'nonce' => random_string(64),
|
||||
'creator' => $owner['url'] . '#main-key',
|
||||
'created' => DateTimeFormat::utcNow(DateTimeFormat::ATOM)
|
||||
];
|
||||
|
||||
$ohash = self::hash(self::signableOptions($options));
|
||||
$dhash = self::hash(self::signableData($data));
|
||||
$options['signatureValue'] = base64_encode(Crypto::rsaSign($ohash . $dhash, $owner['uprvkey']));
|
||||
|
||||
return array_merge($data, ['signature' => $options]);
|
||||
}
|
||||
|
||||
private static function signableData($data)
|
||||
{
|
||||
unset($data['signature']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
private static function signableOptions($options)
|
||||
{
|
||||
$newopts = ['@context' => 'https://w3id.org/identity/v1'];
|
||||
|
||||
unset($options['type']);
|
||||
unset($options['id']);
|
||||
unset($options['signatureValue']);
|
||||
|
||||
return array_merge($newopts, $options);
|
||||
}
|
||||
|
||||
private static function hash($obj)
|
||||
{
|
||||
return hash('sha256', JsonLD::normalize($obj));
|
||||
}
|
||||
}
|
34
src/Worker/APDelivery.php
Normal file
34
src/Worker/APDelivery.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Worker/APDelivery.php
|
||||
*/
|
||||
namespace Friendica\Worker;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
|
||||
class APDelivery extends BaseObject
|
||||
{
|
||||
public static function execute($cmd, $item_id, $inbox, $uid)
|
||||
{
|
||||
logger('Invoked: ' . $cmd . ': ' . $item_id . ' to ' . $inbox, LOGGER_DEBUG);
|
||||
|
||||
if ($cmd == Delivery::MAIL) {
|
||||
} elseif ($cmd == Delivery::SUGGESTION) {
|
||||
} elseif ($cmd == Delivery::RELOCATION) {
|
||||
} elseif ($cmd == Delivery::REMOVAL) {
|
||||
ActivityPub::transmitProfileDeletion($uid, $inbox);
|
||||
} elseif ($cmd == Delivery::PROFILEUPDATE) {
|
||||
ActivityPub::transmitProfileUpdate($uid, $inbox);
|
||||
} else {
|
||||
$data = ActivityPub::createActivityFromItem($item_id);
|
||||
if (!empty($data)) {
|
||||
HTTPSignature::transmit($data, $inbox, $uid);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ class Delivery extends BaseObject
|
|||
const POST = 'wall-new';
|
||||
const COMMENT = 'comment-new';
|
||||
const REMOVAL = 'removeme';
|
||||
const PROFILEUPDATE = 'profileupdate';
|
||||
|
||||
public static function execute($cmd, $item_id, $contact_id)
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@ use Friendica\Model\Item;
|
|||
use Friendica\Model\PushSubscriber;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\OStatus;
|
||||
use Friendica\Protocol\Salmon;
|
||||
|
@ -98,6 +99,14 @@ class Notifier
|
|||
foreach ($r as $contact) {
|
||||
Contact::terminateFriendship($user, $contact, true);
|
||||
}
|
||||
|
||||
$inboxes = ActivityPub::fetchTargetInboxesforUser(0);
|
||||
foreach ($inboxes as $inbox) {
|
||||
logger('Account removal for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
|
||||
Worker::add(['priority' => $a->queue['priority'], 'created' => $a->queue['created'], 'dont_fork' => true],
|
||||
'APDelivery', Delivery::REMOVAL, '', $inbox, $uid);
|
||||
}
|
||||
|
||||
return;
|
||||
} elseif ($cmd == Delivery::RELOCATION) {
|
||||
$normal_mode = false;
|
||||
|
@ -413,6 +422,24 @@ class Notifier
|
|||
}
|
||||
}
|
||||
|
||||
$inboxes = [];
|
||||
|
||||
if ($target_item['origin']) {
|
||||
$inboxes = ActivityPub::fetchTargetInboxes($target_item, $uid);
|
||||
}
|
||||
|
||||
if ($parent['origin']) {
|
||||
$parent_inboxes = ActivityPub::fetchTargetInboxes($parent, $uid);
|
||||
$inboxes = array_merge($inboxes, $parent_inboxes);
|
||||
}
|
||||
|
||||
foreach ($inboxes as $inbox) {
|
||||
logger('Deliver ' . $item_id .' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
|
||||
|
||||
Worker::add(['priority' => $a->queue['priority'], 'created' => $a->queue['created'], 'dont_fork' => true],
|
||||
'APDelivery', $cmd, $item_id, $inbox, $uid);
|
||||
}
|
||||
|
||||
// send salmon slaps to mentioned remote tags (@foo@example.com) in OStatus posts
|
||||
// They are especially used for notifications to OStatus users that don't follow us.
|
||||
if (!Config::get('system', 'dfrn_only') && count($url_recipients) && ($public_message || $push_notify) && $normal_mode) {
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Worker/ProfileUpdate.php
|
||||
* @brief Send updated profile data to Diaspora
|
||||
* @brief Send updated profile data to Diaspora and ActivityPub
|
||||
*/
|
||||
|
||||
namespace Friendica\Worker;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Core\Worker;
|
||||
|
||||
class ProfileUpdate {
|
||||
public static function execute($uid = 0) {
|
||||
|
@ -14,6 +17,16 @@ class ProfileUpdate {
|
|||
return;
|
||||
}
|
||||
|
||||
$a = BaseObject::getApp();
|
||||
|
||||
$inboxes = ActivityPub::fetchTargetInboxesforUser($uid);
|
||||
|
||||
foreach ($inboxes as $inbox) {
|
||||
logger('Profile update for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
|
||||
Worker::add(['priority' => $a->queue['priority'], 'created' => $a->queue['created'], 'dont_fork' => true],
|
||||
'APDelivery', Delivery::PROFILEUPDATE, '', $inbox, $uid);
|
||||
}
|
||||
|
||||
Diaspora::sendProfile($uid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,13 +29,13 @@ class UpdateGContact
|
|||
return;
|
||||
}
|
||||
|
||||
if (!in_array($r[0]["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
if (!in_array($r[0]["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = Probe::uri($r[0]["url"]);
|
||||
|
||||
if (!in_array($data["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
if (!in_array($data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
if ($r[0]["server_url"] != "") {
|
||||
PortableContact::checkServer($r[0]["server_url"], $r[0]["network"]);
|
||||
}
|
||||
|
|
1427
util/messages.po
1427
util/messages.po
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue