mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 17:23:56 -05:00
Fix/controller ad (#7445)
Reapply my previous commit add kerberos to excludes in the webpack for the extension Fixes #7443 Verified on Windows and MacOS
This commit is contained in:
35
extensions/big-data-cluster/src/bigDataCluster/auth.ts
Normal file
35
extensions/big-data-cluster/src/bigDataCluster/auth.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as kerberos from 'kerberos';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export async function authenticateKerberos(hostname: string): Promise<string> {
|
||||
const service = 'HTTP' + (process.platform === 'win32' ? '/' : '@') + hostname;
|
||||
const mechOID = kerberos.GSS_MECH_OID_KRB5;
|
||||
let client = await kerberos.initializeClient(service, { mechOID });
|
||||
let response = await client.step('');
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
export type HostAndIp = { host: string, port: string };
|
||||
|
||||
export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
|
||||
let authority = vscode.Uri.parse(endpoint).authority;
|
||||
let hostAndPortRegex = /^(.*)([,:](\d+))/g;
|
||||
let match = hostAndPortRegex.exec(authority);
|
||||
if (match) {
|
||||
return {
|
||||
host: match[1],
|
||||
port: match[3]
|
||||
};
|
||||
}
|
||||
return {
|
||||
host: authority,
|
||||
port: undefined
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,3 +70,5 @@ export namespace cssStyles {
|
||||
export const unselectedTabDiv = { 'border-bottom': '1px solid #ccc' };
|
||||
export const lastUpdatedText = { ...text, 'color': '#595959' };
|
||||
}
|
||||
|
||||
export type AuthType = 'integrated' | 'basic';
|
||||
|
||||
@@ -631,8 +631,6 @@ export class BdcRouterApi {
|
||||
|
||||
this.authentications.default.applyToRequest(localVarRequestOptions);
|
||||
|
||||
this.authentications.basicAuth.applyToRequest(localVarRequestOptions);
|
||||
|
||||
if (Object.keys(localVarFormParams).length) {
|
||||
if (localVarUseFormData) {
|
||||
(<any>localVarRequestOptions).formData = localVarFormParams;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,15 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as request from 'request';
|
||||
import { BdcRouterApi, Authentication, DefaultApi, EndpointModel, BdcStatusModel } from './apiGenerated';
|
||||
import { authenticateKerberos, getHostAndPortFromEndpoint } from '../auth';
|
||||
import { BdcRouterApi, Authentication, EndpointModel, BdcStatusModel } from './apiGenerated';
|
||||
import { TokenRouterApi } from './clusterApiGenerated2';
|
||||
import { AuthType } from '../constants';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
class AuthConfiguration implements Authentication {
|
||||
public username: string = '';
|
||||
public password: string = '';
|
||||
class SslAuth implements Authentication {
|
||||
|
||||
constructor(private _ignoreSslVerification: boolean) {
|
||||
}
|
||||
@@ -23,65 +24,153 @@ class AuthConfiguration implements Authentication {
|
||||
}
|
||||
}
|
||||
|
||||
export class KerberosAuth extends SslAuth implements Authentication {
|
||||
|
||||
constructor(public kerberosToken: string, ignoreSslVerification: boolean) {
|
||||
super(ignoreSslVerification);
|
||||
}
|
||||
|
||||
applyToRequest(requestOptions: request.Options): void {
|
||||
super.applyToRequest(requestOptions);
|
||||
if (requestOptions && requestOptions.headers) {
|
||||
requestOptions.headers['Authorization'] = `Negotiate ${this.kerberosToken}`;
|
||||
}
|
||||
requestOptions.auth = undefined;
|
||||
}
|
||||
}
|
||||
export class BasicAuth extends SslAuth implements Authentication {
|
||||
constructor(public username: string, public password: string, ignoreSslVerification: boolean) {
|
||||
super(ignoreSslVerification);
|
||||
}
|
||||
|
||||
applyToRequest(requestOptions: request.Options): void {
|
||||
super.applyToRequest(requestOptions);
|
||||
requestOptions.auth = {
|
||||
username: this.username, password: this.password
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class OAuthWithSsl extends SslAuth implements Authentication {
|
||||
public accessToken: string = '';
|
||||
|
||||
applyToRequest(requestOptions: request.Options): void {
|
||||
super.applyToRequest(requestOptions);
|
||||
if (requestOptions && requestOptions.headers) {
|
||||
requestOptions.headers['Authorization'] = `Bearer ${this.accessToken}`;
|
||||
}
|
||||
requestOptions.auth = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class BdcApiWrapper extends BdcRouterApi {
|
||||
constructor(basePathOrUsername: string, password?: string, basePath?: string, ignoreSslVerification?: boolean) {
|
||||
super(basePathOrUsername, password, basePath);
|
||||
this.authentications.default = new AuthConfiguration(!!ignoreSslVerification);
|
||||
this.password = password;
|
||||
this.username = basePathOrUsername;
|
||||
constructor(basePathOrUsername: string, password: string, basePath: string, auth: Authentication) {
|
||||
if (password) {
|
||||
super(basePathOrUsername, password, basePath);
|
||||
} else {
|
||||
super(basePath, undefined, undefined);
|
||||
}
|
||||
this.authentications.default = auth;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEndPoints(
|
||||
url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
ignoreSslVerification?: boolean
|
||||
): Promise<IEndPointsResponse> {
|
||||
export class ClusterController {
|
||||
|
||||
if (!url || !username || !password) {
|
||||
return undefined;
|
||||
private authPromise: Promise<Authentication>;
|
||||
private _url: string;
|
||||
|
||||
constructor(url: string,
|
||||
private authType: AuthType,
|
||||
private username?: string,
|
||||
private password?: string,
|
||||
ignoreSslVerification?: boolean
|
||||
) {
|
||||
if (!url || (authType === 'basic' && (!username || !password))) {
|
||||
throw new Error('Missing required inputs for Cluster controller API (URL, username, password)');
|
||||
}
|
||||
this._url = adjustUrl(url);
|
||||
if (this.authType === 'basic') {
|
||||
this.authPromise = Promise.resolve(new BasicAuth(username, password, !!ignoreSslVerification));
|
||||
} else {
|
||||
this.authPromise = this.requestTokenUsingKerberos(ignoreSslVerification);
|
||||
}
|
||||
}
|
||||
|
||||
url = adjustUrl(url);
|
||||
let endPointApi = new BdcApiWrapper(username, password, url, !!ignoreSslVerification);
|
||||
private async requestTokenUsingKerberos(ignoreSslVerification?: boolean): Promise<Authentication> {
|
||||
let supportsKerberos = await this.verifyKerberosSupported(ignoreSslVerification);
|
||||
if (!supportsKerberos) {
|
||||
throw new Error(localize('error.no.activedirectory', "This cluster does not support Windows authentication"));
|
||||
}
|
||||
|
||||
try {
|
||||
let result = await endPointApi.endpointsGet();
|
||||
return {
|
||||
response: result.response as IHttpResponse,
|
||||
endPoints: result.body as EndpointModel[]
|
||||
};
|
||||
} catch (error) {
|
||||
throw new ControllerError(error, localize('bdc.error.getEndPoints', "Error retrieving endpoints from {0}", url));
|
||||
try {
|
||||
|
||||
// AD auth is available, login to keberos and convert to token auth for all future calls
|
||||
let host = getHostAndPortFromEndpoint(this._url).host;
|
||||
let kerberosToken = await authenticateKerberos(host);
|
||||
let tokenApi = new TokenRouterApi(this._url);
|
||||
tokenApi.setDefaultAuthentication(new KerberosAuth(kerberosToken, !!ignoreSslVerification));
|
||||
let result = await tokenApi.apiV1TokenPost();
|
||||
let auth = new OAuthWithSsl(ignoreSslVerification);
|
||||
auth.accessToken = result.body.accessToken;
|
||||
return auth;
|
||||
} catch (error) {
|
||||
let controllerErr = new ControllerError(error, localize('bdc.error.tokenPost', "Error during authentication"));
|
||||
if (controllerErr.code === 401) {
|
||||
throw new Error(localize('bdc.error.unauthorized', "You do not have permission to log into this cluster using Windows Authentication"));
|
||||
}
|
||||
// Else throw the error as-is
|
||||
throw controllerErr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async verifyKerberosSupported(ignoreSslVerification: boolean): Promise<boolean> {
|
||||
let tokenApi = new TokenRouterApi(this._url);
|
||||
tokenApi.setDefaultAuthentication(new SslAuth(!!ignoreSslVerification));
|
||||
try {
|
||||
await tokenApi.apiV1TokenPost();
|
||||
// If we get to here, the route for endpoints doesn't require auth so state this is false
|
||||
return false;
|
||||
}
|
||||
catch (error) {
|
||||
let auths = error && error.response && error.response.statusCode === 401 && error.response.headers['www-authenticate'];
|
||||
return auths && auths.includes('Negotiate');
|
||||
}
|
||||
}
|
||||
|
||||
public async getEndPoints(): Promise<IEndPointsResponse> {
|
||||
let auth = await this.authPromise;
|
||||
let endPointApi = new BdcApiWrapper(this.username, this.password, this._url, auth);
|
||||
let options: any = {};
|
||||
try {
|
||||
let result = await endPointApi.endpointsGet(options);
|
||||
return {
|
||||
response: result.response as IHttpResponse,
|
||||
endPoints: result.body as EndpointModel[]
|
||||
};
|
||||
} catch (error) {
|
||||
// TODO handle 401 by reauthenticating
|
||||
throw new ControllerError(error, localize('bdc.error.getEndPoints', "Error retrieving endpoints from {0}", this._url));
|
||||
}
|
||||
}
|
||||
|
||||
public async getBdcStatus(): Promise<IBdcStatusResponse> {
|
||||
let auth = await this.authPromise;
|
||||
const bdcApi = new BdcApiWrapper(this.username, this.password, this._url, auth);
|
||||
|
||||
try {
|
||||
const bdcStatus = await bdcApi.getBdcStatus('', '', /*all*/ true);
|
||||
return {
|
||||
response: bdcStatus.response,
|
||||
bdcStatus: bdcStatus.body
|
||||
};
|
||||
} catch (error) {
|
||||
// TODO handle 401 by reauthenticating
|
||||
throw new ControllerError(error, localize('bdc.error.getBdcStatus', "Error retrieving BDC status from {0}", this._url));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getBdcStatus(
|
||||
url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
ignoreSslVerification?: boolean
|
||||
): Promise<IBdcStatusResponse> {
|
||||
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
url = adjustUrl(url);
|
||||
const bdcApi = new BdcApiWrapper(username, password, url, ignoreSslVerification);
|
||||
|
||||
try {
|
||||
const bdcStatus = await bdcApi.getBdcStatus('', '', /*all*/ true);
|
||||
return {
|
||||
response: bdcStatus.response,
|
||||
bdcStatus: bdcStatus.body
|
||||
};
|
||||
} catch (error) {
|
||||
throw new ControllerError(error, localize('bdc.error.getBdcStatus', "Error retrieving BDC status from {0}", url));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes missing protocol and wrong character for port entered by user
|
||||
*/
|
||||
@@ -122,8 +211,7 @@ export interface IHttpResponse {
|
||||
}
|
||||
|
||||
export class ControllerError extends Error {
|
||||
public code?: string;
|
||||
public errno?: string;
|
||||
public code?: number;
|
||||
public reason?: string;
|
||||
public address?: string;
|
||||
|
||||
@@ -136,7 +224,7 @@ export class ControllerError extends Error {
|
||||
super(messagePrefix);
|
||||
// Pull out the response information containing details about the failure
|
||||
if (error.response) {
|
||||
this.code = error.response.statusCode || '';
|
||||
this.code = error.response.statusCode;
|
||||
this.message += `${error.response.statusMessage ? ` - ${error.response.statusMessage}` : ''}` || '';
|
||||
this.address = error.response.url || '';
|
||||
}
|
||||
|
||||
@@ -7,40 +7,74 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { getEndPoints, ControllerError } from '../controller/clusterControllerApi';
|
||||
import { ClusterController, ControllerError } from '../controller/clusterControllerApi';
|
||||
import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
|
||||
import { TreeNode } from '../tree/treeNode';
|
||||
import { showErrorMessage } from '../utils';
|
||||
import { AuthType } from '../constants';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const basicAuthDisplay = localize('basicAuthName', "Basic");
|
||||
const integratedAuthDisplay = localize('integratedAuthName', "Windows Authentication");
|
||||
|
||||
function getAuthCategory(name: AuthType): azdata.CategoryValue {
|
||||
if (name === 'basic') {
|
||||
return { name: name, displayName: basicAuthDisplay };
|
||||
}
|
||||
return { name: name, displayName: integratedAuthDisplay };
|
||||
}
|
||||
|
||||
export class AddControllerDialogModel {
|
||||
|
||||
private _canceled = false;
|
||||
|
||||
private _authTypes: azdata.CategoryValue[];
|
||||
constructor(
|
||||
public treeDataProvider: ControllerTreeDataProvider,
|
||||
public node?: TreeNode,
|
||||
public prefilledUrl?: string,
|
||||
public prefilledAuth?: azdata.CategoryValue,
|
||||
public prefilledUsername?: string,
|
||||
public prefilledPassword?: string,
|
||||
public prefilledRememberPassword?: boolean
|
||||
) {
|
||||
this.prefilledUrl = prefilledUrl || (node && node['url']);
|
||||
this.prefilledAuth = prefilledAuth;
|
||||
if (!prefilledAuth) {
|
||||
let auth = (node && node['auth']) || 'basic';
|
||||
this.prefilledAuth = getAuthCategory(auth);
|
||||
}
|
||||
this.prefilledUsername = prefilledUsername || (node && node['username']);
|
||||
this.prefilledPassword = prefilledPassword || (node && node['password']);
|
||||
this.prefilledRememberPassword = prefilledRememberPassword || (node && node['rememberPassword']);
|
||||
}
|
||||
|
||||
public async onComplete(url: string, username: string, password: string, rememberPassword: boolean): Promise<void> {
|
||||
public get authCategories(): azdata.CategoryValue[] {
|
||||
if (!this._authTypes) {
|
||||
this._authTypes = [getAuthCategory('basic'), getAuthCategory('integrated')];
|
||||
}
|
||||
return this._authTypes;
|
||||
}
|
||||
|
||||
public async onComplete(url: string, auth: AuthType, username: string, password: string, rememberPassword: boolean): Promise<void> {
|
||||
try {
|
||||
|
||||
if (auth === 'basic') {
|
||||
// Verify username and password as we can't make them required in the UI
|
||||
if (!username) {
|
||||
throw new Error(localize('err.controller.username.required', "Username is required"));
|
||||
} else if (!password) {
|
||||
throw new Error(localize('err.controller.password.required', "Password is required"));
|
||||
}
|
||||
}
|
||||
// We pre-fetch the endpoints here to verify that the information entered is correct (the user is able to connect)
|
||||
let response = await getEndPoints(url, username, password, true);
|
||||
let controller = new ClusterController(url, auth, username, password, true);
|
||||
let response = await controller.getEndPoints();
|
||||
if (response && response.endPoints) {
|
||||
if (this._canceled) {
|
||||
return;
|
||||
}
|
||||
this.treeDataProvider.addController(url, username, password, rememberPassword);
|
||||
this.treeDataProvider.addController(url, auth, username, password, rememberPassword);
|
||||
await this.treeDataProvider.saveControllers();
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -70,6 +104,7 @@ export class AddControllerDialog {
|
||||
private uiModelBuilder: azdata.ModelBuilder;
|
||||
|
||||
private urlInputBox: azdata.InputBoxComponent;
|
||||
private authDropdown: azdata.DropDownComponent;
|
||||
private usernameInputBox: azdata.InputBoxComponent;
|
||||
private passwordInputBox: azdata.InputBoxComponent;
|
||||
private rememberPwCheckBox: azdata.CheckBoxComponent;
|
||||
@@ -92,6 +127,12 @@ export class AddControllerDialog {
|
||||
placeHolder: localize('textUrlLower', 'url'),
|
||||
value: this.model.prefilledUrl
|
||||
}).component();
|
||||
this.authDropdown = this.uiModelBuilder.dropDown().withProperties({
|
||||
values: this.model.authCategories,
|
||||
value: this.model.prefilledAuth,
|
||||
editable: false,
|
||||
}).component();
|
||||
this.authDropdown.onValueChanged(e => this.onAuthChanged());
|
||||
this.usernameInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
placeHolder: localize('textUsernameLower', 'username'),
|
||||
@@ -117,14 +158,18 @@ export class AddControllerDialog {
|
||||
component: this.urlInputBox,
|
||||
title: localize('textUrlCapital', 'URL'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.authDropdown,
|
||||
title: localize('textAuthCapital', 'Authentication type'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.usernameInputBox,
|
||||
title: localize('textUsernameCapital', 'Username'),
|
||||
required: true
|
||||
required: false
|
||||
}, {
|
||||
component: this.passwordInputBox,
|
||||
title: localize('textPasswordCapital', 'Password'),
|
||||
required: true
|
||||
required: false
|
||||
}, {
|
||||
component: this.rememberPwCheckBox,
|
||||
title: ''
|
||||
@@ -142,17 +187,36 @@ export class AddControllerDialog {
|
||||
this.dialog.cancelButton.label = localize('textCancel', 'Cancel');
|
||||
}
|
||||
|
||||
private get authValue(): AuthType {
|
||||
return (<azdata.CategoryValue>this.authDropdown.value).name as AuthType;
|
||||
}
|
||||
|
||||
private onAuthChanged(): void {
|
||||
let isBasic = this.authValue === 'basic';
|
||||
this.usernameInputBox.enabled = isBasic;
|
||||
this.passwordInputBox.enabled = isBasic;
|
||||
this.rememberPwCheckBox.enabled = isBasic;
|
||||
if (!isBasic) {
|
||||
this.usernameInputBox.value = '';
|
||||
this.passwordInputBox.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
private async validate(): Promise<boolean> {
|
||||
let url = this.urlInputBox && this.urlInputBox.value;
|
||||
let auth = this.authValue;
|
||||
let username = this.usernameInputBox && this.usernameInputBox.value;
|
||||
let password = this.passwordInputBox && this.passwordInputBox.value;
|
||||
let rememberPassword = this.passwordInputBox && !!this.rememberPwCheckBox.checked;
|
||||
|
||||
try {
|
||||
await this.model.onComplete(url, username, password, rememberPassword);
|
||||
await this.model.onComplete(url, auth, username, password, rememberPassword);
|
||||
return true;
|
||||
} catch (error) {
|
||||
showErrorMessage(error);
|
||||
this.dialog.message = {
|
||||
text: (typeof error === 'string') ? error : error.message,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
if (this.model && this.model.onError) {
|
||||
await this.model.onError(error as ControllerError);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { getBdcStatus, getEndPoints } from '../controller/clusterControllerApi';
|
||||
import { ClusterController } from '../controller/clusterControllerApi';
|
||||
import { EndpointModel, BdcStatusModel } from '../controller/apiGenerated';
|
||||
import { showErrorMessage, Endpoint } from '../utils';
|
||||
import { AuthType } from '../constants';
|
||||
|
||||
export class BdcDashboardModel {
|
||||
|
||||
private _clusterController: ClusterController;
|
||||
private _bdcStatus: BdcStatusModel;
|
||||
private _endpoints: EndpointModel[] = [];
|
||||
private _bdcStatusLastUpdated: Date;
|
||||
@@ -22,7 +24,8 @@ export class BdcDashboardModel {
|
||||
public onDidUpdateEndpoints = this._onDidUpdateEndpoints.event;
|
||||
public onDidUpdateBdcStatus = this._onDidUpdateBdcStatus.event;
|
||||
|
||||
constructor(private url: string, private username: string, private password: string) {
|
||||
constructor(url: string, auth: AuthType, username: string, private password: string, ignoreSslVerification = true) {
|
||||
this._clusterController = new ClusterController(url, auth, username, password, ignoreSslVerification);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@@ -44,18 +47,20 @@ export class BdcDashboardModel {
|
||||
|
||||
public async refresh(): Promise<void> {
|
||||
await Promise.all([
|
||||
getBdcStatus(this.url, this.username, this.password, true).then(response => {
|
||||
this._clusterController.getBdcStatus().then(response => {
|
||||
this._bdcStatus = response.bdcStatus;
|
||||
this._bdcStatusLastUpdated = new Date();
|
||||
this._onDidUpdateBdcStatus.fire(this.bdcStatus);
|
||||
}),
|
||||
getEndPoints(this.url, this.username, this.password, true).then(response => {
|
||||
this._clusterController.getEndPoints().then(response => {
|
||||
this._endpoints = response.endPoints || [];
|
||||
fixEndpoints(this._endpoints);
|
||||
this._endpointsLastUpdated = new Date();
|
||||
this._onDidUpdateEndpoints.fire(this.serviceEndpoints);
|
||||
})
|
||||
]).catch(error => showErrorMessage(error));
|
||||
]).catch(error => {
|
||||
showErrorMessage(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,11 +13,13 @@ import { AddControllerNode } from './addControllerNode';
|
||||
import { ControllerRootNode, ControllerNode } from './controllerTreeNode';
|
||||
import { showErrorMessage } from '../utils';
|
||||
import { LoadingControllerNode } from './loadingControllerNode';
|
||||
import { AuthType } from '../constants';
|
||||
|
||||
const CredentialNamespace = 'clusterControllerCredentials';
|
||||
|
||||
interface IControllerInfoSlim {
|
||||
url: string;
|
||||
auth: AuthType;
|
||||
username: string;
|
||||
password?: string;
|
||||
rememberPassword: boolean;
|
||||
@@ -57,17 +59,18 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
|
||||
public addController(
|
||||
url: string,
|
||||
auth: AuthType,
|
||||
username: string,
|
||||
password: string,
|
||||
rememberPassword: boolean
|
||||
): void {
|
||||
this.removeNonControllerNodes();
|
||||
this.root.addControllerNode(url, username, password, rememberPassword);
|
||||
this.root.addControllerNode(url, auth, username, password, rememberPassword);
|
||||
this.notifyNodeChanged();
|
||||
}
|
||||
|
||||
public deleteController(url: string, username: string): ControllerNode {
|
||||
let deleted = this.root.deleteControllerNode(url, username);
|
||||
public deleteController(url: string, auth: AuthType, username: string): ControllerNode {
|
||||
let deleted = this.root.deleteControllerNode(url, auth, username);
|
||||
if (deleted) {
|
||||
this.notifyNodeChanged();
|
||||
}
|
||||
@@ -115,8 +118,12 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
if (c.rememberPassword) {
|
||||
password = await this.getPassword(c.url, c.username);
|
||||
}
|
||||
if (!c.auth) {
|
||||
// Added before we had added authentication
|
||||
c.auth = 'basic';
|
||||
}
|
||||
this.root.addChild(new ControllerNode(
|
||||
c.url, c.username, password, c.rememberPassword,
|
||||
c.url, c.auth, c.username, password, c.rememberPassword,
|
||||
undefined, this.root, this, undefined
|
||||
));
|
||||
}
|
||||
@@ -135,6 +142,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
let controller = e as ControllerNode;
|
||||
return {
|
||||
url: controller.url,
|
||||
auth: controller.auth,
|
||||
username: controller.username,
|
||||
password: controller.password,
|
||||
rememberPassword: !!controller.rememberPassword
|
||||
@@ -144,6 +152,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
let controllersWithoutPassword = controllers.map((e): IControllerInfoSlim => {
|
||||
return {
|
||||
url: e.url,
|
||||
auth: e.auth,
|
||||
username: e.username,
|
||||
rememberPassword: e.rememberPassword
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { IconPathHelper, BdcItemType, IconPath } from '../constants';
|
||||
import { IconPathHelper, BdcItemType, IconPath, AuthType } from '../constants';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -108,27 +108,28 @@ export class ControllerRootNode extends ControllerTreeNode {
|
||||
|
||||
public addControllerNode(
|
||||
url: string,
|
||||
auth: AuthType,
|
||||
username: string,
|
||||
password: string,
|
||||
rememberPassword: boolean
|
||||
): void {
|
||||
let controllerNode = this.getExistingControllerNode(url, username);
|
||||
let controllerNode = this.getExistingControllerNode(url, auth, username);
|
||||
if (controllerNode) {
|
||||
controllerNode.password = password;
|
||||
controllerNode.rememberPassword = rememberPassword;
|
||||
controllerNode.clearChildren();
|
||||
} else {
|
||||
controllerNode = new ControllerNode(url, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
|
||||
controllerNode = new ControllerNode(url, auth, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
|
||||
this.addChild(controllerNode);
|
||||
}
|
||||
}
|
||||
|
||||
public deleteControllerNode(url: string, username: string): ControllerNode {
|
||||
if (!url || !username) {
|
||||
public deleteControllerNode(url: string, auth: AuthType, username: string): ControllerNode {
|
||||
if (!url || (auth === 'basic' && !username)) {
|
||||
return undefined;
|
||||
}
|
||||
let nodes = this.children as ControllerNode[];
|
||||
let index = nodes.findIndex(e => e.url === url && e.username === username);
|
||||
let index = nodes.findIndex(e => isControllerMatch(e, url, auth, username));
|
||||
let deleted = undefined;
|
||||
if (index >= 0) {
|
||||
deleted = nodes.splice(index, 1);
|
||||
@@ -136,12 +137,12 @@ export class ControllerRootNode extends ControllerTreeNode {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
private getExistingControllerNode(url: string, username: string): ControllerNode {
|
||||
private getExistingControllerNode(url: string, auth: AuthType, username: string): ControllerNode {
|
||||
if (!url || !username) {
|
||||
return undefined;
|
||||
}
|
||||
let nodes = this.children as ControllerNode[];
|
||||
return nodes.find(e => e.url === url && e.username === username);
|
||||
return nodes.find(e => isControllerMatch(e, url, auth, username));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +150,7 @@ export class ControllerNode extends ControllerTreeNode {
|
||||
|
||||
constructor(
|
||||
private _url: string,
|
||||
private _auth: AuthType,
|
||||
private _username: string,
|
||||
private _password: string,
|
||||
private _rememberPassword: boolean,
|
||||
@@ -177,26 +179,24 @@ export class ControllerNode extends ControllerTreeNode {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
return url.trim().replace(/ /g, '').replace(/^.+\:\/\//, '').replace(/:(\d+)$/, ',$1');
|
||||
return url.trim().replace(/ /g, '').replace(/^.+\:\/\//, '');
|
||||
}
|
||||
|
||||
public get url() {
|
||||
public get url(): string {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
public set url(url: string) {
|
||||
this._url = url;
|
||||
|
||||
public get auth(): AuthType {
|
||||
return this._auth;
|
||||
}
|
||||
|
||||
public get username() {
|
||||
|
||||
public get username(): string {
|
||||
return this._username;
|
||||
}
|
||||
|
||||
public set username(username: string) {
|
||||
this._username = username;
|
||||
}
|
||||
|
||||
public get password() {
|
||||
public get password(): string {
|
||||
return this._password;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,15 @@ export class ControllerNode extends ControllerTreeNode {
|
||||
}
|
||||
|
||||
public set label(label: string) {
|
||||
super.label = label || `controller: ${ControllerNode.toIpAndPort(this._url)} (${this._username})`;
|
||||
super.label = label || this.generateLabel();
|
||||
}
|
||||
|
||||
private generateLabel(): string {
|
||||
let label = `controller: ${ControllerNode.toIpAndPort(this._url)}`;
|
||||
if (this._auth === 'basic') {
|
||||
label += ` (${this._username})`;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
@@ -229,3 +237,7 @@ export class ControllerNode extends ControllerTreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
function isControllerMatch(node: ControllerNode, url: string, auth: string, username: string): unknown {
|
||||
return node.url === url && node.auth === auth && node.username === username;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,10 @@ export function showErrorMessage(error: any, prefixText?: string): void {
|
||||
if (typeof error === 'string') {
|
||||
text += error as string;
|
||||
} else if (typeof error === 'object' && error !== null) {
|
||||
let message = error.message;
|
||||
let code = error.code || error.errno;
|
||||
text += `${message}${code ? ` (${code})` : ''}`;
|
||||
text += error.message;
|
||||
if (error.code && error.code > 0) {
|
||||
text += ` (${error.code})`;
|
||||
}
|
||||
} else {
|
||||
text += `${error}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user