mirror of
https://git.sekbaer.de/Friendica/friendica.git
synced 2025-06-11 17:44:27 +02:00
OStatus support removed
This commit is contained in:
parent
eb066b258d
commit
e8a3be6820
87 changed files with 773 additions and 4383 deletions
|
@ -1285,8 +1285,6 @@ class Receiver
|
|||
DBA::close($parents);
|
||||
}
|
||||
|
||||
self::switchContacts($receivers, $actor);
|
||||
|
||||
// "birdsitelive" is a service that mirrors tweets into the fediverse
|
||||
// These posts can be fetched without authentication, but are not marked as public
|
||||
// We treat them as unlisted posts to be able to handle them.
|
||||
|
@ -1369,62 +1367,6 @@ class Receiver
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches existing contacts to ActivityPub
|
||||
*
|
||||
* @param integer $cid Contact ID
|
||||
* @param integer $uid User ID
|
||||
* @param string $url Profile URL
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function switchContact(int $cid, int $uid, string $url)
|
||||
{
|
||||
if (DBA::exists('contact', ['id' => $cid, 'network' => Protocol::ACTIVITYPUB])) {
|
||||
Logger::info('Contact is already ActivityPub', ['id' => $cid, 'uid' => $uid, 'url' => $url]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Contact::updateFromProbe($cid)) {
|
||||
Logger::info('Update was successful', ['id' => $cid, 'uid' => $uid, 'url' => $url]);
|
||||
}
|
||||
|
||||
// Send a new follow request to be sure that the connection still exists
|
||||
if (($uid != 0) && DBA::exists('contact', ['id' => $cid, 'rel' => [Contact::SHARING, Contact::FRIEND], 'network' => Protocol::ACTIVITYPUB])) {
|
||||
Logger::info('Contact had been switched to ActivityPub. Sending a new follow request.', ['uid' => $uid, 'url' => $url]);
|
||||
ActivityPub\Transmitter::sendActivity('Follow', $url, $uid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO Fix documentation and type-hints
|
||||
*
|
||||
* @param $receivers
|
||||
* @param $actor
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function switchContacts($receivers, $actor)
|
||||
{
|
||||
if (empty($actor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($receivers as $receiver) {
|
||||
$contact = DBA::selectFirst('contact', ['id'], ['uid' => $receiver['uid'], 'network' => Protocol::OSTATUS, 'nurl' => Strings::normaliseLink($actor)]);
|
||||
if (DBA::isResult($contact)) {
|
||||
self::switchContact($contact['id'], $receiver['uid'], $actor);
|
||||
}
|
||||
|
||||
$contact = DBA::selectFirst('contact', ['id'], ['uid' => $receiver['uid'], 'network' => Protocol::OSTATUS, 'alias' => [Strings::normaliseLink($actor), $actor]]);
|
||||
if (DBA::isResult($contact)) {
|
||||
self::switchContact($contact['id'], $receiver['uid'], $actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO Fix documentation and type-hints
|
||||
*
|
||||
|
|
|
@ -642,7 +642,7 @@ class Transmitter
|
|||
$networks = Protocol::FEDERATED;
|
||||
} else {
|
||||
// For now only send to these contacts:
|
||||
$networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS];
|
||||
$networks = [Protocol::ACTIVITYPUB];
|
||||
}
|
||||
|
||||
$data = ['to' => [], 'cc' => [], 'bto' => [], 'bcc' => [], 'audience' => $audience];
|
||||
|
@ -1019,7 +1019,7 @@ class Transmitter
|
|||
$networks = Protocol::FEDERATED;
|
||||
} else {
|
||||
// For now only send to these contacts:
|
||||
$networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS];
|
||||
$networks = [Protocol::ACTIVITYPUB];
|
||||
}
|
||||
|
||||
$condition = [
|
||||
|
|
|
@ -85,8 +85,7 @@ class DFRN
|
|||
$user['uprvkey'] = $user['prvkey'];
|
||||
} else {
|
||||
$user = ['importer_uid' => 0, 'uprvkey' => '', 'timezone' => 'UTC',
|
||||
'nickname' => '', 'sprvkey' => '', 'spubkey' => '',
|
||||
'page-flags' => 0, 'account-type' => 0, 'prvnets' => 0];
|
||||
'nickname' => '', 'page-flags' => 0, 'account-type' => 0, 'prvnets' => 0];
|
||||
}
|
||||
|
||||
return array_merge($contact, $user);
|
||||
|
@ -363,21 +362,6 @@ class DFRN
|
|||
$attributes = ['rel' => 'alternate', 'type' => 'text/html', 'href' => $alternatelink];
|
||||
XML::addElement($doc, $root, 'link', '', $attributes);
|
||||
|
||||
|
||||
if ($public) {
|
||||
// DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed.
|
||||
OStatus::addHubLink($doc, $root, $owner['nick']);
|
||||
|
||||
$attributes = ['rel' => 'salmon', 'href' => DI::baseUrl() . '/salmon/' . $owner['nick']];
|
||||
XML::addElement($doc, $root, 'link', '', $attributes);
|
||||
|
||||
$attributes = ['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => DI::baseUrl() . '/salmon/' . $owner['nick']];
|
||||
XML::addElement($doc, $root, 'link', '', $attributes);
|
||||
|
||||
$attributes = ['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => DI::baseUrl() . '/salmon/' . $owner['nick']];
|
||||
XML::addElement($doc, $root, 'link', '', $attributes);
|
||||
}
|
||||
|
||||
// For backward compatibility we keep this element
|
||||
if (in_array($owner['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_COMM_MAN])) {
|
||||
XML::addElement($doc, $root, 'dfrn:community', 1);
|
||||
|
|
|
@ -799,7 +799,7 @@ class Feed
|
|||
$frequency = [];
|
||||
$oldest = time();
|
||||
$newest = 0;
|
||||
$oldest_date = $newest_date = '';
|
||||
$newest_date = '';
|
||||
|
||||
foreach ($creation_dates as $date) {
|
||||
$timestamp = strtotime($date);
|
||||
|
@ -822,7 +822,6 @@ class Feed
|
|||
}
|
||||
if ($oldest > $day) {
|
||||
$oldest = $day;
|
||||
$oldest_date = $date;
|
||||
}
|
||||
|
||||
if ($newest < $day) {
|
||||
|
@ -919,11 +918,6 @@ class Feed
|
|||
$rating = 9;
|
||||
}
|
||||
|
||||
// Friendica and OStatus are checked once a day
|
||||
if (in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS])) {
|
||||
$rating = 8;
|
||||
}
|
||||
|
||||
// Check archived contacts or contacts with unsupported protocols once a month
|
||||
if ($contact['archive'] || in_array($contact['network'], [Protocol::ZOT, Protocol::PHANTOM])) {
|
||||
$rating = 10;
|
||||
|
@ -1030,10 +1024,9 @@ class Feed
|
|||
|
||||
$condition = [
|
||||
"`uid` = ? AND `received` > ? AND NOT `deleted` AND `gravity` IN (?, ?)
|
||||
AND `private` != ? AND `visible` AND `wall` AND `parent-network` IN (?, ?, ?, ?)",
|
||||
AND `private` != ? AND `visible` AND `wall` AND `parent-network` IN (?, ?, ?)",
|
||||
$owner['uid'], $check_date, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT,
|
||||
Item::PRIVATE, Protocol::ACTIVITYPUB,
|
||||
Protocol::OSTATUS, Protocol::DFRN, Protocol::DIASPORA
|
||||
Item::PRIVATE, Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA
|
||||
];
|
||||
|
||||
if ($filter === 'comments') {
|
||||
|
@ -1123,8 +1116,6 @@ class Feed
|
|||
$attributes = ['href' => $owner['url'], 'rel' => 'alternate', 'type' => 'text/html'];
|
||||
XML::addElement($doc, $root, 'link', '', $attributes);
|
||||
|
||||
OStatus::addHubLink($doc, $root, $owner['nick']);
|
||||
|
||||
$attributes = ['href' => DI::baseUrl() . $selfUri, 'rel' => 'self', 'type' => 'application/atom+xml'];
|
||||
XML::addElement($doc, $root, 'link', '', $attributes);
|
||||
|
||||
|
@ -1166,7 +1157,7 @@ class Feed
|
|||
Logger::info('Feed entry author does not match feed owner', ['owner' => $owner['url'], 'author' => $item['author-link']]);
|
||||
}
|
||||
|
||||
$entry = OStatus::entryHeader($doc, $owner, $item, false);
|
||||
$entry = self::entryHeader($doc, $owner, $item, false);
|
||||
|
||||
self::entryContent($doc, $entry, $item, self::getTitle($item), '', true);
|
||||
|
||||
|
@ -1191,7 +1182,7 @@ class Feed
|
|||
private static function entryContent(DOMDocument $doc, DOMElement $entry, array $item, $title, string $verb = '', bool $complete = true)
|
||||
{
|
||||
if ($verb == '') {
|
||||
$verb = OStatus::constructVerb($item);
|
||||
$verb = self::constructVerb($item);
|
||||
}
|
||||
|
||||
XML::addElement($doc, $entry, 'id', $item['uri']);
|
||||
|
@ -1278,7 +1269,7 @@ class Feed
|
|||
}
|
||||
}
|
||||
|
||||
OStatus::getAttachment($doc, $entry, $item);
|
||||
self::getAttachment($doc, $entry, $item);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1346,4 +1337,89 @@ class Feed
|
|||
}
|
||||
return $replace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds attachment data to the XML document
|
||||
*
|
||||
* @param DOMDocument $doc XML document
|
||||
* @param DOMElement $root XML root element where the hub links are added
|
||||
* @param array $item Data of the item that is to be posted
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function getAttachment(DOMDocument $doc, DOMElement $root, array $item)
|
||||
{
|
||||
foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO, Post\Media::DOCUMENT, Post\Media::TORRENT]) as $attachment) {
|
||||
$attributes = ['rel' => 'enclosure',
|
||||
'href' => $attachment['url'],
|
||||
'type' => $attachment['mimetype']];
|
||||
|
||||
if (!empty($attachment['size'])) {
|
||||
$attributes['length'] = intval($attachment['size']);
|
||||
}
|
||||
if (!empty($attachment['description'])) {
|
||||
$attributes['title'] = $attachment['description'];
|
||||
}
|
||||
|
||||
XML::addElement($doc, $root, 'link', '', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO Picture attachments should look like this:
|
||||
* <a href="https://status.pirati.ca/attachment/572819" title="https://status.pirati.ca/file/heluecht-20151202T222602-rd3u49p.gif"
|
||||
* class="attachment thumbnail" id="attachment-572819" rel="nofollow external">https://status.pirati.ca/attachment/572819</a>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the given activity if present - otherwise returns the "post" activity
|
||||
*
|
||||
* @param array $item Data of the item that is to be posted
|
||||
* @return string activity
|
||||
*/
|
||||
private static function constructVerb(array $item): string
|
||||
{
|
||||
if (!empty($item['verb'])) {
|
||||
return $item['verb'];
|
||||
}
|
||||
|
||||
return Activity::POST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header element to the XML document
|
||||
*
|
||||
* @param DOMDocument $doc XML document
|
||||
* @param array $owner Contact data of the poster
|
||||
* @param array $item
|
||||
* @param bool $toplevel Is it for en entry element (false) or a feed entry (true)?
|
||||
* @return DOMElement The entry element where the elements are added
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function entryHeader(DOMDocument $doc, array $owner, array $item, bool $toplevel): DOMElement
|
||||
{
|
||||
if (!$toplevel) {
|
||||
$entry = $doc->createElement('entry');
|
||||
|
||||
if ($owner['contact-type'] == Contact::TYPE_COMMUNITY) {
|
||||
$entry->setAttribute('xmlns:activity', ActivityNamespace::ACTIVITY);
|
||||
|
||||
$contact = Contact::getByURL($item['author-link']) ?: $owner;
|
||||
$contact['nickname'] = $contact['nickname'] ?? $contact['nick'];
|
||||
$author = self::addAuthor($doc, $contact, false);
|
||||
$entry->appendChild($author);
|
||||
}
|
||||
} else {
|
||||
$entry = $doc->createElementNS(ActivityNamespace::ATOM1, 'entry');
|
||||
|
||||
$entry->setAttribute('xmlns:thr', ActivityNamespace::THREAD);
|
||||
$entry->setAttribute('xmlns:poco', ActivityNamespace::POCO);
|
||||
|
||||
$author = self::addAuthor($doc, $owner);
|
||||
$entry->appendChild($author);
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,17 +7,7 @@
|
|||
|
||||
namespace Friendica\Protocol;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\Salmon\Format\Magic;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
|
||||
/**
|
||||
|
@ -28,206 +18,6 @@ use phpseclib3\Crypt\PublicKeyLoader;
|
|||
*/
|
||||
class Salmon
|
||||
{
|
||||
/**
|
||||
* @param string $uri Uniform Resource Identifier
|
||||
* @param string $keyhash encoded key
|
||||
* @return string Key or empty string on any errors
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getKey(string $uri, string $keyhash): string
|
||||
{
|
||||
$ret = [];
|
||||
|
||||
Logger::info('Fetching salmon key for ' . $uri);
|
||||
|
||||
$arr = Probe::lrdd($uri);
|
||||
|
||||
if (is_array($arr)) {
|
||||
foreach ($arr as $a) {
|
||||
if ($a['@attributes']['rel'] === 'magic-public-key') {
|
||||
$ret[] = $a['@attributes']['href'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
// We have found at least one key URL
|
||||
// If it's inline, parse it - otherwise get the key
|
||||
|
||||
if (count($ret) > 0) {
|
||||
for ($x = 0; $x < count($ret); $x++) {
|
||||
if (substr($ret[$x], 0, 5) === 'data:') {
|
||||
if (strstr($ret[$x], ',')) {
|
||||
$ret[$x] = substr($ret[$x], strpos($ret[$x], ',') + 1);
|
||||
} else {
|
||||
$ret[$x] = substr($ret[$x], 5);
|
||||
}
|
||||
} elseif (Strings::normaliseLink($ret[$x]) == 'http://') {
|
||||
$ret[$x] = DI::httpClient()->fetch($ret[$x], HttpClientAccept::MAGIC_KEY, 0, '', HttpClientRequest::SALMON);
|
||||
Logger::debug('Fetched public key', ['url' => $ret[$x]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Logger::notice('Key located', ['ret' => $ret]);
|
||||
|
||||
if (count($ret) == 1) {
|
||||
/* We only found one key so we don't care if the hash matches.
|
||||
* If it's the wrong key we'll find out soon enough because
|
||||
* message verification will fail. This also covers some older
|
||||
* software which don't supply a keyhash. As long as they only
|
||||
* have one key we'll be right.
|
||||
*/
|
||||
return (string) $ret[0];
|
||||
} else {
|
||||
foreach ($ret as $a) {
|
||||
$hash = Strings::base64UrlEncode(hash('sha256', $a));
|
||||
if ($hash == $keyhash) {
|
||||
return $a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $owner owner
|
||||
* @param string $url url
|
||||
* @param string $slap slap
|
||||
* @return integer
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function slapper(array $owner, string $url, string $slap): int
|
||||
{
|
||||
// does contact have a salmon endpoint?
|
||||
|
||||
if (!strlen($url)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!$owner['sprvkey']) {
|
||||
Logger::notice(sprintf(
|
||||
"user '%s' (%d) does not have a salmon private key. Send failed.",
|
||||
$owner['name'],
|
||||
$owner['uid']
|
||||
));
|
||||
return -1;
|
||||
}
|
||||
|
||||
Logger::info('slapper called for ' . $url . '. Data: ' . $slap);
|
||||
|
||||
// create a magic envelope
|
||||
|
||||
$data = Strings::base64UrlEncode($slap);
|
||||
$data_type = 'application/atom+xml';
|
||||
$encoding = 'base64url';
|
||||
$algorithm = 'RSA-SHA256';
|
||||
$keyhash = Strings::base64UrlEncode(hash('sha256', self::salmonKey($owner['spubkey'])), true);
|
||||
|
||||
$precomputed = '.' . Strings::base64UrlEncode($data_type) . '.' . Strings::base64UrlEncode($encoding) . '.' . Strings::base64UrlEncode($algorithm);
|
||||
|
||||
// GNU Social format
|
||||
$signature = Strings::base64UrlEncode(Crypto::rsaSign($data . $precomputed, $owner['sprvkey']));
|
||||
|
||||
// Compliant format
|
||||
$signature2 = Strings::base64UrlEncode(Crypto::rsaSign(str_replace('=', '', $data . $precomputed), $owner['sprvkey']));
|
||||
|
||||
// Old Status.net format
|
||||
$signature3 = Strings::base64UrlEncode(Crypto::rsaSign($data, $owner['sprvkey']));
|
||||
|
||||
// At first try the non compliant method that works for GNU Social
|
||||
$xmldata = [
|
||||
'me:env' => [
|
||||
'me:data' => $data,
|
||||
'@attributes' => ['type' => $data_type],
|
||||
'me:encoding' => $encoding,
|
||||
'me:alg' => $algorithm,
|
||||
'me:sig' => $signature,
|
||||
'@attributes2' => ['key_id' => $keyhash],
|
||||
]
|
||||
];
|
||||
|
||||
$namespaces = ['me' => ActivityNamespace::SALMON_ME];
|
||||
|
||||
$salmon = XML::fromArray($xmldata, $dummy, false, $namespaces);
|
||||
|
||||
// slap them
|
||||
$postResult = DI::httpClient()->post($url, $salmon, [
|
||||
'Content-type' => 'application/magic-envelope+xml',
|
||||
'Content-length' => strlen($salmon),
|
||||
], 0, HttpClientRequest::SALMON);
|
||||
|
||||
$return_code = $postResult->getReturnCode();
|
||||
|
||||
// check for success, e.g. 2xx
|
||||
|
||||
if ($return_code > 299) {
|
||||
Logger::notice('GNU Social salmon failed. Falling back to compliant mode');
|
||||
|
||||
// Now try the compliant mode that normally isn't used for GNU Social
|
||||
$xmldata = [
|
||||
'me:env' => [
|
||||
'me:data' => $data,
|
||||
'@attributes' => ['type' => $data_type],
|
||||
'me:encoding' => $encoding,
|
||||
'me:alg' => $algorithm,
|
||||
'me:sig' => $signature2,
|
||||
'@attributes2' => ['key_id' => $keyhash]
|
||||
]
|
||||
];
|
||||
|
||||
$salmon = XML::fromArray($xmldata, $dummy, false, $namespaces);
|
||||
|
||||
// slap them
|
||||
$postResult = DI::httpClient()->post($url, $salmon, [
|
||||
'Content-type' => 'application/magic-envelope+xml',
|
||||
'Content-length' => strlen($salmon),
|
||||
], 0, HttpClientRequest::SALMON);
|
||||
$return_code = $postResult->getReturnCode();
|
||||
}
|
||||
|
||||
if ($return_code > 299) {
|
||||
Logger::notice('compliant salmon failed. Falling back to old status.net');
|
||||
|
||||
// Last try. This will most likely fail as well.
|
||||
$xmldata = [
|
||||
'me:env' => [
|
||||
'me:data' => $data,
|
||||
'@attributes' => ['type' => $data_type],
|
||||
'me:encoding' => $encoding,
|
||||
'me:alg' => $algorithm,
|
||||
'me:sig' => $signature3,
|
||||
'@attributes2' => ['key_id' => $keyhash],
|
||||
]
|
||||
];
|
||||
|
||||
$salmon = XML::fromArray($xmldata, $dummy, false, $namespaces);
|
||||
|
||||
// slap them
|
||||
$postResult = DI::httpClient()->post($url, $salmon, [
|
||||
'Content-type' => 'application/magic-envelope+xml',
|
||||
'Content-length' => strlen($salmon)
|
||||
], 0, HttpClientRequest::SALMON);
|
||||
$return_code = $postResult->getReturnCode();
|
||||
}
|
||||
|
||||
Item::incrementOutbound(Protocol::OSTATUS);
|
||||
Logger::info('slapper for ' . $url . ' returned ' . $return_code);
|
||||
|
||||
if (!$return_code) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (($return_code == 503) && $postResult->inHeader('retry-after')) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (($return_code >= 200) && ($return_code < 300)) ? 0 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pubkey public key
|
||||
* @return string
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue