Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
pull/1563/head
Maxence Lange 2023-01-05 08:58:13 -01:00
rodzic 5ec03e2db5
commit cbcd62e3af
14 zmienionych plików z 268 dodań i 100 usunięć

Wyświetl plik

@ -33,12 +33,10 @@ namespace OCA\Social\Command;
use Exception;
use OC\Core\Command\Base;
use OCA\Social\Interfaces\Actor\PersonInterface;
use OCA\Social\Service\AccountService;
use OCA\Social\Service\CacheActorService;
use OCA\Social\Service\ConfigService;
use OCP\IUserManager;
use OCP\Server;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@ -76,12 +74,7 @@ class AccountDelete extends Base {
protected function execute(InputInterface $input, OutputInterface $output): int {
$account = $input->getArgument('account');
// TODO: broadcast to other instance
throw new Exception('not fully available');
$actor = $this->cacheActorService->getFromLocalAccount($account);
$personInterface = Server::get(PersonInterface::class);
$personInterface->deleteActor($actor->getId());
$this->accountService->deleteActor($account);
return 0;
}

Wyświetl plik

@ -68,8 +68,11 @@ class CacheRefresh extends Base {
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$result = $this->accountService->blindKeyRotation();
$output->writeLn($result . ' key pairs refreshed');
// $result = $this->accountService->blindKeyRotation();
// $output->writeLn($result . ' key pairs refreshed');
$result = $this->accountService->manageDeletedActors();
$output->writeLn($result . ' local accounts deleted');
$result = $this->accountService->manageCacheLocalActors();
$output->writeLn($result . ' local accounts regenerated');

Wyświetl plik

@ -67,7 +67,12 @@ class Cache extends TimedJob {
*/
protected function run($argument) {
try {
$this->accountService->blindKeyRotation();
// $this->accountService->blindKeyRotation();
} catch (Exception $e) {
}
try {
$this->accountService->manageDeletedActors();
} catch (Exception $e) {
}

Wyświetl plik

@ -40,6 +40,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
class ActorsRequest extends ActorsRequestBuilder {
/**
* Create a new Person in the database.
*
* @throws SocialAppConfigException
*/
public function create(Person $actor): void {
@ -119,9 +120,9 @@ class ActorsRequest extends ActorsRequestBuilder {
*/
public function getFromId(string $id): Person {
$qb = $this->getActorsSelectSql();
$this->limitToIdString($qb, $id);
$qb->limitToIdString($id);
$cursor = $qb->executeQuery();
$cursor = $qb->execute();
$data = $cursor->fetch();
$cursor->closeCursor();
@ -158,6 +159,28 @@ class ActorsRequest extends ActorsRequestBuilder {
}
public function setAsDeleted(string $handle): void {
$qb = $this->getActorsUpdateSql();
$qb->set(
'deleted',
$qb->createNamedParameter(new DateTime('now'), IQueryBuilder::PARAM_DATE)
);
$qb->limitToPreferredUsername($handle);
$qb->execute();
}
/**
* @param string $handle
*/
public function delete(string $handle): void {
$qb = $this->getActorsDeleteSql();
$qb->limitToPreferredUsername($handle);
$qb->execute();
}
/**
* @return Person[]
* @throws SocialAppConfigException

Wyświetl plik

@ -75,7 +75,7 @@ class ActorsRequestBuilder extends CoreRequestBuilder {
/** @noinspection PhpMethodParametersCountMismatchInspection */
$qb->select(
'a.id', 'a.id_prim', 'a.user_id', 'a.preferred_username', 'a.name', 'a.summary',
'a.public_key', 'a.avatar_version', 'a.private_key', 'a.creation'
'a.public_key', 'a.avatar_version', 'a.private_key', 'a.creation', 'a.deleted'
)
->from(self::TABLE_ACTORS, 'a');

Wyświetl plik

@ -34,8 +34,8 @@ use DateTime;
use Exception;
use OCA\Social\Exceptions\CacheActorDoesNotExistException;
use OCA\Social\Model\ActivityPub\Actor\Person;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
class CacheActorsRequest extends CacheActorsRequestBuilder {
public const CACHE_TTL = 60 * 24; // 1d
@ -245,4 +245,22 @@ class CacheActorsRequest extends CacheActorsRequestBuilder {
$qb->execute();
}
/**
* @return array
*/
public function getSharedInboxes(): array {
$qb = $this->getQueryBuilder();
$qb->selectDistinct('shared_inbox')
->from(self::TABLE_CACHE_ACTORS);
$inbox = [];
$cursor = $qb->execute();
while ($data = $cursor->fetch()) {
$inbox[] = $data['shared_inbox'];
}
$cursor->closeCursor();
return $inbox;
}
}

Wyświetl plik

@ -336,7 +336,6 @@ class SocialLimitsQueryBuilder extends SocialCrossQueryBuilder {
$pf = $this->getDefaultSelectAlias();
if ($options->getSince() > 0) {
$options->setInverted(true);
$this->andWhere($expr->gt($pf . '.nid', $this->createNamedParameter($options->getSince())));
}
@ -345,6 +344,7 @@ class SocialLimitsQueryBuilder extends SocialCrossQueryBuilder {
}
if ($options->getMinId() > 0) {
$options->setInverted(true);
$this->andWhere($expr->gt($pf . '.nid', $this->createNamedParameter($options->getMinId())));
}

Wyświetl plik

@ -222,6 +222,12 @@ class Version1000Date20221118000001 extends SimpleMigrationStep {
'notnull' => false,
]
);
$table->addColumn(
'deleted', Types::DATETIME,
[
'notnull' => false,
]
);
$table->setPrimaryKey(['id_prim']);
}

Wyświetl plik

@ -31,7 +31,6 @@ declare(strict_types=1);
namespace OCA\Social\Model\ActivityPub\Actor;
use OCA\Social\Tools\IQueryRow;
use DateTime;
use Exception;
use JsonSerializable;
@ -42,6 +41,7 @@ use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Exceptions\UrlCloudException;
use OCA\Social\Model\ActivityPub\ACore;
use OCA\Social\Model\ActivityPub\Object\Image;
use OCA\Social\Tools\IQueryRow;
use OCA\Social\Traits\TDetails;
/**
@ -62,53 +62,30 @@ class Person extends ACore implements IQueryRow, JsonSerializable {
private string $userId = '';
private string $name = '';
private string $preferredUsername = '';
private string $displayName = '';
private string $description = '';
private string $publicKey = '';
private string $privateKey = '';
private int $creation = 0;
private int $deleted = 0;
private string $account = '';
private string $following = '';
private string $followers = '';
private string $inbox = '';
private string $outbox = '';
private string $sharedInbox = '';
private string $featured = '';
private string $avatar = '';
private string $header = '';
private bool $locked = false;
private bool $bot = false;
private bool $discoverable = false;
private string $privacy = 'public';
private bool $sensitive = false;
private string $language = 'en';
private int $avatarVersion = -1;
private string $viewerLink = '';
/**
@ -307,6 +284,25 @@ class Person extends ACore implements IQueryRow, JsonSerializable {
}
/**
* @return int
*/
public function getDeleted(): int {
return $this->deleted;
}
/**
* @param int $deleted
*
* @return Person
*/
public function setDeleted(int $deleted): self {
$this->deleted = $deleted;
return $this;
}
/**
* @return string
*/
@ -661,8 +657,21 @@ class Person extends ACore implements IQueryRow, JsonSerializable {
->setDetailsAll($this->getArray('details', $data, []));
try {
$dTime = new DateTime($this->get('creation', $data, 'yesterday'));
$this->setCreation($dTime->getTimestamp());
$cTime = new DateTime($this->get('creation', $data, 'yesterday'));
$this->setCreation($cTime->getTimestamp());
} catch (Exception $e) {
}
try {
$deletedValue = $this->get('deleted', $data);
if ($deletedValue === '') {
return;
}
$dTime = new DateTime();
$deleted = $dTime->getTimestamp();
if ($deleted > 0) {
$this->setDeleted($deleted);
}
} catch (Exception $e) {
}
}

Wyświetl plik

@ -46,6 +46,7 @@ class InstancePath implements JsonSerializable {
public const TYPE_INBOX = 1;
public const TYPE_GLOBAL = 2;
public const TYPE_FOLLOWERS = 3;
public const TYPE_ALL = 4;
public const PRIORITY_NONE = 0;
public const PRIORITY_LOW = 1;

Wyświetl plik

@ -32,6 +32,7 @@ namespace OCA\Social\Service;
use Exception;
use OC\User\NoUserException;
use OCA\Social\AP;
use OCA\Social\Db\ActorsRequest;
use OCA\Social\Db\FollowsRequest;
use OCA\Social\Db\StreamRequest;
@ -43,11 +44,17 @@ use OCA\Social\Exceptions\ItemUnknownException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Exceptions\StreamNotFoundException;
use OCA\Social\Exceptions\UrlCloudException;
use OCA\Social\Interfaces\Actor\PersonInterface;
use OCA\Social\Model\ActivityPub\ACore;
use OCA\Social\Model\ActivityPub\Activity\Delete;
use OCA\Social\Model\ActivityPub\Actor\Person;
use OCA\Social\Model\InstancePath;
use OCA\Social\Tools\Traits\TArrayTools;
use OCP\Accounts\IAccountManager;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
/**
* Class ActorService
@ -56,7 +63,7 @@ use OCP\IUserSession;
*/
class AccountService {
public const KEY_PAIR_LIFESPAN = 7;
public const TIME_RETENTION = 3600; // seconds before fully delete account
use TArrayTools;
private IUserManager $userManager;
@ -66,10 +73,12 @@ class AccountService {
private FollowsRequest $followsRequest;
private StreamRequest $streamRequest;
private ActorService $actorService;
private ActivityService $activityService;
private AccountService $accountService;
private SignatureService $signatureService;
private DocumentService $documentService;
private ConfigService $configService;
private MiscService $miscService;
private LoggerInterface $logger;
public function __construct(
IUserManager $userManager,
@ -79,10 +88,11 @@ class AccountService {
FollowsRequest $followsRequest,
StreamRequest $streamRequest,
ActorService $actorService,
ActivityService $activityService,
DocumentService $documentService,
SignatureService $signatureService,
ConfigService $configService,
MiscService $miscService
LoggerInterface $logger
) {
$this->userManager = $userManager;
$this->userSession = $userSession;
@ -91,10 +101,11 @@ class AccountService {
$this->followsRequest = $followsRequest;
$this->streamRequest = $streamRequest;
$this->actorService = $actorService;
$this->activityService = $activityService;
$this->documentService = $documentService;
$this->signatureService = $signatureService;
$this->configService = $configService;
$this->miscService = $miscService;
$this->logger = $logger;
}
@ -156,7 +167,7 @@ class AccountService {
* @throws ItemAlreadyExistsException
*/
public function getActorFromUserId(string $userId, bool $create = false): Person {
$this->miscService->confirmUserId($userId);
$this->confirmUserId($userId);
try {
$actor = $this->actorsRequest->getFromUserId($userId);
} catch (ActorDoesNotExistException $e) {
@ -191,11 +202,16 @@ class AccountService {
* @throws UrlCloudException
*/
public function createActor(string $userId, string $username) {
$this->miscService->confirmUserId($userId);
$this->confirmUserId($userId);
$this->checkActorUsername($username);
try {
$this->actorsRequest->getFromUsername($username);
$actor = $this->actorsRequest->getFromUsername($username);
if ($actor->getDeleted() > 0) {
throw new AccountAlreadyExistsException(
'actor with that name was deleted but is still in retention. Please try again later'
);
}
throw new AccountAlreadyExistsException('actor with that name already exist');
} catch (ActorDoesNotExistException $e) {
/* we do nohtin */
@ -223,6 +239,46 @@ class AccountService {
}
/**
* @param string $handle
*
* @throws ItemUnknownException
* @throws SocialAppConfigException
*/
public function deleteActor(string $handle): void {
try {
$actor = $this->actorsRequest->getFromUsername($handle);
} catch (ActorDoesNotExistException $e) {
return;
}
// set as deleted locally
$this->actorsRequest->setAsDeleted($actor->getPreferredUsername());
// delete related data
/** @var PersonInterface $interface */
$interface = AP::$activityPub->getInterfaceFromType(Person::TYPE);
$interface->deleteActor($actor);
// broadcast delete event
$delete = new Delete();
$delete->setId($actor->getId() . '#delete');
$delete->setActorId($actor->getId());
$delete->setToArray([ACore::CONTEXT_PUBLIC]);
$delete->setObjectId($actor->getId());
$delete->addInstancePath(
new InstancePath(
$actor->getInbox(),
InstancePath::TYPE_ALL,
InstancePath::PRIORITY_LOW
)
);
$this->signatureService->signObject($actor, $delete);
$this->activityService->request($delete);
}
/**
* @param string $username
*
@ -305,9 +361,7 @@ class AccountService {
$actor->setName($displayNameProperty->getValue());
}
} catch (Exception $e) {
$this->miscService->log(
'Issue while trying to updateCacheLocalActorName: ' . $e->getMessage(), 1
);
$this->logger->error('Issue while trying to updateCacheLocalActorName: ' . $e->getMessage());
}
}
@ -322,6 +376,29 @@ class AccountService {
}
/**
* @return int
* @throws Exception
*/
public function manageDeletedActors(): int {
$entries = $this->actorsRequest->getAll();
$deleted = 0;
foreach ($entries as $item) {
// delete after an hour
if ($item->getDeleted() === 0) {
continue;
}
if ($item->getDeleted() < (time() - self::TIME_RETENTION)) {
$this->actorsRequest->delete($item->getPreferredUsername());
$deleted++;
}
}
return $deleted;
}
/**
* @return int
* @throws Exception
@ -359,4 +436,23 @@ class AccountService {
return $count;
}
/**
* @param string $userId
*
* @return IUser
* @throws NoUserException
*/
public function confirmUserId(string &$userId): IUser {
$user = $this->userManager->get($userId);
if ($user === null) {
throw new NoUserException('user does not exist');
}
$userId = $user->getUID();
return $user;
}
}

Wyświetl plik

@ -32,6 +32,7 @@ namespace OCA\Social\Service;
use Exception;
use OCA\Social\AP;
use OCA\Social\Db\CacheActorsRequest;
use OCA\Social\Db\FollowsRequest;
use OCA\Social\Db\StreamRequest;
use OCA\Social\Exceptions\ActorDoesNotExistException;
@ -74,30 +75,32 @@ class ActivityService {
private StreamRequest $streamRequest;
private FollowsRequest $followsRequest;
private CacheActorsRequest $cacheActorsRequest;
private SignatureService $signatureService;
private RequestQueueService $requestQueueService;
private AccountService $accountService;
private ConfigService $configService;
private CurlService $curlService;
private MiscService $miscService;
private LoggerInterface $logger;
private ?array $failInstances = null;
public function __construct(
StreamRequest $streamRequest, FollowsRequest $followsRequest,
SignatureService $signatureService, RequestQueueService $requestQueueService,
AccountService $accountService, CurlService $curlService, ConfigService $configService,
MiscService $miscService, LoggerInterface $logger
StreamRequest $streamRequest,
FollowsRequest $followsRequest,
CacheActorsRequest $cacheActorsRequest,
SignatureService $signatureService,
RequestQueueService $requestQueueService,
CurlService $curlService,
ConfigService $configService,
LoggerInterface $logger
) {
$this->streamRequest = $streamRequest;
$this->followsRequest = $followsRequest;
$this->cacheActorsRequest = $cacheActorsRequest;
$this->requestQueueService = $requestQueueService;
$this->accountService = $accountService;
$this->signatureService = $signatureService;
$this->curlService = $curlService;
$this->configService = $configService;
$this->miscService = $miscService;
$this->logger = $logger;
}
@ -254,15 +257,15 @@ class ActivityService {
} catch (UnauthorizedFediverseException | RequestResultNotJsonException $e) {
$this->requestQueueService->endRequest($queue, true);
} catch (ActorDoesNotExistException | RequestContentException | RequestResultSizeException $e) {
$this->miscService->log(
$this->logger->notice(
'Error while managing request: ' . json_encode($request) . ' ' . get_class($e) . ': '
. $e->getMessage(), 1
. $e->getMessage()
);
$this->requestQueueService->deleteRequest($queue);
} catch (RequestNetworkException | RequestServerException $e) {
$this->miscService->log(
$this->logger->notice(
'Temporary error while managing request: RequestServerException - ' . json_encode($request)
. ' - ' . get_class($e) . ': ' . $e->getMessage(), 1
. ' - ' . get_class($e) . ': ' . $e->getMessage()
);
$this->requestQueueService->endRequest($queue, false);
$this->failInstances[] = $host;
@ -279,12 +282,19 @@ class ActivityService {
private function generateInstancePaths(ACore $activity): array {
$instancePaths = [];
foreach ($activity->getInstancePaths() as $instancePath) {
if ($instancePath->getType() === InstancePath::TYPE_FOLLOWERS) {
$instancePaths = array_merge(
$instancePaths, $this->generateInstancePathsFollowers($instancePath)
);
} else {
$instancePaths[] = $instancePath;
switch ($instancePath->getType()) {
case InstancePath::TYPE_FOLLOWERS:
$instancePaths =
array_merge($instancePaths, $this->generateInstancePathsFollowers($instancePath));
break;
case InstancePath::TYPE_ALL:
$instancePaths = array_merge($instancePaths, $this->generateInstancePathsAll());
break;
default:
$instancePaths[] = $instancePath;
break;
}
}
@ -318,14 +328,30 @@ class ActivityService {
$instancePaths[] = new InstancePath(
$sharedInbox, InstancePath::TYPE_GLOBAL, $instancePath->getPriority()
);
// $result[] = $this->generateRequest(
// new InstancePath($sharedInbox, InstancePath::TYPE_GLOBAL), $activity
// );
}
return $instancePaths;
}
/**
* @return InstancePath[]
*/
private function generateInstancePathsAll(): array {
$sharedInboxes = $this->cacheActorsRequest->getSharedInboxes();
$instancePaths = [];
foreach ($sharedInboxes as $sharedInbox) {
$instancePaths[] = new InstancePath(
$sharedInbox,
InstancePath::TYPE_GLOBAL,
InstancePath::PRIORITY_LOW
);
}
return $instancePaths;
}
private function generateRequestFromQueue(RequestQueue $queue): NCRequest {
$path = $queue->getInstance();

Wyświetl plik

@ -32,6 +32,7 @@ namespace OCA\Social\Service;
use Exception;
use OCA\Social\AP;
use OCA\Social\Db\ActorsRequest;
use OCA\Social\Db\CacheActorsRequest;
use OCA\Social\Exceptions\CacheActorDoesNotExistException;
use OCA\Social\Exceptions\InvalidOriginException;
@ -62,6 +63,7 @@ class CacheActorService {
use TArrayTools;
private \OCP\IURLGenerator $urlGenerator;
private ActorsRequest $actorsRequest;
private CacheActorsRequest $cacheActorsRequest;
private CurlService $curlService;
private FediverseService $fediverseService;
@ -73,6 +75,7 @@ class CacheActorService {
*/
public function __construct(
IUrlGenerator $urlGenerator,
ActorsRequest $actorsRequest,
CacheActorsRequest $cacheActorsRequest,
CurlService $curlService,
FediverseService $fediverseService,
@ -80,6 +83,7 @@ class CacheActorService {
LoggerInterface $logger
) {
$this->urlGenerator = $urlGenerator;
$this->actorsRequest = $actorsRequest;
$this->cacheActorsRequest = $cacheActorsRequest;
$this->curlService = $curlService;
$this->fediverseService = $fediverseService;
@ -170,13 +174,18 @@ class CacheActorService {
list($account, $instance) = explode('@', $account, 2);
}
if ($instance === ''
|| $this->configService->getCloudHost() === $instance
|| $this->configService->getSocialAddress() === $instance) {
return $this->cacheActorsRequest->getFromLocalAccount($account);
if ($instance !== ''
&& $this->configService->getCloudHost() !== $instance
&& $this->configService->getSocialAddress() !== $instance) {
throw new CacheActorDoesNotExistException('Address does is not local');
}
throw new CacheActorDoesNotExistException('Address does is not local');
$actor = $this->actorsRequest->getFromUsername($account);
if ($actor->getDeleted() > 0) {
throw new CacheActorDoesNotExistException('Account is deleted');
}
return $this->cacheActorsRequest->getFromLocalAccount($account);
}
/**

Wyświetl plik

@ -31,9 +31,7 @@ declare(strict_types=1);
namespace OCA\Social\Service;
use OC\User\NoUserException;
use OCA\Social\AppInfo\Application;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Util;
use Psr\Log\LoggerInterface;
@ -76,23 +74,4 @@ class MiscService {
return $ver[0];
}
/**
* @param string $userId
*
* @return IUser
* @throws NoUserException
*/
public function confirmUserId(string &$userId): IUser {
$user = $this->userManager->get($userId);
if ($user === null) {
throw new NoUserException('user does not exist');
}
$userId = $user->getUID();
return $user;
}
}