From aee2ea2fda29f5f639a63add9f6b4b94b73ea992 Mon Sep 17 00:00:00 2001 From: Edward Ly Date: Thu, 4 Jul 2024 08:38:05 -0700 Subject: [PATCH] Replace Slack OAuth config with Zulip config Signed-off-by: Edward Ly --- appinfo/routes.php | 4 - lib/AppInfo/Application.php | 2 - lib/Controller/ConfigController.php | 236 +--------------------------- lib/Service/ZulipAPIService.php | 124 --------------- lib/Settings/Admin.php | 58 ------- lib/Settings/AdminSection.php | 51 ------ lib/Settings/Personal.php | 22 +-- src/adminSettings.js | 21 --- src/components/AdminSettings.vue | 170 -------------------- src/components/PersonalSettings.vue | 163 ++++++++----------- src/filesplugin.js | 94 +---------- src/utils.js | 87 ---------- webpack.js | 1 - 13 files changed, 79 insertions(+), 954 deletions(-) delete mode 100644 lib/Settings/Admin.php delete mode 100644 lib/Settings/AdminSection.php delete mode 100644 src/adminSettings.js delete mode 100644 src/components/AdminSettings.vue diff --git a/appinfo/routes.php b/appinfo/routes.php index d09c686..0a74ea4 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -14,11 +14,7 @@ return [ 'routes' => [ ['name' => 'config#isUserConnected', 'url' => '/is-connected', 'verb' => 'GET'], - ['name' => 'config#getFilesToSend', 'url' => '/files-to-send', 'verb' => 'GET'], - ['name' => 'config#oauthRedirect', 'url' => '/oauth-redirect', 'verb' => 'GET'], ['name' => 'config#setConfig', 'url' => '/config', 'verb' => 'PUT'], - ['name' => 'config#setAdminConfig', 'url' => '/admin-config', 'verb' => 'PUT'], - ['name' => 'config#popupSuccessPage', 'url' => '/popup-success', 'verb' => 'GET'], ['name' => 'zulipAPI#sendMessage', 'url' => '/sendMessage', 'verb' => 'POST'], ['name' => 'zulipAPI#sendPublicLinks', 'url' => '/sendPublicLinks', 'verb' => 'POST'], diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 96916fd..d3b814e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -21,8 +21,6 @@ class Application extends App implements IBootstrap { public const APP_ID = 'integration_zulip'; public const INTEGRATION_USER_AGENT = 'Nextcloud Zulip Integration'; - public const ZULIP_API_URL = 'https://zulip.com/api/'; - public const ZULIP_OAUTH_ACCESS_URL = 'https://zulip.com/api/oauth.v2.access'; public function __construct(array $urlParams = []) { parent::__construct(self::APP_ID, $urlParams); diff --git a/lib/Controller/ConfigController.php b/lib/Controller/ConfigController.php index 5fd4b4a..d55283b 100644 --- a/lib/Controller/ConfigController.php +++ b/lib/Controller/ConfigController.php @@ -13,22 +13,13 @@ namespace OCA\Zulip\Controller; -use DateTime; use Exception; use OCA\Zulip\AppInfo\Application; -use OCA\Zulip\Service\ZulipAPIService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; -use OCP\AppFramework\Http\RedirectResponse; -use OCP\AppFramework\Http\TemplateResponse; -use OCP\AppFramework\Services\IInitialState; use OCP\IConfig; -use OCP\IL10N; use OCP\IRequest; -use OCP\IURLGenerator; use OCP\PreConditionNotMetException; -use OCP\Security\ICrypto; -use Psr\Log\LoggerInterface; class ConfigController extends Controller { @@ -36,12 +27,6 @@ public function __construct( string $appName, IRequest $request, private IConfig $config, - private IURLGenerator $urlGenerator, - private IL10N $l, - private IInitialState $initialStateService, - private ZulipAPIService $zulipAPIService, - private ICrypto $crypto, - private LoggerInterface $logger, private ?string $userId ) { parent::__construct($appName, $request); @@ -53,40 +38,12 @@ public function __construct( * @return DataResponse */ public function isUserConnected(): DataResponse { - $token = $this->config->getUserValue($this->userId, Application::APP_ID, 'token'); - $clientID = $this->config->getAppValue(Application::APP_ID, 'client_id'); - $clientSecret = $this->config->getAppValue(Application::APP_ID, 'client_secret'); - $oauthPossible = $clientID !== '' && $clientSecret !== ''; - $usePopup = $this->config->getAppValue(Application::APP_ID, 'use_popup', '0'); + $url = $this->config->getUserValue($this->userId, Application::APP_ID, 'url'); + $email = $this->config->getUserValue($this->userId, Application::APP_ID, 'email'); + $apiKey = $this->config->getUserValue($this->userId, Application::APP_ID, 'api_key'); return new DataResponse([ - 'connected' => ($token !== ''), - 'oauth_possible' => $oauthPossible, - 'use_popup' => ($usePopup === '1'), - 'client_id' => $clientID, - ]); - } - - /** - * @NoAdminRequired - * - * @return DataResponse - */ - public function getFilesToSend(): DataResponse { - $token = $this->config->getUserValue($this->userId, Application::APP_ID, 'token'); - - if ($token === '') { - return new DataResponse(['message' => 'Not connected']); - } - - $fileIdsToSendAfterOAuth = $this->config->getUserValue($this->userId, Application::APP_ID, 'file_ids_to_send_after_oauth'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'file_ids_to_send_after_oauth'); - $currentDirAfterOAuth = $this->config->getUserValue($this->userId, Application::APP_ID, 'current_dir_after_oauth'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'current_dir_after_oauth'); - - return new DataResponse([ - 'file_ids_to_send_after_oauth' => $fileIdsToSendAfterOAuth, - 'current_dir_after_oauth' => $currentDirAfterOAuth, + 'connected' => ($url !== '' && $email !== '' && $apiKey !== ''), ]); } @@ -103,189 +60,6 @@ public function setConfig(array $values): DataResponse { $this->config->setUserValue($this->userId, Application::APP_ID, $key, $value); } - $result = []; - - if (isset($values['token'])) { - if ($values['token'] === '') { - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'user_id'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'user_displayname'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'token'); - $result['user_id'] = ''; - $result['user_displayname'] = ''; - } - - // if the token is set, cleanup refresh token and expiration date - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'refresh_token'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'token_expires_at'); - } - return new DataResponse($result); - } - - /** - * set admin config values - * - * @param array $values - * @return DataResponse - */ - public function setAdminConfig(array $values): DataResponse { - foreach ($values as $key => $value) { - try { - if ($key === 'client_secret' && $value !== '') { - $value = $this->crypto->encrypt($value); - } - } catch (Exception $e) { - $this->config->setAppValue(Application::APP_ID, 'client_secret', ''); - // logger takes care not to leak the secret - $this->logger->error('Could not encrypt client secret', ['exception' => $e]); - return new DataResponse(['message' => $this->l->t('Could not encrypt client secret')]); - } - - $this->config->setAppValue(Application::APP_ID, $key, $value); - } - return new DataResponse(1); - } - - /** - * @NoAdminRequired - * @NoCSRFRequired - * - * @param string $user_id - * @param string $user_displayname - * @return TemplateResponse - */ - public function popupSuccessPage(string $user_id, string $user_displayname): TemplateResponse { - $this->initialStateService->provideInitialState('popup-data', [ - 'user_id' => $user_id, - 'user_displayname' => $user_displayname, - ]); - return new TemplateResponse(Application::APP_ID, 'popupSuccess', [], TemplateResponse::RENDER_AS_GUEST); - } - - /** - * receive oauth code and get oauth access token - * @NoAdminRequired - * @NoCSRFRequired - * - * @param string $code - * @param string $state - * @return RedirectResponse - * @throws PreConditionNotMetException - */ - public function oauthRedirect(string $code = '', string $state = ''): RedirectResponse { - $configState = $this->config->getUserValue($this->userId, Application::APP_ID, 'oauth_state'); - $clientID = $this->config->getAppValue(Application::APP_ID, 'client_id'); - $clientSecret = $this->config->getAppValue(Application::APP_ID, 'client_secret'); - - // decrypt client secret - try { - $clientSecret = $this->crypto->decrypt($clientSecret); - } catch (Exception $e) { - $this->logger->error('Could not decrypt client secret', ['exception' => $e]); - return new RedirectResponse( - $this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'connected-accounts']) . - '?result=error&message=' . $this->l->t('Invalid client secret') - ); - } - - // anyway, reset state - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'oauth_state'); - - if ($clientID && $clientSecret && $configState !== '' && $configState === $state) { - $redirect_uri = $this->config->getUserValue($this->userId, Application::APP_ID, 'redirect_uri', ''); - - $result = $this->zulipAPIService->requestOAuthAccessToken(Application::ZULIP_OAUTH_ACCESS_URL, [ - 'client_id' => $clientID, - 'client_secret' => $clientSecret, - 'code' => $code, - 'redirect_uri' => $redirect_uri, - 'grant_type' => 'authorization_code' - ], 'POST'); - - if (isset($result['authed_user'], $result['authed_user']['access_token'], $result['authed_user']['id'])) { - $accessToken = $result['authed_user']['access_token']; - $refreshToken = $result['authed_user']['refresh_token'] ?? ''; - - if (isset($result['authed_user']['expires_in'])) { - $nowTs = (new Datetime())->getTimestamp(); - $expiresAt = $nowTs + (int) $result['authed_user']['expires_in']; - $this->config->setUserValue($this->userId, Application::APP_ID, 'token_expires_at', strval($expiresAt)); - } - - $this->config->setUserValue($this->userId, Application::APP_ID, 'token', $accessToken); - $this->config->setUserValue($this->userId, Application::APP_ID, 'refresh_token', $refreshToken); - - $userInfo = $this->storeUserInfo($result['authed_user']['id']); - $usePopup = $this->config->getAppValue(Application::APP_ID, 'use_popup', '0') === '1'; - - if ($usePopup) { - return new RedirectResponse( - $this->urlGenerator->linkToRoute('integration_zulip.config.popupSuccessPage', [ - 'user_id' => $userInfo['user_id'] ?? '', - 'user_displayname' => $userInfo['user_displayname'] ?? '', - ]) - ); - } else { - $oauthOrigin = $this->config->getUserValue($this->userId, Application::APP_ID, 'oauth_origin'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'oauth_origin'); - - if ($oauthOrigin === 'settings') { - return new RedirectResponse( - $this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'connected-accounts']) . - '?result=success' - ); - } elseif (preg_match('/^files--.*/', $oauthOrigin)) { - $parts = explode('--', $oauthOrigin); - if (count($parts) > 1) { - // $path = preg_replace('/^files--/', '', $oauthOrigin); - $path = $parts[1]; - if (count($parts) > 2) { - $this->config->setUserValue($this->userId, Application::APP_ID, 'file_ids_to_send_after_oauth', $parts[2]); - $this->config->setUserValue($this->userId, Application::APP_ID, 'current_dir_after_oauth', $path); - } - return new RedirectResponse( - $this->urlGenerator->linkToRoute('files.view.index', ['dir' => $path]) - ); - } - } - } - } - - $result = $this->l->t('Error getting OAuth access token. ' . ($result['error'] ?? '')); - } else { - $result = $this->l->t('Error during OAuth exchanges'); - } - return new RedirectResponse( - $this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'connected-accounts']) . - '?result=error&message=' . urlencode($result) - ); - } - - /** - * @return array{user_id: string, user_displayname: string} - * @throws PreConditionNotMetException - */ - private function storeUserInfo(string $zulipUserId = ''): array { - $info = $this->zulipAPIService->request($this->userId, 'users.info', [ - 'user' => $zulipUserId, - ]); - - if (isset($info['user'], $info['user']['id'], $info['user']['real_name']) - ) { - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_id', $info['user']['id']); - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_displayname', $info['user']['real_name']); - - return [ - 'user_id' => $info['user']['id'], - 'user_displayname' => $info['user']['real_name'], - ]; - } else { - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_id', ''); - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_displayname', ''); - - return [ - 'user_id' => '', - 'user_displayname' => '', - ]; - } + return new DataResponse([]); } } diff --git a/lib/Service/ZulipAPIService.php b/lib/Service/ZulipAPIService.php index 11aeda8..0147537 100644 --- a/lib/Service/ZulipAPIService.php +++ b/lib/Service/ZulipAPIService.php @@ -347,130 +347,6 @@ public function sendFile(string $userId, int $fileId, string $channelId, string */ public function request(string $userId, string $endPoint, array $params = [], string $method = 'GET', bool $jsonResponse = true, bool $zulipApiRequest = true) { - $this->checkTokenExpiration($userId); return $this->networkService->request($userId, $endPoint, $params, $method, $jsonResponse, $zulipApiRequest); } - - /** - * @param string $userId - * @return void - * @throws \OCP\PreConditionNotMetException - */ - private function checkTokenExpiration(string $userId): void { - $refreshToken = $this->config->getUserValue($userId, Application::APP_ID, 'refresh_token'); - $expireAt = $this->config->getUserValue($userId, Application::APP_ID, 'token_expires_at'); - if ($refreshToken !== '' && $expireAt !== '') { - $nowTs = (new DateTime())->getTimestamp(); - $expireAt = (int) $expireAt; - // if token expires in less than a minute or is already expired - if ($nowTs > $expireAt - 60) { - $this->refreshToken($userId); - } - } - } - - /** - * @param string $userId - * @return bool - * @throws \OCP\PreConditionNotMetException - */ - private function refreshToken(string $userId): bool { - $clientID = $this->config->getAppValue(Application::APP_ID, 'client_id'); - $clientSecret = $this->config->getAppValue(Application::APP_ID, 'client_secret'); - $refreshToken = $this->config->getUserValue($userId, Application::APP_ID, 'refresh_token'); - - if (!$refreshToken) { - $this->logger->error('No Zulip refresh token found', ['app' => Application::APP_ID]); - return false; - } - - try { - $clientSecret = $this->crypto->decrypt($clientSecret); - } catch (Exception $e) { - $this->logger->error('Unable to decrypt Zulip secrets', ['app' => Application::APP_ID]); - return false; - } - - $result = $this->requestOAuthAccessToken(Application::ZULIP_OAUTH_ACCESS_URL, [ - 'client_id' => $clientID, - 'client_secret' => $clientSecret, - 'grant_type' => 'refresh_token', - 'refresh_token' => $refreshToken, - ], 'POST'); - - if (isset($result['access_token'])) { - $this->logger->info('Zulip access token successfully refreshed', ['app' => Application::APP_ID]); - - $accessToken = $result['access_token']; - $refreshToken = $result['refresh_token']; - $this->config->setUserValue($userId, Application::APP_ID, 'token', $accessToken); - $this->config->setUserValue($userId, Application::APP_ID, 'refresh_token', $refreshToken); - - if (isset($result['expires_in'])) { - $nowTs = (new DateTime())->getTimestamp(); - $expiresAt = $nowTs + (int) $result['expires_in']; - $this->config->setUserValue($userId, Application::APP_ID, 'token_expires_at', strval($expiresAt)); - } - - return true; - } else { - // impossible to refresh the token - $this->logger->error( - 'Token is not valid anymore. Impossible to refresh it: ' - . ($result['error'] ?? '') . ' ' - . ($result['error_description'] ?? '[no error description]'), - ['app' => Application::APP_ID] - ); - - return false; - } - } - - /** - * @param string $url - * @param array $params - * @param string $method - * @return array - */ - public function requestOAuthAccessToken(string $url, array $params = [], string $method = 'GET'): array { - try { - $options = [ - 'headers' => [ - 'User-Agent' => Application::INTEGRATION_USER_AGENT, - ] - ]; - - if (count($params) > 0) { - if ($method === 'GET') { - $paramsContent = http_build_query($params); - $url .= '?' . $paramsContent; - } else { - $options['body'] = $params; - } - } - - if ($method === 'GET') { - $response = $this->client->get($url, $options); - } elseif ($method === 'POST') { - $response = $this->client->post($url, $options); - } elseif ($method === 'PUT') { - $response = $this->client->put($url, $options); - } elseif ($method === 'DELETE') { - $response = $this->client->delete($url, $options); - } else { - return ['error' => $this->l10n->t('Bad HTTP method')]; - } - $body = $response->getBody(); - $respCode = $response->getStatusCode(); - - if ($respCode >= 400) { - return ['error' => $this->l10n->t('OAuth access token refused')]; - } else { - return json_decode($body, true); - } - } catch (Exception $e) { - $this->logger->warning('Zulip OAuth error : '.$e->getMessage(), ['app' => Application::APP_ID]); - return ['error' => $e->getMessage()]; - } - } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php deleted file mode 100644 index bef8d22..0000000 --- a/lib/Settings/Admin.php +++ /dev/null @@ -1,58 +0,0 @@ -config->getAppValue(Application::APP_ID, 'client_id'); - $clientSecret = $this->config->getAppValue(Application::APP_ID, 'client_secret'); - $usePopup = $this->config->getAppValue(Application::APP_ID, 'use_popup', '0'); - - try { - if ($clientSecret !== '') { - $clientSecret = $this->crypto->decrypt($clientSecret); - } - } catch (Exception $e) { - // logger takes care not to leak the secret - $this->logger->error('Failed to decrypt client secret', ['exception' => $e]); - $clientSecret = ''; - } - - $adminConfig = [ - 'client_id' => $clientID, - 'client_secret' => $clientSecret, - 'use_popup' => ($usePopup === '1'), - ]; - $this->initialStateService->provideInitialState('admin-config', $adminConfig); - return new TemplateResponse(Application::APP_ID, 'adminSettings'); - } - - public function getSection(): string { - return 'connected-accounts'; - } - - public function getPriority(): int { - return 10; - } -} diff --git a/lib/Settings/AdminSection.php b/lib/Settings/AdminSection.php deleted file mode 100644 index 7ea537b..0000000 --- a/lib/Settings/AdminSection.php +++ /dev/null @@ -1,51 +0,0 @@ -l->t('Connected accounts'); - } - - /** - * @return int whether the form should be rather on the top or bottom of - * the settings navigation. The sections are arranged in ascending order of - * the priority values. It is required to return a value between 0 and 99. - */ - public function getPriority(): int { - return 80; - } - - /** - * @return string The relative path to a an icon describing the section - */ - public function getIcon(): string { - return $this->urlGenerator->imagePath('core', 'categories/integration.svg'); - } -} diff --git a/lib/Settings/Personal.php b/lib/Settings/Personal.php index 3392ceb..0e288c4 100644 --- a/lib/Settings/Personal.php +++ b/lib/Settings/Personal.php @@ -21,25 +21,15 @@ public function __construct( * @return TemplateResponse */ public function getForm(): TemplateResponse { - $token = $this->config->getUserValue($this->userId, Application::APP_ID, 'token'); - $userId = $this->config->getUserValue($this->userId, Application::APP_ID, 'user_id'); - $userDisplayName = $this->config->getUserValue($this->userId, Application::APP_ID, 'user_displayname'); + $url = $this->config->getUserValue($this->userId, Application::APP_ID, 'url'); + $email = $this->config->getUserValue($this->userId, Application::APP_ID, 'email'); + $apiKey = $this->config->getUserValue($this->userId, Application::APP_ID, 'api_key'); $fileActionEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'file_action_enabled', '1') === '1'; - // for OAuth - $clientID = $this->config->getAppValue(Application::APP_ID, 'client_id'); - // don't need to decrypt it, just need to know if it's set - $clientSecret = $this->config->getAppValue(Application::APP_ID, 'client_secret') !== '' - ? 'dummyClientSecret' : ''; - $usePopup = $this->config->getAppValue(Application::APP_ID, 'use_popup', '0'); - $userConfig = [ - 'token' => $token ? 'dummyTokenContent' : '', - 'client_id' => $clientID, - 'client_secret' => $clientSecret, - 'use_popup' => ($usePopup === '1'), - 'user_id' => $userId, - 'user_displayname' => $userDisplayName, + 'url' => $url, + 'email' => $email, + 'api_key' => $apiKey, 'file_action_enabled' => $fileActionEnabled, ]; $this->initialStateService->provideInitialState('user-config', $userConfig); diff --git a/src/adminSettings.js b/src/adminSettings.js deleted file mode 100644 index ae11d1d..0000000 --- a/src/adminSettings.js +++ /dev/null @@ -1,21 +0,0 @@ -/* jshint esversion: 6 */ - -/** - * Nextcloud - Zulip - * - * - * This file is licensed under the Affero General Public License version 3 or - * later. See the COPYING file. - * - * @author Julien Veyssier - * @author Anupam Kumar - * @copyright Julien Veyssier 2022 - * @copyright Anupam Kumar 2023 - */ - -import Vue from 'vue' -import './bootstrap.js' -import AdminSettings from './components/AdminSettings.vue' - -const VueAdminSettings = Vue.extend(AdminSettings) -new VueAdminSettings().$mount('#zulip_prefs') diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue deleted file mode 100644 index 6f13733..0000000 --- a/src/components/AdminSettings.vue +++ /dev/null @@ -1,170 +0,0 @@ - - - - - diff --git a/src/components/PersonalSettings.vue b/src/components/PersonalSettings.vue index 29391d1..f1469ef 100644 --- a/src/components/PersonalSettings.vue +++ b/src/components/PersonalSettings.vue @@ -4,35 +4,47 @@ {{ t('integration_zulip', 'Zulip integration') }} -

- {{ t('integration_zulip', 'The admin must fill in client ID and client secret for you to continue from here') }} -

-
- - - {{ t('integration_zulip', 'Connect to Zulip') }} - -
- -

@@ -46,31 +58,31 @@ @@ -197,10 +162,14 @@ export default { } .line { - width: 450px; - display: flex; - align-items: center; - justify-content: space-between; + > label { + width: 250px; + display: flex; + align-items: center; + } + > input { + width: 350px; + } } } diff --git a/src/filesplugin.js b/src/filesplugin.js index 5238092..516940c 100644 --- a/src/filesplugin.js +++ b/src/filesplugin.js @@ -14,10 +14,9 @@ import moment from '@nextcloud/moment' import { generateUrl } from '@nextcloud/router' import { showSuccess, showError } from '@nextcloud/dialogs' import { translate as t, translatePlural as n } from '@nextcloud/l10n' -import { oauthConnect, oauthConnectConfirmDialog, gotoSettingsConfirmDialog, SEND_TYPE } from './utils.js' +import { gotoSettingsConfirmDialog, SEND_TYPE } from './utils.js' import { - registerFileAction, Permission, FileAction, - davGetClient, davGetDefaultPropfind, davResultToNode, davRootPath, FileType, + registerFileAction, Permission, FileAction, FileType, } from '@nextcloud/files' import { subscribe } from '@nextcloud/event-bus' import ZulipIcon from '../img/app-dark.svg' @@ -93,92 +92,11 @@ function sendSelectedNodes(nodes) { }) if (OCA.Zulip.zulipConnected) { openChannelSelector(formattedNodes) - } else if (OCA.Zulip.oauthPossible) { - connectToZulip(formattedNodes) } else { gotoSettingsConfirmDialog() } } -function checkIfFilesToSend() { - const urlCheckConnection = generateUrl('/apps/integration_zulip/files-to-send') - axios.get(urlCheckConnection) - .then((response) => { - const fileIdsStr = response?.data?.file_ids_to_send_after_oauth - const currentDir = response?.data?.current_dir_after_oauth - if (fileIdsStr && currentDir) { - sendFileIdsAfterOAuth(fileIdsStr, currentDir) - } else { - if (DEBUG) console.debug('[Zulip] nothing to send') - } - }) - .catch((error) => { - console.error(error) - }) -} - -/** - * In case we successfully connected with oauth and got redirected back to files - * actually go on with the files that were previously selected - * - * @param {string} fileIdsStr list of files to send - * @param {string} currentDir path to the current dir - */ -async function sendFileIdsAfterOAuth(fileIdsStr, currentDir) { - if (DEBUG) console.debug('[Zulip] in sendFileIdsAfterOAuth, fileIdsStr, currentDir', fileIdsStr, currentDir) - // this is only true after an OAuth connection initated from a file action - if (fileIdsStr) { - // get files info - const client = davGetClient() - const results = await client.getDirectoryContents(`${davRootPath}${currentDir}`, { - details: true, - // Query all required properties for a Node - data: davGetDefaultPropfind(), - }) - const nodes = results.data.map((r) => davResultToNode(r)) - - const fileIds = fileIdsStr.split(',') - const files = fileIds.map((fid) => { - const f = nodes.find((n) => n.fileid === parseInt(fid)) - if (f) { - return { - id: f.fileid, - name: f.basename, - type: f.type, - size: f.size, - } - } - return null - }).filter((e) => e !== null) - if (DEBUG) console.debug('[Zulip] in sendFileIdsAfterOAuth, after changeDirectory, files:', files) - if (files.length) { - if (DEBUG) console.debug('[Zulip] in sendFileIdsAfterOAuth, after changeDirectory, call openChannelSelector') - openChannelSelector(files) - } - } -} - -function connectToZulip(selectedFiles = []) { - oauthConnectConfirmDialog(OCA.Zulip.clientId).then((result) => { - if (result) { - if (OCA.Zulip.usePopup) { - oauthConnect(OCA.Zulip.clientId, null, true) - .then(() => { - OCA.Zulip.zulipConnected = true - openChannelSelector(selectedFiles) - }) - } else { - const selectedFilesIds = selectedFiles.map(f => f.id) - const currentDirectory = OCA.Zulip.currentFileList?.folder?.attributes?.filename - oauthConnect( - OCA.Zulip.clientId, - 'files--' + currentDirectory + '--' + selectedFilesIds.join(','), - ) - } - } - }) -} - async function sendPublicLinks(channelId, channelName, comment, permission, expirationDate, password) { const req = { fileIds: OCA.Zulip.filesToSend.map((f) => f.id), @@ -344,15 +262,7 @@ function errorCallback(error) { // get Zulip state axios.get(IS_CONNECTED_URL).then((response) => { OCA.Zulip.zulipConnected = response.data.connected - OCA.Zulip.oauthPossible = response.data.oauth_possible - OCA.Zulip.usePopup = response.data.use_popup - OCA.Zulip.clientId = response.data.client_id if (DEBUG) console.debug('[Zulip] OCA.Zulip', OCA.Zulip) }).catch((error) => { console.error(error) }) - -document.addEventListener('DOMContentLoaded', () => { - if (DEBUG) console.debug('[Zulip] before checkIfFilesToSend') - checkIfFilesToSend() -}) diff --git a/src/utils.js b/src/utils.js index cbef260..4955a23 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,12 +1,8 @@ import { generateUrl } from '@nextcloud/router' -import axios from '@nextcloud/axios' -import { showError } from '@nextcloud/dialogs' import FileIcon from 'vue-material-design-icons/File.vue' import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue' import LinkVariantIcon from 'vue-material-design-icons/LinkVariant.vue' -const ZULIP_OAUTH_URL = 'https://zulip.com/oauth/v2/authorize' - let mytimer = 0 export function delay(callback, ms) { return function() { @@ -19,89 +15,6 @@ export function delay(callback, ms) { } } -export function oauthConnect(clientId, oauthOrigin, usePopup = false) { - const oauthState = Math.random().toString(36).substring(3) - const redirectUri - = window.location.protocol + '//' + window.location.host - + generateUrl('/apps/integration_zulip/oauth-redirect') - const userScopes = 'channels:read,groups:read,im:read,mpim:read,users:read,chat:write,files:write' - - const requestUrl = ZULIP_OAUTH_URL - + '?client_id=' + encodeURIComponent(clientId) - + '&state=' + encodeURIComponent(oauthState) - + '&redirect_uri=' + encodeURIComponent(redirectUri) - + '&user_scope=' + encodeURIComponent(userScopes) - - const req = { - values: { - oauth_state: oauthState, - oauth_origin: usePopup ? undefined : oauthOrigin, - redirect_uri: redirectUri, - }, - } - - const url = generateUrl('/apps/integration_zulip/config') - - return new Promise((resolve) => { - axios.put(url, req).then(() => { - if (usePopup) { - const ssoWindow = window.open( - requestUrl, - t('integration_zulip', 'Sign in with Zulip'), - 'toolbar=no, menubar=no, width=600, height=700') - if (!ssoWindow) { - showError(t('integration_zulip', 'Failed to open Zulip OAuth popup window, please allow popups')) - return - } - ssoWindow.focus() - - window.addEventListener('message', (event) => { - console.debug('Child window message received', event) - resolve(event.data) - }) - } else { - window.location.href = requestUrl - } - }).catch((error) => { - showError( - t('integration_zulip', 'Failed to save Zulip OAuth state') - + ': ' + (error.response?.request?.responseText ?? ''), - ) - console.error(error) - }) - }) -} - -export function oauthConnectConfirmDialog() { - return new Promise((resolve) => { - const settingsLink = generateUrl('/settings/user/connected-accounts') - const linkText = t('integration_zulip', 'Connected accounts') - const settingsHtmlLink = `${linkText}` - OC.dialogs.message( - t('integration_zulip', 'You need to connect before using the Zulip integration.') - + '

' - + t('integration_zulip', - 'You can set Zulip API keys in the {settingsHtmlLink} section of your personal settings.', - { settingsHtmlLink }, - null, - { escape: false }), - t('integration_zulip', 'Connect to Zulip'), - 'none', - { - type: OC.dialogs.YES_NO_BUTTONS, - confirm: t('integration_zulip', 'Connect'), - confirmClasses: 'success', - cancel: t('integration_zulip', 'Cancel'), - }, - (result) => { - resolve(result) - }, - true, - true, - ) - }) -} - export function gotoSettingsConfirmDialog() { const settingsLink = generateUrl('/settings/user/connected-accounts') OC.dialogs.message( diff --git a/webpack.js b/webpack.js index 543476b..53b1130 100644 --- a/webpack.js +++ b/webpack.js @@ -15,7 +15,6 @@ webpackConfig.stats = { const appId = 'integration_zulip' webpackConfig.entry = { personalSettings: { import: path.join(__dirname, 'src', 'personalSettings.js'), filename: appId + '-personalSettings.js' }, - adminSettings: { import: path.join(__dirname, 'src', 'adminSettings.js'), filename: appId + '-adminSettings.js' }, filesplugin: { import: path.join(__dirname, 'src', 'filesplugin.js'), filename: appId + '-filesplugin.js' }, popupSuccess: { import: path.join(__dirname, 'src', 'popupSuccess.js'), filename: appId + '-popupSuccess.js' }, }