summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaxence Lange <maxence@artificial-owl.com>2020-09-21 14:47:31 -0100
committerGitHub <noreply@github.com>2020-09-21 14:47:31 -0100
commit88cd3e9259172f5e1beeace4c79677330457b117 (patch)
tree6bc61c276e8dc5be7079395e7bdad0da72eef9d3
parentf433e88e533bceb3e053eb3c2b8d9cf19bf9c99e (diff)
parente57fcd2a36c43d3c3ff6905d09d1c23da5a6d231 (diff)
Merge pull request #985 from nextcloud/feature/noid/compat-with-clients
[wip] Oauth for 3rdparty clients
-rw-r--r--appinfo/routes.php20
-rw-r--r--lib/Command/Timeline.php2
-rw-r--r--lib/Controller/ActivityPubController.php26
-rw-r--r--lib/Controller/ApiController.php350
-rw-r--r--lib/Controller/NavigationController.php2
-rw-r--r--lib/Controller/OAuthController.php342
-rw-r--r--lib/Db/ActionsRequestBuilder.php5
-rw-r--r--lib/Db/CacheActorsRequest.php11
-rw-r--r--lib/Db/CacheActorsRequestBuilder.php17
-rw-r--r--lib/Db/ClientRequest.php171
-rw-r--r--lib/Db/ClientRequestBuilder.php159
-rw-r--r--lib/Db/CoreRequestBuilder.php114
-rw-r--r--lib/Db/FollowsRequest.php61
-rw-r--r--lib/Db/FollowsRequestBuilder.php41
-rw-r--r--lib/Db/InstancesRequest.php82
-rw-r--r--lib/Db/InstancesRequestBuilder.php171
-rw-r--r--lib/Db/SocialCoreQueryBuilder.php16
-rw-r--r--lib/Db/SocialCrossQueryBuilder.php113
-rw-r--r--lib/Db/SocialLimitsQueryBuilder.php44
-rw-r--r--lib/Db/SocialQueryBuilder.php19
-rw-r--r--lib/Db/StreamDestRequest.php6
-rw-r--r--lib/Db/StreamRequest.php99
-rw-r--r--lib/Db/StreamRequestBuilder.php15
-rw-r--r--lib/Exceptions/ClientException.php (renamed from lib/Exceptions/FollowDoesNotExistException.php)2
-rw-r--r--lib/Exceptions/ClientNotFoundException.php39
-rw-r--r--lib/Exceptions/FollowNotFoundException.php39
-rw-r--r--lib/Exceptions/InstanceDoesNotExistException.php39
-rw-r--r--lib/Interfaces/Object/DocumentInterface.php7
-rw-r--r--lib/Interfaces/Object/FollowInterface.php4
-rw-r--r--lib/Migration/Version0003Date20200611000001.php267
-rw-r--r--lib/Migration/Version0003Date20200823023911.php454
-rw-r--r--lib/Migration/Version0003Date20200921103342.php (renamed from lib/Migration/Version0003Date20200730213528.php)51
-rw-r--r--lib/Model/ActivityPub/ACore.php54
-rw-r--r--lib/Model/ActivityPub/Actor/Person.php313
-rw-r--r--lib/Model/ActivityPub/Item.php22
-rw-r--r--lib/Model/ActivityPub/Stream.php85
-rw-r--r--lib/Model/Client/Options/CoreOptions.php69
-rw-r--r--lib/Model/Client/Options/TimelineOptions.php296
-rw-r--r--lib/Model/Client/SocialClient.php469
-rw-r--r--lib/Model/Instance.php441
-rw-r--r--lib/Service/AccountService.php9
-rw-r--r--lib/Service/ClientService.php207
-rw-r--r--lib/Service/FollowService.php10
-rw-r--r--lib/Service/InstanceService.php90
-rw-r--r--lib/Service/PostService.php2
-rw-r--r--lib/Service/StreamService.php27
46 files changed, 4573 insertions, 309 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 93ea98bf..377ff9aa 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -52,6 +52,7 @@ return [
['name' => 'ActivityPub#actor', 'url' => '/users/{username}', 'verb' => 'GET'],
['name' => 'ActivityPub#actorAlias', 'url' => '/@{username}/', 'verb' => 'GET'],
['name' => 'ActivityPub#inbox', 'url' => '/@{username}/inbox', 'verb' => 'POST'],
+ ['name' => 'ActivityPub#getInbox', 'url' => '/@{username}/inbox', 'verb' => 'GET'],
['name' => 'ActivityPub#sharedInbox', 'url' => '/inbox', 'verb' => 'POST'],
['name' => 'ActivityPub#outbox', 'url' => '/@{username}/outbox', 'verb' => 'GET'],
@@ -61,10 +62,29 @@ return [
['name' => 'ActivityPub#displayPost', 'url' => '/@{username}/{token}', 'verb' => 'GET'],
+ // OStatus
['name' => 'OStatus#subscribe', 'url' => '/ostatus/follow/', 'verb' => 'GET'],
['name' => 'OStatus#followRemote', 'url' => '/api/v1/ostatus/followRemote/{local}', 'verb' => 'GET'],
['name' => 'OStatus#getLink', 'url' => '/api/v1/ostatus/link/{local}/{account}', 'verb' => 'GET'],
+ // OAuth
+ ['name' => 'OAuth#nodeinfo', 'url' => '/.well-known/nodeinfo', 'verb' => 'GET'],
+ ['name' => 'OAuth#nodeinfo2', 'url' => '/nodeinfo/2.0', 'verb' => 'GET'],
+ ['name' => 'OAuth#apps', 'url' => '/api/v1/apps', 'verb' => 'POST'],
+ ['name' => 'OAuth#authorize', 'url' => '/oauth/authorize', 'verb' => 'GET'],
+ ['name' => 'OAuth#token', 'url' => '/oauth/token', 'verb' => 'POST'],
+
+ // Api for 3rd party
+ ['name' => 'Api#appsCredentials', 'url' => '/api/v1/apps/verify_credentials', 'verb' => 'GET'],
+ ['name' => 'Api#verifyCredentials', 'url' => '/api/v1/accounts/verify_credentials', 'verb' => 'GET'],
+ ['name' => 'Api#instance', 'url' => '/api/v1/instance/', 'verb' => 'GET'],
+ ['name' => 'Api#customEmojis', 'url' => '/api/v1/custom_emojis', 'verb' => 'GET'],
+ ['name' => 'Api#savedSearches', 'url' => '/api/saved_searches/list.json', 'verb' => 'GET'],
+ ['name' => 'Api#timelines', 'url' => '/api/v1/timelines/{timeline}/', 'verb' => 'GET'],
+ ['name' => 'Api#notifications', 'url' => '/api/v1/notifications', 'verb' => 'GET'],
+
+ // Api for local front-end
+ // TODO: front-end should be using the new ApiController
['name' => 'Local#streamHome', 'url' => '/api/v1/stream/home', 'verb' => 'GET'],
['name' => 'Local#streamNotifications', 'url' => '/api/v1/stream/notifications', 'verb' => 'GET'],
['name' => 'Local#streamTimeline', 'url' => '/api/v1/stream/timeline', 'verb' => 'GET'],
diff --git a/lib/Command/Timeline.php b/lib/Command/Timeline.php
index e7062ba1..d8334b34 100644
--- a/lib/Command/Timeline.php
+++ b/lib/Command/Timeline.php
@@ -149,7 +149,7 @@ class Timeline extends ExtendedBase {
$this->streamRequest->setViewer($actor);
switch ($timeline) {
case 'home':
- $stream = $this->streamRequest->getTimelineHome(0, $this->count);
+ $stream = $this->streamRequest->getTimelineHome_dep(0, $this->count);
$this->outputStreams($stream);
break;
diff --git a/lib/Controller/ActivityPubController.php b/lib/Controller/ActivityPubController.php
index df6215aa..1138bc64 100644
--- a/lib/Controller/ActivityPubController.php
+++ b/lib/Controller/ActivityPubController.php
@@ -34,7 +34,7 @@ use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse;
use daita\MySmallPhpTools\Traits\TAsync;
use daita\MySmallPhpTools\Traits\TStringTools;
use Exception;
-use OC\AppFramework\Http;
+use OCP\AppFramework\Http;
use OCA\Social\AppInfo\Application;
use OCA\Social\Exceptions\AccountDoesNotExistException;
use OCA\Social\Exceptions\ItemUnknownException;
@@ -203,7 +203,6 @@ class ActivityPubController extends Controller {
* @return Response
*/
public function sharedInbox(): Response {
-
try {
$body = file_get_contents('php://input');
$this->miscService->log('[<<] sharedInbox: ' . $body, 1);
@@ -282,6 +281,29 @@ class ActivityPubController extends Controller {
/**
+ * Method is called when a remote ActivityPub server wants to GET in the INBOX of a USER
+ * Checking that the user exists, and that the header is properly signed.
+ *
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @param string $username
+ *
+ * @return Response
+ */
+ public function getInbox(string $username): Response {
+ try {
+ $body = file_get_contents('php://input');
+ $actor = $this->cacheActorService->getFromLocalAccount($username);
+
+ return $this->success();
+ } catch (Exception $e) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ }
+ }
+
+
+ /**
* Outbox. does nothing.
*
* @NoCSRFRequired
diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php
new file mode 100644
index 00000000..1b4be8a7
--- /dev/null
+++ b/lib/Controller/ApiController.php
@@ -0,0 +1,350 @@
+<?php
+declare(strict_types=1);
+
+
+/**
+ * Nextcloud - Social Support
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ * @copyright 2018, Maxence Lange <maxence@artificial-owl.com>
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Social\Controller;
+
+
+use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse;
+use Exception;
+use OCA\Social\AppInfo\Application;
+use OCA\Social\Exceptions\AccountDoesNotExistException;
+use OCA\Social\Exceptions\ClientNotFoundException;
+use OCA\Social\Exceptions\InstanceDoesNotExistException;
+use OCA\Social\Model\ActivityPub\ACore;
+use OCA\Social\Model\ActivityPub\Actor\Person;
+use OCA\Social\Model\ActivityPub\Stream;
+use OCA\Social\Model\Client\Options\TimelineOptions;
+use OCA\Social\Model\Client\SocialClient;
+use OCA\Social\Service\AccountService;
+use OCA\Social\Service\CacheActorService;
+use OCA\Social\Service\ClientService;
+use OCA\Social\Service\ConfigService;
+use OCA\Social\Service\FollowService;
+use OCA\Social\Service\InstanceService;
+use OCA\Social\Service\MiscService;
+use OCA\Social\Service\StreamService;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\IRequest;
+use OCP\IUserSession;
+
+
+/**
+ * Class ApiController
+ *
+ * @package OCA\Social\Controller
+ */
+class ApiController extends Controller {
+
+
+ use TNCDataResponse;
+
+
+ /** @var IUserSession */
+ private $userSession;
+
+ /** @var InstanceService */
+ private $instanceService;
+
+ /** @var ClientService */
+ private $clientService;
+
+ /** @var AccountService */
+ private $accountService;
+
+ /** @var CacheActorService */
+ private $cacheActorService;
+
+ /** @var FollowService */
+ private $followService;
+
+ /** @var StreamService */
+ private $streamService;
+
+ /** @var ConfigService */
+ private $configService;
+
+ /** @var MiscService */
+ private $miscService;
+
+
+ /** @var string */
+ private $bearer = '';
+
+ /** @var SocialClient */
+ private $client;
+
+ /** @var Person */
+ private $viewer;
+
+
+ /**
+ * ActivityStreamController constructor.
+ *
+ * @param IRequest $request
+ * @param IUserSession $userSession
+ * @param InstanceService $instanceService
+ * @param ClientService $clientService
+ * @param AccountService $accountService
+ * @param CacheActorService $cacheActorService
+ * @param FollowService $followService
+ * @param StreamService $streamService
+ * @param ConfigService $configService
+ * @param MiscService $miscService
+ */
+ public function __construct(
+ IRequest $request, IUserSession $userSession, InstanceService $instanceService,
+ ClientService $clientService, AccountService $accountService, CacheActorService $cacheActorService,
+ FollowService $followService, StreamService $streamService, ConfigService $configService,
+ MiscService $miscService
+ ) {
+ parent::__construct(Application::APP_NAME, $request);
+
+ $this->userSession = $userSession;
+ $this->instanceService = $instanceService;
+ $this->clientService = $clientService;
+ $this->accountService = $accountService;
+ $this->cacheActorService = $cacheActorService;
+ $this->followService = $followService;
+ $this->streamService = $streamService;
+ $this->configService = $configService;
+ $this->miscService = $miscService;
+
+ $authHeader = trim($this->request->getHeader('Authorization'));
+ if (strpos($authHeader, ' ')) {
+ list($authType, $authToken) = explode(' ', $authHeader);
+ if (strtolower($authType) === 'bearer') {
+ $this->bearer = $authToken;
+ }
+ }
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @return DataResponse
+ */
+ public function appsCredentials() {
+ try {
+ $this->initViewer(true);
+
+ if ($this->client === null) {
+ return new DataResponse(
+ [
+ 'name' => 'Nextcloud Social',
+ 'website' => 'https://github.com/nextcloud/social/'
+ ], Http::STATUS_OK
+ );
+ } else {
+ return new DataResponse(
+ [
+ 'name' => $this->client->getAppName(),
+ 'website' => $this->client->getAppWebsite()
+ ], Http::STATUS_OK
+ );
+ }
+ } catch (Exception $e) {
+ return $this->error($e->getMessage());
+ }
+
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @return DataResponse
+ */
+ public function verifyCredentials() {
+ try {
+ $this->initViewer(true);
+
+ return new DataResponse($this->viewer, Http::STATUS_OK);
+ } catch (Exception $e) {
+ return $this->error($e->getMessage());
+ }
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @return DataResponse
+ */
+ public function customEmojis(): DataResponse {
+ return new DataResponse([], Http::STATUS_OK);
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @return DataResponse
+ */
+ public function savedSearches(): DataResponse {
+ try {
+ $this->initViewer(true);
+
+ return new DataResponse([], Http::STATUS_OK);
+ } catch (Exception $e) {
+ return $this->error($e->getMessage());
+ }
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @return DataResponse
+ */
+ public function notifications(): DataResponse {
+ try {
+ $this->initViewer(true);
+
+ return new DataResponse([], Http::STATUS_OK);
+ } catch (Exception $e) {
+ return $this->error($e->getMessage());
+ }
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @return DataResponse
+ * @throws InstanceDoesNotExistException
+ */
+ public function instance(): DataResponse {
+ $local = $this->instanceService->getLocal(Stream::FORMAT_LOCAL);
+
+ return new DataResponse($local, Http::STATUS_OK);
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @param string $timeline
+ * @param int $limit
+ *
+ * @return DataResponse
+ */
+ public function timelines(string $timeline, int $limit = 20): DataResponse {
+ $options = new TimelineOptions($this->request);
+ $options->setFormat(Stream::FORMAT_LOCAL);
+ $options->setTimeline($timeline);
+ $options->setLimit($limit);
+
+ try {
+ $this->initViewer(true);
+ $posts = $this->streamService->getTimeline($options);
+
+ return new DataResponse($posts, Http::STATUS_OK);
+ } catch (Exception $e) {
+ return $this->error($e->getMessage());
+ }
+ }
+
+
+ /**
+ *
+ * @param bool $exception
+ *
+ * @return bool
+ * @throws ClientNotFoundException
+ */
+ private function initViewer(bool $exception = false): bool {
+ try {
+ $userId = $this->currentSession();
+
+ $this->miscService->log(
+ '[ApiController] initViewer: ' . $userId . ' (bearer=' . $this->bearer . ')', 0
+ );
+
+ $account = $this->accountService->getActorFromUserId($userId);
+ $this->viewer = $this->cacheActorService->getFromLocalAccount($account->getPreferredUsername());
+ $this->viewer->setExportFormat(ACore::FORMAT_LOCAL);
+
+ $this->streamService->setViewer($this->viewer);
+ $this->followService->setViewer($this->viewer);
+ $this->cacheActorService->setViewer($this->viewer);
+
+ return true;
+ } catch (Exception $e) {
+ if ($exception) {
+ throw new ClientNotFoundException('the access_token was revoked');
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * @return string
+ * @throws AccountDoesNotExistException
+ * @throws ClientNotFoundException
+ */
+ private function currentSession(): string {
+ $user = $this->userSession->getUser();
+ if ($user !== null) {
+ return $user->getUID();
+ }
+
+ if ($this->bearer !== '') {
+ $this->client = $this->clientService->getFromToken($this->bearer);
+
+ return $this->client->getAuthUserId();
+ }
+
+ throw new AccountDoesNotExistException('userId not defined');
+ }
+
+
+ /**
+ * @param string $error
+ *
+ * @return DataResponse
+ */
+ private function error(string $error): DataResponse {
+ return new DataResponse(['error' => $error], Http::STATUS_UNAUTHORIZED);
+ }
+
+}
+
+
diff --git a/lib/Controller/NavigationController.php b/lib/Controller/NavigationController.php
index b1b0dcd3..8111804f 100644
--- a/lib/Controller/NavigationController.php
+++ b/lib/Controller/NavigationController.php
@@ -35,7 +35,7 @@ use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse;
use daita\MySmallPhpTools\Traits\TArrayTools;
use Exception;
use OC;
-use OC\AppFramework\Http;
+use OCP\AppFramework\Http;
use OC\User\NoUserException;
use OCA\Social\AppInfo\Application;
use OCA\Social\Exceptions\AccountAlreadyExistsException;
diff --git a/lib/Controller/OAuthController.php b/lib/Controller/OAuthController.php
new file mode 100644
index 00000000..3f46cdc9
--- /dev/null
+++ b/lib/Controller/OAuthController.php
@@ -0,0 +1,342 @@
+<?php
+declare(strict_types=1);
+
+
+/**
+ * Nextcloud - Social Support
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ * @copyright 2018, Maxence Lange <maxence@artificial-owl.com>
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Social\Controller;
+
+
+use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse;
+use Exception;
+use OCA\Social\AppInfo\Application;
+use OCA\Social\Exceptions\ClientException;
+use OCA\Social\Exceptions\ClientNotFoundException;
+use OCA\Social\Exceptions\InstanceDoesNotExistException;
+use OCA\Social\Model\Client\SocialClient;
+use OCA\Social\Service\AccountService;
+use OCA\Social\Service\CacheActorService;
+use OCA\Social\Service\ClientService;
+use OCA\Social\Service\ConfigService;
+use OCA\Social\Service\InstanceService;
+use OCA\Social\Service\MiscService;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IUserSession;
+
+
+class OAuthController extends Controller {
+
+
+ use TNCDataResponse;
+
+
+ /** @var IUserSession */
+ private $userSession;
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var InstanceService */
+ private $instanceService;
+
+ /** @var AccountService */
+ private $accountService;
+
+ /** @var CacheActorService */
+ private $cacheActorService;
+
+ /** @var ClientService */
+ private $clientService;
+
+ /** @var ConfigService */
+ private $configService;
+
+ /** @var MiscService */
+ private $miscService;
+
+
+ /**
+ * ActivityStreamController constructor.
+ *
+ * @param IRequest $request
+ * @param IUserSession $userSession
+ * @param IURLGenerator $urlGenerator
+ * @param InstanceService $instanceService
+ * @param AccountService $accountService
+ * @param CacheActorService $cacheActorService
+ * @param ClientService $clientService
+ * @param ConfigService $configService
+ * @param MiscService $miscService
+ */
+ public function __construct(
+ IRequest $request, IUserSession $userSession, IURLGenerator $urlGenerator,
+ InstanceService $instanceService, AccountService $accountService,
+ CacheActorService $cacheActorService, ClientService $clientService, ConfigService $configService,
+ MiscService $miscService
+ ) {
+ parent::__construct(Application::APP_NAME, $request);
+
+ $this->userSession = $userSession;
+ $this->urlGenerator = $urlGenerator;
+ $this->instanceService = $instanceService;
+ $this->accountService = $accountService;
+ $this->cacheActorService = $cacheActorService;
+ $this->clientService = $clientService;
+ $this->configService = $configService;
+ $this->miscService = $miscService;
+
+ $body = file_get_contents('php://input');
+ $this->miscService->log('[OAuthController] input: ' . $body, 0);
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @return Response
+ */
+ public function nodeinfo(): Response {
+ $nodeInfo = [
+ 'links' => [
+ 'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0',
+ 'href' => $this->urlGenerator->linkToRouteAbsolute('social.OAuth.nodeinfo2')
+ ]
+ ];
<