Skip to content

Commit

Permalink
Merge branch 'master' into codeCoverage/70
Browse files Browse the repository at this point in the history
  • Loading branch information
Aditya Bist committed Dec 5, 2019
2 parents 7f0aa81 + 29a4c09 commit 59c6dc4
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 82 deletions.
5 changes: 4 additions & 1 deletion localization/xliff/enu/constants/localizedConstants.enu.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
<source xml:lang="en">Profile Name</source>
</trans-unit>
<trans-unit id="profileNamePlaceholder">
<source xml:lang="en">[Optional] Enter a name for this profile</source>
<source xml:lang="en">[Optional] Enter a display name for this connection profile</source>
</trans-unit>
<trans-unit id="filepathPrompt">
<source xml:lang="en">File path</source>
Expand Down Expand Up @@ -251,6 +251,9 @@
<trans-unit id="msgPromptAzureExtensionActivatedSignedIn">
<source xml:lang="en">Azure Account extension activated and signed in.</source>
</trans-unit>
<trans-unit id="msgPromptFirewallRuleCreated">
<source xml:lang="en">Firewall rule successfully created.</source>
</trans-unit>
<trans-unit id="msgInvalidIpAddress">
<source xml:lang="en">Invalid IP Address </source>
</trans-unit>
Expand Down
4 changes: 2 additions & 2 deletions src/connectionconfig/connectionconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';

import * as vscode from 'vscode';
import * as Constants from '../constants/constants';
import * as LocalizedConstants from '../constants/localizedConstants';
import * as Utils from '../models/utils';
Expand Down Expand Up @@ -150,7 +150,7 @@ export class ConnectionConfig implements IConnectionConfig {
// Save the file
const self = this;
return new Promise<void>((resolve, reject) => {
self._vscodeWrapper.getConfiguration(Constants.extensionName).update(Constants.connectionsArrayName, profiles, true).then(() => {
self._vscodeWrapper.setConfiguration(Constants.extensionName, Constants.connectionsArrayName, profiles).then(() => {
resolve();
}, err => {
reject(err);
Expand Down
1 change: 1 addition & 0 deletions src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,4 @@ export const sqlToolsServiceConfigKey = 'service';
export const v1SqlToolsServiceConfigKey = 'v1Service';
export const scriptSelectText = 'SELECT TOP (1000) * FROM ';
export const tenantDisplayName = 'Microsoft';
export const firewallErrorMessage = 'To enable access, use the Windows Azure Management Portal or run sp_set_firewall_rule on the master database to create a firewall rule for this IP address or address range.';
20 changes: 17 additions & 3 deletions src/controllers/mainController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export default class MainController implements vscode.Disposable {
} else if (databaseName === LocalizedConstants.defaultDatabaseLabel) {
connectionCredentials.database = '';
}
treeNodeInfo.connectionCredentials = connectionCredentials;
await self.onNewQuery(treeNodeInfo);
}));

Expand Down Expand Up @@ -930,14 +931,27 @@ export default class MainController implements vscode.Disposable {
for (let conn of newConnections) {
// if a connection is not connected
// that means it was added manually
const uri = ObjectExplorerUtils.getNodeUriFromProfile(<IConnectionProfile>conn);
const newConnectionProfile = <IConnectionProfile>conn;
const uri = ObjectExplorerUtils.getNodeUriFromProfile(newConnectionProfile);
if (!this.connectionManager.isActiveConnection(conn) &&
!this.connectionManager.isConnecting(uri)) {
// add a disconnected node for it
needsRefresh = true;
// add a disconnected node for the connection
this._objectExplorerProvider.addDisconnectedNode(conn);
needsRefresh = true;
}
}

// Get rid of all passwords in the settings file
for (let conn of userConnections) {
if (!Utils.isEmpty(conn.password)) {
// save the password in the credential store if save password is true
await this.connectionManager.connectionStore.saveProfilePasswordIfNeeded(conn);
}
conn.password = '';
}

this._vscodeWrapper.setConfiguration(Constants.extensionName, Constants.connectionsArrayName, userConnections);

if (needsRefresh) {
this._objectExplorerProvider.refresh(undefined);
}
Expand Down
7 changes: 7 additions & 0 deletions src/controllers/vscodeWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,13 @@ export default class VscodeWrapper {
}

/**
* Change a configuration setting
*/
public setConfiguration(extensionName: string, resource: string, value: any): Thenable<void> {
return this.getConfiguration(extensionName).update(resource, value, vscode.ConfigurationTarget.Global);
}

/*
* Called when there's a change in the extensions
*/
public get onDidChangeExtensions(): vscode.Event<void> {
Expand Down
12 changes: 7 additions & 5 deletions src/models/connectionCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class ConnectionCredentials implements IConnectionCredentials {
return details;
}

public static ensureRequiredPropertiesSet(
public static async ensureRequiredPropertiesSet(
credentials: IConnectionCredentials,
isProfile: boolean,
isPasswordRequired: boolean,
Expand All @@ -94,7 +94,7 @@ export class ConnectionCredentials implements IConnectionCredentials {
connectionStore: ConnectionStore,
defaultProfileValues?: IConnectionCredentials): Promise<IConnectionCredentials> {

let questions: IQuestion[] = ConnectionCredentials.getRequiredCredentialValuesQuestions(credentials, false, isPasswordRequired, defaultProfileValues);
let questions: IQuestion[] = await ConnectionCredentials.getRequiredCredentialValuesQuestions(credentials, false, isPasswordRequired, connectionStore, defaultProfileValues);
let unprocessedCredentials: IConnectionCredentials = Object.assign({}, credentials);

// Potentially ask to save password
Expand Down Expand Up @@ -153,11 +153,12 @@ export class ConnectionCredentials implements IConnectionCredentials {
}

// gets a set of questions that ensure all required and core values are set
protected static getRequiredCredentialValuesQuestions(
protected static async getRequiredCredentialValuesQuestions(
credentials: IConnectionCredentials,
promptForDbName: boolean,
isPasswordRequired: boolean,
defaultProfileValues?: IConnectionCredentials): IQuestion[] {
connectionStore: ConnectionStore,
defaultProfileValues?: IConnectionCredentials): Promise<IQuestion[]> {

let authenticationChoices: INameValueChoice[] = ConnectionCredentials.getAuthenticationTypesChoice();

Expand Down Expand Up @@ -235,7 +236,8 @@ export class ConnectionCredentials implements IConnectionCredentials {
(<IConnectionProfile>credentials).emptyPasswordInput = utils.isEmpty(credentials.password);
}
}
}
},
default: defaultProfileValues ? await connectionStore.lookupPassword(defaultProfileValues) : undefined
}
];
return questions;
Expand Down
5 changes: 3 additions & 2 deletions src/models/connectionProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IConnectionProfile, AuthenticationTypes } from './interfaces';
import { ConnectionCredentials } from './connectionCredentials';
import { QuestionTypes, IQuestion, IPrompter, INameValueChoice } from '../prompts/question';
import * as utils from './utils';
import { ConnectionStore } from './connectionStore';

// Concrete implementation of the IConnectionProfile interface

Expand All @@ -26,7 +27,7 @@ export class ConnectionProfile extends ConnectionCredentials implements IConnect
* @param {IConnectionProfile} (optional) default profile values that will be prefilled for questions, if any
* @returns Promise - resolves to undefined if profile creation was not completed, or IConnectionProfile if completed
*/
public static createProfile(prompter: IPrompter, defaultProfileValues?: IConnectionProfile): Promise<IConnectionProfile> {
public static async createProfile(prompter: IPrompter, connectionStore: ConnectionStore, defaultProfileValues?: IConnectionProfile): Promise<IConnectionProfile> {
let profile: ConnectionProfile = new ConnectionProfile();
// Ensure all core properties are entered
let authOptions: INameValueChoice[] = ConnectionCredentials.getAuthenticationTypesChoice();
Expand All @@ -35,7 +36,7 @@ export class ConnectionProfile extends ConnectionCredentials implements IConnect
profile.authenticationType = authOptions[0].value;
}

let questions: IQuestion[] = ConnectionCredentials.getRequiredCredentialValuesQuestions(profile, true, false, defaultProfileValues);
let questions: IQuestion[] = await ConnectionCredentials.getRequiredCredentialValuesQuestions(profile, true, false, connectionStore, defaultProfileValues);
// Check if password needs to be saved
questions.push(
{
Expand Down
13 changes: 12 additions & 1 deletion src/models/connectionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export class ConnectionStore {
});
}

private saveProfilePasswordIfNeeded(profile: IConnectionProfile): Promise<boolean> {
public saveProfilePasswordIfNeeded(profile: IConnectionProfile): Promise<boolean> {
if (!profile.savePassword) {
return Promise.resolve(true);
}
Expand Down Expand Up @@ -401,6 +401,17 @@ export class ConnectionStore {
return result;
}


/**
* Removes password from a saved profile and credential store
*/
public async removeProfilePassword(connection: IConnectionCredentials): Promise<void> {
// if the password is saved in the credential store, remove it
let profile = connection as IConnectionProfile;
profile.password = '';
await this.saveProfile(profile);
}

// Load connections from user preferences
public loadAllConnections(addRecentConnections: boolean = false): IConnectionCredentialsQuickPickItem[] {
let quickPickItems: IConnectionCredentialsQuickPickItem[] = [];
Expand Down
51 changes: 41 additions & 10 deletions src/objectExplorer/objectExplorerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,23 @@ export class ObjectExplorerService {
if (self._currentNode.connectionCredentials.password) {
self._currentNode.connectionCredentials.password = '';
}
self.updateNode(self._currentNode);
self._currentNode = undefined;
let error = LocalizedConstants.connectErrorLabel;
if (result.errorMessage) {
error += ` : ${result.errorMessage}`;
}
self._connectionManager.vscodeWrapper.showErrorMessage(error);
const promise = self._sessionIdToPromiseMap.get(result.sessionId);

// handle session failure because of firewall issue
if (ObjectExplorerUtils.isFirewallError(result.errorMessage)) {
let handleFirewallResult = await self._connectionManager.firewallService.handleFirewallRule(Constants.errorFirewallRule, result.errorMessage);
const nodeUri = ObjectExplorerUtils.getNodeUri(self._currentNode);
const profile = <IConnectionProfile>self._currentNode.connectionCredentials;
self.updateNode(self._currentNode);
self._currentNode = undefined;
self._connectionManager.connectionUI.handleFirewallError(nodeUri, profile, handleFirewallResult.ipAddress);
}

if (promise) {
return promise.resolve(undefined);
}
Expand Down Expand Up @@ -244,6 +253,26 @@ export class ObjectExplorerService {
return [new AddConnectionTreeNode()];
}

/**
* Handles a generic OE create session failure by creating a
* sign in node
*/
private createSignInNode(element: TreeNodeInfo): AccountSignInTreeNode[] {
const signInNode = new AccountSignInTreeNode(element);
this._treeNodeToChildrenMap.set(element, [signInNode]);
return [signInNode];
}

/**
* Handles a connection error after an OE session is
* sucessfully created by creating a connect node
*/
private createConnectTreeNode(element: TreeNodeInfo): ConnectTreeNode[] {
const connectNode = new ConnectTreeNode(element);
this._treeNodeToChildrenMap.set(element, [connectNode]);
return [connectNode];
}

async getChildren(element?: TreeNodeInfo): Promise<vscode.TreeItem[]> {
if (element) {
if (element !== this._currentNode) {
Expand Down Expand Up @@ -275,17 +304,19 @@ export class ObjectExplorerService {
const sessionId = await this.createSession(promise, element.connectionCredentials);
if (sessionId) {
let node = await promise;
// if password failed
// if the server was found but connection failed
if (!node) {
const connectNode = new ConnectTreeNode(element);
this._treeNodeToChildrenMap.set(element, [connectNode]);
return [connectNode];
let profile = element.connectionCredentials as IConnectionProfile;
let password = await this._connectionManager.connectionStore.lookupPassword(profile);
if (password) {
return this.createSignInNode(element);
} else {
return this.createConnectTreeNode(element);
}
}
} else {
// If node create session failed
const signInNode = new AccountSignInTreeNode(element);
this._treeNodeToChildrenMap.set(element, [signInNode]);
return [signInNode];
// If node create session failed (server wasn't found)
return this.createSignInNode(element);
}
// otherwise expand the node by refreshing the root
// to add connected context key
Expand Down
4 changes: 4 additions & 0 deletions src/objectExplorer/objectExplorerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ export class ObjectExplorerUtils {
}
return LocalizedConstants.defaultDatabaseLabel;
}

public static isFirewallError(errorMessage: string): boolean {
return errorMessage.includes(Constants.firewallErrorMessage);
}
}
3 changes: 3 additions & 0 deletions src/prompts/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export default class InputPrompt extends Prompt {
}

this._options.placeHolder = placeHolder;
if (this._question.default) {
this._options.value = this._question.default;
}

return this._vscodeWrapper.showInputBox(this._options)
.then(result => {
Expand Down

0 comments on commit 59c6dc4

Please sign in to comment.