From 23bd05ea687e6822acf13162e9976a237e93da32 Mon Sep 17 00:00:00 2001 From: Arvind Ranasaria Date: Mon, 31 Aug 2020 18:57:22 -0700 Subject: [PATCH] Eula handling for Azdata (#12035) * Eula handling for Azdata * PR feedback * pr feedback * pr feedback * remove extra await Co-authored-by: chgagnon --- extensions/azdata/src/azdata.ts | 22 +++++++++- extensions/azdata/src/constants.ts | 3 ++ extensions/azdata/src/extension.ts | 48 +++++++++++++++------ extensions/azdata/src/localizedConstants.ts | 6 ++- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/extensions/azdata/src/azdata.ts b/extensions/azdata/src/azdata.ts index e4dfde4cad..f277809138 100644 --- a/extensions/azdata/src/azdata.ts +++ b/extensions/azdata/src/azdata.ts @@ -11,8 +11,8 @@ import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from import { HttpClient } from './common/httpClient'; import Logger from './common/logger'; import { getErrorMessage, searchForCmd } from './common/utils'; +import { acceptEula, azdataConfigSection, debugConfigKey, doNotPromptInstallMemento, doNotPromptUpdateMemento, eulaUrl, installationReadmeUrl, microsoftPrivacyStatementUrl, requiredVersion } from './constants'; import * as loc from './localizedConstants'; -import { azdataConfigSection, debugConfigKey, requiredVersion as requiredVersion, installationReadmeUrl, doNotPromptInstallMemento, doNotPromptUpdateMemento } from './constants'; export const azdataHostname = 'https://aka.ms'; export const azdataUri = 'azdata-msi'; @@ -291,6 +291,26 @@ export async function manuallyInstallOrUpgradeAzdata(context: vscode.ExtensionCo // await vscode.window.showTextDocument(vscode.Uri.parse(downloadedFile)); } +/** + * Prompts user to accept EULA it if was not previously accepted. Stores and returns the user response to EULA prompt. + * @param memento - memento where the user response is stored. + * pre-requisite, the calling code has to ensure that the eula has not yet been previously accepted by the user. + * returns true if the user accepted the EULA. + */ + +export async function promptForEula(memento: vscode.Memento): Promise { + Logger.show(); + Logger.log(loc.promptForEulaLog(microsoftPrivacyStatementUrl, eulaUrl)); + const reply = await vscode.window.showInformationMessage(loc.promptForEula(microsoftPrivacyStatementUrl, eulaUrl), loc.yes, loc.no); + Logger.log(loc.userResponseToEulaPrompt(reply)); + if (reply === loc.yes) { + await memento.update(acceptEula, true); + return true; + } else { + return false; + } +} + /** * Downloads the Windows installer and runs it */ diff --git a/extensions/azdata/src/constants.ts b/extensions/azdata/src/constants.ts index 7d768308da..85c77c277d 100644 --- a/extensions/azdata/src/constants.ts +++ b/extensions/azdata/src/constants.ts @@ -5,6 +5,9 @@ export const azdataConfigSection = 'azdata'; export const debugConfigKey = 'logDebugInfo'; +export const acceptEula = 'acceptEula'; +export const microsoftPrivacyStatementUrl = 'https://privacy.microsoft.com/en-us/privacystatement'; +export const eulaUrl = 'https://aka.ms/eula-azdata-en'; export const requiredVersion = '20.1.1'; export const doNotPromptInstallMemento = 'azdata.doNotPromptInstall'; export const doNotPromptUpdateMemento = 'azdata.doNotPromptUpdate'; diff --git a/extensions/azdata/src/extension.ts b/extensions/azdata/src/extension.ts index d1da40b1f4..a20ab0b513 100644 --- a/extensions/azdata/src/extension.ts +++ b/extensions/azdata/src/extension.ts @@ -3,15 +3,28 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import * as azdataExt from 'azdata-ext'; -import { findAzdata, IAzdataTool, manuallyInstallOrUpgradeAzdata } from './azdata'; +import * as vscode from 'vscode'; +import { findAzdata, IAzdataTool, manuallyInstallOrUpgradeAzdata, promptForEula } from './azdata'; +import Logger from './common/logger'; +import * as constants from './constants'; import * as loc from './localizedConstants'; let localAzdata: IAzdataTool | undefined = undefined; +let eulaAccepted: boolean = false; export async function activate(context: vscode.ExtensionContext): Promise { localAzdata = await checkForAzdata(); + eulaAccepted = !!context.globalState.get(constants.acceptEula); + if (!eulaAccepted) { + // Don't block on this since we want extension to finish activating without requiring user actions. + // If EULA has not been accepted then we will check again while executing azdata commands. + promptForEula(context.globalState) + .then(userResponse => { + eulaAccepted = userResponse; + }) + .catch(err => console.log(err)); + } // Don't block on this since we want the extension to finish activating without user actions manuallyInstallOrUpgradeAzdata(context, localAzdata) .catch(err => console.log(err)); @@ -20,22 +33,22 @@ export async function activate(context: vscode.ExtensionContext): Promise { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass); }, endpoint: { list: async () => { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.dc.endpoint.list(); } }, config: { list: async () => { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.dc.config.list(); }, show: async () => { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.dc.config.show(); } } @@ -43,11 +56,11 @@ export async function activate(context: vscode.ExtensionContext): Promise { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.postgres.server.list(); }, show: async (name: string) => { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.postgres.server.show(name); } } @@ -55,36 +68,43 @@ export async function activate(context: vscode.ExtensionContext): Promise { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.sql.mi.delete(name); }, list: async () => { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.sql.mi.list(); }, show: async (name: string) => { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.arc.sql.mi.show(name); } } } }, login: async (endpoint: string, username: string, password: string) => { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.login(endpoint, username, password); }, version: async () => { - throwIfNoAzdata(); + await throwIfNoAzdataOrEulaNotAccepted(context); return localAzdata!.version(); } } }; } -function throwIfNoAzdata(): void { +async function throwIfNoAzdataOrEulaNotAccepted(context: vscode.ExtensionContext): Promise { if (!localAzdata) { throw new Error(loc.noAzdata); } + if (!eulaAccepted) { + eulaAccepted = await promptForEula(context.globalState); + } + if (!eulaAccepted) { + Logger.log(loc.eulaNotAccepted); + throw new Error(loc.eulaNotAccepted); + } } async function checkForAzdata(): Promise { diff --git a/extensions/azdata/src/localizedConstants.ts b/extensions/azdata/src/localizedConstants.ts index dbb4aa10a8..e0ffbcdec5 100644 --- a/extensions/azdata/src/localizedConstants.ts +++ b/extensions/azdata/src/localizedConstants.ts @@ -35,6 +35,10 @@ export const unexpectedCommandError = (errMsg: string): string => localize('azda export const upgradeError = (err: any): string => localize('azdata.upgradeError', "Error upgrading azdata : {0}", err.message ?? err); export const upgradeCheckSkipped = localize('azdata.updateCheckSkipped', "No check for new azdata version availability performed as azdata was not found to be installed"); export const unexpectedExitCode = (code: number, err: string): string => localize('azdata.unexpectedExitCode', "Unexpected exit code from command : {1} ({0})", code, err); -export const noAzdata = localize('azdata.NoAzdata', "No azdata available"); +export const noAzdata = localize('azdata.noAzdata', "No azdata available"); +export const eulaNotAccepted = localize('azdata.eulaNotAccepted', "Microsoft Privacy statement and Azure Data CLI license terms have not been accepted"); export const installManually = (expectedVersion: string, instructionsUrl: string) => localize('azdata.installManually', "azdata is not installed. Version: {0} needs to be installed or some features may not work. Please install it manually using these [instructions]({1}). Restart ADS when installation is done.", expectedVersion, instructionsUrl); export const installCorrectVersionManually = (currentVersion: string, expectedVersion: string, instructionsUrl: string) => localize('azdata.installCorrectVersionManually', "azdata version: {0} is installed, version: {1} needs to be installed or some features may not work. Please uninstall the current version and then install the correct version manually using these [instructions]({2}). Restart ADS when installation is done.", currentVersion, expectedVersion, instructionsUrl); +export const promptForEula = (privacyStatementUrl: string, eulaUrl: string) => localize('azdata.promptForEula', "It is required to accept the [Microsoft Privacy Statement]({0}) and the [Azure Data CLI license terms]({1}) to use this extension. Declining this will result in some features not working.", privacyStatementUrl, eulaUrl); +export const promptForEulaLog = (privacyStatementUrl: string, eulaUrl: string) => localize('azdata.promptForEulaLog', "Prompting the user to accept the following: {0}", promptForEula(privacyStatementUrl, eulaUrl)); +export const userResponseToEulaPrompt = (response: string | undefined) => localize('azdata.promptForEulaResponse', "User response to Eula prompt: {0}", response);