* @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\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse; use OC\User\NoUserException; use OCA\Social\AppInfo\Application; use OCA\Social\Exceptions\AccountAlreadyExistsException; use OCA\Social\Exceptions\ActorDoesNotExistException; use OCA\Social\Exceptions\ClientAppDoesNotExistException; use OCA\Social\Exceptions\ClientAuthDoesNotExistException; use OCA\Social\Exceptions\ClientException; use OCA\Social\Exceptions\ItemAlreadyExistsException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Exceptions\UrlCloudException; use OCA\Social\Model\Client\ClientApp; use OCA\Social\Model\Client\ClientAuth; use OCA\Social\Model\Client\ClientToken; use OCA\Social\Service\AccountService; use OCA\Social\Service\CacheActorService; use OCA\Social\Service\ClientService; use OCA\Social\Service\ConfigService; 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\IUserSession; class OAuthController extends Controller { use TNCDataResponse; /** @var IUserSession */ private $userSession; /** @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 AccountService $accountService * @param CacheActorService $cacheActorService * @param ClientService $clientService * @param ConfigService $configService * @param MiscService $miscService */ public function __construct( IRequest $request, IUserSession $userSession, AccountService $accountService, CacheActorService $cacheActorService, ClientService $clientService, ConfigService $configService, MiscService $miscService ) { parent::__construct(Application::APP_NAME, $request); $this->userSession = $userSession; $this->accountService = $accountService; $this->cacheActorService = $cacheActorService; $this->clientService = $clientService; $this->configService = $configService; $this->miscService = $miscService; $body = file_get_contents('php://input'); $this->miscService->log('[ClientService] input: ' . $body, 1); } /** * @NoCSRFRequired * @PublicPage * * @param string $website * @param string $redirect_uris * @param string $scopes * @param string $client_name * * @return Response * @throws ClientException */ public function apps( string $client_name, string $redirect_uris, string $website = '', string $scopes = 'read' ): Response { // TODO: manage array from request if (!is_array($redirect_uris)) { $redirect_uris = [$redirect_uris]; } $clientApp = new ClientApp(); $clientApp->setWebsite($website); $clientApp->setRedirectUris($redirect_uris); $clientApp->setScopesFromString($scopes); $clientApp->setName($client_name); $this->clientService->createClient($clientApp); return new DataResponse($clientApp, Http::STATUS_OK); } /** * @NoCSRFRequired * @NoAdminRequired * * @param string $client_id * @param string $redirect_uri * @param string $response_type * @param string $scope * * @return DataResponse * @throws AccountAlreadyExistsException * @throws ActorDoesNotExistException * @throws ClientAppDoesNotExistException * @throws ClientException * @throws ItemAlreadyExistsException * @throws NoUserException * @throws SocialAppConfigException * @throws UrlCloudException */ public function authorize( string $client_id, string $redirect_uri, string $response_type, string $scope = 'read' ): DataResponse { $user = $this->userSession->getUser(); $account = $this->accountService->getActorFromUserId($user->getUID()); if ($response_type !== 'code') { return new DataResponse(['error' => 'invalid_type'], Http::STATUS_BAD_REQUEST); } $clientApp = $this->clientService->getClientByClientId($client_id); $this->clientService->confirmData( $clientApp, [ 'scopes' => $scope, ] ); $clientAuth = new ClientAuth(); $clientAuth->setRedirectUri($redirect_uri); $clientAuth->setScopes(explode(' ', $scope)); $clientAuth->setAccount($account->getPreferredUsername()); $clientAuth->setUserId($user->getUID()); $this->clientService->authClient($clientApp, $clientAuth); $code = $clientAuth->getCode(); if ($redirect_uri !== 'urn:ietf:wg:oauth:2.0:oob') { header('Location: ' . $redirect_uri . '?code=' . $code); exit(); } // TODO : finalize result if no redirect_url return new DataResponse( [ // 'access_token' => '', // "token_type" => "Bearer", // "scope" => "read write follow push", // "created_at" => 1573979017 ], Http::STATUS_OK ); } /** * @NoCSRFRequired * @NoAdminRequired * @PublicPage * * @param string $client_id * @param string $client_secret * @param string $redirect_uri * @param string $grant_type * @param string $scope * @param string $code * * @return DataResponse * @throws ClientAppDoesNotExistException * @throws ClientException * @throws ClientAuthDoesNotExistException */ public function token( string $client_id, string $client_secret, string $redirect_uri, string $grant_type, string $scope = 'read', string $code = '' ) { $clientApp = $this->clientService->getClientByClientId($client_id); $this->clientService->confirmData( $clientApp, [ 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'scopes' => $scope ] ); $clientToken = new ClientToken(); $clientToken->setScopes(explode(' ', $scope)); if ($grant_type === 'authorization_code') { if ($code === '') { return new DataResponse(['error' => 'missing code'], Http::STATUS_BAD_REQUEST); } $clientAuth = $this->clientService->getAuthByCode($code); $this->clientService->generateToken($clientApp, $clientAuth, $clientToken); } else if ($grant_type === 'client_credentials') { // TODO: manage client_credentials } else { return new DataResponse(['error' => 'invalid value for grant_type'], Http::STATUS_BAD_REQUEST); } if ($clientToken->getToken() === '') { return new DataResponse(['error' => 'issue generating access_token'], Http::STATUS_BAD_REQUEST); } return new DataResponse( [ "access_token" => $clientToken->getToken(), "token_type" => 'Bearer', "scope" => $scope, "created_at" => $clientToken->getCreation() ], 200 ); } /** * @NoCSRFRequired * @NoAdminRequired * @PublicPage * * @return DataResponse */ public function appsCredentials() { return new DataResponse( [ 'name' => 'Twidere for Android', 'website' => 'https://github.com/TwidereProject/' ], 200 ); } /** * @NoCSRFRequired * @NoAdminRequired * @PublicPage * * @return DataResponse */ public function accountsCredentials() { return new DataResponse( [ "id" => "137148", "username" => "cult", "acct" => "cult", "display_name" => "Maxence Lange", "locked" => false, "bot" => false, "discoverable" => null, "group" => false, "created_at" => "2017-05-11T09=>20=>28.055Z", "note" => "\u003cp\u003e\u003c/p\u003e", "url" => "https://test.artificial-owl.com/index.php/apps/social/@cult", "avatar" => "https://mastodon.social/avatars/original/missing.png", "avatar_static" => "https://mastodon.social/avatars/original/missing.png", "header" => "https://mastodon.social/headers/original/missing.png", "header_static" => "https://mastodon.social/headers/original/missing.png", "followers_count" => 9, "following_count" => 5, "statuses_count" => 13, "last_status_at" => "2019-09-15", "source" => [ "privacy" => "public", "sensitive" => false, "language" => null, "note" => "", "fields" => [], "follow_requests_count" => 0 ], "emojis" => [], "fields" => [] ], 200 ); } }