From 3a80055b459b9ca1bbdf000d4d322877d4ed3a40 Mon Sep 17 00:00:00 2001 From: Marcel Kloubert Date: Sun, 4 Jun 2017 14:36:50 +0200 Subject: [PATCH] password prompt for DropBox --- CHANGELOG.md | 2 +- package.json | 5 + src/contracts.ts | 10 ++ src/i18.ts | 1 + src/lang/de.ts | 1 + src/lang/en.ts | 1 + src/plugins/dropbox.ts | 378 +++++++++++++++++++++++------------------ 7 files changed, 230 insertions(+), 168 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b81d655..36b7e42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 9.16.0 (June 4th, 2017; password prompts) -* password box is shown now if no password is defined in [API](https://github.com/mkloubert/vs-deploy/wiki/target_api), [FTP](https://github.com/mkloubert/vs-deploy/wiki/target_ftp) and [mail](https://github.com/mkloubert/vs-deploy/wiki/target_mail) targets +* password box is shown now if no password is defined in [API](https://github.com/mkloubert/vs-deploy/wiki/target_api), [DropBox](https://github.com/mkloubert/vs-deploy/wiki/target_dropbox), [FTP](https://github.com/mkloubert/vs-deploy/wiki/target_ftp) and [mail](https://github.com/mkloubert/vs-deploy/wiki/target_mail) targets ## 9.15.1 (June 4th, 2017; bugfixes and password prompt for SFTP) diff --git a/package.json b/package.json index a49797d..90882bc 100644 --- a/package.json +++ b/package.json @@ -6862,6 +6862,11 @@ "description": "The algorithm for the password to use.", "default": "aes-256-ctr" }, + "promptForToken": { + "type": "boolean", + "description": "Prompt for a access token if not defined.", + "default": true + }, "description": { "type": "string", "description": "A description for the target." diff --git a/src/contracts.ts b/src/contracts.ts index fc0297c..979caa3 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -111,6 +111,16 @@ export const EVENT_DEPLOYONSAVE_ENABLE = 'deploy.deployOnSave.enable'; */ export const EVENT_DEPLOYONSAVE_TOGGLE = 'deploy.deployOnSave.toggle'; +/** + * An object that can handle passwords. + */ +export interface AccessTokenObject { + /** + * Prompt for a access token if not defined. + */ + promptForToken?: boolean; +} + /** * A deploy operation for compiling files that is invoked AFTER * ALL files have been deployed. diff --git a/src/i18.ts b/src/i18.ts index 0290a02..4e4237c 100644 --- a/src/i18.ts +++ b/src/i18.ts @@ -305,6 +305,7 @@ export interface Translation { }, }, prompts?: { + inputAccessToken?: string; inputPassword?: string; }, pull?: { diff --git a/src/lang/de.ts b/src/lang/de.ts index de2e759..34b289c 100644 --- a/src/lang/de.ts +++ b/src/lang/de.ts @@ -296,6 +296,7 @@ export const translation: Translation = { }, }, prompts: { + inputAccessToken: 'Geben Sie das Zugriffs-Token an...', inputPassword: 'Geben Sie das Passwort an...', }, pull: { diff --git a/src/lang/en.ts b/src/lang/en.ts index 56dc9d7..bb4b16e 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -298,6 +298,7 @@ export const translation: Translation = { }, }, prompts: { + inputAccessToken: 'Input the access token...', inputPassword: 'Input the password...', }, pull: { diff --git a/src/plugins/dropbox.ts b/src/plugins/dropbox.ts index 952dde9..f1a90e1 100644 --- a/src/plugins/dropbox.ts +++ b/src/plugins/dropbox.ts @@ -32,11 +32,12 @@ import * as HTTPs from 'https'; import * as i18 from '../i18'; import * as Moment from 'moment'; import * as Path from 'path'; +import * as vscode from 'vscode'; const PATH_SEP = '/'; -interface DeployTargetDropbox extends deploy_contracts.TransformableDeployTarget, deploy_contracts.PasswordObject { +interface DeployTargetDropbox extends deploy_contracts.TransformableDeployTarget, deploy_contracts.AccessTokenObject { dir?: string; empty?: boolean; password?: string; @@ -108,10 +109,13 @@ class DropboxPlugin extends deploy_objects.DeployPluginWithContextBase> { + opts: deploy_contracts.DeployFileOptions, + direction: deploy_contracts.DeployDirection): Promise> { let me = this; let dir = getDirFromTarget(target); + target = deploy_helpers.cloneObject(target); + let empty = deploy_helpers.toBooleanSafe(target.empty); return new Promise>((resolve, reject) => { @@ -125,8 +129,6 @@ class DropboxPlugin extends deploy_objects.DeployPluginWithContextBase { + let doEmpty = false; + switch (direction) { + case deploy_contracts.DeployDirection.Deploy: + doEmpty = empty; + break; + } - let headersToSubmit = { - 'Authorization': `Bearer ${ctx.token}`, - 'Content-Type': 'application/json', - }; + if (doEmpty) { + let targetDirectory = toDropboxPath(dir); + + let headersToSubmit = { + 'Authorization': `Bearer ${ctx.token}`, + 'Content-Type': 'application/json', + }; + + let cursor: string; + let deleteItem: (item: string) => Promise; + let deleteNextFiles: () => void; + let hasMoreItemsToDelete = true; + + // delete an item + deleteItem = (item: string): Promise => { + return new Promise((resolve, reject) => { + let deletionCompleted = (err?: any) => { + if (err) { + reject(err); + } + else { + resolve(err); + } + }; + + try { + let req = HTTPs.request({ + headers: headersToSubmit, + host: 'api.dropboxapi.com', + method: 'POST', + path: '/2/files/delete', + port: 443, + protocol: 'https:', + }, (resp) => { + let err: Error; + + switch (resp.statusCode) { + case 200: + case 409: // not found + // OK + break; + + default: + err = new Error(i18.t('plugins.dropbox.unknownResponse', + resp.statusCode, 2, resp.statusMessage)); + break; + } + + deletionCompleted(err); + }); + + req.once('error', (err) => { + if (err) { + deletionCompleted(err); + } + }); - let cursor: string; - let deleteItem: (item: string) => Promise; - let deleteNextFiles: () => void; - let hasMoreItemsToDelete = true; - - // delete an item - deleteItem = (item: string): Promise => { - return new Promise((resolve, reject) => { - let deletionCompleted = (err?: any) => { - if (err) { - reject(err); + let json = JSON.stringify({ + path: item, + }); + + req.write(json); + + req.end(); } - else { - resolve(err); + catch (e) { + deletionCompleted(e); } - }; + }); + }; + + // delete next files + deleteNextFiles = () => { + if (!hasMoreItemsToDelete) { + completed(null, wrapper); // nothing more to delete + return; + } + let apiPath: string; + let dataToSend: any; + if (cursor) { + apiPath = '/2/files/list_folder/continue'; + + dataToSend = { + "cursor": cursor, + }; + } + else { + apiPath = '/2/files/list_folder'; + + dataToSend = { + "path": targetDirectory, + "recursive": false, + "include_media_info": false, + "include_deleted": false, + "include_has_explicit_shared_members": false, + }; + } + try { let req = HTTPs.request({ headers: headersToSubmit, host: 'api.dropboxapi.com', method: 'POST', - path: '/2/files/delete', + path: apiPath, port: 443, protocol: 'https:', }, (resp) => { - let err: Error; - - switch (resp.statusCode) { - case 200: - case 409: // not found - // OK - break; - - default: - err = new Error(i18.t('plugins.dropbox.unknownResponse', - resp.statusCode, 2, resp.statusMessage)); - break; - } + hasMoreItemsToDelete = false; + + deploy_helpers.getHttpBody(resp).then((body) => { + try { + let err: Error; + + switch (resp.statusCode) { + case 200: + case 409: // not found + let json = body.toString('utf8'); + if (json) { + let result: DropboxListFolderResult = JSON.parse(json); + if (result) { + hasMoreItemsToDelete = result.has_more; + cursor = result.cursor; + + if (result.entries) { + let entriesToDo = result.entries.filter(x => x); + + let deleteNextEntry: () => void; + deleteNextEntry = () => { + if (entriesToDo.length < 1) { + deleteNextFiles(); + return; + } + + let e = entriesToDo.shift(); + + deleteItem(e.path_display).then(() => { + deleteNextEntry(); + }).catch((err) => { + completed(err); + }); + }; + + deleteNextEntry(); + } + else { + deleteNextFiles(); + } + } + } + break; - deletionCompleted(err); + default: + err = new Error(i18.t('plugins.dropbox.unknownResponse', + resp.statusCode, 2, resp.statusMessage)); + break; + } + + if (err) { + completed(err); + } + else { + completed(null, wrapper); + } + } + catch (e) { + completed(e); + } + }).catch((err) => { + completed(err); + }); }); req.once('error', (err) => { if (err) { - deletionCompleted(err); + completed(err); } }); - let json = JSON.stringify({ - path: item, - }); - - req.write(json); + if (dataToSend) { + req.write(JSON.stringify(dataToSend)); + } req.end(); } catch (e) { - deletionCompleted(e); + completed(e); } - }); - }; - - // delete next files - deleteNextFiles = () => { - if (!hasMoreItemsToDelete) { - completed(null, wrapper); // nothing more to delete - return; - } - - let apiPath: string; - let dataToSend: any; - if (cursor) { - apiPath = '/2/files/list_folder/continue'; - - dataToSend = { - "cursor": cursor, - }; - } - else { - apiPath = '/2/files/list_folder'; - - dataToSend = { - "path": targetDirectory, - "recursive": false, - "include_media_info": false, - "include_deleted": false, - "include_has_explicit_shared_members": false, - }; - } - - try { - let req = HTTPs.request({ - headers: headersToSubmit, - host: 'api.dropboxapi.com', - method: 'POST', - path: apiPath, - port: 443, - protocol: 'https:', - }, (resp) => { - hasMoreItemsToDelete = false; - - deploy_helpers.getHttpBody(resp).then((body) => { - try { - let err: Error; - - switch (resp.statusCode) { - case 200: - case 409: // not found - let json = body.toString('utf8'); - if (json) { - let result: DropboxListFolderResult = JSON.parse(json); - if (result) { - hasMoreItemsToDelete = result.has_more; - cursor = result.cursor; - - if (result.entries) { - let entriesToDo = result.entries.filter(x => x); - - let deleteNextEntry: () => void; - deleteNextEntry = () => { - if (entriesToDo.length < 1) { - deleteNextFiles(); - return; - } - - let e = entriesToDo.shift(); - - deleteItem(e.path_display).then(() => { - deleteNextEntry(); - }).catch((err) => { - completed(err); - }); - }; - - deleteNextEntry(); - } - else { - deleteNextFiles(); - } - } - } - break; - - default: - err = new Error(i18.t('plugins.dropbox.unknownResponse', - resp.statusCode, 2, resp.statusMessage)); - break; - } + }; - if (err) { - completed(err); - } - else { - completed(null, wrapper); - } - } - catch (e) { - completed(e); - } - }).catch((err) => { - completed(err); - }); - }); + deleteNextFiles(); + } + else { + completed(null, wrapper); + } + }; - req.once('error', (err) => { - if (err) { - completed(err); - } - }); + let askForTokenIfNeeded = () => { + let showTokenPrompt = false; + if (deploy_helpers.isEmptyString(ctx.token)) { + // user defined, but no password + showTokenPrompt = deploy_helpers.toBooleanSafe(target.promptForToken, true); + } - if (dataToSend) { - req.write(JSON.stringify(dataToSend)); + if (showTokenPrompt) { + vscode.window.showInputBox({ + placeHolder: i18.t('prompts.inputAccessToken'), + password: true, + }).then((tokenFromUser) => { + if (deploy_helpers.isEmptyString(tokenFromUser)) { + ctx.hasCancelled = true; // cancelled + + completed(null, wrapper); } + else { + ctx.token = tokenFromUser.trim(); - req.end(); - } - catch (e) { - completed(e); - } - }; + prepareWrapper(); + } + }, (err) => { + completed(err); + }); + } + else { + prepareWrapper(); + } + }; - deleteNextFiles(); - } - else { - completed(null, wrapper); - } + askForTokenIfNeeded(); } catch (e) { completed(e);