diff --git a/appinfo/database.xml b/appinfo/database.xml index 1a615ac0..fe39af89 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -64,6 +64,7 @@ true + // @deprecated service_id integer @@ -77,30 +78,41 @@ 63 + + username + text + 63 + + + // @deprecated account text 127 + // @deprecated account_id integer 7 + // @deprecated status integer 1 + // @deprecated auth text 2000 + // @deprecated config text @@ -115,5 +127,261 @@ + + *dbprefix*social_server_actors + + + + id + integer + 7 + true + true + true + true + + + + type + text + 15 + true + + + + user_id + text + 63 + true + + + + preferred_username + text + 127 + true + + + + public_key + text + 1000 + + + + private_key + text + 2000 + + + + creation + timestamp + + + +
+ + + *dbprefix*social_server_activities + + + + id + text + 127 + true + + + + activity + text + 6000 + true + + + + object + text + 127 + true + + + +
+ + + *dbprefix*social_server_notes + + + + id + text + 127 + true + + + + + + + + + + + to + text + 127 + true + + + + to_array + text + 2000 + true + + + + cc + text + 127 + true + + + + bcc + text + 127 + true + + + + content + text + 3000 + true + + + + summary + text + 255 + true + + + + published + text + 31 + true + + + + attributed_to + text + 127 + + + + in_reply_to + text + 127 + + + + creation + timestamp + + + +
+ + + *dbprefix*social_cache_actors + + + + id + integer + 8 + true + true + true + true + + + + account + text + 127 + true + + + + url + text + 127 + true + + + + actor + text + 8000 + true + + + + creation + timestamp + + + +
+ + + *dbprefix*social_cache_actors + + + + id + integer + 8 + true + true + true + true + + + + account + text + 127 + true + + + + url + text + 127 + true + + + + actor + text + 8000 + true + + + + creation + timestamp + + + +
+ diff --git a/appinfo/info.xml b/appinfo/info.xml index 8c9e1088..058e8160 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ Social 🎉 Nextcloud becomes part of the federated social networks! - 0.0.10 + 0.0.17 agpl Maxence Lange Julius Härtl @@ -26,4 +26,8 @@ + + OCA\Social\Command\NoteCreate + + diff --git a/appinfo/routes.php b/appinfo/routes.php index a5276275..20642577 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -39,6 +39,22 @@ return [ [ 'name' => 'OAuth2#setCode', 'url' => '/client/oauth2/redirect/{serviceId}/', 'verb' => 'GET' - ] - ] + ], + + ['name' => 'Account#create', 'url' => '/local/account/{username}', 'verb' => 'POST'], + + ['name' => 'ActivityPub#sharedInbox', 'url' => '/inbox', 'verb' => 'POST'], + ['name' => 'ActivityPub#actor', 'url' => '/users/{username}', 'verb' => 'GET'], + ['name' => 'ActivityPub#aliasactor', 'url' => '/@{username}', 'verb' => 'GET'], + ['name' => 'ActivityPub#inbox', 'url' => '/@{username}/inbox', 'verb' => 'POST'], + ['name' => 'ActivityPub#outbox', 'url' => '/@{username}/outbox', 'verb' => 'POST'], + ['name' => 'ActivityPub#followers', 'url' => '/@{username}/followers', 'verb' => 'GET'], + ['name' => 'ActivityPub#following', 'url' => '/@{username}/following', 'verb' => 'GET'], + + ['name' => 'SocialPub#displayPost', 'url' => '/@{username}/{postId}', 'verb' => 'GET'] +, + ['name' => 'ActivityPub#test', 'url' => '/inbox/{username}', 'verb' => 'POST'], + + +] ]; diff --git a/lib/Command/NoteCreate.php b/lib/Command/NoteCreate.php new file mode 100644 index 00000000..48c1193f --- /dev/null +++ b/lib/Command/NoteCreate.php @@ -0,0 +1,150 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +namespace OCA\Social\Command; + +use Exception; +use OC\Core\Command\Base; +use OCA\Social\Service\ActivityPub\NoteService; +use OCA\Social\Service\ActivityPubService; +use OCA\Social\Service\ActorService; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\CurlService; +use OCA\Social\Service\MiscService; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + + +class NoteCreate extends Base { + + /** @var ConfigService */ + private $configService; + + /** @var ActivityPubService */ + private $activityPubService; + + /** @var ActorService */ + private $actorService; + + /** @var NoteService */ + private $noteService; + + /** @var CurlService */ + private $curlService; + + /** @var MiscService */ + private $miscService; + + + /** + * Index constructor. + * + * @param ActivityPubService $activityPubService + * @param ActorService $actorService + * @param NoteService $noteService + * @param CurlService $curlService + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + ActivityPubService $activityPubService, ActorService $actorService, + NoteService $noteService, CurlService $curlService, + ConfigService $configService, MiscService $miscService + ) { + parent::__construct(); + + $this->activityPubService = $activityPubService; + $this->actorService = $actorService; + $this->noteService = $noteService; + $this->curlService = $curlService; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + /** + * + */ + protected function configure() { + parent::configure(); + $this->setName('social:note:create') + ->addOption( + 'replyTo', 'r', InputOption::VALUE_OPTIONAL, 'in reply to an existing thread' + ) + ->addOption( + 'to', 't', InputOption::VALUE_OPTIONAL, 'to (default Public)' + ) + ->addArgument('userid', InputArgument::REQUIRED, 'userId of the author') + ->addArgument('content', InputArgument::REQUIRED, 'content of the post') + ->setDescription('Create a new note'); + } + + + /** + * @param InputInterface $input + * @param OutputInterface $output + * + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) { + + $userId = $input->getArgument('userid'); + $content = $input->getArgument('content'); + $to = $input->getOption('to'); + $replyTo = $input->getOption('replyTo'); + + $note = $this->noteService->generateNote($userId, $content, ActivityPubService::TO_PUBLIC); + + if ($to !== null) { + $note->setTo($to); + $note->addTag( + [ + 'type' => 'Mention', + 'href' => $to + ] + ); + } + + + if ($replyTo !== null) { + $note->setInReplyTo($replyTo); + } + + $result = $this->activityPubService->createActivity($userId, $note, $activity); + + echo 'object: ' . json_encode($activity, JSON_PRETTY_PRINT) . "\n"; + echo 'result: ' . json_encode($result, JSON_PRETTY_PRINT) . "\n"; + + } + +} + diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php new file mode 100644 index 00000000..d68c0cca --- /dev/null +++ b/lib/Controller/AccountController.php @@ -0,0 +1,106 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Controller; + +use daita\Traits\TArrayTools; +use daita\Traits\TNCDataResponse; +use Exception; +use OCA\Social\AppInfo\Application; +use OCA\Social\Service\ActivityPubService; +use OCA\Social\Service\ActivityStreamsService; +use OCA\Social\Service\ActorService; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\MiscService; +use OCA\Social\Service\ServiceAccountsService; +use OCA\Social\Service\ServicesService; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\DataResponse; +use OCP\IRequest; + + +class AccountController extends Controller { + + use TNCDataResponse; + + /** @var string */ + private $userId; + + /** @var ConfigService */ + private $configService; + + /** @var ActorService */ + private $actorService; + + /** @var MiscService */ + private $miscService; + + + /** + * AccountController constructor. + * + * @param IRequest $request + * @param string $userId + * @param ConfigService $configService + * @param ActorService $actorService + * @param MiscService $miscService + */ + public function __construct( + IRequest $request, string $userId, ConfigService $configService, + ActorService $actorService, MiscService $miscService + ) { + parent::__construct(Application::APP_NAME, $request); + + $this->userId = $userId; + $this->configService = $configService; + $this->actorService = $actorService; + $this->miscService = $miscService; + } + + + /** + * @NoAdminRequired + * + * @param array $data + * + * @return DataResponse + */ + public function create(string $username): DataResponse { + try { + $this->actorService->createActor($this->userId, $username); + + return $this->success([]); + } catch (Exception $e) { + return $this->fail($e->getMessage()); + } + } + + +} + diff --git a/lib/Controller/ActivityPubController.php b/lib/Controller/ActivityPubController.php new file mode 100644 index 00000000..083ddecb --- /dev/null +++ b/lib/Controller/ActivityPubController.php @@ -0,0 +1,250 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Controller; + + +use daita\Traits\TNCDataResponse; +use Exception; +use OCA\Social\AppInfo\Application; +use OCA\Social\Service\ActivityPubService; +use OCA\Social\Service\ActorService; +use OCA\Social\Service\MiscService; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Response; +use OCP\IRequest; + + +class ActivityPubController extends Controller { + + + use TNCDataResponse; + + + /** @var SocialPubController */ + private $socialPubController; + + /** @var ActivityPubService */ + private $activityPubService; + + /** @var ActorService */ + private $actorService; + + /** @var MiscService */ + private $miscService; + + + /** + * ActivityPubController constructor. + * + * @param SocialPubController $socialPubController + * @param ActivityPubService $activityPubService + * @param ActorService $actorService + * @param IRequest $request + * @param MiscService $miscService + */ + public function __construct( + SocialPubController $socialPubController, ActivityPubService $activityPubService, + ActorService $actorService, IRequest $request, + MiscService $miscService + ) { + parent::__construct(Application::APP_NAME, $request); + + $this->socialPubController = $socialPubController; + $this->activityPubService = $activityPubService; + $this->actorService = $actorService; + $this->miscService = $miscService; + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * + * @return Response + */ + public function actor(string $username) { + if (!$this->checkSourceActivityStreams()) { + return $this->socialPubController->actor($username); + } + + try { +// $this->activityPubService->generateActor($userId); + + $actor = $this->actorService->getActor($username); + + return $this->directSuccess($actor); + } catch (Exception $e) { + return $this->fail($e->getMessage()); + } + } + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * + * @return Response + */ + public function aliasactor(string $username) { + return $this->actor($username); + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @return Response + */ + public function sharedInbox() { + return $this->success([]); + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @return Response + */ + public function inbox($username) { + + try { + $this->activityPubService->checkRequest($this->request); +// $this->noteService->receiving(file_get_contents('php://input')); + $body = file_get_contents('php://input'); +$this->miscService->log('### ' . $body); + return $this->success([]); + } catch (Exception $e) { + return $this->fail($e->getMessage()); + } + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * + * @return Response + */ + public function test($username, $body) { + + return $this->success([$username]); +// $this->miscService->log('#### ' . $toto . ' ' . json_encode($_SERVER) . ' ' . json_encode($_POST)); +// try { +// return $this->success(['author' => $author]); +// } catch (Exception $e) { +// return $this->fail('ddsaads'); +// } + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * + * @return Response + */ + public function outbox($username) { + return $this->success([$username]); + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * + * @return Response + */ + public function followers($username) { + if (!$this->checkSourceActivityStreams()) { + return $this->socialPubController->followers($username); + } + + return $this->success([$username]); + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * + * @return Response + */ + public function following($username) { + if (!$this->checkSourceActivityStreams()) { + return $this->socialPubController->following($username); + } + + return $this->success([$username]); + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * @param $postId + * + * @return Response + */ + public function displayPost($username, $postId) { + return $this->success([$username, $postId]); + } + + + /** + * + */ + private function checkSourceActivityStreams() { + + return true; + if ($this->request->getHeader('Accept') + === 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"') { + return true; + } + + return false; + } +} + + diff --git a/lib/Controller/SocialPubController.php b/lib/Controller/SocialPubController.php new file mode 100644 index 00000000..d51b3754 --- /dev/null +++ b/lib/Controller/SocialPubController.php @@ -0,0 +1,132 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Controller; + + +use daita\Traits\TNCDataResponse; +use OCA\Social\AppInfo\Application; +use OCA\Social\Service\ActivityPubService; +use OCA\Social\Service\ActorService; +use OCA\Social\Service\MiscService; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IRequest; + +class SocialPubController extends Controller { + + + use TNCDataResponse; + + /** @var ActivityPubService */ + private $activityPubService; + + /** @var ActorService */ + private $actorService; + + /** @var MiscService */ + private $miscService; + + + /** + * ActivityPubController constructor. + * + * @param ActivityPubService $activityPubService + * @param ActorService $actorService + * @param IRequest $request + * @param MiscService $miscService + */ + public function __construct( + ActivityPubService $activityPubService, ActorService $actorService, IRequest $request, + MiscService $miscService + ) { + parent::__construct(Application::APP_NAME, $request); + + $this->activityPubService = $activityPubService; + $this->actorService = $actorService; + $this->miscService = $miscService; + } + + + /** + * @NoCSRFRequired + * @PublicPage + e* + * @param string $username + * + * @return Response + */ + public function actor(string $username) { + return new TemplateResponse(Application::APP_NAME, 'actor', [], 'blank'); + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * + * @return Response + */ + public function followers($username) { + return new TemplateResponse(Application::APP_NAME, 'followers', [], 'blank'); + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * + * @return Response + */ + public function following($username) { + return new TemplateResponse(Application::APP_NAME, 'following', [], 'blank'); + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $username + * @param $postId + * + * @return Response + */ + public function displayPost($username, $postId) { + return $this->success([$username, $postId]); + } + +} + + diff --git a/lib/Db/ActorsRequest.php b/lib/Db/ActorsRequest.php new file mode 100644 index 00000000..0b80f610 --- /dev/null +++ b/lib/Db/ActorsRequest.php @@ -0,0 +1,173 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Db; + + +use OCA\Social\Exceptions\ActorDoesNotExistException; +use OCA\Social\Model\ActivityPub\Actor; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\MiscService; +use OCP\IDBConnection; + +class ActorsRequest extends ActorsRequestBuilder { + + + /** + * ServicesRequest constructor. + * + * @param IDBConnection $connection + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + IDBConnection $connection, ConfigService $configService, MiscService $miscService + ) { + parent::__construct($connection, $configService, $miscService); + } + + + /** + * @param Actor $actor + * + * @return int + * @throws \Exception + */ + public function create(Actor $actor): int { + + try { + $qb = $this->getActorsInsertSql(); + $qb->setValue('type', $qb->createNamedParameter($actor->getType())) + ->setValue('user_id', $qb->createNamedParameter($actor->getUserId())) + ->setValue( + 'preferred_username', $qb->createNamedParameter($actor->getPreferredUsername()) + ) + ->setValue('public_key', $qb->createNamedParameter($actor->getPublicKey())) + ->setValue('private_key', $qb->createNamedParameter($actor->getPrivateKey())); + + $qb->execute(); + + return $qb->getLastInsertId(); + } catch (\Exception $e) { + throw $e; + } + } + + + /** + * return service. + * + * @param string $username + * + * @return Actor + * @throws ActorDoesNotExistException + */ + public function getFromUsername(string $username): Actor { + $qb = $this->getActorsSelectSql(); + $this->limitToPreferredUsername($qb, $username); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new ActorDoesNotExistException('Actor not found'); + } + + return $this->parseActorsSelectSql($data); + } + + + + /** + * return service. + * + * @param string $userId + * + * @return Actor + * @throws ActorDoesNotExistException + */ + public function getFromUserId(string $userId): Actor { + $qb = $this->getActorsSelectSql(); + $this->limitToUserId($qb, $userId); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new ActorDoesNotExistException('Actor not found'); + } + + return $this->parseActorsSelectSql($data); + } + + + + +// +// /** +// * @param Service $service +// * +// * @return bool +// */ +// public function update(Service $service): bool { +// +// try { +// $this->getService($service->getId()); +// } catch (ServiceDoesNotExistException $e) { +// return false; +// } +// +// $qb = $this->getServicesUpdateSql(); +// $qb->set('address', $qb->createNamedParameter($service->getAddress())); +// $qb->set('config', $qb->createNamedParameter(json_encode($service->getConfigAll()))); +// $qb->set('status', $qb->createNamedParameter($service->getStatus())); +// $qb->set('config', $qb->createNamedParameter(json_encode($service->getConfigAll()))); +// +// $this->limitToId($qb, $service->getId()); +// +// $qb->execute(); +// +// return true; +// } +// + +// /** +// * @param int $serviceId +// */ +// public function delete(int $serviceId) { +// $qb = $this->getServicesDeleteSql(); +// $this->limitToId($qb, $serviceId); +// +// $qb->execute(); +// } + + +} diff --git a/lib/Db/ActorsRequestBuilder.php b/lib/Db/ActorsRequestBuilder.php new file mode 100644 index 00000000..7bc5d7d3 --- /dev/null +++ b/lib/Db/ActorsRequestBuilder.php @@ -0,0 +1,129 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Db; + + +use daita\Traits\TArrayTools; +use OCA\Social\Model\ActivityPub\Actor; +use OCP\DB\QueryBuilder\IQueryBuilder; + +class ActorsRequestBuilder extends CoreRequestBuilder { + + + use TArrayTools; + + + /** + * Base of the Sql Insert request + * + * @return IQueryBuilder + */ + protected function getActorsInsertSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_SERVER_ACTORS); + + return $qb; + } + + + /** + * Base of the Sql Update request + * + * @return IQueryBuilder + */ + protected function getActorsUpdateSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->update(self::TABLE_SERVER_ACTORS); + + return $qb; + } + + + /** + * Base of the Sql Select request for Shares + * + * @return IQueryBuilder + */ + protected function getActorsSelectSql() { + $qb = $this->dbConnection->getQueryBuilder(); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->select( + 'sa.id', 'sa.type', 'sa.user_id', 'sa.preferred_username', 'sa.public_key', + 'sa.private_key', 'sa.creation' + ) + ->from(self::TABLE_SERVER_ACTORS, 'sa'); + + $this->defaultSelectAlias = 'sa'; + + return $qb; + } + + + /** + * Base of the Sql Delete request + * + * @return IQueryBuilder + */ + protected function getActorsDeleteSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_SERVER_ACTORS); + + return $qb; + } + + + /** + * @param array $data + * + * @return Actor + */ + protected function parseActorsSelectSql($data): Actor { + $id = $this->configService->getRoot() . '@' . $data['preferred_username']; + $actor = new Actor(); + $actor->setId($id) + ->setType($this->get('type', $data, '')) + ->setRoot($this->configService->getRoot()); + $actor->setUserId($data['user_id']) + ->setPreferredUsername($data['preferred_username']) + ->setPublicKey($data['public_key']) + ->setPrivateKey($data['private_key']) + ->setInbox($id . '/inbox') + ->setOutbox($id . '/outbox') + ->setFollowers($id . '/followers') + ->setFollowing($id . '/following') + ->setSharedInbox($this->configService->getRoot() . 'inbox') + ->setCreation($this->getInt('creation', $data, 0)); + + return $actor; + } + +} + diff --git a/lib/Db/CacheActorsRequest.php b/lib/Db/CacheActorsRequest.php new file mode 100644 index 00000000..4806da79 --- /dev/null +++ b/lib/Db/CacheActorsRequest.php @@ -0,0 +1,130 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Db; + + +use OCA\Social\Exceptions\ActorDoesNotExistException; +use OCA\Social\Exceptions\CacheActorDoesNotExistException; +use OCA\Social\Model\ActivityPub\Actor; +use OCA\Social\Model\ActivityPub\Cache\CacheActor; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\MiscService; +use OCP\IDBConnection; + +class CacheActorsRequest extends CacheActorsRequestBuilder { + + + /** + * ServicesRequest constructor. + * + * @param IDBConnection $connection + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + IDBConnection $connection, ConfigService $configService, MiscService $miscService + ) { + parent::__construct($connection, $configService, $miscService); + } + + + /** + * @param Actor $actor + * + * @return int + * @throws \Exception + */ + public function create(Actor $actor, array $object): int { + + try { + $qb = $this->getCacheActorsInsertSql(); + $qb->setValue('account', $qb->createNamedParameter($actor->getAccount())) + ->setValue('url', $qb->createNamedParameter($actor->getId())) + ->setValue('actor', $qb->createNamedParameter(json_encode($object))); + + $qb->execute(); + + return $qb->getLastInsertId(); + } catch (\Exception $e) { + throw $e; + } + } + + + /** + * return service. + * + * @param string $account + * + * @return CacheActor + * @throws CacheActorDoesNotExistException + */ + public function getFromAccount(string $account): Actor { + $qb = $this->getCacheActorsSelectSql(); + $this->limitToAccount($qb, $account); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new CacheActorDoesNotExistException(); + } + + return $this->parseCacheActorsSelectSql($data); + } + + + /** + * return service. + * + * @param string $url + * + * @return CacheActor + * @throws CacheActorDoesNotExistException + */ + public function getFromUrl(string $url): CacheActor { + $qb = $this->getCacheActorsSelectSql(); + $this->limitToUrl($qb, $url); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new CacheActorDoesNotExistException(); + } + + return $this->parseCacheActorsSelectSql($data); + } + + +} + diff --git a/lib/Db/CacheActorsRequestBuilder.php b/lib/Db/CacheActorsRequestBuilder.php new file mode 100644 index 00000000..ab4add74 --- /dev/null +++ b/lib/Db/CacheActorsRequestBuilder.php @@ -0,0 +1,122 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Db; + + +use daita\Traits\TArrayTools; +use OCA\Social\Model\ActivityPub\Actor; +use OCA\Social\Model\ActivityPub\Cache\CacheActor; +use OCA\Social\Model\Service; +use OCA\Social\Model\ServiceAccount; +use OCP\DB\QueryBuilder\IQueryBuilder; + +class CacheActorsRequestBuilder extends CoreRequestBuilder { + + + use TArrayTools; + + + /** + * Base of the Sql Insert request + * + * @return IQueryBuilder + */ + protected function getCacheActorsInsertSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_CACHE_ACTORS); + + return $qb; + } + + + /** + * Base of the Sql Update request + * + * @return IQueryBuilder + */ + protected function getCacheActorsUpdateSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->update(self::TABLE_CACHE_ACTORS); + + return $qb; + } + + + /** + * Base of the Sql Select request for Shares + * + * @return IQueryBuilder + */ + protected function getCacheActorsSelectSql() { + $qb = $this->dbConnection->getQueryBuilder(); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->select( + 'ca.id', 'ca.account', 'ca.url', 'ca.actor', 'ca.creation' + ) + ->from(self::TABLE_CACHE_ACTORS, 'ca'); + + $this->defaultSelectAlias = 'ca'; + + return $qb; + } + + + /** + * Base of the Sql Delete request + * + * @return IQueryBuilder + */ + protected function getCacheActorsDeleteSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_CACHE_ACTORS); + + return $qb; + } + + + /** + * @param array $data + * + * @return CacheActor + */ + protected function parseCacheActorsSelectSql($data): CacheActor { + $actor = new CacheActor(); + $actor->setId($this->getInt('id', $data)) + ->setAccount($data['account']) + ->setUrl($data['url']) + ->setActor(json_decode($data['actor'], true)) + ->setCreation($this->getInt('creation', $data, 0)); + + return $actor; + } + +} + diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php index eea39b91..fa5d13de 100644 --- a/lib/Db/CoreRequestBuilder.php +++ b/lib/Db/CoreRequestBuilder.php @@ -40,7 +40,11 @@ class CoreRequestBuilder { const TABLE_SERVICES = 'social_services'; const TABLE_ACCOUNTS = 'social_accounts'; - const TABLE_OAUTH2_TOKENS = 'social_oauth2_tokens'; + + const TABLE_SERVER_ACTORS = 'social_server_actors'; + const TABLE_SERVER_NOTES = 'social_server_notes'; + + const TABLE_CACHE_ACTORS = 'social_cache_actors'; /** @var IDBConnection */ @@ -95,6 +99,17 @@ class CoreRequestBuilder { } + /** + * Limit the request to the OwnerId + * + * @param IQueryBuilder $qb + * @param string $userId + */ + protected function limitToPreferredUsername(IQueryBuilder &$qb, $userId) { + $this->limitToDBField($qb, 'preferred_username', $userId); + } + + /** * Limit the request to the OwnerId * @@ -128,6 +143,17 @@ class CoreRequestBuilder { } + /** + * Limit the request to the url + * + * @param IQueryBuilder $qb + * @param string $url + */ + protected function limitToUrl(IQueryBuilder &$qb, string $url) { + $this->limitToDBField($qb, 'url', $url); + } + + /** * Limit the request to the status * diff --git a/lib/Db/NotesRequest.php b/lib/Db/NotesRequest.php new file mode 100644 index 00000000..ec1fa58f --- /dev/null +++ b/lib/Db/NotesRequest.php @@ -0,0 +1,86 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Db; + + +use OCA\Social\Exceptions\ActorDoesNotExistException; +use OCA\Social\Model\ActivityPub\Note; +use OCA\Social\Model\ActivityPub\Actor; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\MiscService; +use OCP\IDBConnection; + +class NotesRequest extends NotesRequestBuilder { + + + /** + * ServicesRequest constructor. + * + * @param IDBConnection $connection + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + IDBConnection $connection, ConfigService $configService, MiscService $miscService + ) { + parent::__construct($connection, $configService, $miscService); + } + + + /** + * @param Note $note + * + * @return int + * @throws \Exception + */ + public function create(Note $note): int { + + try { + $qb = $this->getNotesInsertSql(); + $qb->setValue('id', $qb->createNamedParameter($note->getId())) + ->setValue('to', $qb->createNamedParameter($note->getTo())) + ->setValue('to_array', $qb->createNamedParameter(json_encode($note->getToArray()))) + ->setValue('cc', $qb->createNamedParameter(json_encode($note->getCc()))) + ->setValue('bcc', $qb->createNamedParameter(json_encode($note->getBcc()))) + ->setValue('content', $qb->createNamedParameter($note->getContent())) + ->setValue('summary', $qb->createNamedParameter($note->getSummary())) + ->setValue('published', $qb->createNamedParameter($note->getPublished())) + ->setValue('attributed_to', $qb->createNamedParameter($note->getAttributedTo())) + ->setValue('in_reply_to', $qb->createNamedParameter($note->getInReplyTo())); + + $qb->execute(); + + return $qb->getLastInsertId(); + } catch (\Exception $e) { + throw $e; + } + } + +} diff --git a/lib/Db/NotesRequestBuilder.php b/lib/Db/NotesRequestBuilder.php new file mode 100644 index 00000000..45735a7c --- /dev/null +++ b/lib/Db/NotesRequestBuilder.php @@ -0,0 +1,124 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Db; + + +use daita\Traits\TArrayTools; +use OCA\Social\Model\ActivityPub\Note; +use OCP\DB\QueryBuilder\IQueryBuilder; + +class NotesRequestBuilder extends CoreRequestBuilder { + + + use TArrayTools; + + + /** + * Base of the Sql Insert request + * + * @return IQueryBuilder + */ + protected function getNotesInsertSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_SERVER_NOTES); + + return $qb; + } + + + /** + * Base of the Sql Update request + * + * @return IQueryBuilder + */ + protected function getNotesUpdateSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->update(self::TABLE_SERVER_NOTES); + + return $qb; + } + + + /** + * Base of the Sql Select request for Shares + * + * @return IQueryBuilder + */ + protected function getNotesSelectSql() { + $qb = $this->dbConnection->getQueryBuilder(); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->select( + 'sn.id', 'sn.to', 'sn.to_array', 'sn.cc', 'sn.bcc', 'sn.content', 'sn_summary', + 'sn.published', 'sn.attributed_to', 'sn.in_reply_to', 'sn.creation' + ) + ->from(self::TABLE_SERVER_NOTES, 'sn'); + + $this->defaultSelectAlias = 'sn'; + + return $qb; + } + + + /** + * Base of the Sql Delete request + * + * @return IQueryBuilder + */ + protected function getNotesDeleteSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_SERVER_NOTES); + + return $qb; + } + + + /** + * @param array $data + * + * @return Note + */ + protected function parseNotesSelectSql($data): Note { + $note = new Note(); + $note->setId($data['id']) + ->setTo($data['to']) + ->setToArray(json_decode($data['to_array'], true)) + ->setCc(json_decode($data['cc'], true)) + ->setBcc(json_decode($data['bcc'])); + $note->setContent($data['content']) + ->setPublished($data['published']) + ->setAttributedTo($data['attributed_to']) + ->setInReplyTo($data['in_reply_to']); + + return $note; + } + +} + diff --git a/lib/Exceptions/ActorAlreadyExistsException.php b/lib/Exceptions/ActorAlreadyExistsException.php new file mode 100644 index 00000000..a2556c9e --- /dev/null +++ b/lib/Exceptions/ActorAlreadyExistsException.php @@ -0,0 +1,8 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Model; + + +use daita\Traits\TArrayTools; +use JsonSerializable; + +class APHosts implements JsonSerializable { + + use TArrayTools; + + /** @var string */ + private $address; + + /** @var array */ + private $uriIds = []; + + public function __construct(string $address = '') { + $this->address = $address; + } + + + /** + * @return string + */ + public function getAddress(): string { + return $this->address; + } + + + /** + * @param string $uriId + * + * @return APHosts + */ + public function addUriId(string $uriId): APHosts { + $this->uriIds[] = $uriId; + + return $this; + } + + /** + * @return array + */ + public function getUriIds(): array { + return $this->uriIds; + } + + + public function jsonSerialize() { + return [ + 'address' => $this->address, + 'urlIds' => $this->getUriIds() + ]; + } + + +} + diff --git a/lib/Model/ActivityPub/ActivityCreate.php b/lib/Model/ActivityPub/ActivityCreate.php new file mode 100644 index 00000000..f1946ba5 --- /dev/null +++ b/lib/Model/ActivityPub/ActivityCreate.php @@ -0,0 +1,62 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Model\ActivityPub; + + +use JsonSerializable; + +class ActivityCreate extends Core implements JsonSerializable { + + + /** + * ActivityCreate constructor. + * + * @param bool $isTopLevel + */ + public function __construct(bool $isTopLevel = false) { + parent::__construct($isTopLevel); + + $this->setType('Create'); + } + + + /** + * @return array + */ + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + ] + ); + } + +} + diff --git a/lib/Model/ActivityPub/Actor.php b/lib/Model/ActivityPub/Actor.php new file mode 100644 index 00000000..25bba176 --- /dev/null +++ b/lib/Model/ActivityPub/Actor.php @@ -0,0 +1,330 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Model\ActivityPub; + + +use JsonSerializable; +use OCA\Social\Service\ActivityPubService; + +class Actor extends Core implements JsonSerializable { + + /** @var string */ + private $userId = ''; + + /** @var string */ + private $preferredUsername = ''; + + /** @var string */ + private $publicKey = ''; + + /** @var string */ + private $privateKey = ''; + + /** @var int */ + private $creation = 0; + + /** @var string */ + private $account = ''; + + /** @var string */ + private $following = ''; + + /** @var string */ + private $followers = ''; + + /** @var string */ + private $inbox = ''; + + /** @var string */ + private $outbox = ''; + + /** @var string */ + private $sharedInbox = ''; + + + /** + * Actor constructor. + * + * @param bool $isTopLevel + */ + public function __construct(bool $isTopLevel = false) { + parent::__construct($isTopLevel); + + $this->setType('Person'); + } + + + /** + * @return string + */ + public function getUserId(): string { + return $this->userId; + } + + /** + * @param string $userId + * + * @return Actor + */ + public function setUserId(string $userId): Actor { + $this->userId = $userId; + + if ($this->getPreferredUsername() === '') { + $this->setPreferredUsername($userId); + } + + return $this; + } + + + /** + * @return string + */ + public function getPreferredUsername(): string { + return $this->preferredUsername; + } + + /** + * @param string $preferredUsername + * + * @return Actor + */ + public function setPreferredUsername(string $preferredUsername): Actor { + if ($preferredUsername !== '') { + $this->preferredUsername = $preferredUsername; + } + + return $this; + } + + + /** + * @return string + */ + public function getPublicKey(): string { + return $this->publicKey; + } + + /** + * @param string $publicKey + * + * @return Actor + */ + public function setPublicKey(string $publicKey): Actor { + $this->publicKey = $publicKey; + + return $this; + } + + + /** + * @return string + */ + public function getPrivateKey(): string { + return $this->privateKey; + } + + /** + * @param string $privateKey + * + * @return Actor + */ + public function setPrivateKey(string $privateKey): Actor { + $this->privateKey = $privateKey; + + return $this; + } + + + /** + * @return int + */ + public function getCreation(): int { + return $this->creation; + } + + /** + * @param int $creation + * + * @return Actor + */ + public function setCreation(int $creation): Actor { + $this->creation = $creation; + + return $this; + } + + + /** + * @return string + */ + public function getFollowing(): string { + return $this->following; + } + + /** + * @param string $following + * + * @return Actor + */ + public function setFollowing(string $following): Actor { + $this->following = $following; + + return $this; + } + + /** + * @return string + */ + public function getFollowers(): string { + return $this->followers; + } + + /** + * @param string $followers + * + * @return Actor + */ + public function setFollowers(string $followers): Actor { + $this->followers = $followers; + + return $this; + } + + /** + * @return string + */ + public function getAccount(): string { + return $this->account; + } + + /** + * @param string $account + * + * @return Actor + */ + public function setAccount(string $account): Actor { + $this->account = $account; + + return $this; + } + + + /** + * @return string + */ + public function getInbox(): string { + return $this->inbox; + } + + /** + * @param string $inbox + * + * @return Actor + */ + public function setInbox(string $inbox): Actor { + $this->inbox = $inbox; + + return $this; + } + + /** + * @return string + */ + public function getOutbox(): string { + return $this->outbox; + } + + /** + * @param string $outbox + * + * @return Actor + */ + public function setOutbox(string $outbox): Actor { + $this->outbox = $outbox; + + return $this; + } + + /** + * @return string + */ + public function getSharedInbox(): string { + return $this->sharedInbox; + } + + /** + * @param string $sharedInbox + * + * @return Actor + */ + public function setSharedInbox(string $sharedInbox): Actor { + $this->sharedInbox = $sharedInbox; + + return $this; + } + + + /** + * @return array + */ + public function jsonSerialize(): array { + return [ + '@context' => [ + ActivityPubService::CONTEXT_ACTIVITYSTREAMS, + ActivityPubService::CONTEXT_SECURITY + ], + 'aliases' => [ + $this->getRoot() . '@' . $this->getPreferredUsername(), + $this->getRoot() . 'users/' . $this->getPreferredUsername() + ], + + 'id' => $this->getId(), + 'type' => $this->getType(), + 'preferredUsername' => $this->getPreferredUsername(), + 'inbox' => $this->getInbox(), + 'outbox' => $this->getOutbox(), + 'following' => $this->getFollowing(), + 'followers' => $this->getFollowers(), + 'url' => $this->getId(), + 'endpoints' => + ['sharedInbox' => $this->getSharedInbox()], + + 'publicKey' => [ + 'id' => $this->getId() . '#main-key', + 'owner' => $this->getId(), + 'publicKeyPem' => $this->getPublicKey() + ] + ]; + } + + +} + diff --git a/lib/Model/ActivityPub/Cache/CacheActor.php b/lib/Model/ActivityPub/Cache/CacheActor.php new file mode 100644 index 00000000..27768ae5 --- /dev/null +++ b/lib/Model/ActivityPub/Cache/CacheActor.php @@ -0,0 +1,157 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Model\ActivityPub\Cache; + + +class CacheActor { + + /** @var int */ + private $id; + + /** @var string */ + private $account = ''; + + /** @var string */ + private $url; + + /** @var array */ + private $actor = []; + + /** @var int */ + private $creation = 0; + + + /** + * CacheActor constructor. + * + * @param int $id + */ + public function __construct($id = 0) { + $this->id = $id; + } + + + /** + * @return int + */ + public function getId(): int { + return $this->id; + } + + /** + * @param int $id + * + * @return CacheActor + */ + public function setId(int $id): CacheActor { + $this->account = $id; + + return $this; + } + + + /** + * @return string + */ + public function getAccount(): string { + return $this->account; + } + + /** + * @param string $account + * + * @return CacheActor + */ + public function setAccount(string $account): CacheActor { + $this->account = $account; + + return $this; + } + + + /** + * @return string + */ + public function getUrl(): string { + return $this->url; + } + + /** + * @param string $url + * + * @return CacheActor + */ + public function setUrl(string $url): CacheActor { + $this->url = $url; + + return $this; + } + + + /** + * @return array + */ + public function getActor(): array { + return $this->actor; + } + + /** + * @param array $actor + * + * @return CacheActor + */ + public function setActor(array $actor): CacheActor { + $this->actor = $actor; + + return $this; + } + + + /** + * @return int + */ + public function getCreation(): int { + return $this->creation; + } + + /** + * @param int $creation + * + * @return CacheActor + */ + public function setCreation(int $creation): CacheActor { + $this->creation = $creation; + + return $this; + } + + +} + diff --git a/lib/Model/ActivityPub/Core.php b/lib/Model/ActivityPub/Core.php new file mode 100644 index 00000000..24d1dcd4 --- /dev/null +++ b/lib/Model/ActivityPub/Core.php @@ -0,0 +1,443 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Model\ActivityPub; + + +use JsonSerializable; +use OCA\Social\Service\ICoreService; + +class Core implements JsonSerializable { + + + /** @var string */ + private $root = ''; + + /** @var bool */ + private $isTopLevel = false; + + /** @var string */ + private $address = ''; + + /** @var string */ + private $id = ''; + + /** @var string */ + private $type; + + /** @var string */ + private $to = ''; + + /** @var array */ + private $toArray = []; + + /** @var array */ + private $cc = []; + + /** @var array */ + private $bcc = []; + + /** @var Actor */ + private $actor; + + /** @var array */ + private $tags = []; + + /** @var array */ + private $entries = []; + + /** @var Core */ + private $object = null; + + /** @var ICoreService */ + private $saveAs; + + /** + * Core constructor. + * + * @param bool $isTopLevel + */ + public function __construct(bool $isTopLevel = false) { + $this->isTopLevel = $isTopLevel; + } + + + /** + * @return string + */ + public function getId(): string { + return $this->id; + } + + + /** + * @param string $id + * + * @return Core + */ + public function setId(string $id): Core { + $this->id = $id; + + return $this; + } + + + /** + * @return string + */ + public function getType(): string { + return $this->type; + } + + /** + * @param string $type + * + * @return Core + */ + public function setType(string $type): Core { + $this->type = $type; + + return $this; + } + + /** + * @return Actor + */ + public function getActor(): Actor { + return $this->actor; + } + + /** + * @param Actor $actor + * + * @return Core + */ + public function setActor(Actor $actor): Core { + $this->actor = $actor; + + return $this; + } + + /** + * @return bool + */ + public function gotActor(): bool { + if ($this->actor === null) { + return false; + } + + return true; + } + + + /** + * @return string + */ + public function getRoot(): string { + return $this->root; + } + + /** + * @param string $path + * + * @return Core + */ + public function setRoot(string $path): Core { + $this->root = $path; + + return $this; + } + + + /** + * @return string + */ + public function getAddress(): string { + return $this->address; + } + + /** + * @param string $address + * + * @return Core + */ + public function setAddress(string $address) { + $this->address = $address; + + return $this; + } + + + /** + * @return string + */ + public function getTo(): string { + return $this->to; + } + + /** + * @param string $to + * + * @return Core + */ + public function setTo(string $to): Core { + $this->to = $to; + + return $this; + } + + + /** + * @return array + */ + public function getToArray(): array { + return $this->toArray; + } + + /** + * @param array $toArray + * + * @return Core + */ + public function setToArray(array $toArray): Core { + $this->toArray = $toArray; + + return $this; + } + + + /** + * @return array + */ + public function getCc(): array { + return $this->cc; + } + + /** + * @param array $cc + * + * @return Core + */ + public function setCc(array $cc): Core { + $this->cc = $cc; + + return $this; + } + + + /** + * @return array + */ + public function getBcc(): array { + return $this->bcc; + } + + /** + * @param array $bcc + * + * @return Core + */ + public function setBcc(array $bcc): Core { + $this->bcc = $bcc; + + return $this; + } + + + /** + * @param array $tag + * + * @return Core + */ + public function addTag(array $tag): Core { + $this->tags[] = $tag; + + return $this; + } + + /** + * @return array + */ + public function getTags(): array { + return $this->tags; + } + + /** + * @param array $tag + * + * @return Core + */ + public function setTags(array $tag): Core { + $this->tags = $tag; + + return $this; + } + + + /** + * @return bool + */ + public function gotObject(): bool { + if ($this->object === null) { + return false; + } + + return true; + } + + /** + * @return Core + */ + public function getObject(): Core { + return $this->object; + } + + /** + * @param Core $object + * + * @return Core + */ + public function setObject(Core $object): Core { + $this->object = $object; + + return $this; + } + + + /** + * @return bool + */ + public function isTopLevel(): bool { + return $this->isTopLevel; + } + + + /** + * @param array $arr + * + * @return Core + */ + public function setEntries(array $arr): Core { + $this->entries = $arr; + + return $this; + } + + /** + * @return array + */ + public function getEntries(): array { + return $this->entries; + } + + /** + * @param string $k + * @param string $v + * + * @return Core + */ + public function addEntry(string $k, string $v): Core { + if ($v === '') { + unset($this->entries[$k]); + + return $this; + } + + $this->entries[$k] = $v; + + return $this; + } + + + /** + * @param string $k + * @param array $v + * + * @return Core + */ + public function addEntryArray(string $k, array $v): Core { + $this->entries[$k] = $v; + + return $this; + } + + + /** + * @param ICoreService $class + */ + public function saveAs(ICoreService $class) { + $this->saveAs = $class; + } + + /** + * @return ICoreService + */ + public function savingAs() { + return $this->saveAs; + } + + + /** + * @return array + */ + public function jsonSerialize(): array { + $this->addEntry('id', $this->getId()); + $this->addEntry('type', $this->getType()); + $this->addEntry('url', $this->getId()); + + if ($this->getToArray() === []) { + $this->addEntry('to', $this->getTo()); + } else { + $this->addEntryArray('to', $this->getToArray()); + } + + $this->addEntryArray('cc', $this->getCc()); + + if ($this->gotActor()) { + $this->addEntry( + 'actor', $this->getActor() + ->getId() + ); + } + + if ($this->getTags() !== []) { + $this->addEntryArray('tag', $this->getTags()); + } + + $arr = $this->getEntries(); + if ($this->gotObject()) { + $arr['object'] = $this->getObject(); + } + + return $arr; + } + +} + + diff --git a/lib/Model/ActivityPub/Note.php b/lib/Model/ActivityPub/Note.php new file mode 100644 index 00000000..741ae542 --- /dev/null +++ b/lib/Model/ActivityPub/Note.php @@ -0,0 +1,190 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Model\ActivityPub; + + +use JsonSerializable; + +class Note extends Core implements JsonSerializable { + + + /** @var string */ + private $content; + + /** @var string */ + private $summary = ''; + + /** @var string */ + private $published; + + /** @var string */ + private $attributedTo; + + /** @var string */ + private $inReplyTo = ''; + + + /** + * Note constructor. + * + * @param bool $isTopLevel + */ + public function __construct(bool $isTopLevel = false) { + parent::__construct($isTopLevel); + + $this->setType('Note'); + } + + + /** + * @return string + */ + public function getContent(): string { + return $this->content; + } + + /** + * @param string $content + * + * @return Note + */ + public function setContent(string $content): Note { + $this->content = $content; + + return $this; + } + + + /** + * @return string + */ + public function getSummary(): string { + return $this->summary; + } + + /** + * @param string $summary + * + * @return Note + */ + public function setSummary(string $summary): Note { + $this->summary = $summary; + + return $this; + } + + + /** + * @return string + */ + public function getPublished(): string { + return $this->published; + } + + /** + * @param string $published + * + * @return Note + */ + public function setPublished(string $published): Note { + $this->published = $published; + + return $this; + } + + /** + * @return string + */ + public function getAttributedTo(): string { + return $this->attributedTo; + } + + /** + * @param string $attributedTo + * + * @return Note + */ + public function setAttributedTo(string $attributedTo): Note { + $this->attributedTo = $attributedTo; + + return $this; + } + + /** + * @return string + */ + public function getInReplyTo(): string { + return $this->inReplyTo; + } + + /** + * @param string $inReplyTo + * + * @return Note + */ + public function setInReplyTo(string $inReplyTo): Note { + $this->inReplyTo = $inReplyTo; + + return $this; + } + + + + + + + + + + + +// public function +//"published": "2018-06-23T17:17:11Z", +//"attributedTo": "https://my-example.com/actor", +//"inReplyTo": "https://mastodon.social/@Gargron/100254678717223630", + + + /** + * @return array + */ + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + 'content' => $this->getContent(), + 'published' => $this->getPublished(), + 'attributedTo' => $this->getRoot() . $this->getAttributedTo(), + 'inReplyTo' => $this->getInReplyTo() + ] + ); + } + +} + diff --git a/lib/Service/ActivityPub/InboxService.php b/lib/Service/ActivityPub/InboxService.php new file mode 100644 index 00000000..de02401b --- /dev/null +++ b/lib/Service/ActivityPub/InboxService.php @@ -0,0 +1,76 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Service\ActivityPub; + + +use Exception; +use OC\User\NoUserException; +use OCA\Social\Db\NotesRequest; +use OCA\Social\Exceptions\ActorDoesNotExistException; +use OCA\Social\Model\ActivityPub\Core; +use OCA\Social\Model\ActivityPub\Note; +use OCA\Social\Service\ActivityStreamsService; +use OCA\Social\Service\ActorService; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\CurlService; +use OCA\Social\Service\ICoreService; +use OCA\Social\Service\MiscService; + +class InboxService implements ICoreService { + + /** @var InboxRequest */ + private $inboxRequest; + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * InboxService constructor. + * + * @param InboxRequest $inboxRequest + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + InboxRequest $inboxRequest, + ConfigService $configService, + MiscService $miscService + ) { + $this->inboxRequest = $inboxRequest; + $this->configService = $configService; + $this->miscService = $miscService; + } + +} + diff --git a/lib/Service/ActivityPub/NoteService.php b/lib/Service/ActivityPub/NoteService.php new file mode 100644 index 00000000..8e461514 --- /dev/null +++ b/lib/Service/ActivityPub/NoteService.php @@ -0,0 +1,130 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Service\ActivityPub; + + +use Exception; +use OC\User\NoUserException; +use OCA\Social\Db\NotesRequest; +use OCA\Social\Exceptions\ActorDoesNotExistException; +use OCA\Social\Model\ActivityPub\Core; +use OCA\Social\Model\ActivityPub\Note; +use OCA\Social\Service\ActivityStreamsService; +use OCA\Social\Service\ActorService; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\CurlService; +use OCA\Social\Service\ICoreService; +use OCA\Social\Service\MiscService; + +class NoteService implements ICoreService { + + /** @var NotesRequest */ + private $notesRequest; + + /** @var ActivityStreamsService */ + private $activityStreamsService; + + /** @var ActorService */ + private $actorService; + + /** @var CurlService */ + private $curlService; + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * NoteService constructor. + * + * @param NotesRequest $notesRequest + * @param ActivityStreamsService $activityStreamsService + * @param ActorService $actorService + * @param CurlService $curlService + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + NotesRequest $notesRequest, ActivityStreamsService $activityStreamsService, + ActorService $actorService, + CurlService $curlService, + ConfigService $configService, + MiscService $miscService + ) { + $this->notesRequest = $notesRequest; + $this->activityStreamsService = $activityStreamsService; + $this->actorService = $actorService; + $this->curlService = $curlService; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + /** + * @param string $userId + * @param string $content + * @param string $to + * + * @return Note + * @throws ActorDoesNotExistException + * @throws NoUserException + */ + public function generateNote(string $userId, string $content, string $to) { + $note = new Note(); + $actor = $this->actorService->getActorFromUserId($userId); + + $note->setId($this->configService->generateId('@' . $actor->getPreferredUsername())); + $note->setPublished(date("c")); + $note->setAttributedTo( + $this->configService->getRoot() . '@' . $actor->getPreferredUsername() + ); + $note->setTo($to); + $note->setContent($content); + + $note->saveAs($this); + + return $note; + } + + + /** + * @param Core $note + * + * @throws Exception + */ + public function save(Core $note) { + /** @var Note $note */ + $this->notesRequest->create($note); + } + +} diff --git a/lib/Service/ActivityPubService.php b/lib/Service/ActivityPubService.php new file mode 100644 index 00000000..949a83d5 --- /dev/null +++ b/lib/Service/ActivityPubService.php @@ -0,0 +1,491 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Service; + + +use daita\Model\Request; +use DateTime; +use Exception; +use OC\User\NoUserException; +use OCA\Social\Db\ActorsRequest; +use OCA\Social\Exceptions\ActorDoesNotExistException; +use OCA\Social\Exceptions\APIRequestException; +use OCA\Social\Exceptions\InvalidAccessTokenException; +use OCA\Social\Exceptions\MovedPermanentlyException; +use OCA\Social\Model\ActivityPub\ActivityCreate; +use OCA\Social\Model\ActivityPub\Core; +use OCA\Social\Model\ActivityPub\Note; +use OCA\Social\Model\APHosts; +use OCP\IRequest; + +class ActivityPubService { + + + const CONTEXT_ACTIVITYSTREAMS = 'https://www.w3.org/ns/activitystreams'; + const CONTEXT_SECURITY = 'https://w3id.org/security/v1'; + + const TO_PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'; + + const DATE_FORMAT = 'D, d M Y H:i:s T'; + const DATE_DELAY = 30; + + /** @var ActivityStreamsService */ + private $activityStreamsService; + + /** @var ActorsRequest */ + private $actorsRequest; + + /** @var ActorService */ + private $actorService; + + /** @var ConfigService */ + private $configService; + + /** @var CurlService */ + private $curlService; + + /** @var MiscService */ + private $miscService; + + + /** + * ActivityStreamsService constructor. + * + * @param ActivityStreamsService $activityStreamsService + * @param CurlService $curlService + * @param ActorsRequest $actorsRequest + * @param ActorService $actorService + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + ActivityStreamsService $activityStreamsService, + CurlService $curlService, + ActorsRequest $actorsRequest, + ActorService $actorService, + ConfigService $configService, MiscService $miscService + ) { + $this->activityStreamsService = $activityStreamsService; + $this->curlService = $curlService; + $this->actorsRequest = $actorsRequest; + $this->actorService = $actorService; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + public function test() { + + + } + + + /** + * @param string $userId + * + * @param Core $item + * @param Core $activity + * + * @return array + * @throws ActorDoesNotExistException + * @throws NoUserException + * @throws APIRequestException + * @throws InvalidAccessTokenException + * @throws MovedPermanentlyException + */ + public function createActivity($userId, Core $item, Core &$activity = null): array { + + $activity = new ActivityCreate(true); +// $this->activityStreamsService->initCore($activity); + + $actor = $this->actorService->getActorFromUserId($userId); + $activity->setId($item->getId() . '/activity'); + + if ($item->getToArray() !== []) { + $activity->setToArray($item->getToArray()); + } else { + $activity->setTo($item->getTo()); + } + + $activity->setActor($actor); + $activity->setObject($item); + + $this->setupCore($activity); + $result = $this->request($activity); + + return $result; + } + + + /** + * @param Core $activity + * + * @return array + * @throws APIRequestException + * @throws InvalidAccessTokenException + * @throws MovedPermanentlyException + */ + public function request(Core $activity) { + $hosts = $this->getAPHostsFromActivity($activity); + + $result = []; + foreach ($hosts as $host) { + $request = $this->generateRequest($host, $activity); + $result[] = $this->curlService->request($request); + } + + return $result; + } + + + /** + * @param APHosts $host + * @param Core $activity + * + * @return Request + */ + public function generateRequest(APHosts $host, Core $activity): Request { + + $document = json_encode($activity); + $date = date(self::DATE_FORMAT); + + $localActor = $activity->getActor(); + $uriIds = $host->getUriIds(); + $remoteActor = $this->getRemoteActor($uriIds[0]); + + $localActorLink = + $this->configService->getRoot() . '@' . $localActor->getPreferredUsername(); + $signature = "(request-target): post /users/testSocial/inbox\nhost: " . $host->getAddress() + . "\ndate: " . $date; + + openssl_sign($signature, $signed, $localActor->getPrivateKey(), OPENSSL_ALGO_SHA256); + + $signed = base64_encode($signed); + $header = + 'keyId="' . $localActorLink . '",headers="(request-target) host date",signature="' + . $signed . '"'; + + $request = new Request('/users/testSocial/inbox', Request::TYPE_POST); + $request->addHeader('Host: ' . $host->getAddress()); + $request->addHeader('Date: ' . $date); + $request->addHeader('Signature: ' . $header); + + $request->setDataJson($document); + $request->setAddress($host->getAddress()); + + return $request; + } + + + /** + * @param IRequest $request + * + * @throws APIRequestException + * @throws InvalidAccessTokenException + * @throws MovedPermanentlyException + * @throws Exception + */ + public function checkRequest(IRequest $request) { + $dTime = new DateTime($request->getHeader('date')); + $dTime->format(self::DATE_FORMAT); + + if ($dTime->getTimestamp() < (time() - self::DATE_DELAY)) { + throw new Exception('object is too old'); + } + + $this->checkSignature($request); + } + + +// /** +// * @param Core $activity +// * +// * @return array +// */ +// private function getHostsFromActivity(Core $activity) { +// +// $hosts = []; +// $hosts[] = $this->getHostFromUriId($activity->getTo()); +// foreach ($activity->getToArray() as $to) { +// $hosts[] = $this->getHostFromUriId($to); +// } +// +// if ($activity instanceof Note) { +// /** @var Note $activity */ +// $hosts[] = $this->getHostFromUriId($activity->getInReplyTo()); +// } +// +// $hosts = $this->cleaningHosts($hosts); +// +// return $hosts; +// } + + + /** + * @param Core $activity + * + * @return APHosts[] + */ + private function getAPHostsFromActivity(Core $activity) { + + $hosts = []; +// $uriIds = []; + $this->addAPHosts($activity->getTo(), $hosts); + foreach ($activity->getToArray() as $to) { + $this->addAPHosts($to, $hosts); +// $uriIds[] = $to; + } + + if ($activity instanceof Note) { + /** @var Note $activity */ + $this->addAPHosts($activity->getInReplyTo(), $hosts); +// $uriIds[] = $activity->getInReplyTo(); + } + +// $uriId = $this->cleaningHosts($uriId); + + return $hosts; + } + + + /** + * @param string $uriId + * @param APHosts[] $hosts + */ + private function addAPHosts(string $uriId, array &$hosts) { + $address = $this->getHostFromUriId($uriId); + if ($address === '') { + return; + } + + foreach ($hosts as $host) { + if ($host->getAddress() === $address) { + $host->addUriId($uriId); + + return; + } + } + + $apHost = new APHosts($address); + $apHost->addUriId($uriId); + $hosts[] = $apHost; + } + + + /** + * @param string $uriId + * + * @return mixed + * @throws APIRequestException + * @throws InvalidAccessTokenException + * @throws MovedPermanentlyException + */ + private function getRemoteActor(string $uriId) { + $actor = $this->actorService->getFromUri($uriId); + + return $actor; + } + + + /** + * @param string $uriId + * + * @return string + */ + private function getHostFromUriId(string $uriId) { + $ignoreThose = [ + '', + 'https://www.w3.org/ns/activitystreams#Public' + ]; + + if (in_array($uriId, $ignoreThose)) { + return ''; + } + + $url = parse_url($uriId); + if (!is_array($url) || !array_key_exists('host', $url)) { + return ''; + } + + return $url['host']; + } + + +// /** +// * @param array $hosts +// * +// * @return array +// */ +// private function cleaningHosts(array $hosts) { +// $ret = []; +// foreach ($hosts as $host) { +// if ($host === '') { +// continue; +// } +// +// $ret[] = $host; +// } +// +// return $ret; +// } + + + /** + * @param IRequest $request + * + * @throws APIRequestException + * @throws InvalidAccessTokenException + * @throws MovedPermanentlyException + * @throws Exception + */ + private function checkSignature(IRequest $request) { + $signatureHeader = $request->getHeader('Signature'); + + $sign = $this->parseSignatureHeader($signatureHeader); + $this->miscService->mustContains(['keyId', 'headers', 'signature'], $sign); + + $keyId = $sign['keyId']; + $headers = $sign['headers']; + $signed = base64_decode($sign['signature']); + $estimated = $this->generateEstimatedSignature($headers, $request); + + $publicKey = $this->retrieveKey($keyId); + if (openssl_verify($estimated, $signed, $publicKey, 'sha256') !== 1) { + throw new Exception('signature cannot be checked'); + } + + } + + + /** + * @param string $headers + * @param IRequest $request + * + * @return string + */ + private function generateEstimatedSignature(string $headers, IRequest $request): string { + $keys = explode(' ', $headers); + + $estimated = "(request-target): post /apps/social/@maxence/inbox"; + foreach ($keys as $key) { + if ($key === '(request-target)') { + continue; + } + + $estimated .= "\n" . $key . ': ' . $request->getHeader($key); + } + + return $estimated; + } + + + /** + * @param $signatureHeader + * + * @return array + */ + private function parseSignatureHeader($signatureHeader) { + $sign = []; + + $entries = explode(',', $signatureHeader); + foreach ($entries as $entry) { + list($k, $v) = explode('=', $entry, 2); + preg_match('/"([^"]+)"/', $v, $varr); + $v = trim($varr[0], '"'); + + $sign[$k] = $v; + } + + return $sign; + } + + + /** + * @param $keyId + * + * @return array + * @throws MovedPermanentlyException + * @throws APIRequestException + * @throws InvalidAccessTokenException + */ + private function retrieveKey($keyId) { + $actor = $this->retrieveObject($keyId); + + return $actor['publicKey']['publicKeyPem']; + } + +// +// /** +// * @param $id +// * +// * @return mixed +// * @throws MovedPermanentlyException +// * @throws APIRequestException +// * @throws InvalidAccessTokenException +// */ +// private function retrieveObject($id) { +// $url = parse_url($id); +// +// $request = new Request($url['path'], Request::TYPE_GET); +// $request->setAddress($url['host']); +// +//// $key = $url['fragment']; +// +// return $this->curlService->request($request); +// } + + + /** + * @param Core $activity + */ + private function setupCore(Core $activity) { + +// $this->initCore($activity); + if ($activity->isTopLevel()) { + $activity->addEntry('@context', self::CONTEXT_ACTIVITYSTREAMS); + } + + $coreService = $activity->savingAs(); + if ($coreService !== null) { + $coreService->save($activity); + } + + if ($activity->gotObject()) { + $this->setupCore($activity->getObject()); + } + } + + +// public function setRoot(IActivityPub $actor) { +// $actor->setRoot('https://test.artificial-owl.com/apps/social'); +// } + + +} diff --git a/lib/Service/ActivityStreamsService.php b/lib/Service/ActivityStreamsService.php index 9a76a31c..a73341d0 100644 --- a/lib/Service/ActivityStreamsService.php +++ b/lib/Service/ActivityStreamsService.php @@ -35,6 +35,7 @@ use Exception; use OCA\Social\Db\ServiceAccountsRequest; use OCA\Social\Exceptions\ActivityStreamsRequestException; use OCA\Social\Exceptions\InvalidAccessTokenException; +use OCA\Social\Model\ActivityPub\Core; use OCA\Social\Model\ServiceAccount; use OCA\Social\Traits\TOAuth2; @@ -57,8 +58,8 @@ class ActivityStreamsService { /** @var ConfigService */ private $configService; - /** @var CurlService */ - private $curlService; + /** @var ClientCurlService */ + private $clientCurlService; /** @var MiscService */ private $miscService; @@ -69,20 +70,32 @@ class ActivityStreamsService { * * @param ServiceAccountsRequest $serviceAccountsRequest * @param ConfigService $configService - * @param CurlService $curlService + * @param ClientCurlService $clientCurlService * @param MiscService $miscService */ public function __construct( ServiceAccountsRequest $serviceAccountsRequest, ConfigService $configService, - CurlService $curlService, MiscService $miscService + ClientCurlService $clientCurlService, MiscService $miscService ) { $this->serviceAccountsRequest = $serviceAccountsRequest; $this->configService = $configService; - $this->curlService = $curlService; + $this->clientCurlService = $clientCurlService; $this->miscService = $miscService; } + /** + * @param Core $core + */ + public function initCore(Core &$core) { + $core->setRoot('https://test.artificial-owl.com/apps/social'); + } + + + + + // TODO : clean below ! + /** * @param ServiceAccount $account * @@ -146,7 +159,7 @@ class ActivityStreamsService { */ private function request(ServiceAccount $account, Request $request) { try { - return $this->curlService->request($account, $request, true); + return $this->clientCurlService->request($account, $request, true); } catch (InvalidAccessTokenException $e) { // $this->oAuth2TokensRequest->resetToken($auth); throw new ActivityStreamsRequestException($e->getMessage()); diff --git a/lib/Service/ActorService.php b/lib/Service/ActorService.php new file mode 100644 index 00000000..3856eef7 --- /dev/null +++ b/lib/Service/ActorService.php @@ -0,0 +1,232 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Service; + + +use daita\Traits\TArrayTools; +use Exception; +use OC\User\NoUserException; +use OCA\Social\Db\ActorsRequest; +use OCA\Social\Db\CacheActorsRequest; +use OCA\Social\Exceptions\ActorAlreadyExistsException; +use OCA\Social\Exceptions\ActorDoesNotExistException; +use OCA\Social\Exceptions\APIRequestException; +use OCA\Social\Exceptions\CacheActorDoesNotExistException; +use OCA\Social\Exceptions\InvalidAccessTokenException; +use OCA\Social\Exceptions\MovedPermanentlyException; +use OCA\Social\Model\ActivityPub\Actor; + +class ActorService { + + + use TArrayTools; + + + /** @var UriIdService */ + private $uriIdService; + + /** @var ConfigService */ + private $configService; + + /** @var ActorsRequest */ + private $actorsRequest; + + /** @var CacheActorsRequest */ + private $cacheActorsRequest; + + /** @var MiscService */ + private $miscService; + + + /** + * ActivityStreamsService constructor. + * + * @param ActorsRequest $actorsRequest + * @param CacheActorsRequest $cacheActorsRequest + * @param UriIdService $uriIdService + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + ActorsRequest $actorsRequest, + CacheActorsRequest $cacheActorsRequest, UriIdService $uriIdService, + ConfigService $configService, MiscService $miscService + ) { + $this->configService = $configService; + $this->uriIdService = $uriIdService; + $this->actorsRequest = $actorsRequest; + $this->cacheActorsRequest = $cacheActorsRequest; + $this->miscService = $miscService; + } + + + /** + * @param string $username + * + * @return Actor + * @throws ActorDoesNotExistException + * @throws NoUserException + */ + public function getActor(string $username): Actor { + $actor = $this->actorsRequest->getFromUsername($username); + + return $actor; + } + + + /** + * @param string $userId + * + * @return Actor + * @throws ActorDoesNotExistException + * @throws NoUserException + */ + public function getActorFromUserId(string $userId): Actor { + $this->miscService->confirmUserId($userId); + $actor = $this->actorsRequest->getFromUserId($userId); + + return $actor; + } + + + /** + * @param string $uriId + * + * @return Actor + * @throws APIRequestException + * @throws InvalidAccessTokenException + * @throws MovedPermanentlyException + * @throws Exception + */ + public function getFromUri(string $uriId) { + + try { + $cache = $this->cacheActorsRequest->getFromUrl($uriId); + + return $this->generateActor($cache->getActor()); + } catch (CacheActorDoesNotExistException $e) { + $object = $this->uriIdService->retrieveObject($uriId); + $actor = $this->generateActor($object); + $this->cacheActorsRequest->create($actor, $object); + + return $actor; + } + } + + + /** + * @param array $object + * + * @return Actor + */ + public function generateActor(array $object) { + $actor = new Actor(); + + $actor->setId($this->get('id', $object)); + $actor->setFollowers($this->get('followers', $object)); + $actor->setFollowing($this->get('following', $object)); + $actor->setInbox($this->get('inbox', $object)); + $actor->setOutbox($this->get('outbox', $object)); + $actor->setPublicKey($object['publicKey']['publicKeyPem']); + $actor->setPreferredUsername($this->get('preferredUsername', $object)); + $actor->setAccount('@' . $actor->getPreferredUsername() . '@' . $object['_address']); + +// $actor->setSharedInbox($this->get('')) + + return $actor; + } + + + /** + * @param string $userId + * + * @throws ActorAlreadyExistsException + * @throws NoUserException + * @throws Exception + */ + public function createActor(string $userId, string $username) { + + $this->miscService->confirmUserId($userId); + $this->checkActorUsername($username); + $this->configService->setCoreValue('public_webfinger', 'social/lib/webfinger.php'); + + try { + $this->actorsRequest->getFromUsername($username); + throw new ActorAlreadyExistsException('actor with that name already exist'); + } catch (ActorDoesNotExistException $e) { + /* we do nohtin */ + } + + try { + $this->actorsRequest->getFromUserId($userId); + throw new ActorAlreadyExistsException('account for this user already exist'); + } catch (ActorDoesNotExistException $e) { + /* we do nohtin */ + } + + $actor = new Actor(); + $actor->setUserId($userId); + $actor->setPreferredUsername($username); + + $this->generateKeys($actor); + $this->actorsRequest->create($actor); + } + + + /** + * @param $username + * + * @return bool + */ + private function checkActorUsername($username) { + return; + } + + /** + * @param Actor $actor + */ + private function generateKeys(Actor &$actor) { + $res = openssl_pkey_new( + [ + "digest_alg" => "rsa", + "private_key_bits" => 2048, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + ] + ); + + openssl_pkey_export($res, $privateKey); + $publicKey = openssl_pkey_get_details($res)['key']; + + $actor->setPublicKey($publicKey); + $actor->setPrivateKey($privateKey); + } + + +} diff --git a/lib/Service/ClientCurlService.php b/lib/Service/ClientCurlService.php new file mode 100644 index 00000000..d5fcb72c --- /dev/null +++ b/lib/Service/ClientCurlService.php @@ -0,0 +1,214 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Service; + + +use daita\Model\Request; +use OCA\Social\Exceptions\APIRequestException; +use OCA\Social\Exceptions\InvalidAccessTokenException; +use OCA\Social\Exceptions\MovedPermanentlyException; +use OCA\Social\Model\Service; +use OCA\Social\Model\ServiceAccount; + +class ClientCurlService { + + + /** @var MiscService */ + private $miscService; + + + /** + * ClientCurlService constructor. + * + * @param MiscService $miscService + */ + public function __construct(MiscService $miscService) { + $this->miscService = $miscService; + } + + + /** + * @param ServiceAccount $account + * @param Request $request + * @param bool $authed + * + * @return array + * @throws InvalidAccessTokenException + * @throws MovedPermanentlyException + * @throws APIRequestException + */ + public function request(ServiceAccount $account, Request $request, bool $authed = true) { + + $curl = $this->initRequest($account, $request, $authed); + $this->initRequestPost($curl, $request); + $this->initRequestPut($curl, $request); + $this->initRequestDelete($curl, $request); + + $result = curl_exec($curl); + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + + echo 'code: ' . $code . "\n"; + echo 'result: ' . json_encode($result) . "\n"; + + $this->parseRequestResultCode301($code); + $this->parseRequestResultCode401($code); + $this->parseRequestResultCode404($code); +// $this->parseRequestResultCode503($code); +// $this->parseRequestResultCode500($code); +// $this->parseRequestResult($result); + + + return json_decode($result, true); + } + + + /** + * @param ServiceAccount $account + * @param Request $request + * @param bool $authed + * + * @return resource + */ + private function initRequest(ServiceAccount $account, Request $request, bool $authed) { + + $curl = $this->generateCurlRequest($account, $request); + $headers = $request->getHeaders(); + + if ($authed) { + $headers[] = 'Authorization: Bearer ' . $account->getAuth('token'); + } + + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curl, CURLOPT_TIMEOUT, 20); + + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + + return $curl; + } + + + /** + * @param ServiceAccount $account + * @param Request $request + * + * @return resource + */ + private function generateCurlRequest(ServiceAccount $account, Request $request) { + $service = $account->getService(); + $url = 'https://' . $service->getAddress() . $request->getParsedUrl(); + + if ($request->getType() !== Request::TYPE_GET) { + $curl = curl_init($url); + } else { + $curl = curl_init($url . '?' . $request->getDataBody()); + } + + return $curl; + } + + + /** + * @param resource $curl + * @param Request $request + */ + private function initRequestPost($curl, Request $request) { + if ($request->getType() !== Request::TYPE_POST) { + return; + } + + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getDataBody()); + } + + + /** + * @param resource $curl + * @param Request $request + */ + private function initRequestPut($curl, Request $request) { + if ($request->getType() !== Request::TYPE_PUT) { + return; + } + + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getDataBody()); + } + + + /** + * @param resource $curl + * @param Request $request + */ + private function initRequestDelete($curl, Request $request) { + if ($request->getType() !== Request::TYPE_DELETE) { + return; + } + + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getDataBody()); + } + + + /** + * @param int $code + * + * @throws MovedPermanentlyException + */ + private function parseRequestResultCode301($code) { + if ($code === 301) { + throw new MovedPermanentlyException('301 Moved Permanently'); + } + } + + /** + * @param int $code + * + * @throws InvalidAccessTokenException + */ + private function parseRequestResultCode401($code) { + if ($code === 401) { + throw new InvalidAccessTokenException('401 Access Token Invalid'); + } + } + + /** + * @param int $code + * + * @throws APIRequestException + */ + private function parseRequestResultCode404($code) { + if ($code === 404) { + throw new APIRequestException('404 Not Found'); + } + } + + +} diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index c165b76e..be155bee 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -70,7 +70,8 @@ class ConfigService { * @param MiscService $miscService */ public function __construct( - string $userId, IConfig $config, IRequest $request, MiscService $miscService + $userId, IConfig $config, IRequest $request, + MiscService $miscService ) { $this->userId = $userId; $this->config = $config; @@ -176,6 +177,15 @@ class ConfigService { } + /** + * @param string $key + * @param string $value + */ + public function setCoreValue(string $key, string $value) { + $this->config->setAppValue('core', $key, $value); + } + + /** * @param $key * @@ -189,6 +199,32 @@ class ConfigService { return $this->request->getServerHost(); } + + /** + * @return string + */ + public function getRoot(): string { + return 'https://test.artificial-owl.com/apps/social/'; + } + + + /** + * @param string $path + * @param bool $generateId + * + * @return string + */ + public function generateId(string $path = '', $generateId = true): string { + $this->miscService->formatPath($path); + + $id = $this->getRoot() . $path; + if ($generateId === true) { + $id .= time() . crc32(uniqid()); + } + + return $id; + } + // /** // * @return array // */ diff --git a/lib/Service/CurlService.php b/lib/Service/CurlService.php index 3560aa3a..15f57399 100644 --- a/lib/Service/CurlService.php +++ b/lib/Service/CurlService.php @@ -34,6 +34,7 @@ use daita\Model\Request; use OCA\Social\Exceptions\APIRequestException; use OCA\Social\Exceptions\InvalidAccessTokenException; use OCA\Social\Exceptions\MovedPermanentlyException; +use OCA\Social\Model\Service; use OCA\Social\Model\ServiceAccount; class CurlService { @@ -44,7 +45,7 @@ class CurlService { /** - * CurlService constructor. + * ClientCurlService constructor. * * @param MiscService $miscService */ @@ -54,51 +55,53 @@ class CurlService { /** - * @param ServiceAccount $account * @param Request $request - * @param bool $authed * * @return array + * @throws APIRequestException * @throws InvalidAccessTokenException * @throws MovedPermanentlyException - * @throws APIRequestException */ - public function request(ServiceAccount $account, Request $request, bool $authed = true) { + public function request(Request $request): array { + + $curl = $this->initRequest($request); - $curl = $this->initRequest($account, $request, $authed); $this->initRequestPost($curl, $request); $this->initRequestPut($curl, $request); $this->initRequestDelete($curl, $request); $result = curl_exec($curl); $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $this->parseRequestResultCode301($code); - $this->parseRequestResultCode401($code); +// $this->parseRequestResultCode401($code); $this->parseRequestResultCode404($code); // $this->parseRequestResultCode503($code); // $this->parseRequestResultCode500($code); // $this->parseRequestResult($result); + $ret = json_decode($result, true); + if (!is_array($ret)) { + $ret = ['_result' => $result]; + } - return json_decode($result, true); + $ret['_address'] = $request->getAddress(); + $ret['_code'] = $code; + + return $ret; } /** - * @param ServiceAccount $account * @param Request $request - * @param bool $authed * * @return resource */ - private function initRequest(ServiceAccount $account, Request $request, bool $authed) { + private function initRequest(Request $request) { - $curl = $this->generateCurlRequest($account, $request); - $headers = []; - - if ($authed) { - $headers[] = 'Authorization: Bearer ' . $account->getAuth('token'); - } + $curl = $this->generateCurlRequest($request); + $headers = $request->getHeaders(); + $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($curl, CURLOPT_TIMEOUT, 20); @@ -111,14 +114,12 @@ class CurlService { /** - * @param ServiceAccount $account * @param Request $request * * @return resource */ - private function generateCurlRequest(ServiceAccount $account, Request $request) { - $service = $account->getService(); - $url = 'https://' . $service->getAddress() . $request->getParsedUrl(); + private function generateCurlRequest(Request $request) { + $url = 'https://' . $request->getAddress() . $request->getParsedUrl(); if ($request->getType() !== Request::TYPE_GET) { $curl = curl_init($url); @@ -183,16 +184,6 @@ class CurlService { } } - /** - * @param int $code - * - * @throws InvalidAccessTokenException - */ - private function parseRequestResultCode401($code) { - if ($code === 401) { - throw new InvalidAccessTokenException('401 Access Token Invalid'); - } - } /** * @param int $code diff --git a/lib/Service/ICoreService.php b/lib/Service/ICoreService.php new file mode 100644 index 00000000..f0b94dcd --- /dev/null +++ b/lib/Service/ICoreService.php @@ -0,0 +1,39 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Service; + + +use OCA\Social\Model\ActivityPub\Core; + +interface ICoreService { + + public function save(Core $core); + +} diff --git a/lib/Service/MiscService.php b/lib/Service/MiscService.php index 01e8bbfe..19dd7477 100644 --- a/lib/Service/MiscService.php +++ b/lib/Service/MiscService.php @@ -29,8 +29,11 @@ declare(strict_types=1); namespace OCA\Social\Service; +use Exception; +use OC\User\NoUserException; use OCA\Social\AppInfo\Application; use OCP\ILogger; +use OCP\IUserManager; class MiscService { @@ -38,14 +41,19 @@ class MiscService { /** @var ILogger */ private $logger; + /** @var IUserManager */ + private $userManager; + /** * MiscService constructor. * * @param ILogger $logger + * @param IUserManager $userManager */ - public function __construct(ILogger $logger) { + public function __construct(ILogger $logger, IUserManager $userManager) { $this->logger = $logger; + $this->userManager = $userManager; } @@ -63,6 +71,20 @@ class MiscService { } + /** + * @param array $keys + * @param array $arr + * + * @throws Exception + */ + public function mustContains(array $keys, array $arr) { + foreach ($keys as $key) { + if (!array_key_exists($key, $arr)) { + throw new Exception('missing elements'); + } + } + } + public static function noEndSlash($path) { if (substr($path, -1) === '/') { $path = substr($path, 0, -1); @@ -72,5 +94,39 @@ class MiscService { } + /** + * @param string $path + */ + public function formatPath(string &$path) { + if ($path === '') { + return; + } + + if (substr($path, 0, 1) === '/') { + $path = substr($path, 1); + } + + if (substr($path, -1) !== '/') { + $path .= '/'; + } + } + + + /** + * @param string $userId + * + * @throws NoUserException + */ + public function confirmUserId(string &$userId) { + $user = $this->userManager->get($userId); + + return; + if ($user === null) { + throw new NoUserException('user does not exist'); + } + + $userId = $user->getUID(); + } + } diff --git a/lib/Service/ServicesService.php b/lib/Service/ServicesService.php index ba23d3ef..0938e8b4 100644 --- a/lib/Service/ServicesService.php +++ b/lib/Service/ServicesService.php @@ -53,8 +53,8 @@ class ServicesService { /** @var ConfigService */ private $configService; - /** @var CurlService */ - private $curlService; + /** @var ClientCurlService */ + private $clientCurlService; /** @var MiscService */ private $miscService; @@ -65,16 +65,16 @@ class ServicesService { * * @param ServicesRequest $servicesRequest * @param ConfigService $configService - * @param CurlService $curlService + * @param ClientCurlService $clientCurlService * @param MiscService $miscService */ public function __construct( - ServicesRequest $servicesRequest, ConfigService $configService, CurlService $curlService, + ServicesRequest $servicesRequest, ConfigService $configService, ClientCurlService $clientCurlService, MiscService $miscService ) { $this->servicesRequest = $servicesRequest; $this->configService = $configService; - $this->curlService = $curlService; + $this->clientCurlService = $clientCurlService; $this->miscService = $miscService; } @@ -151,7 +151,7 @@ class ServicesService { $request = new Request(ActivityStreamsService::URL_CREATE_APP, Request::TYPE_POST); $request->setData($data); - return $this->curlService->request($account, $request, false); + return $this->clientCurlService->request($account, $request, false); } diff --git a/lib/Service/UriIdService.php b/lib/Service/UriIdService.php new file mode 100644 index 00000000..1e3ed300 --- /dev/null +++ b/lib/Service/UriIdService.php @@ -0,0 +1,86 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Service; + + +use daita\Model\Request; +use OCA\Social\Exceptions\APIRequestException; +use OCA\Social\Exceptions\InvalidAccessTokenException; +use OCA\Social\Exceptions\MovedPermanentlyException; + +class UriIdService { + + + /** @var ConfigService */ + private $configService; + + + private $curlService; + + /** @var MiscService */ + private $miscService; + + + /** + * UriIdService constructor. + * + * @param ConfigService $configService + * @param CurlService $curlService + * @param MiscService $miscService + */ + public function __construct( + ConfigService $configService, CurlService $curlService, MiscService $miscService + ) { + $this->configService = $configService; + $this->curlService = $curlService; + $this->miscService = $miscService; + } + + + /** + * @param $id + * + * @return mixed + * @throws MovedPermanentlyException + * @throws APIRequestException + * @throws InvalidAccessTokenException + */ + public function retrieveObject($id) { + $url = parse_url($id); + + $request = new Request($url['path'], Request::TYPE_GET); + $request->setAddress($url['host']); + +// $key = $url['fragment']; + + return $this->curlService->request($request); + } + +} diff --git a/lib/Tools/Model/Request.php b/lib/Tools/Model/Request.php index b19e3e98..463ef067 100644 --- a/lib/Tools/Model/Request.php +++ b/lib/Tools/Model/Request.php @@ -47,6 +47,9 @@ class Request implements \JsonSerializable { /** @var int */ private $type; + /** @var array */ + private $headers = []; + /** @var array */ private $data = []; @@ -88,6 +91,10 @@ class Request implements \JsonSerializable { $url = $this->getUrl(); $ak = array_keys($this->getData()); foreach ($ak as $k) { + if (!is_string($this->data[$k])) { + continue; + } + $url = str_replace(':' . $k, $this->data[$k], $url); } @@ -111,6 +118,32 @@ class Request implements \JsonSerializable { } + public function addHeader($header): Request { + $this->headers[] = $header; + + return $this; + } + + + /** + * @return array + */ + public function getHeaders(): array { + return $this->headers; + } + + /** + * @param array $headers + * + * @return Request + */ + public function setHeaders(array $headers): Request { + $this->headers = $headers; + + return $this; + } + + /** * @return array */ @@ -185,14 +218,14 @@ class Request implements \JsonSerializable { * @return string */ public function getDataBody() { - - if ($this->getData() === []) { - return ''; - } - - return preg_replace( - '/([(%5B)]{1})[0-9]+([(%5D)]{1})/', '$1$2', http_build_query($this->getData()) - ); + return json_encode($this->getData()); +// if ($this->getData() === []) { +// return ''; +// } +// +// return preg_replace( +// '/([(%5B)]{1})[0-9]+([(%5D)]{1})/', '$1$2', http_build_query($this->getData()) +// ); } diff --git a/lib/Tools/Traits/TNCDataResponse.php b/lib/Tools/Traits/TNCDataResponse.php index 13d9e278..12ef036d 100644 --- a/lib/Tools/Traits/TNCDataResponse.php +++ b/lib/Tools/Traits/TNCDataResponse.php @@ -30,6 +30,7 @@ declare(strict_types=1); namespace daita\Traits; +use JsonSerializable; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -60,7 +61,17 @@ trait TNCDataResponse { 'status' => 1 ]; - return new DataResponse($data, Http::STATUS_CREATED); + return new DataResponse($data, Http::STATUS_OK); + } + + + /** + * @param JsonSerializable $result + * + * @return DataResponse + */ + private function directSuccess(JsonSerializable $result): DataResponse { + return new DataResponse($result, Http::STATUS_OK); } } diff --git a/lib/webfinger.php b/lib/webfinger.php new file mode 100644 index 00000000..8973ee19 --- /dev/null +++ b/lib/webfinger.php @@ -0,0 +1,66 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social; + +require_once __DIR__ . '/../appinfo/autoload.php'; +require_once(__DIR__ . '/../lib/autoload.php'); + +if (!array_key_exists('resource', $_GET)) { + echo 'missing resource'; + exit(); +} + +$subject = $_GET['resource']; + +$urlGenerator = \OC::$server->getURLGenerator(); + +list($type, $account) = explode(':', $subject, 2); +if ($type !== 'acct') { + echo 'no acct'; + exit(); +} + + +$username = substr($account, 0, strrpos($account, '@')); +$finger = [ + 'subject' => $subject, + 'links' => [ + [ + 'rel' => 'self', + 'type' => 'application/activity+json', + // 'href' => 'https://test.artificial-owl.com/apps/social/@' . $username + 'href' => $urlGenerator->linkToRouteAbsolute( + 'social.ActivityPub.aliasactor', ['username' => $username] + ) + ] + ] +]; + +header('Content-type: application/json'); +echo json_encode($finger); diff --git a/templates/actor.php b/templates/actor.php new file mode 100644 index 00000000..cab3b5fc --- /dev/null +++ b/templates/actor.php @@ -0,0 +1 @@ +ACTOR ! diff --git a/templates/followers.php b/templates/followers.php new file mode 100644 index 00000000..82235440 --- /dev/null +++ b/templates/followers.php @@ -0,0 +1 @@ +FOLLOWERS !!!1 diff --git a/templates/following.php b/templates/following.php new file mode 100644 index 00000000..c040e4d9 --- /dev/null +++ b/templates/following.php @@ -0,0 +1 @@ +FOLLOWING