mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Extensions Cleanup (#359)
* clean up extensions * updated copyrights * formatting
This commit is contained in:
0
extensions/account-provider-azure/.gitignore
vendored
Normal file
0
extensions/account-provider-azure/.gitignore
vendored
Normal file
3
extensions/account-provider-azure/.vscodeignore
Normal file
3
extensions/account-provider-azure/.vscodeignore
Normal file
@@ -0,0 +1,3 @@
|
||||
src/**
|
||||
tsconfig.json
|
||||
npm-shrinkwrap.json
|
||||
348
extensions/account-provider-azure/npm-shrinkwrap.json
generated
Normal file
348
extensions/account-provider-azure/npm-shrinkwrap.json
generated
Normal file
@@ -0,0 +1,348 @@
|
||||
{
|
||||
"name": "account-provider-azure",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "8.5.1",
|
||||
"from": "@types/node@>=8.0.47 <9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.1.tgz"
|
||||
},
|
||||
"adal-node": {
|
||||
"version": "0.1.25",
|
||||
"from": "adal-node@0.1.25",
|
||||
"resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.25.tgz"
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"from": "ansi-regex@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz"
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "2.2.1",
|
||||
"from": "ansi-styles@>=2.2.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz"
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.1.11",
|
||||
"from": "asn1@0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz"
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "0.1.5",
|
||||
"from": "assert-plus@>=0.1.5 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.0",
|
||||
"from": "async@>=0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz"
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.5.0",
|
||||
"from": "aws-sign2@>=0.5.0 <0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz"
|
||||
},
|
||||
"base64url": {
|
||||
"version": "2.0.0",
|
||||
"from": "base64url@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz"
|
||||
},
|
||||
"bl": {
|
||||
"version": "1.0.3",
|
||||
"from": "bl@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz"
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "2.11.0",
|
||||
"from": "bluebird@>=2.9.30 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz"
|
||||
},
|
||||
"boom": {
|
||||
"version": "2.10.1",
|
||||
"from": "boom@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
|
||||
},
|
||||
"buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"from": "buffer-equal-constant-time@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz"
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.11.0",
|
||||
"from": "caseless@>=0.11.0 <0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"from": "chalk@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.5",
|
||||
"from": "combined-stream@>=1.0.1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz"
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.12.2",
|
||||
"from": "commander@>=2.8.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz"
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"from": "core-util-is@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "2.0.5",
|
||||
"from": "cryptiles@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
|
||||
},
|
||||
"ctype": {
|
||||
"version": "0.5.3",
|
||||
"from": "ctype@0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz"
|
||||
},
|
||||
"date-utils": {
|
||||
"version": "1.2.21",
|
||||
"from": "date-utils@*",
|
||||
"resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz"
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"from": "delayed-stream@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||
},
|
||||
"ecdsa-sig-formatter": {
|
||||
"version": "1.0.9",
|
||||
"from": "ecdsa-sig-formatter@1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz"
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"from": "escape-string-regexp@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.1",
|
||||
"from": "extend@>=3.0.0 <3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz"
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"from": "forever-agent@>=0.6.0 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
|
||||
},
|
||||
"form-data": {
|
||||
"version": "1.0.1",
|
||||
"from": "form-data@>=1.0.0-rc1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz"
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.0.0",
|
||||
"from": "generate-function@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
|
||||
},
|
||||
"generate-object-property": {
|
||||
"version": "1.2.0",
|
||||
"from": "generate-object-property@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz"
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "1.8.0",
|
||||
"from": "har-validator@>=1.6.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz"
|
||||
},
|
||||
"has-ansi": {
|
||||
"version": "2.0.0",
|
||||
"from": "has-ansi@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
|
||||
},
|
||||
"hawk": {
|
||||
"version": "3.1.3",
|
||||
"from": "hawk@>=3.1.0 <3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz"
|
||||
},
|
||||
"hoek": {
|
||||
"version": "2.16.3",
|
||||
"from": "hoek@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "0.11.0",
|
||||
"from": "http-signature@>=0.11.0 <0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"from": "inherits@>=2.0.1 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "2.17.1",
|
||||
"from": "is-my-json-valid@>=2.12.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz"
|
||||
},
|
||||
"is-property": {
|
||||
"version": "1.0.2",
|
||||
"from": "is-property@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"from": "isarray@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"from": "isstream@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"from": "json-stringify-safe@>=5.0.0 <5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "4.0.1",
|
||||
"from": "jsonpointer@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz"
|
||||
},
|
||||
"jwa": {
|
||||
"version": "1.1.5",
|
||||
"from": "jwa@>=1.1.4 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz"
|
||||
},
|
||||
"jws": {
|
||||
"version": "3.1.4",
|
||||
"from": "jws@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz"
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.4",
|
||||
"from": "lodash@>=4.14.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz"
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.30.0",
|
||||
"from": "mime-db@>=1.30.0 <1.31.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz"
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.17",
|
||||
"from": "mime-types@>=2.1.2 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz"
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.8.2",
|
||||
"from": "oauth-sign@>=0.8.0 <0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz"
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"from": "process-nextick-args@>=1.0.6 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz"
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"from": "punycode@>=1.4.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz"
|
||||
},
|
||||
"qs": {
|
||||
"version": "5.1.0",
|
||||
"from": "qs@>=5.1.0 <5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz"
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.0.6",
|
||||
"from": "readable-stream@>=2.0.5 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz"
|
||||
},
|
||||
"request": {
|
||||
"version": "2.63.0",
|
||||
"from": "request@2.63.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.63.0.tgz",
|
||||
"dependencies": {
|
||||
"node-uuid": {
|
||||
"version": "1.4.8",
|
||||
"from": "node-uuid@>=1.4.0 <1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"from": "safe-buffer@>=5.0.1 <6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
|
||||
},
|
||||
"sntp": {
|
||||
"version": "1.0.9",
|
||||
"from": "sntp@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@>=0.10.0 <0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
|
||||
},
|
||||
"stringstream": {
|
||||
"version": "0.0.5",
|
||||
"from": "stringstream@>=0.0.4 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"from": "strip-ansi@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "2.0.0",
|
||||
"from": "supports-color@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.3",
|
||||
"from": "tough-cookie@>=0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz"
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.4.3",
|
||||
"from": "tunnel-agent@>=0.4.0 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz"
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.8.3",
|
||||
"from": "underscore@>=1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz"
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"from": "util-deprecate@>=1.0.1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.1.0",
|
||||
"from": "uuid@>=3.1.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz"
|
||||
},
|
||||
"vscode-nls": {
|
||||
"version": "2.0.2",
|
||||
"from": "vscode-nls@2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz"
|
||||
},
|
||||
"xmldom": {
|
||||
"version": "0.1.27",
|
||||
"from": "xmldom@>=0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz"
|
||||
},
|
||||
"xpath.js": {
|
||||
"version": "1.0.7",
|
||||
"from": "xpath.js@>=1.0.5 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.0.7.tgz"
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"from": "xtend@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/account-provider-azure/package.disabled.json
Normal file
17
extensions/account-provider-azure/package.disabled.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"accounts.azure.enableUsGovCloud": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%config.enableUsGovCloudDescription%"
|
||||
},
|
||||
"accounts.azure.enableChinaCloud": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%config.enableChinaCloudDescription%"
|
||||
},
|
||||
"accounts.azure.enableGermanyCloud": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%config.enableGermanyCloudDescription%"
|
||||
}
|
||||
}
|
||||
55
extensions/account-provider-azure/package.json
Normal file
55
extensions/account-provider-azure/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "account-provider-azure",
|
||||
"version": "0.0.1",
|
||||
"publisher": "Microsoft",
|
||||
"engines": { "vscode": "*" },
|
||||
"main": "./out/main",
|
||||
"activationEvents": [ "*" ],
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:account-provider-azure"
|
||||
},
|
||||
"dependencies": {
|
||||
"adal-node": "0.1.25",
|
||||
"request": "2.63.0",
|
||||
"vscode-nls": "2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^8.0.24"
|
||||
},
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "extension.clearTokenCache",
|
||||
"title": "%extension.clearTokenCache%",
|
||||
"category": "Azure Accounts"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Azure Account Configuration",
|
||||
"properties": {
|
||||
"accounts.azure.enablePublicCloud": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%config.enablePublicCloudDescription%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"account-type": [
|
||||
{
|
||||
"id": "microsoft",
|
||||
"icon": {
|
||||
"light": "./out/account-provider/media/microsoft_account_light.svg",
|
||||
"dark": "./out/account-provider/media/microsoft_account_dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "work_school",
|
||||
"icon": {
|
||||
"light": "./out/account-provider/media/work_school_account_light.svg",
|
||||
"dark": "./out/account-provider/media/work_school_account_dark.svg"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
7
extensions/account-provider-azure/package.nls.json
Normal file
7
extensions/account-provider-azure/package.nls.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extension.clearTokenCache": "Clear Azure Account Token Cache",
|
||||
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
||||
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
||||
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
|
||||
"config.enableGermanyCloudDescription": "Should Azure Germany integration be enabled"
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as adal from 'adal-node';
|
||||
import * as data from 'data';
|
||||
import * as request from 'request';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as vscode from 'vscode';
|
||||
import * as url from 'url';
|
||||
import {
|
||||
Arguments,
|
||||
AzureAccount,
|
||||
AzureAccountProviderMetadata,
|
||||
AzureAccountSecurityTokenCollection,
|
||||
Tenant
|
||||
} from './interfaces';
|
||||
import TokenCache from './tokenCache';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AzureAccountProvider implements data.AccountProvider {
|
||||
// CONSTANTS ///////////////////////////////////////////////////////////
|
||||
private static WorkSchoolAccountType: string = 'work_school';
|
||||
private static MicrosoftAccountType: string = 'microsoft';
|
||||
private static AadCommonTenant: string = 'common';
|
||||
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _args: Arguments;
|
||||
private _autoOAuthCancelled: boolean;
|
||||
private _commonAuthorityUrl: string;
|
||||
private _inProgressAutoOAuth: InProgressAutoOAuth;
|
||||
private _isInitialized: boolean;
|
||||
|
||||
constructor(private _metadata: AzureAccountProviderMetadata, private _tokenCache: TokenCache) {
|
||||
this._args = {
|
||||
host: this._metadata.settings.host,
|
||||
clientId: this._metadata.settings.clientId
|
||||
};
|
||||
this._autoOAuthCancelled = false;
|
||||
this._inProgressAutoOAuth = null;
|
||||
this._isInitialized = false;
|
||||
|
||||
this._commonAuthorityUrl = url.resolve(this._metadata.settings.host, AzureAccountProvider.AadCommonTenant);
|
||||
}
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public autoOAuthCancelled(): Thenable<void> {
|
||||
return this.doIfInitialized(() => this.cancelAutoOAuth());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all tokens that belong to the given account from the token cache
|
||||
* @param {"data".AccountKey} accountKey Key identifying the account to delete tokens for
|
||||
* @returns {Thenable<void>} Promise to clear requested tokens from the token cache
|
||||
*/
|
||||
public clear(accountKey: data.AccountKey): Thenable<void> {
|
||||
return this.doIfInitialized(() => this.clearAccountTokens(accountKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the entire token cache. Invoked by command palette action.
|
||||
* @returns {Thenable<void>} Promise to clear the token cache
|
||||
*/
|
||||
public clearTokenCache(): Thenable<void> {
|
||||
return this._tokenCache.clear();
|
||||
}
|
||||
|
||||
public getSecurityToken(account: AzureAccount): Thenable<AzureAccountSecurityTokenCollection> {
|
||||
return this.doIfInitialized(() => this.getAccessTokens(account));
|
||||
}
|
||||
|
||||
public initialize(restoredAccounts: data.Account[]): Thenable<data.Account[]> {
|
||||
let self = this;
|
||||
|
||||
let rehydrationTasks: Thenable<data.Account>[] = [];
|
||||
for (let account of restoredAccounts) {
|
||||
// Purge any invalid accounts
|
||||
if (!account) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Refresh the contextual logo based on whether the account is a MS account
|
||||
account.displayInfo.accountType = account.properties.isMsAccount
|
||||
? AzureAccountProvider.MicrosoftAccountType
|
||||
: AzureAccountProvider.WorkSchoolAccountType;
|
||||
|
||||
// Attempt to get fresh tokens. If this fails then the account is stale.
|
||||
// NOTE: Based on ADAL implementation, getting tokens should use the refresh token if necessary
|
||||
let task = this.getAccessTokens(account)
|
||||
.then(
|
||||
() => {
|
||||
return account;
|
||||
},
|
||||
() => {
|
||||
account.isStale = true;
|
||||
return account;
|
||||
}
|
||||
);
|
||||
rehydrationTasks.push(task);
|
||||
}
|
||||
|
||||
// Collect the rehydration tasks and mark the provider as initialized
|
||||
return Promise.all(rehydrationTasks)
|
||||
.then(accounts => {
|
||||
self._isInitialized = true;
|
||||
return accounts;
|
||||
});
|
||||
}
|
||||
|
||||
public prompt(): Thenable<AzureAccount> {
|
||||
return this.doIfInitialized(() => this.signIn(true));
|
||||
}
|
||||
|
||||
public refresh(account: AzureAccount): Thenable<AzureAccount> {
|
||||
return this.doIfInitialized(() => this.signIn(false));
|
||||
}
|
||||
|
||||
// PRIVATE METHODS /////////////////////////////////////////////////////
|
||||
private cancelAutoOAuth(): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
if (!this._inProgressAutoOAuth) {
|
||||
console.warn('Attempted to cancel auto OAuth when auto OAuth is not in progress!');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Indicate oauth was cancelled by the user
|
||||
let inProgress = self._inProgressAutoOAuth;
|
||||
self._autoOAuthCancelled = true;
|
||||
self._inProgressAutoOAuth = null;
|
||||
|
||||
// Use the auth context that was originally used to open the polling request, and cancel the polling
|
||||
let context = inProgress.context;
|
||||
context.cancelRequestToGetTokenWithDeviceCode(inProgress.userCodeInfo, err => {
|
||||
// Callback is only called in failure scenarios.
|
||||
if (err) {
|
||||
console.warn(`Error while cancelling auto OAuth: ${err}`);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private clearAccountTokens(accountKey: data.AccountKey): Thenable<void> {
|
||||
// Put together a query to look up any tokens associated with the account key
|
||||
let query = <adal.TokenResponse>{ userId: accountKey.accountId };
|
||||
|
||||
// 1) Look up the tokens associated with the query
|
||||
// 2) Remove them
|
||||
return this._tokenCache.findThenable(query)
|
||||
.then(results => this._tokenCache.removeThenable(results));
|
||||
}
|
||||
|
||||
private doIfInitialized<T>(op: () => Thenable<T>): Thenable<T> {
|
||||
return this._isInitialized
|
||||
? op()
|
||||
: Promise.reject(localize('accountProviderNotInitialized', 'Account provider not initialized, cannot perform action'));
|
||||
}
|
||||
|
||||
private getAccessTokens(account: AzureAccount): Thenable<AzureAccountSecurityTokenCollection> {
|
||||
let self = this;
|
||||
|
||||
let accessTokenPromises: Thenable<void>[] = [];
|
||||
let tokenCollection: AzureAccountSecurityTokenCollection = {};
|
||||
for (let tenant of account.properties.tenants) {
|
||||
let promise = new Promise<void>((resolve, reject) => {
|
||||
let authorityUrl = url.resolve(self._metadata.settings.host, tenant.id);
|
||||
let context = new adal.AuthenticationContext(authorityUrl, null, self._tokenCache);
|
||||
|
||||
context.acquireToken(
|
||||
self._metadata.settings.armResource.id,
|
||||
tenant.userId,
|
||||
self._metadata.settings.clientId,
|
||||
(error: Error, response: adal.TokenResponse | adal.ErrorResponse) => {
|
||||
// Handle errors first
|
||||
if (error) {
|
||||
// TODO: We'll assume for now that the account is stale, though that might not be accurate
|
||||
account.isStale = true;
|
||||
data.accounts.accountUpdated(account);
|
||||
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// We know that the response was not an error
|
||||
let tokenResponse = <adal.TokenResponse>response;
|
||||
|
||||
// Generate a token object and add it to the collection
|
||||
tokenCollection[tenant.id] = {
|
||||
expiresOn: tokenResponse.expiresOn,
|
||||
resource: tokenResponse.resource,
|
||||
token: tokenResponse.accessToken,
|
||||
tokenType: tokenResponse.tokenType
|
||||
};
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
accessTokenPromises.push(promise);
|
||||
}
|
||||
|
||||
// Wait until all the tokens have been acquired then return the collection
|
||||
return Promise.all(accessTokenPromises)
|
||||
.then(() => tokenCollection);
|
||||
}
|
||||
|
||||
private getDeviceLoginUserCode(): Thenable<InProgressAutoOAuth> {
|
||||
let self = this;
|
||||
|
||||
// Create authentication context and acquire user code
|
||||
return new Promise<InProgressAutoOAuth>((resolve, reject) => {
|
||||
let context = new adal.AuthenticationContext(self._commonAuthorityUrl, null, self._tokenCache);
|
||||
context.acquireUserCode(self._metadata.settings.signInResourceId, self._metadata.settings.clientId, vscode.env.language,
|
||||
(err, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
let result: InProgressAutoOAuth = {
|
||||
context: context,
|
||||
userCodeInfo: response
|
||||
};
|
||||
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private getDeviceLoginToken(oAuth: InProgressAutoOAuth, isAddAccount: boolean): Thenable<adal.TokenResponse> {
|
||||
let self = this;
|
||||
|
||||
// 1) Open the auto OAuth dialog
|
||||
// 2) Begin the acquiring token polling
|
||||
// 3) When that completes via callback, close the auto oauth
|
||||
let title = isAddAccount ?
|
||||
localize('addAccount', 'Add {0} account', self._metadata.displayName) :
|
||||
localize('refreshAccount', 'Refresh {0} account', self._metadata.displayName);
|
||||
return data.accounts.beginAutoOAuthDeviceCode(self._metadata.id, title, oAuth.userCodeInfo.message, oAuth.userCodeInfo.userCode, oAuth.userCodeInfo.verificationUrl)
|
||||
.then(() => {
|
||||
return new Promise<adal.TokenResponse>((resolve, reject) => {
|
||||
let context = oAuth.context;
|
||||
context.acquireTokenWithDeviceCode(self._metadata.settings.signInResourceId, self._metadata.settings.clientId, oAuth.userCodeInfo,
|
||||
(err, response) => {
|
||||
if (err) {
|
||||
if (self._autoOAuthCancelled) {
|
||||
// Auto OAuth was cancelled by the user, indicate this with the error we return
|
||||
reject(<data.UserCancelledSignInError>{ userCancelledSignIn: true });
|
||||
} else {
|
||||
// Auto OAuth failed for some other reason
|
||||
data.accounts.endAutoOAuthDeviceCode();
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
data.accounts.endAutoOAuthDeviceCode();
|
||||
resolve(<adal.TokenResponse>response);
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getTenants(userId: string, homeTenant: string): Thenable<Tenant[]> {
|
||||
let self = this;
|
||||
|
||||
// 1) Get a token we can use for looking up the tenant IDs
|
||||
// 2) Send a request to the ARM endpoint (the root management API) to get the list of tenant IDs
|
||||
// 3) For all the tenants
|
||||
// b) Get a token we can use for the AAD Graph API
|
||||
// a) Get the display name of the tenant
|
||||
// c) create a tenant object
|
||||
// 4) Sort to make sure the "home tenant" is the first tenant on the list
|
||||
return this.getToken(userId, AzureAccountProvider.AadCommonTenant, this._metadata.settings.armResource.id)
|
||||
.then((armToken: adal.TokenResponse) => {
|
||||
let tenantUri = url.resolve(self._metadata.settings.armResource.endpoint, 'tenants?api-version=2015-01-01');
|
||||
return self.makeWebRequest(armToken, tenantUri);
|
||||
})
|
||||
.then((tenantResponse: any[]) => {
|
||||
let promises: Thenable<Tenant>[] = tenantResponse.map(value => {
|
||||
return self.getToken(userId, value.tenantId, self._metadata.settings.graphResource.id)
|
||||
.then((graphToken: adal.TokenResponse) => {
|
||||
let tenantDetailsUri = url.resolve(self._metadata.settings.graphResource.endpoint, value.tenantId + '/');
|
||||
tenantDetailsUri = url.resolve(tenantDetailsUri, 'tenantDetails?api-version=2013-04-05');
|
||||
return self.makeWebRequest(graphToken, tenantDetailsUri);
|
||||
})
|
||||
.then((tenantDetails: any) => {
|
||||
return <Tenant>{
|
||||
id: value.tenantId,
|
||||
userId: userId,
|
||||
displayName: tenantDetails.length && tenantDetails[0].displayName
|
||||
? tenantDetails[0].displayName
|
||||
: localize('azureWorkAccountDisplayName', 'Work or school account')
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then((tenants: Tenant[]) => {
|
||||
let homeTenantIndex = tenants.findIndex(tenant => tenant.id === homeTenant);
|
||||
if (homeTenantIndex >= 0) {
|
||||
let homeTenant = tenants.splice(homeTenantIndex, 1);
|
||||
tenants.unshift(homeTenant[0]);
|
||||
}
|
||||
return tenants;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a token for the given user ID for the specific tenant ID. If the token can, it
|
||||
* will be retrieved from the cache as per the ADAL API. AFAIK, the ADAL API will also utilize
|
||||
* the refresh token if there aren't any unexpired tokens to use.
|
||||
* @param {string} userId ID of the user to get a token for
|
||||
* @param {string} tenantId Tenant to get the token for
|
||||
* @param {string} resourceId ID of the resource the token will be good for
|
||||
* @returns {Thenable<TokenResponse>} Promise to return a token. Rejected if retrieving the token fails.
|
||||
*/
|
||||
private getToken(userId: string, tenantId: string, resourceId: string): Thenable<adal.TokenResponse> {
|
||||
let self = this;
|
||||
|
||||
return new Promise<adal.TokenResponse>((resolve, reject) => {
|
||||
let authorityUrl = url.resolve(self._metadata.settings.host, tenantId);
|
||||
let context = new adal.AuthenticationContext(authorityUrl, null, self._tokenCache);
|
||||
context.acquireToken(resourceId, userId, self._metadata.settings.clientId,
|
||||
(error: Error, response: adal.TokenResponse | adal.ErrorResponse) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(<adal.TokenResponse>response);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a web request using the provided bearer token
|
||||
* @param {TokenResponse} accessToken Bearer token for accessing the provided URI
|
||||
* @param {string} uri URI to access
|
||||
* @returns {Thenable<any>} Promise to return the deserialized body of the request. Rejected if error occurred.
|
||||
*/
|
||||
private makeWebRequest(accessToken: adal.TokenResponse, uri: string): Thenable<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
// Setup parameters for the request
|
||||
// NOTE: setting json true means the returned object will be deserialized
|
||||
let params = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken.accessToken}`
|
||||
},
|
||||
json: true
|
||||
};
|
||||
|
||||
// Setup the callback to resolve/reject this promise
|
||||
let callback = (error, response, body: { error: any; value: any; }) => {
|
||||
if (error || body.error) {
|
||||
reject(error || JSON.stringify(body.error));
|
||||
} else {
|
||||
resolve(body.value);
|
||||
}
|
||||
};
|
||||
|
||||
// Make the request
|
||||
request.get(uri, params, callback);
|
||||
});
|
||||
}
|
||||
|
||||
private signIn(isAddAccount: boolean): Thenable<AzureAccount> {
|
||||
let self = this;
|
||||
|
||||
// 1) Get the user code for this login
|
||||
// 2) Get an access token from the device code
|
||||
// 3) Get the list of tenants
|
||||
// 4) Generate the AzureAccount object and return it
|
||||
let tokenResponse: adal.TokenResponse = null;
|
||||
return this.getDeviceLoginUserCode()
|
||||
.then((result: InProgressAutoOAuth) => {
|
||||
self._autoOAuthCancelled = false;
|
||||
self._inProgressAutoOAuth = result;
|
||||
return self.getDeviceLoginToken(self._inProgressAutoOAuth, isAddAccount);
|
||||
})
|
||||
.then((response: adal.TokenResponse) => {
|
||||
tokenResponse = response;
|
||||
self._autoOAuthCancelled = false;
|
||||
self._inProgressAutoOAuth = null;
|
||||
return self.getTenants(tokenResponse.userId, tokenResponse.userId);
|
||||
})
|
||||
.then((tenants: Tenant[]) => {
|
||||
// Figure out where we're getting the identity from
|
||||
let identityProvider = tokenResponse.identityProvider;
|
||||
if (identityProvider) {
|
||||
identityProvider = identityProvider.toLowerCase();
|
||||
}
|
||||
|
||||
// Determine if this is a microsoft account
|
||||
let msa = identityProvider && (
|
||||
identityProvider.indexOf('live.com') !== -1 ||
|
||||
identityProvider.indexOf('live-int.com') !== -1 ||
|
||||
identityProvider.indexOf('f8cdef31-a31e-4b4a-93e4-5f571e91255a') !== -1 ||
|
||||
identityProvider.indexOf('ea8a4392-515e-481f-879e-6571ff2a8a36') !== -1);
|
||||
|
||||
// Calculate the display name for the user
|
||||
let displayName = (tokenResponse.givenName && tokenResponse.familyName)
|
||||
? `${tokenResponse.givenName} ${tokenResponse.familyName}`
|
||||
: tokenResponse.userId;
|
||||
|
||||
// Calculate the home tenant display name to use for the contextual display name
|
||||
let contextualDisplayName = msa
|
||||
? localize('microsoftAccountDisplayName', 'Microsoft Account')
|
||||
: tenants[0].displayName;
|
||||
|
||||
// Calculate the account type
|
||||
let accountType = msa
|
||||
? AzureAccountProvider.MicrosoftAccountType
|
||||
: AzureAccountProvider.WorkSchoolAccountType;
|
||||
|
||||
return <AzureAccount>{
|
||||
key: {
|
||||
providerId: self._metadata.id,
|
||||
accountId: tokenResponse.userId
|
||||
},
|
||||
name: tokenResponse.userId,
|
||||
displayInfo: {
|
||||
accountType: accountType,
|
||||
contextualDisplayName: contextualDisplayName,
|
||||
displayName: displayName
|
||||
},
|
||||
properties: {
|
||||
isMsAccount: msa,
|
||||
tenants: tenants
|
||||
},
|
||||
isStale: false
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface InProgressAutoOAuth {
|
||||
context: adal.AuthenticationContext;
|
||||
userCodeInfo: adal.UserCodeInfo;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as constants from '../constants';
|
||||
import * as data from 'data';
|
||||
import * as events from 'events';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import CredentialServiceTokenCache from './tokenCache';
|
||||
import providerSettings from './providerSettings';
|
||||
import { AzureAccountProvider } from './azureAccountProvider';
|
||||
import { AzureAccountProviderMetadata, ProviderSettings } from './interfaces';
|
||||
|
||||
let localize = nls.loadMessageBundle();
|
||||
|
||||
export class AzureAccountProviderService implements vscode.Disposable {
|
||||
// CONSTANTS ///////////////////////////////////////////////////////////////
|
||||
private static CommandClearTokenCache = 'extension.clearTokenCache';
|
||||
private static ConfigurationSection = 'accounts.azure';
|
||||
private static CredentialNamespace = 'azureAccountProviderCredentials';
|
||||
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////////
|
||||
private _accountDisposals: { [accountProviderId: string]: vscode.Disposable };
|
||||
private _accountProviders: { [accountProviderId: string]: AzureAccountProvider };
|
||||
private _credentialProvider: data.CredentialProvider;
|
||||
private _configChangePromiseChain: Thenable<void>;
|
||||
private _currentConfig: vscode.WorkspaceConfiguration;
|
||||
private _event: events.EventEmitter;
|
||||
|
||||
constructor(private _context: vscode.ExtensionContext, private _userStoragePath: string) {
|
||||
this._accountDisposals = {};
|
||||
this._accountProviders = {};
|
||||
this._configChangePromiseChain = Promise.resolve();
|
||||
this._currentConfig = null;
|
||||
this._event = new events.EventEmitter();
|
||||
}
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public activate(): Thenable<boolean> {
|
||||
let self = this;
|
||||
|
||||
// Register commands
|
||||
this._context.subscriptions.push(vscode.commands.registerCommand(
|
||||
AzureAccountProviderService.CommandClearTokenCache,
|
||||
() => { self._event.emit(AzureAccountProviderService.CommandClearTokenCache); }
|
||||
));
|
||||
this._event.on(AzureAccountProviderService.CommandClearTokenCache, () => { self.onClearTokenCache(); });
|
||||
|
||||
// 1) Get a credential provider
|
||||
// 2a) Store the credential provider for use later
|
||||
// 2b) Register the configuration change handler
|
||||
// 2c) Perform an initial config change handling
|
||||
return data.credentials.getProvider(AzureAccountProviderService.CredentialNamespace)
|
||||
.then(credProvider => {
|
||||
self._credentialProvider = credProvider;
|
||||
|
||||
self._context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(self.onDidChangeConfiguration, self));
|
||||
self.onDidChangeConfiguration();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() { }
|
||||
|
||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||
private onClearTokenCache(): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
let promises: Thenable<void>[] = providerSettings.map(provider => {
|
||||
return self._accountProviders[provider.metadata.id].clearTokenCache();
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(
|
||||
() => {
|
||||
let message = localize('clearTokenCacheSuccess', 'Token cache successfully cleared');
|
||||
vscode.window.showInformationMessage(`${constants.extensionName}: ${message}`);
|
||||
},
|
||||
err => {
|
||||
let message = localize('clearTokenCacheFailure', 'Failed to clear token cache');
|
||||
vscode.window.showErrorMessage(`${constants.extensionName}: ${message}: ${err}`);
|
||||
});
|
||||
}
|
||||
|
||||
private onDidChangeConfiguration(): void {
|
||||
let self = this;
|
||||
|
||||
// Add a new change processing onto the existing promise change
|
||||
this._configChangePromiseChain = this._configChangePromiseChain.then(() => {
|
||||
// Grab the stored config and the latest config
|
||||
let newConfig = vscode.workspace.getConfiguration(AzureAccountProviderService.ConfigurationSection);
|
||||
let oldConfig = self._currentConfig;
|
||||
self._currentConfig = newConfig;
|
||||
|
||||
// Determine what providers need to be changed
|
||||
let providerChanges: Thenable<void>[] = [];
|
||||
for (let provider of providerSettings) {
|
||||
// If the old config doesn't exist, then assume everything was disabled
|
||||
// There will always be a new config value
|
||||
let oldConfigValue = oldConfig
|
||||
? oldConfig.get<boolean>(provider.configKey)
|
||||
: false;
|
||||
let newConfigValue = newConfig.get<boolean>(provider.configKey);
|
||||
|
||||
// Case 1: Provider config has not changed - do nothing
|
||||
if (oldConfigValue === newConfigValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Case 2: Provider was enabled and is now disabled - unregister provider
|
||||
if (oldConfigValue && !newConfigValue) {
|
||||
providerChanges.push(self.unregisterAccountProvider(provider));
|
||||
}
|
||||
|
||||
// Case 3: Provider was disabled and is now enabled - register provider
|
||||
if (!oldConfigValue && newConfigValue) {
|
||||
providerChanges.push(self.registerAccountProvider(provider));
|
||||
}
|
||||
}
|
||||
|
||||
// Process all the changes before continuing
|
||||
return Promise.all(providerChanges);
|
||||
}).then(null, () => { return Promise.resolve(); });
|
||||
}
|
||||
|
||||
private registerAccountProvider(provider: ProviderSettings): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let tokenCacheKey = `azureTokenCache-${provider.metadata.id}`;
|
||||
let tokenCachePath = path.join(this._userStoragePath, tokenCacheKey);
|
||||
let tokenCache = new CredentialServiceTokenCache(self._credentialProvider, tokenCacheKey, tokenCachePath);
|
||||
let accountProvider = new AzureAccountProvider(<AzureAccountProviderMetadata>provider.metadata, tokenCache);
|
||||
self._accountProviders[provider.metadata.id] = accountProvider;
|
||||
self._accountDisposals[provider.metadata.id] = data.accounts.registerAccountProvider(provider.metadata, accountProvider);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
console.error(`Failed to register account provider: ${e}`);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private unregisterAccountProvider(provider: ProviderSettings): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
self._accountDisposals[provider.metadata.id].dispose();
|
||||
delete self._accountProviders[provider.metadata.id];
|
||||
delete self._accountDisposals[provider.metadata.id];
|
||||
resolve();
|
||||
} catch (e) {
|
||||
console.error(`Failed to unregister account provider: ${e}`);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
'use strict';
|
||||
|
||||
import * as data from 'data';
|
||||
|
||||
/**
|
||||
* Represents a tenant (an Azure Active Directory instance) to which a user has access
|
||||
*/
|
||||
export interface Tenant {
|
||||
/**
|
||||
* Globally unique identifier of the tenant
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Display name of the tenant
|
||||
*/
|
||||
displayName: string;
|
||||
|
||||
/**
|
||||
* Identifier of the user in the tenant
|
||||
*/
|
||||
userId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a resource exposed by an Azure Active Directory
|
||||
*/
|
||||
export interface Resource {
|
||||
/**
|
||||
* Identifier of the resource
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Endpoint url used to access the resource
|
||||
*/
|
||||
endpoint: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the arguments that identify an instantiation of the AAD account provider
|
||||
*/
|
||||
export interface Arguments {
|
||||
/**
|
||||
* Host of the authority
|
||||
*/
|
||||
host: string;
|
||||
|
||||
/**
|
||||
* Identifier of the client application
|
||||
*/
|
||||
clientId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents settings for an AAD account provider
|
||||
*/
|
||||
export interface Settings {
|
||||
/**
|
||||
* Host of the authority
|
||||
*/
|
||||
host?: string;
|
||||
|
||||
/**
|
||||
* Identifier of the client application
|
||||
*/
|
||||
clientId?: string;
|
||||
|
||||
/**
|
||||
* Identifier of the resource to request when signing in
|
||||
*/
|
||||
signInResourceId?: string;
|
||||
|
||||
/**
|
||||
* Information that describes the AAD graph resource
|
||||
*/
|
||||
graphResource?: Resource;
|
||||
|
||||
/**
|
||||
* Information that describes the Azure resource management resource
|
||||
*/
|
||||
armResource?: Resource;
|
||||
|
||||
/**
|
||||
* A list of tenant IDs to authenticate against. If defined, then these IDs will be used
|
||||
* instead of querying the tenants endpoint of the armResource
|
||||
*/
|
||||
adTenants?: string[];
|
||||
|
||||
// AuthorizationCodeGrantFlowSettings //////////////////////////////////
|
||||
|
||||
/**
|
||||
* An optional site ID that brands the interactive aspect of sign in
|
||||
*/
|
||||
siteId?: string;
|
||||
|
||||
/**
|
||||
* Redirect URI that is used to signify the end of the interactive aspect of sign it
|
||||
*/
|
||||
redirectUri?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping of configuration key with the metadata to instantiate the account provider
|
||||
*/
|
||||
export interface ProviderSettings {
|
||||
/**
|
||||
* Key for configuration regarding whether the account provider is enabled
|
||||
*/
|
||||
configKey: string;
|
||||
|
||||
/**
|
||||
* Metadata for the provider
|
||||
*/
|
||||
metadata: AzureAccountProviderMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of account provider metadata to override settings type for Azure account providers
|
||||
*/
|
||||
export interface AzureAccountProviderMetadata extends data.AccountProviderMetadata {
|
||||
/**
|
||||
* Azure specific account provider settings.
|
||||
*/
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties specific to an Azure account
|
||||
*/
|
||||
export interface AzureAccountProperties {
|
||||
/**
|
||||
* Whether or not the account is a Microsoft account
|
||||
*/
|
||||
isMsAccount: boolean;
|
||||
|
||||
/**
|
||||
* A list of tenants (aka directories) that the account belongs to
|
||||
*/
|
||||
tenants: Tenant[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Override of the Account type to enforce properties that are AzureAccountProperties
|
||||
*/
|
||||
export interface AzureAccount extends data.Account {
|
||||
/**
|
||||
* AzureAccountProperties specifically used for Azure accounts
|
||||
*/
|
||||
properties: AzureAccountProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token returned from a request for an access token
|
||||
*/
|
||||
export interface AzureAccountSecurityToken {
|
||||
/**
|
||||
* Access token, itself
|
||||
*/
|
||||
token: string;
|
||||
|
||||
/**
|
||||
* Date that the token expires on
|
||||
*/
|
||||
expiresOn: Date | string;
|
||||
|
||||
/**
|
||||
* Name of the resource the token is good for (ie, management.core.windows.net)
|
||||
*/
|
||||
resource: string;
|
||||
|
||||
/**
|
||||
* Type of the token (pretty much always 'Bearer')
|
||||
*/
|
||||
tokenType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Azure account security token maps a tenant ID to the information returned from a request to get
|
||||
* an access token. The list of tenants correspond to the tenants in the account properties.
|
||||
*/
|
||||
export type AzureAccountSecurityTokenCollection = {[tenantId: string]: AzureAccountSecurityToken};
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><defs><style>.cls-1{fill:#fff;}</style></defs><title>microsoft_logo-white</title><rect class="cls-1" x="0.06" y="0.06" width="5.65" height="5.65"/><rect class="cls-1" x="6.29" y="0.06" width="5.65" height="5.65"/><rect class="cls-1" x="0.06" y="6.3" width="5.65" height="5.65"/><rect class="cls-1" x="6.29" y="6.3" width="5.65" height="5.65"/></svg>
|
||||
|
After Width: | Height: | Size: 442 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}</style></defs><title>microsoft_account_Blk</title><rect class="cls-1" width="7.6" height="7.6"/><rect class="cls-1" x="8.4" width="7.6" height="7.6"/><rect class="cls-1" y="8.4" width="7.6" height="7.6"/><rect class="cls-1" x="8.4" y="8.4" width="7.6" height="7.6"/></svg>
|
||||
|
After Width: | Height: | Size: 400 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>other_account_inverse_16x16</title><path class="cls-1" d="M7.92.29A7.71,7.71,0,1,0,15.64,8,7.73,7.73,0,0,0,7.92.29ZM4.12,13.54a4,4,0,0,1,.13-.93,3.69,3.69,0,0,1,1-1.66A3.56,3.56,0,0,1,6,10.37a4,4,0,0,1,.9-.38,4.17,4.17,0,0,1,1-.13,3.79,3.79,0,0,1,1.48.29,3.61,3.61,0,0,1,2,2,3.74,3.74,0,0,1,.29,1.47,6.62,6.62,0,0,1-3.7,1.12A6.71,6.71,0,0,1,4.12,13.54Zm2-5a2.76,2.76,0,0,1-.54-.79,2.43,2.43,0,0,1-.19-1,2.4,2.4,0,0,1,.19-1A2.82,2.82,0,0,1,6.1,5a2.81,2.81,0,0,1,.8-.54,2.4,2.4,0,0,1,1-.19,2.43,2.43,0,0,1,1,.19A2.76,2.76,0,0,1,9.63,5a2.46,2.46,0,0,1,.54.8,2.4,2.4,0,0,1,.2,1,2.44,2.44,0,0,1-.2,1A2.59,2.59,0,0,1,8.84,9a2.44,2.44,0,0,1-1,.2,2.4,2.4,0,0,1-1-.2A2.46,2.46,0,0,1,6.1,8.49Zm6.12,4.66a4.39,4.39,0,0,0-.18-.89,4.22,4.22,0,0,0-.57-1.19A4.24,4.24,0,0,0,9.36,9.49,3.41,3.41,0,0,0,10,9a3.36,3.36,0,0,0,.84-1.42A3.32,3.32,0,0,0,11,6.74a3.09,3.09,0,0,0-.24-1.22,3.26,3.26,0,0,0-.67-1,3,3,0,0,0-1-.67,3,3,0,0,0-1.22-.25,2.93,2.93,0,0,0-1.22.25A3.07,3.07,0,0,0,5,5.51a2.93,2.93,0,0,0-.25,1.22,3,3,0,0,0,.12.84,3,3,0,0,0,.32.76A3.3,3.3,0,0,0,5.7,9a3.06,3.06,0,0,0,.68.5,4.41,4.41,0,0,0-1.19.65,4.1,4.1,0,0,0-.91,1,4.18,4.18,0,0,0-.58,1.18,4.41,4.41,0,0,0-.18.8A6.66,6.66,0,0,1,1.2,8a6.72,6.72,0,1,1,11,5.15Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>other_account_16x16</title><path d="M8,.28A7.71,7.71,0,1,0,15.72,8,7.73,7.73,0,0,0,8,.28ZM4.2,13.53a4,4,0,0,1,.13-.93,3.69,3.69,0,0,1,1-1.66A3.56,3.56,0,0,1,6,10.36a4,4,0,0,1,.9-.38,4.17,4.17,0,0,1,1-.13,3.79,3.79,0,0,1,1.48.29,3.61,3.61,0,0,1,2,2,3.74,3.74,0,0,1,.29,1.47A6.62,6.62,0,0,1,8,14.71,6.71,6.71,0,0,1,4.2,13.53Zm2-5a2.76,2.76,0,0,1-.54-.79,2.43,2.43,0,0,1-.19-1,2.4,2.4,0,0,1,.19-1A2.82,2.82,0,0,1,6.18,5,2.81,2.81,0,0,1,7,4.42a2.4,2.4,0,0,1,1-.19,2.43,2.43,0,0,1,1,.19A2.76,2.76,0,0,1,9.71,5a2.46,2.46,0,0,1,.54.8,2.4,2.4,0,0,1,.2,1,2.44,2.44,0,0,1-.2,1A2.59,2.59,0,0,1,8.92,9a2.44,2.44,0,0,1-1,.2A2.4,2.4,0,0,1,7,9,2.46,2.46,0,0,1,6.18,8.49Zm6.12,4.66a4.39,4.39,0,0,0-.18-.89,4.22,4.22,0,0,0-.57-1.19A4.24,4.24,0,0,0,9.44,9.48a3.41,3.41,0,0,0,.68-.5A3.36,3.36,0,0,0,11,7.56a3.32,3.32,0,0,0,.11-.83,3.09,3.09,0,0,0-.24-1.22,3.26,3.26,0,0,0-.67-1,3,3,0,0,0-1-.67A3,3,0,0,0,7.95,3.6a2.93,2.93,0,0,0-1.22.25A3.07,3.07,0,0,0,5.07,5.51a2.93,2.93,0,0,0-.25,1.22,3,3,0,0,0,.12.84,3,3,0,0,0,.32.76A3.3,3.3,0,0,0,5.78,9a3.06,3.06,0,0,0,.68.5,4.41,4.41,0,0,0-1.19.65,4.1,4.1,0,0,0-.91,1,4.18,4.18,0,0,0-.58,1.18,4.41,4.41,0,0,0-.18.8A6.66,6.66,0,0,1,1.28,8a6.72,6.72,0,1,1,11,5.15Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ProviderSettings } from './interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const publicAzureSettings: ProviderSettings = {
|
||||
configKey: 'enablePublicCloud',
|
||||
metadata: {
|
||||
displayName: localize('publicCloudDisplayName', 'Azure'),
|
||||
id: 'azurePublicCloud',
|
||||
settings: {
|
||||
host: 'https://login.microsoftonline.com/',
|
||||
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
|
||||
signInResourceId: 'https://management.core.windows.net/',
|
||||
graphResource: {
|
||||
id: 'https://graph.windows.net/',
|
||||
endpoint: 'https://graph.windows.net'
|
||||
},
|
||||
armResource: {
|
||||
id: 'https://management.core.windows.net/',
|
||||
endpoint: 'https://management.azure.com'
|
||||
},
|
||||
redirectUri: 'http://localhost/redirect'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Leaving for reference
|
||||
const usGovAzureSettings: ProviderSettings = {
|
||||
configKey: 'enableUsGovCloud',
|
||||
metadata: {
|
||||
displayName: localize('usGovCloudDisplayName', 'Azure (US Government)'),
|
||||
id: 'usGovAzureCloud',
|
||||
settings: {
|
||||
host: 'https://login.microsoftonline.com/',
|
||||
clientId: 'TBD',
|
||||
signInResourceId: 'https://management.core.usgovcloudapi.net/',
|
||||
graphResource: {
|
||||
id: 'https://graph.usgovcloudapi.net/',
|
||||
endpoint: 'https://graph.usgovcloudapi.net'
|
||||
},
|
||||
armResource: {
|
||||
id: 'https://management.core.usgovcloudapi.net/',
|
||||
endpoint: 'https://management.usgovcloudapi.net'
|
||||
},
|
||||
redirectUri: 'http://localhost/redirect'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const chinaAzureSettings: ProviderSettings = {
|
||||
configKey: 'enableChinaCloud',
|
||||
metadata: {
|
||||
displayName: localize('chinaCloudDisplayName', 'Azure (China)'),
|
||||
id: 'chinaAzureCloud',
|
||||
settings: {
|
||||
host: 'https://login.chinacloudapi.cn/',
|
||||
clientId: 'TBD',
|
||||
signInResourceId: 'https://management.core.chinacloudapi.cn/',
|
||||
graphResource: {
|
||||
id: 'https://graph.chinacloudapi.cn/',
|
||||
endpoint: 'https://graph.chinacloudapi.cn'
|
||||
},
|
||||
armResource: {
|
||||
id: 'https://management.core.chinacloudapi.cn/',
|
||||
endpoint: 'https://managemement.chinacloudapi.net'
|
||||
},
|
||||
redirectUri: 'http://localhost/redirect'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const germanyAzureSettings: ProviderSettings = {
|
||||
configKey: 'enableGermanyCloud',
|
||||
metadata: {
|
||||
displayName: localize('germanyCloud', 'Azure (Germany)'),
|
||||
id: 'germanyAzureCloud',
|
||||
settings: {
|
||||
host: 'https://login.microsoftazure.de/',
|
||||
clientId: 'TBD',
|
||||
signInResourceId: 'https://management.core.cloudapi.de/',
|
||||
graphResource: {
|
||||
id: 'https://graph.cloudapi.de/',
|
||||
endpoint: 'https://graph.cloudapi.de'
|
||||
},
|
||||
armResource: {
|
||||
id: 'https://management.core.cloudapi.de/',
|
||||
endpoint: 'https://management.microsoftazure.de'
|
||||
},
|
||||
redirectUri: 'http://localhost/redirect'
|
||||
}
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
// TODO: Enable China, Germany, and US Gov clouds: (#3031)
|
||||
export default <ProviderSettings[]>[publicAzureSettings, /*chinaAzureSettings, germanyAzureSettings, usGovAzureSettings*/];
|
||||
@@ -0,0 +1,291 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as adal from 'adal-node';
|
||||
import * as data from 'data';
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export default class TokenCache implements adal.TokenCache {
|
||||
private static CipherAlgorithm = 'aes256';
|
||||
private static CipherAlgorithmIvLength = 16;
|
||||
private static CipherKeyLength = 32;
|
||||
private static FsOptions = { encoding: 'ascii' };
|
||||
|
||||
private _activeOperation: Thenable<any>;
|
||||
|
||||
constructor(
|
||||
private _credentialProvider: data.CredentialProvider,
|
||||
private _credentialServiceKey: string,
|
||||
private _cacheSerializationPath: string
|
||||
) {
|
||||
}
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public add(entries: adal.TokenResponse[], callback: (error: Error, result: boolean) => void): void {
|
||||
let self = this;
|
||||
|
||||
this.doOperation(() => {
|
||||
return self.readCache()
|
||||
.then(cache => self.addToCache(cache, entries))
|
||||
.then(updatedCache => self.writeCache(updatedCache))
|
||||
.then(
|
||||
() => callback(null, false),
|
||||
(err) => callback(err, true)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public clear(): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
// 1) Delete encrypted serialization file
|
||||
// If we got an 'ENOENT' response, the file doesn't exist, which is fine
|
||||
// 3) Delete the encryption key
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(self._cacheSerializationPath, err => {
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => { return self._credentialProvider.deleteCredential(self._credentialServiceKey); })
|
||||
.then(() => { });
|
||||
}
|
||||
|
||||
public find(query: any, callback: (error: Error, results: any[]) => void): void {
|
||||
let self = this;
|
||||
|
||||
this.doOperation(() => {
|
||||
return self.readCache()
|
||||
.then(cache => {
|
||||
return cache.filter(
|
||||
entry => TokenCache.findByPartial(entry, query)
|
||||
);
|
||||
})
|
||||
.then(
|
||||
results => callback(null, results),
|
||||
(err) => callback(err, null)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to make callback-based find method into a thenable method
|
||||
* @param query Partial object to use to look up tokens. Ideally should be partial of adal.TokenResponse
|
||||
* @returns {Thenable<any[]>} Promise to return the matching adal.TokenResponse objects.
|
||||
* Rejected if an error was sent in the callback
|
||||
*/
|
||||
public findThenable(query: any): Thenable<any[]> {
|
||||
let self = this;
|
||||
|
||||
return new Promise<any[]>((resolve, reject) => {
|
||||
self.find(query, (error: Error, results: any[]) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(results);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public remove(entries: adal.TokenResponse[], callback: (error: Error, result: null) => void): void {
|
||||
let self = this;
|
||||
|
||||
this.doOperation(() => {
|
||||
return this.readCache()
|
||||
.then(cache => self.removeFromCache(cache, entries))
|
||||
.then(updatedCache => self.writeCache(updatedCache))
|
||||
.then(
|
||||
() => callback(null, null),
|
||||
(err) => callback(err, null)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to make callback-based remove method into a thenable method
|
||||
* @param {TokenResponse[]} entries Array of entries to remove from the token cache
|
||||
* @returns {Thenable<void>} Promise to remove the given tokens from the token cache
|
||||
* Rejected if an error was sent in the callback
|
||||
*/
|
||||
public removeThenable(entries: adal.TokenResponse[]): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
self.remove(entries, (error: Error, result: null) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// PRIVATE METHODS /////////////////////////////////////////////////////
|
||||
private static findByKeyHelper(entry1: adal.TokenResponse, entry2: adal.TokenResponse): boolean {
|
||||
return entry1._authority === entry2._authority
|
||||
&& entry1._clientId === entry2._clientId
|
||||
&& entry1.userId === entry2.userId
|
||||
&& entry1.resource === entry2.resource;
|
||||
}
|
||||
|
||||
private static findByPartial(entry: adal.TokenResponse, query: object): boolean {
|
||||
for (let key in query) {
|
||||
if (entry[key] === undefined || entry[key] !== query[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private doOperation<T>(op: () => Thenable<T>): void {
|
||||
// Initialize the active operation to an empty promise if necessary
|
||||
let activeOperation = this._activeOperation || Promise.resolve<any>(null);
|
||||
|
||||
// Chain the operation to perform to the end of the existing promise
|
||||
activeOperation = activeOperation.then(op);
|
||||
|
||||
// Add a catch at the end to make sure we can continue after any errors
|
||||
activeOperation = activeOperation.then(null, err => {
|
||||
console.error(`Failed to perform token cache operation: ${err}`);
|
||||
});
|
||||
|
||||
// Point the current active operation to this one
|
||||
this._activeOperation = activeOperation;
|
||||
}
|
||||
|
||||
private addToCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] {
|
||||
// First remove entries from the db that are being updated
|
||||
cache = this.removeFromCache(cache, entries);
|
||||
|
||||
// Then add the new entries to the cache
|
||||
entries.forEach((entry: adal.TokenResponse) => {
|
||||
cache.push(entry);
|
||||
});
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
private getOrCreateEncryptionParams(): Thenable<EncryptionParams> {
|
||||
let self = this;
|
||||
|
||||
return this._credentialProvider.readCredential(this._credentialServiceKey)
|
||||
.then(credential => {
|
||||
if (credential.password) {
|
||||
// We already have encryption params, deserialize them
|
||||
let splitValues = credential.password.split('|');
|
||||
if (splitValues.length === 2 && splitValues[0] && splitValues[1]) {
|
||||
try {
|
||||
return <EncryptionParams>{
|
||||
key: new Buffer(splitValues[0], 'hex'),
|
||||
initializationVector: new Buffer(splitValues[1], 'hex')
|
||||
};
|
||||
} catch (e) {
|
||||
// Swallow the error and fall through to generate new params
|
||||
console.warn('Failed to deserialize encryption params, new ones will be generated.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We haven't stored encryption values, so generate them
|
||||
let encryptKey = crypto.randomBytes(TokenCache.CipherKeyLength);
|
||||
let initializationVector = crypto.randomBytes(TokenCache.CipherAlgorithmIvLength);
|
||||
|
||||
// Serialize the values
|
||||
let serializedValues = `${encryptKey.toString('hex')}|${initializationVector.toString('hex')}`;
|
||||
return self._credentialProvider.saveCredential(self._credentialServiceKey, serializedValues)
|
||||
.then(() => {
|
||||
return <EncryptionParams>{
|
||||
key: encryptKey,
|
||||
initializationVector: initializationVector
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private readCache(): Thenable<adal.TokenResponse[]> {
|
||||
let self = this;
|
||||
|
||||
// NOTE: File system operations are performed synchronously to avoid annoying nested callbacks
|
||||
// 1) Get the encryption key
|
||||
// 2) Read the encrypted token cache file
|
||||
// 3) Decrypt the file contents
|
||||
// 4) Deserialize and return
|
||||
return this.getOrCreateEncryptionParams()
|
||||
.then(encryptionParams => {
|
||||
try {
|
||||
let cacheCipher = fs.readFileSync(self._cacheSerializationPath, TokenCache.FsOptions);
|
||||
|
||||
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
|
||||
let cacheJson = decipher.update(cacheCipher, 'hex', 'binary');
|
||||
cacheJson += decipher.final('binary');
|
||||
|
||||
// Deserialize the JSON into the array of tokens
|
||||
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
|
||||
for (let objIndex in cacheObj) {
|
||||
// Rehydrate Date objects since they will always serialize as a string
|
||||
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
|
||||
}
|
||||
|
||||
return cacheObj;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
.then(null, err => {
|
||||
// If reading the token cache fails, we'll just assume the tokens are garbage
|
||||
console.warn(`Failed to read token cache: ${err}`);
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
private removeFromCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] {
|
||||
entries.forEach((entry: adal.TokenResponse) => {
|
||||
// Check to see if the entry exists
|
||||
let match = cache.findIndex(entry2 => TokenCache.findByKeyHelper(entry, entry2));
|
||||
if (match >= 0) {
|
||||
// Entry exists, remove it from cache
|
||||
cache.splice(match, 1);
|
||||
}
|
||||
});
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
private writeCache(cache: adal.TokenResponse[]): Thenable<void> {
|
||||
let self = this;
|
||||
// NOTE: File system operations are being done synchronously to avoid annoying callback nesting
|
||||
// 1) Get (or generate) the encryption key
|
||||
// 2) Stringify the token cache entries
|
||||
// 4) Encrypt the JSON
|
||||
// 3) Write to the file
|
||||
return this.getOrCreateEncryptionParams()
|
||||
.then(encryptionParams => {
|
||||
try {
|
||||
let cacheJson = JSON.stringify(cache);
|
||||
|
||||
let cipher = crypto.createCipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
|
||||
let cacheCipher = cipher.update(cacheJson, 'binary', 'hex');
|
||||
cacheCipher += cipher.final('hex');
|
||||
|
||||
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface EncryptionParams {
|
||||
key: Buffer;
|
||||
initializationVector: Buffer;
|
||||
}
|
||||
12
extensions/account-provider-azure/src/constants.ts
Normal file
12
extensions/account-provider-azure/src/constants.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export const extensionName = localize('extensionName', 'Azure Accounts');
|
||||
35
extensions/account-provider-azure/src/main.ts
Normal file
35
extensions/account-provider-azure/src/main.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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { Utils } from 'extensions-modules';
|
||||
|
||||
import * as constants from './constants';
|
||||
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
|
||||
|
||||
// EXTENSION ACTIVATION ////////////////////////////////////////////////////
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
// Create the folder for storing the token caches
|
||||
let storagePath = path.join(Utils.getDefaultLogLocation(), constants.extensionName);
|
||||
try {
|
||||
if (!fs.existsSync(storagePath)) {
|
||||
fs.mkdirSync(storagePath);
|
||||
console.log('Initialized Azure account extension storage.');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Initialization of Azure account extension storage failed: ${e}`);
|
||||
console.error('Azure accounts will not be available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the provider service and activate
|
||||
const accountProviderService = new AzureAccountProviderService(context, storagePath);
|
||||
context.subscriptions.push(accountProviderService);
|
||||
accountProviderService.activate();
|
||||
}
|
||||
7
extensions/account-provider-azure/src/typings/ref.d.ts
vendored
Normal file
7
extensions/account-provider-azure/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/data.d.ts'/>
|
||||
20
extensions/account-provider-azure/tsconfig.json
Normal file
20
extensions/account-provider-azure/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "./out",
|
||||
"lib": [
|
||||
"es6", "es2015.promise"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": [
|
||||
"./node_modules"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user