APContact stuff is moved to an own class

This commit is contained in:
Michael 2018-09-26 17:24:29 +00:00
parent f3814ae1fc
commit 9ec30010c5
4 changed files with 197 additions and 193 deletions

175
src/Model/APContact.php Normal file
View 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 getProfileByURL($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, 'uuid', 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;
}
}

View file

@ -12,6 +12,7 @@ use Friendica\Util\HTTPSignature;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\Model\Conversation; use Friendica\Model\Conversation;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\APContact;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Model\Term; use Friendica\Model\Term;
@ -278,9 +279,9 @@ class ActivityPub
$activity = json_decode($conversation['source'], true); $activity = json_decode($conversation['source'], true);
$actor = JsonLD::fetchElement($activity, 'actor', 'id'); $actor = JsonLD::fetchElement($activity, 'actor', 'id');
$profile = ActivityPub::fetchprofile($actor); $profile = APContact::getProfileByURL($actor);
$item_profile = ActivityPub::fetchprofile($item['author-link']); $item_profile = APContact::getProfileByURL($item['author-link']);
$exclude[] = $item['author-link']; $exclude[] = $item['author-link'];
if ($item['gravity'] == GRAVITY_PARENT) { if ($item['gravity'] == GRAVITY_PARENT) {
@ -316,7 +317,7 @@ class ActivityPub
$data = array_merge($data, self::fetchPermissionBlockFromConversation($item)); $data = array_merge($data, self::fetchPermissionBlockFromConversation($item));
$actor_profile = ActivityPub::fetchprofile($item['author-link']); $actor_profile = APContact::getProfileByURL($item['author-link']);
$terms = Term::tagArrayFromItemId($item['id']); $terms = Term::tagArrayFromItemId($item['id']);
@ -332,7 +333,7 @@ class ActivityPub
if ($term['type'] != TERM_MENTION) { if ($term['type'] != TERM_MENTION) {
continue; continue;
} }
$profile = self::fetchprofile($term['url'], false); $profile = APContact::getProfileByURL($term['url'], false);
if (!empty($profile) && empty($contacts[$profile['url']])) { if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['cc'][] = $profile['url']; $data['cc'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url']; $contacts[$profile['url']] = $profile['url'];
@ -371,7 +372,7 @@ class ActivityPub
continue; continue;
} }
$profile = self::fetchprofile($parent['author-link'], false); $profile = APContact::getProfileByURL($parent['author-link'], false);
if (!empty($profile) && empty($contacts[$profile['url']])) { if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['cc'][] = $profile['url']; $data['cc'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url']; $contacts[$profile['url']] = $profile['url'];
@ -381,7 +382,7 @@ class ActivityPub
continue; continue;
} }
$profile = self::fetchprofile($parent['owner-link'], false); $profile = APContact::getProfileByURL($parent['owner-link'], false);
if (!empty($profile) && empty($contacts[$profile['url']])) { if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['cc'][] = $profile['url']; $data['cc'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url']; $contacts[$profile['url']] = $profile['url'];
@ -407,9 +408,9 @@ class ActivityPub
$inboxes = []; $inboxes = [];
if ($item['gravity'] == GRAVITY_ACTIVITY) { if ($item['gravity'] == GRAVITY_ACTIVITY) {
$item_profile = ActivityPub::fetchprofile($item['author-link']); $item_profile = APContact::getProfileByURL($item['author-link']);
} else { } else {
$item_profile = ActivityPub::fetchprofile($item['owner-link']); $item_profile = APContact::getProfileByURL($item['owner-link']);
} }
$elements = ['to', 'cc', 'bto', 'bcc']; $elements = ['to', 'cc', 'bto', 'bcc'];
@ -428,7 +429,7 @@ class ActivityPub
} }
DBA::close($contacts); DBA::close($contacts);
} else { } else {
$profile = self::fetchprofile($receiver); $profile = APContact::getProfileByURL($receiver);
if (!empty($profile)) { if (!empty($profile)) {
$target = defaults($profile, 'sharedinbox', $profile['inbox']); $target = defaults($profile, 'sharedinbox', $profile['inbox']);
$inboxes[$target] = $target; $inboxes[$target] = $target;
@ -644,7 +645,7 @@ class ActivityPub
public static function transmitActivity($activity, $target, $uid) public static function transmitActivity($activity, $target, $uid)
{ {
$profile = self::fetchprofile($target); $profile = APContact::getProfileByURL($target);
$owner = User::getOwnerDataById($uid); $owner = User::getOwnerDataById($uid);
@ -663,7 +664,7 @@ class ActivityPub
public static function transmitContactAccept($target, $id, $uid) public static function transmitContactAccept($target, $id, $uid)
{ {
$profile = self::fetchprofile($target); $profile = APContact::getProfileByURL($target);
$owner = User::getOwnerDataById($uid); $owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams', $data = ['@context' => 'https://www.w3.org/ns/activitystreams',
@ -683,7 +684,7 @@ class ActivityPub
public static function transmitContactReject($target, $id, $uid) public static function transmitContactReject($target, $id, $uid)
{ {
$profile = self::fetchprofile($target); $profile = APContact::getProfileByURL($target);
$owner = User::getOwnerDataById($uid); $owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams', $data = ['@context' => 'https://www.w3.org/ns/activitystreams',
@ -703,7 +704,7 @@ class ActivityPub
public static function transmitContactUndo($target, $uid) public static function transmitContactUndo($target, $uid)
{ {
$profile = self::fetchprofile($target); $profile = APContact::getProfileByURL($target);
$id = System::baseUrl() . '/activity/' . System::createGUID(); $id = System::baseUrl() . '/activity/' . System::createGUID();
@ -738,162 +739,6 @@ class ActivityPub
return json_decode($ret['body'], true); return json_decode($ret['body'], true);
} }
/**
* 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 form 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 fetchprofile($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 = self::fetchContent($url);
if (empty($data) || empty($data['id']) || empty($data['inbox'])) {
return false;
}
$apcontact = [];
$apcontact['url'] = $data['id'];
$apcontact['uuid'] = defaults($data, 'uuid', 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;
}
/** /**
* Fetches a profile from the given url into an array that is compatible to Probe::uri * Fetches a profile from the given url into an array that is compatible to Probe::uri
* *
@ -902,7 +747,7 @@ class ActivityPub
*/ */
public static function probeProfile($url) public static function probeProfile($url)
{ {
$apcontact = self::fetchprofile($url, true); $apcontact = APContact::getProfileByURL($url, true);
if (empty($apcontact)) { if (empty($apcontact)) {
return false; return false;
} }
@ -1158,7 +1003,7 @@ class ActivityPub
} }
if (!empty($actor)) { if (!empty($actor)) {
$profile = self::fetchprofile($actor); $profile = APContact::getProfileByURL($actor);
$followers = defaults($profile, 'followers', ''); $followers = defaults($profile, 'followers', '');
logger('Actor: ' . $actor . ' - Followers: ' . $followers, LOGGER_DEBUG); logger('Actor: ' . $actor . ' - Followers: ' . $followers, LOGGER_DEBUG);
@ -1636,7 +1481,7 @@ class ActivityPub
} }
logger('Updating profile for ' . $activity['object']['id'], LOGGER_DEBUG); logger('Updating profile for ' . $activity['object']['id'], LOGGER_DEBUG);
self::fetchprofile($activity['object']['id'], true); APContact::getProfileByURL($activity['object']['id'], true);
} }
private static function acceptFollowUser($activity) private static function acceptFollowUser($activity)

View file

@ -9,6 +9,7 @@ use Friendica\BaseObject;
use Friendica\Core\Config; use Friendica\Core\Config;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Model\APContact;
use Friendica\Protocol\ActivityPub; use Friendica\Protocol\ActivityPub;
/** /**
@ -393,12 +394,12 @@ class HTTPSignature
{ {
$url = (strpos($id, '#') ? substr($id, 0, strpos($id, '#')) : $id); $url = (strpos($id, '#') ? substr($id, 0, strpos($id, '#')) : $id);
$profile = ActivityPub::fetchprofile($url); $profile = APContact::getProfileByURL($url);
if (!empty($profile)) { if (!empty($profile)) {
logger('Taking key from id ' . $id, LOGGER_DEBUG); logger('Taking key from id ' . $id, LOGGER_DEBUG);
return ['url' => $url, 'pubkey' => $profile['pubkey']]; return ['url' => $url, 'pubkey' => $profile['pubkey']];
} elseif ($url != $actor) { } elseif ($url != $actor) {
$profile = ActivityPub::fetchprofile($actor); $profile = APContact::getProfileByURL($actor);
if (!empty($profile)) { if (!empty($profile)) {
logger('Taking key from actor ' . $actor, LOGGER_DEBUG); logger('Taking key from actor ' . $actor, LOGGER_DEBUG);
return ['url' => $actor, 'pubkey' => $profile['pubkey']]; return ['url' => $actor, 'pubkey' => $profile['pubkey']];

View file

@ -5,6 +5,7 @@ namespace Friendica\Util;
use Friendica\Util\JsonLD; use Friendica\Util\JsonLD;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Protocol\ActivityPub; use Friendica\Protocol\ActivityPub;
use Friendica\Model\APContact;
/** /**
* @brief Implements JSON-LD signatures * @brief Implements JSON-LD signatures
@ -24,30 +25,12 @@ class LDSignature
return false; return false;
} }
/*
$creator = $data['signature']['creator'];
$actor = JsonLD::fetchElement($data, 'actor', 'id');
$url = (strpos($creator, '#') ? substr($creator, 0, strpos($creator, '#')) : $creator);
$profile = ActivityPub::fetchprofile($url);
if (!empty($profile)) {
logger('Taking key from creator ' . $creator, LOGGER_DEBUG);
} elseif ($url != $actor) {
$profile = ActivityPub::fetchprofile($actor);
if (empty($profile)) {
return false;
}
logger('Taking key from actor ' . $actor, LOGGER_DEBUG);
}
*/
$actor = JsonLD::fetchElement($data, 'actor', 'id'); $actor = JsonLD::fetchElement($data, 'actor', 'id');
if (empty($actor)) { if (empty($actor)) {
return false; return false;
} }
$profile = ActivityPub::fetchprofile($actor); $profile = APContact::getProfileByURL($actor);
if (empty($profile['pubkey'])) { if (empty($profile['pubkey'])) {
return false; return false;
} }