forked from grumpydevelop/federator
bugfixes and refactoring, cleanup
This commit is contained in:
parent
f8fb409327
commit
5ef5c97d5e
14 changed files with 426 additions and 364 deletions
|
@ -226,10 +226,12 @@ class Api extends Main
|
||||||
$signature = base64_decode($signatureParts['signature']);
|
$signature = base64_decode($signatureParts['signature']);
|
||||||
$signedHeaders = explode(' ', $signatureParts['headers']);
|
$signedHeaders = explode(' ', $signatureParts['headers']);
|
||||||
$keyId = $signatureParts['keyId'];
|
$keyId = $signatureParts['keyId'];
|
||||||
|
$publicKeyPem = false;
|
||||||
|
if ($this->cache !== null) {
|
||||||
$publicKeyPem = $this->cache->getPublicKey($keyId);
|
$publicKeyPem = $this->cache->getPublicKey($keyId);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isset($publicKeyPem) || $publicKeyPem === false) {
|
if ($publicKeyPem === false) {
|
||||||
// Fetch public key from `keyId` (usually actor URL + #main-key)
|
// Fetch public key from `keyId` (usually actor URL + #main-key)
|
||||||
[$publicKeyData, $info] = \Federator\Main::getFromRemote($keyId, ['Accept: application/activity+json']);
|
[$publicKeyData, $info] = \Federator\Main::getFromRemote($keyId, ['Accept: application/activity+json']);
|
||||||
|
|
||||||
|
@ -251,8 +253,10 @@ class Api extends Main
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the public key for 1 hour
|
// Cache the public key for 1 hour
|
||||||
|
if ($this->cache !== null) {
|
||||||
$this->cache->savePublicKey($keyId, $publicKeyPem);
|
$this->cache->savePublicKey($keyId, $publicKeyPem);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reconstruct the signed string
|
// Reconstruct the signed string
|
||||||
$signedString = '';
|
$signedString = '';
|
||||||
|
|
|
@ -54,6 +54,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
try {
|
try {
|
||||||
$this->main->checkSignature($allHeaders);
|
$this->main->checkSignature($allHeaders);
|
||||||
} catch (\Federator\Exceptions\PermissionDenied $e) {
|
} catch (\Federator\Exceptions\PermissionDenied $e) {
|
||||||
|
error_log("signature check failed");
|
||||||
throw new \Federator\Exceptions\Unauthorized('Inbox::post Signature check failed: ' . $e->getMessage());
|
throw new \Federator\Exceptions\Unauthorized('Inbox::post Signature check failed: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +65,6 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
$connector = $this->main->getConnector();
|
$connector = $this->main->getConnector();
|
||||||
|
|
||||||
$config = $this->main->getConfig();
|
$config = $this->main->getConfig();
|
||||||
|
|
||||||
if (!is_array($activity)) {
|
if (!is_array($activity)) {
|
||||||
throw new \Federator\Exceptions\ServerError('Inbox::post Input wasn\'t of type array');
|
throw new \Federator\Exceptions\ServerError('Inbox::post Input wasn\'t of type array');
|
||||||
}
|
}
|
||||||
|
@ -74,9 +74,9 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
if ($inboxActivity === false) {
|
if ($inboxActivity === false) {
|
||||||
throw new \Federator\Exceptions\ServerError('Inbox::post couldn\'t create inboxActivity');
|
throw new \Federator\Exceptions\ServerError('Inbox::post couldn\'t create inboxActivity');
|
||||||
}
|
}
|
||||||
$user = $inboxActivity->getAActor(); // url of the sender https://contentnation.net/username
|
$actor = $inboxActivity->getAActor(); // url of the sender https://contentnation.net/username
|
||||||
$username = basename((string) (parse_url($user, PHP_URL_PATH) ?? ''));
|
$username = basename((string) (parse_url($actor, PHP_URL_PATH) ?? ''));
|
||||||
$domain = parse_url($user, PHP_URL_HOST);
|
$domain = parse_url($actor, PHP_URL_HOST);
|
||||||
$userId = $username . '@' . $domain;
|
$userId = $username . '@' . $domain;
|
||||||
$user = \Federator\DIO\FedUser::getUserByName(
|
$user = \Federator\DIO\FedUser::getUserByName(
|
||||||
$dbh,
|
$dbh,
|
||||||
|
@ -132,11 +132,31 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
}
|
}
|
||||||
$ourDomain = $config['generic']['externaldomain'];
|
$ourDomain = $config['generic']['externaldomain'];
|
||||||
|
|
||||||
|
$finalReceivers = [];
|
||||||
foreach ($receivers as $receiver) {
|
foreach ($receivers as $receiver) {
|
||||||
if ($receiver === '' || !is_string($receiver)) {
|
if ($receiver === '' || !is_string($receiver)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!str_contains($receiver, $ourDomain) && $receiver !== $_user) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// check if receiver is an actor url from our domain
|
||||||
|
if ($receiver !== $_user) {
|
||||||
|
$receiverName = basename((string) (parse_url($receiver, PHP_URL_PATH) ?? ''));
|
||||||
|
$ourDomain = parse_url($receiver, PHP_URL_HOST);
|
||||||
|
if ($receiverName === null || $ourDomain === null) {
|
||||||
|
error_log('Inbox::post no receiverName or domain found for receiver: ' . $receiver);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($receiverName[0] === '@') {
|
||||||
|
$receiverName = substr($receiverName, 1);
|
||||||
|
}
|
||||||
|
$receiver = $receiverName;
|
||||||
|
}
|
||||||
|
$finalReceivers[] = $receiver;
|
||||||
|
}
|
||||||
|
$finalReceivers = array_unique($finalReceivers); // remove duplicates
|
||||||
|
foreach ($finalReceivers as $receiver) {
|
||||||
if (str_ends_with($receiver, '/followers')) {
|
if (str_ends_with($receiver, '/followers')) {
|
||||||
$actor = $inboxActivity->getAActor();
|
$actor = $inboxActivity->getAActor();
|
||||||
if ($actor === null || !is_string($actor)) {
|
if ($actor === null || !is_string($actor)) {
|
||||||
|
@ -147,6 +167,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
// Extract username from the actor URL
|
// Extract username from the actor URL
|
||||||
$username = basename((string) (parse_url($actor, PHP_URL_PATH) ?? ''));
|
$username = basename((string) (parse_url($actor, PHP_URL_PATH) ?? ''));
|
||||||
$domain = parse_url($actor, PHP_URL_HOST);
|
$domain = parse_url($actor, PHP_URL_HOST);
|
||||||
|
error_log("url $actor to username $username domain $domain");
|
||||||
if ($username === null || $domain === null) {
|
if ($username === null || $domain === null) {
|
||||||
error_log('Inbox::post no username or domain found for recipient: ' . $receiver);
|
error_log('Inbox::post no username or domain found for recipient: ' . $receiver);
|
||||||
continue;
|
continue;
|
||||||
|
@ -168,19 +189,6 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
$users = array_merge($users, array_column($followers, 'id'));
|
$users = array_merge($users, array_column($followers, 'id'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// check if receiver is an actor url from our domain
|
|
||||||
if (!str_contains($receiver, $ourDomain) && $receiver !== $_user) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($receiver !== $_user) {
|
|
||||||
$receiverName = basename((string) (parse_url($receiver, PHP_URL_PATH) ?? ''));
|
|
||||||
$ourDomain = parse_url($receiver, PHP_URL_HOST);
|
|
||||||
if ($receiverName === null || $ourDomain === null) {
|
|
||||||
error_log('Inbox::post no receiverName or domain found for receiver: ' . $receiver);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$receiver = $receiverName;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
$localUser = \Federator\DIO\User::getUserByName(
|
$localUser = \Federator\DIO\User::getUserByName(
|
||||||
$dbh,
|
$dbh,
|
||||||
|
@ -193,7 +201,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($localUser === null || $localUser->id === null) {
|
if ($localUser === null || $localUser->id === null) {
|
||||||
error_log('Inbox::post couldn\'t find user: ' . $receiver);
|
error_log('Inbox::post 210 couldn\'t find user: ' . $receiver);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$users[] = $localUser->id;
|
$users[] = $localUser->id;
|
||||||
|
@ -203,7 +211,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
$users = array_unique($users); // remove duplicates
|
$users = array_unique($users); // remove duplicates
|
||||||
|
|
||||||
if (empty($users)) { // todo remove after proper implementation, debugging for now
|
if (empty($users)) { // todo remove after proper implementation, debugging for now
|
||||||
$rootDir = PROJECT_ROOT . '/';
|
$rootDir = '/tmp/';
|
||||||
// Save the raw input and parsed JSON to a file for inspection
|
// Save the raw input and parsed JSON to a file for inspection
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
$rootDir . 'logs/inbox.log',
|
$rootDir . 'logs/inbox.log',
|
||||||
|
@ -213,6 +221,17 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the Redis backend for Resque
|
||||||
|
$rconfig = parse_ini_file($_SERVER['DOCUMENT_ROOT'] . '/../rediscache.ini');
|
||||||
|
$redisUrl = sprintf(
|
||||||
|
'redis://%s:%s@%s:%d?password-encoding=u',
|
||||||
|
urlencode($rconfig['username']),
|
||||||
|
urlencode($rconfig['password']),
|
||||||
|
$rconfig['host'],
|
||||||
|
intval($rconfig['port'], 10)
|
||||||
|
);
|
||||||
|
\Resque::setBackend($redisUrl);
|
||||||
|
|
||||||
foreach ($users as $receiver) {
|
foreach ($users as $receiver) {
|
||||||
if (!isset($receiver)) {
|
if (!isset($receiver)) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -238,209 +257,6 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
. json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
. json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$connector->sendActivity($user, $inboxActivity);
|
|
||||||
|
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* handle post call for specific user
|
|
||||||
*
|
|
||||||
* @param \mysqli $dbh database handle
|
|
||||||
* @param \Federator\Connector\Connector $connector connector to use
|
|
||||||
* @param \Federator\Cache\Cache|null $cache optional caching service
|
|
||||||
* @param string $_user user that triggered the post
|
|
||||||
* @param string $_recipientId recipient of the post
|
|
||||||
* @param \Federator\Data\ActivityPub\Common\Activity $inboxActivity the activity that we received
|
|
||||||
* @return boolean response
|
|
||||||
*/
|
|
||||||
public static function postForUser($dbh, $connector, $cache, $_user, $_recipientId, $inboxActivity)
|
|
||||||
{
|
|
||||||
if (!isset($_user)) {
|
|
||||||
error_log('Inbox::postForUser no user given');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get sender
|
|
||||||
$user = \Federator\DIO\FedUser::getUserByName(
|
|
||||||
$dbh,
|
|
||||||
$_user,
|
|
||||||
$cache
|
|
||||||
);
|
|
||||||
if ($user === null || $user->id === null) {
|
|
||||||
error_log('Inbox::postForUser couldn\'t find user: ' . $_user);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$type = strtolower($inboxActivity->getType());
|
|
||||||
|
|
||||||
if ($_recipientId === '') {
|
|
||||||
if ($type === 'undo' || $type === 'delete') {
|
|
||||||
switch ($type) {
|
|
||||||
case 'delete':
|
|
||||||
// Delete Note/Post
|
|
||||||
$object = $inboxActivity->getObject();
|
|
||||||
if (is_string($object)) {
|
|
||||||
\Federator\DIO\Posts::deletePost($dbh, $object);
|
|
||||||
} elseif (is_object($object)) {
|
|
||||||
$objectId = $object->getID();
|
|
||||||
\Federator\DIO\Posts::deletePost($dbh, $objectId);
|
|
||||||
} else {
|
|
||||||
error_log('Inbox::postForUser Error in Delete Post for user ' . $user->id
|
|
||||||
. ', object is not a string or object');
|
|
||||||
error_log(' object of type ' . gettype($object));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'undo':
|
|
||||||
$object = $inboxActivity->getObject();
|
|
||||||
if (is_object($object)) {
|
|
||||||
switch (strtolower($object->getType())) {
|
|
||||||
case 'like':
|
|
||||||
case 'dislike':
|
|
||||||
// Undo Like/Dislike (remove like/dislike)
|
|
||||||
$targetId = $object->getID();
|
|
||||||
// \Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId, 'dislike');
|
|
||||||
\Federator\DIO\Posts::deletePost($dbh, $targetId);
|
|
||||||
break;
|
|
||||||
case 'note':
|
|
||||||
case 'article':
|
|
||||||
// Undo Note (remove note)
|
|
||||||
$noteId = $object->getID();
|
|
||||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
error_log('Inbox::postForUser Unhandled activity type ' . $type . ' for user ' . $user->id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$atPos = strpos($_recipientId, '@');
|
|
||||||
if ($atPos !== false) {
|
|
||||||
$_recipientId = substr($_recipientId, 0, $atPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get recipient
|
|
||||||
$recipient = \Federator\DIO\User::getUserByName(
|
|
||||||
$dbh,
|
|
||||||
$_recipientId,
|
|
||||||
$connector,
|
|
||||||
$cache
|
|
||||||
);
|
|
||||||
if ($recipient === null || $recipient->id === null) {
|
|
||||||
error_log('Inbox::postForUser couldn\'t find recipient: ' . $_recipientId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootDir = PROJECT_ROOT . '/';
|
|
||||||
// Save the raw input and parsed JSON to a file for inspection
|
|
||||||
file_put_contents(
|
|
||||||
$rootDir . 'logs/inbox_' . $recipient->id . '.log',
|
|
||||||
date('Y-m-d H:i:s') . ": ==== POST " . $recipient->id . " Inbox Activity ====\n"
|
|
||||||
. json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
|
||||||
FILE_APPEND
|
|
||||||
);
|
|
||||||
|
|
||||||
switch ($type) {
|
|
||||||
case 'follow':
|
|
||||||
$success = \Federator\DIO\Followers::addExternalFollow(
|
|
||||||
$dbh,
|
|
||||||
$inboxActivity->getID(),
|
|
||||||
$user->id,
|
|
||||||
$recipient->id
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($success === false) {
|
|
||||||
error_log('Inbox::postForUser Failed to add follower for user ' . $user->id);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'delete':
|
|
||||||
// Delete Note/Post
|
|
||||||
$object = $inboxActivity->getObject();
|
|
||||||
if (is_string($object)) {
|
|
||||||
\Federator\DIO\Posts::deletePost($dbh, $object);
|
|
||||||
} elseif (is_object($object)) {
|
|
||||||
$objectId = $object->getID();
|
|
||||||
\Federator\DIO\Posts::deletePost($dbh, $objectId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'undo':
|
|
||||||
$object = $inboxActivity->getObject();
|
|
||||||
if (is_object($object)) {
|
|
||||||
switch (strtolower($object->getType())) {
|
|
||||||
case 'follow':
|
|
||||||
$success = false;
|
|
||||||
if ($object instanceof \Federator\Data\ActivityPub\Common\Activity) {
|
|
||||||
$actor = $object->getAActor();
|
|
||||||
if ($actor !== '') {
|
|
||||||
$success = \Federator\DIO\Followers::removeFollow($dbh, $user->id, $recipient->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($success === false) {
|
|
||||||
error_log('Inbox::postForUser Failed to remove follower for user ' . $user->id);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'like':
|
|
||||||
case 'dislike':
|
|
||||||
// Undo Like/Dislike (remove like/dislike)
|
|
||||||
$targetId = $object->getID();
|
|
||||||
\Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId);
|
|
||||||
// \Federator\DIO\Posts::deletePost($dbh, $targetId);
|
|
||||||
break;
|
|
||||||
case 'note':
|
|
||||||
// Undo Note (remove note)
|
|
||||||
$noteId = $object->getID();
|
|
||||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'like':
|
|
||||||
case 'dislike':
|
|
||||||
// Add Like/Dislike
|
|
||||||
$targetId = $inboxActivity->getObject();
|
|
||||||
if (is_string($targetId)) {
|
|
||||||
\Federator\DIO\Votes::addVote($dbh, $user->id, $targetId, $type);
|
|
||||||
} else {
|
|
||||||
error_log('Inbox::postForUser Error in Add Like/Dislike for user ' . $user->id
|
|
||||||
. ', targetId is not a string');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'create':
|
|
||||||
case 'update':
|
|
||||||
$object = $inboxActivity->getObject();
|
|
||||||
if (is_object($object)) {
|
|
||||||
switch (strtolower($object->getType())) {
|
|
||||||
case 'note':
|
|
||||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
|
||||||
break;
|
|
||||||
case 'article':
|
|
||||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error_log('Inbox::postForUser Unhandled activity type $type for user ' . $user->id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -454,7 +454,7 @@ class NewContent implements \Federator\Api\APIInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$response = self::sendActivity($dbh, $host, $user, $recipient, $newActivity);
|
$response = \Federator\DIO\Server::sendActivity($dbh, $host, $user, $recipient, $newActivity);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
error_log('NewContent::postForUser Failed to send activity: ' . $e->getMessage());
|
error_log('NewContent::postForUser Failed to send activity: ' . $e->getMessage());
|
||||||
return false;
|
return false;
|
||||||
|
@ -469,99 +469,6 @@ class NewContent implements \Federator\Api\APIInterface
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* send activity to federated server
|
|
||||||
*
|
|
||||||
* @param \mysqli $dbh database handle
|
|
||||||
* @param string $host host url of our server (e.g. federator)
|
|
||||||
* @param \Federator\Data\User $sender source user
|
|
||||||
* @param \Federator\Data\FedUser $target federated target user
|
|
||||||
* @param \Federator\Data\ActivityPub\Common\Activity $activity activity to send
|
|
||||||
* @return string|true the generated follow ID on success, false on failure
|
|
||||||
*/
|
|
||||||
public static function sendActivity($dbh, $host, $sender, $target, $activity)
|
|
||||||
{
|
|
||||||
if ($dbh === false) {
|
|
||||||
throw new \Federator\Exceptions\ServerError('NewContent::sendActivity Failed to get database handle');
|
|
||||||
}
|
|
||||||
|
|
||||||
$inboxUrl = $target->inboxURL;
|
|
||||||
|
|
||||||
$json = json_encode($activity, JSON_UNESCAPED_SLASHES);
|
|
||||||
|
|
||||||
if ($json === false) {
|
|
||||||
throw new \Exception('Failed to encode JSON: ' . json_last_error_msg());
|
|
||||||
}
|
|
||||||
$digest = 'SHA-256=' . base64_encode(hash('sha256', $json, true));
|
|
||||||
$date = gmdate('D, d M Y H:i:s') . ' GMT';
|
|
||||||
$parsed = parse_url($inboxUrl);
|
|
||||||
if ($parsed === false) {
|
|
||||||
throw new \Exception('Failed to parse URL: ' . $inboxUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($parsed['host']) || !isset($parsed['path'])) {
|
|
||||||
throw new \Exception('Invalid inbox URL: missing host or path');
|
|
||||||
}
|
|
||||||
$extHost = $parsed['host'];
|
|
||||||
$path = $parsed['path'];
|
|
||||||
|
|
||||||
// Build the signature string
|
|
||||||
$signatureString = "(request-target): post {$path}\n" .
|
|
||||||
"host: {$extHost}\n" .
|
|
||||||
"date: {$date}\n" .
|
|
||||||
"digest: {$digest}";
|
|
||||||
|
|
||||||
// Get rsa private key
|
|
||||||
$privateKey = \Federator\DIO\User::getrsaprivate($dbh, $sender->id); // OR from DB
|
|
||||||
if ($privateKey === false) {
|
|
||||||
throw new \Exception('Failed to get private key');
|
|
||||||
}
|
|
||||||
$pkeyId = openssl_pkey_get_private($privateKey);
|
|
||||||
|
|
||||||
if ($pkeyId === false) {
|
|
||||||
throw new \Exception('Invalid private key');
|
|
||||||
}
|
|
||||||
|
|
||||||
openssl_sign($signatureString, $signature, $pkeyId, OPENSSL_ALGO_SHA256);
|
|
||||||
$signature_b64 = base64_encode($signature);
|
|
||||||
|
|
||||||
// Build keyId (public key ID from your actor object)
|
|
||||||
$keyId = $host . '/' . $sender->id . '#main-key';
|
|
||||||
|
|
||||||
$signatureHeader = 'keyId="' . $keyId
|
|
||||||
. '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
|
||||||
|
|
||||||
$ch = curl_init($inboxUrl);
|
|
||||||
if ($ch === false) {
|
|
||||||
throw new \Exception('Failed to initialize cURL');
|
|
||||||
}
|
|
||||||
$headers = [
|
|
||||||
'Host: ' . $extHost,
|
|
||||||
'Date: ' . $date,
|
|
||||||
'Digest: ' . $digest,
|
|
||||||
'Content-Type: application/activity+json',
|
|
||||||
'Signature: ' . $signatureHeader,
|
|
||||||
'Accept: application/activity+json',
|
|
||||||
];
|
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
if ($response === false) {
|
|
||||||
throw new \Exception('Failed to send activity: ' . curl_error($ch));
|
|
||||||
} else {
|
|
||||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
if ($httpcode != 200 && $httpcode != 202) {
|
|
||||||
throw new \Exception('Unexpected HTTP code ' . $httpcode . ':' . $response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get internal represenation as json string
|
* get internal represenation as json string
|
||||||
* @return string json string or html
|
* @return string json string or html
|
||||||
|
|
|
@ -20,7 +20,7 @@ class FedUser
|
||||||
* @param string $_user user/profile name
|
* @param string $_user user/profile name
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected static function addLocalUser($dbh, $user, $_user)
|
protected static function addUserToDB($dbh, $user, $_user)
|
||||||
{
|
{
|
||||||
// check if it is timed out user
|
// check if it is timed out user
|
||||||
$sql = 'select unix_timestamp(`validuntil`) from fedusers where id=?';
|
$sql = 'select unix_timestamp(`validuntil`) from fedusers where id=?';
|
||||||
|
@ -28,7 +28,7 @@ class FedUser
|
||||||
if ($stmt === false) {
|
if ($stmt === false) {
|
||||||
throw new \Federator\Exceptions\ServerError('FedUser::addLocalUser Failed to prepare statement');
|
throw new \Federator\Exceptions\ServerError('FedUser::addLocalUser Failed to prepare statement');
|
||||||
}
|
}
|
||||||
$stmt->bind_param("s", $_user);
|
$stmt->bind_param('s', $_user);
|
||||||
$validuntil = 0;
|
$validuntil = 0;
|
||||||
$ret = $stmt->bind_result($validuntil);
|
$ret = $stmt->bind_result($validuntil);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
@ -118,7 +118,7 @@ class FedUser
|
||||||
$stmt->close();
|
$stmt->close();
|
||||||
// if a new user, create own database entry with additionally needed info
|
// if a new user, create own database entry with additionally needed info
|
||||||
if ($user->id === null || $validuntil < time()) {
|
if ($user->id === null || $validuntil < time()) {
|
||||||
self::addLocalUser($dbh, $user, $_user);
|
self::addUserToDB($dbh, $user, $_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no further processing for now
|
// no further processing for now
|
||||||
|
@ -205,6 +205,9 @@ class FedUser
|
||||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to find self link '
|
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to find self link '
|
||||||
. 'in webfinger for ' . $_name);
|
. 'in webfinger for ' . $_name);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$remoteURL = $_name;
|
||||||
|
}
|
||||||
// fetch the user
|
// fetch the user
|
||||||
$headers = ['Accept: application/activity+json'];
|
$headers = ['Accept: application/activity+json'];
|
||||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
|
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
|
||||||
|
@ -232,11 +235,10 @@ class FedUser
|
||||||
}
|
}
|
||||||
$user = \Federator\Data\FedUser::createFromJson($data);
|
$user = \Federator\Data\FedUser::createFromJson($data);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ($cache !== null && $user !== false) {
|
if ($cache !== null && $user !== false) {
|
||||||
if ($user->id !== null && $user->actorURL !== null) {
|
if ($user->id !== null && $user->actorURL !== null) {
|
||||||
self::addLocalUser($dbh, $user, $_name);
|
self::addUserToDB($dbh, $user, $_name);
|
||||||
}
|
}
|
||||||
$cache->saveRemoteFedUserByName($_name, $user);
|
$cache->saveRemoteFedUserByName($_name, $user);
|
||||||
}
|
}
|
||||||
|
@ -245,4 +247,223 @@ class FedUser
|
||||||
}
|
}
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handle post call for specific user
|
||||||
|
*
|
||||||
|
* @param \Federator\Main $main main instance
|
||||||
|
* @param \mysqli $dbh database handle
|
||||||
|
* @param \Federator\Connector\Connector $connector connector to use
|
||||||
|
* @param \Federator\Cache\Cache|null $cache optional caching service
|
||||||
|
* @param string $_user user that triggered the post
|
||||||
|
* @param string $_recipientId recipient of the post
|
||||||
|
* @param \Federator\Data\ActivityPub\Common\Activity $inboxActivity the activity that we received
|
||||||
|
* @return boolean response
|
||||||
|
*/
|
||||||
|
public static function inboxForUser($main, $dbh, $connector, $cache, $_user, $_recipientId, $inboxActivity)
|
||||||
|
{
|
||||||
|
if (!isset($_user)) {
|
||||||
|
error_log('Inbox::postForUser no user given');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get sender
|
||||||
|
$user = \Federator\DIO\FedUser::getUserByName(
|
||||||
|
$dbh,
|
||||||
|
$_user,
|
||||||
|
$cache
|
||||||
|
);
|
||||||
|
if ($user === null || $user->id === null) {
|
||||||
|
error_log('Inbox::postForUser couldn\'t find user: ' . $_user);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = strtolower($inboxActivity->getType());
|
||||||
|
|
||||||
|
if ($_recipientId === '') {
|
||||||
|
if ($type === 'undo' || $type === 'delete') {
|
||||||
|
switch ($type) {
|
||||||
|
case 'delete':
|
||||||
|
// Delete Note/Post
|
||||||
|
$object = $inboxActivity->getObject();
|
||||||
|
if (is_string($object)) {
|
||||||
|
\Federator\DIO\Posts::deletePost($dbh, $object);
|
||||||
|
} elseif (is_object($object)) {
|
||||||
|
$objectId = $object->getID();
|
||||||
|
\Federator\DIO\Posts::deletePost($dbh, $objectId);
|
||||||
|
} else {
|
||||||
|
error_log('Inbox::postForUser Error in Delete Post for user ' . $user->id
|
||||||
|
. ', object is not a string or object');
|
||||||
|
error_log(' object of type ' . gettype($object));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'undo':
|
||||||
|
$object = $inboxActivity->getObject();
|
||||||
|
if (is_object($object)) {
|
||||||
|
switch (strtolower($object->getType())) {
|
||||||
|
case 'like':
|
||||||
|
case 'dislike':
|
||||||
|
// Undo Like/Dislike (remove like/dislike)
|
||||||
|
$targetId = $object->getID();
|
||||||
|
// \Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId, 'dislike');
|
||||||
|
\Federator\DIO\Posts::deletePost($dbh, $targetId);
|
||||||
|
break;
|
||||||
|
case 'note':
|
||||||
|
case 'article':
|
||||||
|
// Undo Note (remove note)
|
||||||
|
$noteId = $object->getID();
|
||||||
|
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
error_log('Inbox::postForUser Unhandled activity type ' . $type . ' for user ' . $user->id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$atPos = strpos($_recipientId, '@');
|
||||||
|
if ($atPos !== false) {
|
||||||
|
$_recipientId = substr($_recipientId, 0, $atPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get recipient
|
||||||
|
$recipient = \Federator\DIO\User::getUserByName(
|
||||||
|
$dbh,
|
||||||
|
$_recipientId,
|
||||||
|
$connector,
|
||||||
|
$cache
|
||||||
|
);
|
||||||
|
if ($recipient === null || $recipient->id === null) {
|
||||||
|
error_log('Inbox::postForUser couldn\'t find recipient: ' . $_recipientId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootDir = $_SERVER['DOCUMENT_ROOT'] . '../';
|
||||||
|
// Save the raw input and parsed JSON to a file for inspection
|
||||||
|
file_put_contents(
|
||||||
|
$rootDir . 'logs/inbox_' . $recipient->id . '.log',
|
||||||
|
date('Y-m-d H:i:s') . ": ==== POST " . $recipient->id . " Inbox Activity ====\n"
|
||||||
|
. json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||||
|
FILE_APPEND
|
||||||
|
);
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'follow':
|
||||||
|
$success = \Federator\DIO\Followers::addExternalFollow(
|
||||||
|
$dbh,
|
||||||
|
$inboxActivity->getID(),
|
||||||
|
$user->id,
|
||||||
|
$recipient->id
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($success === true) {
|
||||||
|
// send accept back
|
||||||
|
$accept = new \Federator\Data\ActivityPub\Common\Accept();
|
||||||
|
$local = $inboxActivity->getObject();
|
||||||
|
if (is_string($local)) {
|
||||||
|
$accept->setAActor($local);
|
||||||
|
$id = bin2hex(openssl_random_pseudo_bytes(4));
|
||||||
|
$accept->setID($local . '#accepts/follows/' . $id);
|
||||||
|
$obj = new \Federator\Data\ActivityPub\Common\Activity($inboxActivity->getType());
|
||||||
|
$config = $main->getConfig();
|
||||||
|
$ourhost = $config['generic']['protocol'] . '://' . $config['generic']['externaldomain'];
|
||||||
|
$obj->setID($ourhost . '/' . $id);
|
||||||
|
$obj->setAActor($inboxActivity->getAActor());
|
||||||
|
$obj->setObject($local);
|
||||||
|
$accept->setObject($obj);
|
||||||
|
// send
|
||||||
|
\Federator\DIO\Server::sendActivity($dbh, $ourhost, $recipient, $user, $accept);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error_log('Inbox::postForUser Failed to add follower for user ' . $user->id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
// Delete Note/Post
|
||||||
|
$object = $inboxActivity->getObject();
|
||||||
|
if (is_string($object)) {
|
||||||
|
\Federator\DIO\Posts::deletePost($dbh, $object);
|
||||||
|
} elseif (is_object($object)) {
|
||||||
|
$objectId = $object->getID();
|
||||||
|
\Federator\DIO\Posts::deletePost($dbh, $objectId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'undo':
|
||||||
|
$object = $inboxActivity->getObject();
|
||||||
|
if (is_object($object)) {
|
||||||
|
switch (strtolower($object->getType())) {
|
||||||
|
case 'follow':
|
||||||
|
$success = false;
|
||||||
|
if ($object instanceof \Federator\Data\ActivityPub\Common\Activity) {
|
||||||
|
$actor = $object->getAActor();
|
||||||
|
if ($actor !== '') {
|
||||||
|
$success = \Federator\DIO\Followers::removeFollow($dbh, $user->id, $recipient->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($success === false) {
|
||||||
|
error_log('Inbox::postForUser Failed to remove follower for user ' . $user->id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'like':
|
||||||
|
case 'dislike':
|
||||||
|
// Undo Like/Dislike (remove like/dislike)
|
||||||
|
$targetId = $object->getID();
|
||||||
|
\Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId);
|
||||||
|
// \Federator\DIO\Posts::deletePost($dbh, $targetId);
|
||||||
|
break;
|
||||||
|
case 'note':
|
||||||
|
// Undo Note (remove note)
|
||||||
|
$noteId = $object->getID();
|
||||||
|
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'like':
|
||||||
|
case 'dislike':
|
||||||
|
// Add Like/Dislike
|
||||||
|
$targetId = $inboxActivity->getObject();
|
||||||
|
if (is_string($targetId)) {
|
||||||
|
\Federator\DIO\Votes::addVote($dbh, $user->id, $targetId, $type);
|
||||||
|
} else {
|
||||||
|
error_log('Inbox::postForUser Error in Add Like/Dislike for user ' . $user->id
|
||||||
|
. ', targetId is not a string');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'create':
|
||||||
|
case 'update':
|
||||||
|
$object = $inboxActivity->getObject();
|
||||||
|
if (is_object($object)) {
|
||||||
|
switch (strtolower($object->getType())) {
|
||||||
|
case 'note':
|
||||||
|
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||||
|
break;
|
||||||
|
case 'article':
|
||||||
|
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error_log('Inbox::postForUser Unhandled activity type $type for user ' . $user->id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
108
php/federator/dio/server.php
Normal file
108
php/federator/dio/server.php
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2025 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* @author Sascha Nitsch (grumpyveveloper)
|
||||||
|
**/
|
||||||
|
|
||||||
|
namespace Federator\DIO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the Server2Server communication
|
||||||
|
*/
|
||||||
|
class Server
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* send activity to federated server
|
||||||
|
*
|
||||||
|
* @param \mysqli $dbh database handle
|
||||||
|
* @param string $ourhost host url of our server (e.g. federator)
|
||||||
|
* @param \Federator\Data\User $sender source user
|
||||||
|
* @param \Federator\Data\FedUser $receiver federated user to receive the activity
|
||||||
|
* @param \Federator\Data\ActivityPub\Common\Activity $activity activity to send
|
||||||
|
* @return boolean true on success
|
||||||
|
*/
|
||||||
|
public static function sendActivity($dbh, $ourhost, $sender, $receiver, $activity)
|
||||||
|
{
|
||||||
|
$receiverInboxUrl = $receiver->inboxURL;
|
||||||
|
|
||||||
|
$json = json_encode($activity, JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
|
if ($json === false) {
|
||||||
|
throw new \Exception('Failed to encode JSON: ' . json_last_error_msg());
|
||||||
|
}
|
||||||
|
$digest = 'SHA-256=' . base64_encode(hash('sha256', $json, true));
|
||||||
|
$date = gmdate('D, d M Y H:i:s') . ' GMT';
|
||||||
|
echo "inboxurl $receiverInboxUrl\n";
|
||||||
|
$parsedReceiverInboxUrl = parse_url($receiverInboxUrl);
|
||||||
|
if ($parsedReceiverInboxUrl === false) {
|
||||||
|
throw new \Exception('Failed to parse URL: ' . $receiverInboxUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($parsedReceiverInboxUrl['host']) || !isset($parsedReceiverInboxUrl['path'])) {
|
||||||
|
throw new \Exception('Invalid inbox URL: missing host or path');
|
||||||
|
}
|
||||||
|
$extHost = $parsedReceiverInboxUrl['host'];
|
||||||
|
$path = $parsedReceiverInboxUrl['path'];
|
||||||
|
|
||||||
|
// Build the signature string
|
||||||
|
$signatureString = "(request-target): post {$path}\n" .
|
||||||
|
"host: {$extHost}\n" .
|
||||||
|
"date: {$date}\n" .
|
||||||
|
"digest: {$digest}";
|
||||||
|
|
||||||
|
// Get rsa private key
|
||||||
|
$privateKey = \Federator\DIO\User::getrsaprivate($dbh, $sender->id); // OR from DB
|
||||||
|
if ($privateKey === false) {
|
||||||
|
throw new \Exception('Failed to get private key');
|
||||||
|
}
|
||||||
|
$pkeyId = openssl_pkey_get_private($privateKey);
|
||||||
|
|
||||||
|
if ($pkeyId === false) {
|
||||||
|
throw new \Exception('Invalid private key');
|
||||||
|
}
|
||||||
|
echo "signaturestring $signatureString\n";
|
||||||
|
openssl_sign($signatureString, $signature, $pkeyId, OPENSSL_ALGO_SHA256);
|
||||||
|
$signature_b64 = base64_encode($signature);
|
||||||
|
|
||||||
|
// Build keyId (public key ID from your actor object)
|
||||||
|
$keyId = $ourhost . '/' . $sender->id . '#main-key';
|
||||||
|
|
||||||
|
$signatureHeader = 'keyId="' . $keyId
|
||||||
|
. '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
||||||
|
|
||||||
|
$ch = curl_init($receiverInboxUrl);
|
||||||
|
if ($ch === false) {
|
||||||
|
throw new \Exception('Failed to initialize cURL');
|
||||||
|
}
|
||||||
|
$headers = [
|
||||||
|
'Host: ' . $extHost,
|
||||||
|
'Date: ' . $date,
|
||||||
|
'Digest: ' . $digest,
|
||||||
|
'Content-Type: application/activity+json',
|
||||||
|
'Signature: ' . $signatureHeader,
|
||||||
|
'Accept: application/activity+json',
|
||||||
|
];
|
||||||
|
print_r($headers);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new \Exception('Failed to send activity: ' . curl_error($ch));
|
||||||
|
} else {
|
||||||
|
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
if ($httpcode != 200 && $httpcode != 202) {
|
||||||
|
throw new \Exception('Unexpected HTTP code ' . $httpcode . ':' . $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($response !== true) {
|
||||||
|
error_log($response);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,7 +64,8 @@ class InboxJob extends \Federator\Api
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
\Federator\Api\FedUsers\Inbox::postForUser(
|
\Federator\DIO\FedUser::inboxForUser(
|
||||||
|
$this,
|
||||||
$this->dbh,
|
$this->dbh,
|
||||||
$this->connector,
|
$this->connector,
|
||||||
$this->cache,
|
$this->cache,
|
|
@ -20,7 +20,7 @@ class Main
|
||||||
*
|
*
|
||||||
* @var Cache\Cache $cache
|
* @var Cache\Cache $cache
|
||||||
*/
|
*/
|
||||||
protected $cache;
|
protected $cache = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* current config
|
* current config
|
||||||
|
|
|
@ -143,7 +143,8 @@ class Test
|
||||||
$inboxActivity->setAActor('https://mastodon.local/users/admin');
|
$inboxActivity->setAActor('https://mastodon.local/users/admin');
|
||||||
$inboxActivity->setObject($_url);
|
$inboxActivity->setObject($_url);
|
||||||
$inboxActivity->setID("https://mastodon.local/users/admin#like/" . md5($_url));
|
$inboxActivity->setID("https://mastodon.local/users/admin#like/" . md5($_url));
|
||||||
\Federator\Api\FedUsers\Inbox::postForUser(
|
\Federator\DIO\FedUser::inboxForUser(
|
||||||
|
$api,
|
||||||
$dbh,
|
$dbh,
|
||||||
$api->getConnector(),
|
$api->getConnector(),
|
||||||
null,
|
null,
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
define('PROJECT_ROOT', dirname(__DIR__, 3));
|
define('PROJECT_ROOT', dirname(__DIR__, 3));
|
||||||
|
|
||||||
require_once PROJECT_ROOT . '/vendor/autoload.php';
|
require_once PROJECT_ROOT . '/vendor/autoload.php';
|
||||||
|
$_SERVER['DOCUMENT_ROOT'] = PROJECT_ROOT . '/htdocs/';
|
||||||
|
|
||||||
|
spl_autoload_register(static function (string $className) {
|
||||||
|
include PROJECT_ROOT . '/php/' . str_replace("\\", "/", strtolower($className)) . '.php';
|
||||||
|
});
|
||||||
|
|
||||||
$config = parse_ini_file(PROJECT_ROOT . '/rediscache.ini');
|
$config = parse_ini_file(PROJECT_ROOT . '/rediscache.ini');
|
||||||
|
|
||||||
// Set the Redis backend for Resque
|
// Set the Redis backend for Resque
|
||||||
$redisUrl = sprintf(
|
$redisUrl = sprintf(
|
||||||
'redis://%s:%s@%s:%d',
|
'redis://%s:%s@%s:%d?password-encoding=u',
|
||||||
urlencode($config['username']),
|
urlencode($config['username']),
|
||||||
urlencode($config['password']),
|
urlencode($config['password']),
|
||||||
$config['host'],
|
$config['host'],
|
||||||
|
|
|
@ -19,10 +19,10 @@ primary goal is to connect ContentNation via ActivityPub again.
|
||||||
- [X] full cache for users
|
- [X] full cache for users
|
||||||
- [X] webfinger
|
- [X] webfinger
|
||||||
- [X] discovery endpoints
|
- [X] discovery endpoints
|
||||||
- [ ] ap outbox
|
- [X] ap outbox
|
||||||
- [ ] ap inbox
|
- [X] ap inbox
|
||||||
- [ ] support for AP profile in service
|
- [ ] support for AP profile in service
|
||||||
- [ ] support for article
|
- [ ] support for article
|
||||||
- [ ] support for comment
|
- [ ] support for comment
|
||||||
- [ ] posting comments from ap to service
|
- [ ] posting comments from ap to service
|
||||||
- [ ] callback from service to add new input
|
- [X] callback from service to add new input
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
{rdelim}
|
{rdelim}
|
||||||
{rdelim}
|
{rdelim}
|
||||||
],
|
],
|
||||||
"id":"https://{$fqdn}/@{$username}",
|
"id":"https://{$fqdn}/{$username}",
|
||||||
"type":"{$type}",
|
"type":"{$type}",
|
||||||
"following":"https://{$fqdn}/{$username}/following",
|
"following":"https://{$fqdn}/{$username}/following",
|
||||||
"followers":"https://{$fqdn}/{$username}/followers",
|
"followers":"https://{$fqdn}/{$username}/followers",
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
"published":"{$registered}",
|
"published":"{$registered}",
|
||||||
"publicKey":{ldelim}
|
"publicKey":{ldelim}
|
||||||
"id":"https://{$fqdn}/{$username}#main-key",
|
"id":"https://{$fqdn}/{$username}#main-key",
|
||||||
"owner":"https://{$sourcedomain}/@{$username}",
|
"owner":"https://{$sourcedomain}/{$username}",
|
||||||
"publicKeyPem":"{$publickey}"
|
"publicKeyPem":"{$publickey}"
|
||||||
{rdelim},
|
{rdelim},
|
||||||
"tag":[],
|
"tag":[],
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"https://{$sourcedomain}/@{$username}"
|
"https://{$sourcedomain}/@{$username}"
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
{ldelim}"rel": "self", "type": "application/activity+json", "href": "https://{$domain}/@{$username}"{rdelim},
|
{ldelim}"rel": "self", "type": "application/activity+json", "href": "https://{$domain}/{$username}"{rdelim},
|
||||||
{if $type=='Group'}
|
{if $type=='Group'}
|
||||||
{ldelim}"rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://{$domain}/@{$username}/"{rdelim},
|
{ldelim}"rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://{$domain}/@{$username}/"{rdelim},
|
||||||
{/if}
|
{/if}
|
||||||
|
|
Loading…
Add table
Reference in a new issue