diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2020-09-21 14:47:31 -0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-21 14:47:31 -0100 |
commit | 88cd3e9259172f5e1beeace4c79677330457b117 (patch) | |
tree | 6bc61c276e8dc5be7079395e7bdad0da72eef9d3 | |
parent | f433e88e533bceb3e053eb3c2b8d9cf19bf9c99e (diff) | |
parent | e57fcd2a36c43d3c3ff6905d09d1c23da5a6d231 (diff) |
Merge pull request #985 from nextcloud/feature/noid/compat-with-clients
[wip] Oauth for 3rdparty clients
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') + ] + ]; < |