Compare commits

..

10 Commits

Author SHA1 Message Date
Charles Gagnon
3a32679ab5 Update SMO to 4.3.1.2 (#21292)
* Update SMO to 4.3.1.2

* vBump
2022-11-22 09:16:10 -08:00
Lucy Zhang
3fbaa3de93 Fix headers being exported twice from notebooks (#21195) (#21201)
* fix headers being exported twice

* simplify

* fix test
2022-11-10 22:04:01 -08:00
Cory Rivera
ad1c91e3ec Bring back old editor resolving logic to fix saving in notebooks. (#21186) (#21200) 2022-11-10 14:16:58 -08:00
Aasim Khan
a20b3a5d27 Updating id generation logic to avoid substring overlaps (#21181) (#21188) 2022-11-10 13:42:17 -08:00
Cheena Malhotra
59d32e8e89 Fix custom option support in CMS + update providername in provided profile (#21185) 2022-11-10 12:19:38 -08:00
Karl Burtram
dfa72add10 Fix casing on execution plan command titles (#21170) (#21172) 2022-11-08 19:24:35 -08:00
Hai Cao
de8eb2c98e bump STS (#21168) 2022-11-08 16:17:17 -08:00
Karl Burtram
ec073def25 Port VS Code issue 149177 (#21153) (#21159) 2022-11-08 13:04:04 -08:00
Alex Ma
d8279f1a7c Langpack source files update for November release (main branch PR) (#21147) (#21149)
* updated localized XLFS

* update to langpack source files
2022-11-08 10:40:52 -08:00
Alex Ma
9c55574ddd Juno: check in to lego/hb_04604851-bac4-4681-9f74-73de611d6e48_20221107163900168. (#21128) (#21137)
Co-authored-by: Alex Hsu <csigs@users.noreply.github.com>
2022-11-07 16:26:39 -08:00
807 changed files with 62137 additions and 63216 deletions

View File

@@ -18,7 +18,6 @@ Next there are two types of logs to collect:
- Save this text into a file named console.log and attach it to this issue.
- Developer Tools can be closed via Help -> Toggle Developer Tools
**Application Logs**
@@ -47,5 +46,8 @@ Needs Logs - Azure:
# actions for Out of Scope label
Out of Scope:
comment: "Thank you for your feedback! This feature is currently out of scope and we do not plan to work on it in a currently planned release. We will close this issue to keep our backlog focused on requests that we are planning to work on. Please note that users can continue to vote and comment on closed issues, which we encourage as it helps us understand user interest and can provide more details about why a feature is requested."
comment: "Thank you for opening this suggestion! This enhancement is not planned in our
medium-term roadmap. The issue is being closed to reduce active issues to focus on
enhancements that are being considered for an upcoming release. We will review closed issues
with the 'Out of Scope' label when doing long-term planning."
close: true

View File

@@ -142,9 +142,16 @@ jobs:
id: electron-unit-tests
run: DISPLAY=:10 ./scripts/test.sh --runGlob "**/sql/**/*.test.js" --coverage
- name: Run Extension Unit Tests # {{SQL CARBON EDIT}} Rename to extension for clarity
id: electron-extension-unit-tests
- name: Run Extension Unit Tests (Continue on Error) # {{SQL CARBON EDIT}} Run extension unit tests (continue on error)
id: electron-extension-unit-tests-continue-on-error
run: DISPLAY=:10 ./scripts/test-extensions-unit.sh
continueOnError: true
condition: and(succeeded(), eq(variables['EXTENSION_UNIT_TESTS_FAIL_ON_ERROR'], 'false'))
- name: Run Extension Unit Tests (Fail on Error) # {{SQL CARBON EDIT}} Run extension unit tests (fail on error)
id: electron-extension-unit-tests-fail-on-error
run: DISPLAY=:10 ./scripts/test-extensions-unit.sh
condition: and(succeeded(), ne(variables['EXTENSION_UNIT_TESTS_FAIL_ON_ERROR'], 'false'))
# {{SQL CARBON EDIT}} Add coveralls. We merge first to get around issue where parallel builds weren't being combined correctly
- name: Combine code coverage files

View File

@@ -1,4 +1,4 @@
disturl "https://electronjs.org/headers"
target "19.1.8"
target "17.4.11"
runtime "electron"
build_from_source "true"

View File

@@ -1,78 +1,34 @@
# Change Log
## Version 1.40.2
* Release date: December 27, 2022
* Release status: General Availability
### Bug fixes in 1.40.2
* Fix potential elevation of privilege issue using Bash shell on Windows. VS Code issue [#160827](https://github.com/microsoft/vscode/issues/160827)
## Version 1.40.1
* Release date: November 22, 2022
* Release status: General Availability
### Bug fixes in 1.40.1
* Fixed bug that caused folders in the servers tree to display incorrect contents [#21245](https://github.com/microsoft/azuredatastudio/issues/21245)
## Version 1.40.0
* Release date: November 16, 2022
* Release status: General Availability
### What's new in 1.40.0
| New Item | Details |
|----------|---------|
| Connections | Connections for SQL now default to Encrypt = 'True'. |
| ARM64 Support for macOS | ARM64 build for macOS is now available. |
| Table Designer | Announcing the General Availability of the Table Designer. |
| Table Designer | Period columns now added by default when System-Versioning table option is selected. |
| Table Designer | Added support for hash indexes for In-Memory tables, and added support for columnstore indexes. |
| Table Designer | New checkbox added, "Preview Database Updates", when making database changes to ensure that users are aware of potential risks prior to updating the database.|
| Table Designer | "Move Up" and "Move Down" buttons added to support column reordering for Primary Keys. |
| Query Plan Viewer | Announcing the General Availability of the Query Plan Viewer in Azure Data Studio. |
| Query Plan Viewer | Added support for identification of most expensive operator(s) in a plan. |
| Query Plan Viewer | Updates were made to the properties window to allow for full display of text upon hovering over a cell. Full text can also be copied. |
| Query Plan Viewer | Implemented filter functionality in the Properties pane for an execution plan. |
| Query Plan Viewer | Added support for collapsing and expanding all subcategories within the Plan Comparison Properties window. |
| Query History Extension | Announcing the General Availability of the SQL History Extension. |
| Query History Extension | Now includes ability to persist history across multiple user sessions. |
| Query History Extension | Added the ability to limit the number of entries stored in the history. |
| Schema Compare | Users can now open .scmp files directly from the context menu for existing files in the file explorer. |
| Query Editor | Now allows full display for text strings larger than 65,535 characters. |
| Query Editor | Added support for the SHIFT key when making multiple cell selections. |
| MySQL Extension | Support for MySQL extension is now available in preview. |
| Azure SQL Migration Extension | Azure SQL Database Offline Migrations is now available in preview. Customers can use this new capability to save and share reports as needed. |
| Azure SQL Migration Extension | Addition of elastic Azure recommendations model. |
| Database Migration Assessment for Oracle | Assessment tooling for Oracle database migrations to Azure Database for PostgreSQL and Azure SQL available in preview. |
| VS Code merge | VS Code merges to version 1.67. Read [their release notes](https://code.visualstudio.com/updates/v1_67) to learn more. |
| SQL Database Projects | Adds SQL projects support for syntax introduced in SQL Server 2022.|
### Bug fixes in 1.40.0
| New Item | Details |
|----------|---------|
| Connections | Fixed bug that occurred when trying to connect to the Dedicated Admin Connection (DAC) on SQL Server. |
| Connections | Fixed issue with wrong tenant showing up while trying to connect to a database with Azure Active Directory login. |
| Connections | Fixed zoom reset behavior when adding a new connection. |
| Connections | Fixed loading bug what occurred when attempting to sign in to Azure via proxy. |
| Connections | Fixed issue encountered while attempting to connect to a "sleeping" Azure SQL Database. |
| Object Explorer | Fixed the SELECT script generation issue for Synapse Databases. |
| Schema Compare | Fixed error that caused duplication of comment headers when applying schema changes on stored procedure objects. |
| Schema Compare | Fixed issue that prevented schema compare issues when creating a new empty schema with a "DOMAIN\User" pattern. |
| Query Editor | Fixed bug that caused results to be lost upon saving query files. |
| Table Designer | Fixed a bug that caused creation of a new table when renaming an existing table. |
| Query Plan Viewer | Fixed missing index recommendation T-SQL syntax. |
| SQL Projects | Fixed bug in SQL Projects that led to extension not using output path when publishing a project. |
| SQL Projects | Fixed bug that caused .NET install to not be found when using the SQL Projects extension on Linux platforms. |
## Version 1.39.1
* Release date: August 30, 2022
* Release status: General Availability
## What's new in 1.39.1
* Bug fixes in 1.39.1
* Fixed bug that caused Database Trees in server connections to not expand in the Object Explorer.
### Bug fixes in 1.39.1
* Fixed bug that caused Database Trees in server connections to not expand in the Object Explorer.
| Platform |
| --------------------------------------- |
| [Windows User Installer][win-user] |
| [Windows System Installer][win-system] |
| [Windows ZIP][win-zip] |
| [macOS ZIP][osx-zip] |
| [Linux TAR.GZ][linux-zip] |
| [Linux RPM][linux-rpm] |
| [Linux DEB][linux-deb] |
[win-user]: https://go.microsoft.com/fwlink/?linkid=2204567
[win-system]: https://go.microsoft.com/fwlink/?linkid=2204568
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2204772
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2204569
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2204773
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2204774
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2204570
## Version 1.39.0
* Release date: August 24, 2022
* Release status: General Availability
### What's new in 1.39.0
## What's new in 1.39.0
* New Features:
* Deployment Wizard - Azure Data Studio now supports SQL Server 2022 (Preview) in the Deployment Wizard for both local and container installation.
* Object Explorer - Added Ledger icons and scripting support to Object Explorer for Ledger objects.
@@ -102,7 +58,7 @@
## Version 1.38.0
* Release date: July 27, 2022
* Release status: General Availability
### What's new in 1.38.0
## What's new in 1.38.0
* New Features:
* VS Code merges to 1.62 - This release includes updates to VS Code from the three previous VS Code releases. Read [their release notes](https://code.visualstudio.com/updates/v1_62) to learn more.
* Table Designer - New column added to Table Designer for easier access to additional actions specific to individual rows.
@@ -130,7 +86,7 @@
## Version 1.37.0
* Release date: June 15, 2022
* Release status: General Availability
### What's new in this version
## What's new in this version
* New Features:
* Backup & Restore - Backup & Restore to URL is now available in preview for Azure SQL Managed Instances.
* Table Designer - Added API to support computed column capabilities on Table Designer.
@@ -153,7 +109,7 @@
## Version 1.36.2
* Release date: May 20, 2022
* Release status: General Availability
### What's new in this version
## What's new in this version
- Fix connectivity issue with PBI data source
- Fix query plan zoom and icon issues
- Issues fixed in this release https://github.com/microsoft/azuredatastudio/milestone/89?closed=1
@@ -161,14 +117,14 @@
## Version 1.36.1
* Release date: April 22, 2022
* Release status: General Availability
### What's new in this version
## What's new in this version
* April Hotfix addressing these issues https://github.com/microsoft/azuredatastudio/milestone/88?closed=1.
* Hotfix RCA - https://github.com/microsoft/azuredatastudio/wiki/ADS-April-2022-Hotfix-RCA
## Version 1.36.0
* Release date: April 20, 2022
* Release status: General Availability
### What's new in this version
## What's new in this version
- General Availability of the Azure SQL Migration Extension for ADS
- Support for .NET Interactive Notebooks Extension
- New Table Designer Features including support for System Versioned, Graph and Memory Optomized Tables
@@ -178,14 +134,14 @@
## Version 1.35.1
* Release date: March 17, 2022
* Release status: General Availability
### Hotfix release
## Hotfix release
- Fix for [Excel number format #18615](https://github.com/microsoft/azuredatastudio/issues/18615)
- Fix for [Geometry Data Type Returned as Unknown Charset in Results Grid #18630](https://github.com/microsoft/azuredatastudio/issues/18630)
## Version 1.35.0
## Version 1.35.0
* Release date: February 24, 2022
* Release status: General Availability
### What's new in this version
## What's new in this version
* New Features:
* Table Designer - Added functionality for creation and management of tables for SQL Servers. Built using DacFx framework
* Query Plan Viewer - Added functionality for users to view a graphic view of estimated and actual query plans without need for an extension
@@ -198,7 +154,7 @@
## Version 1.34.0
* Release date: December 15, 2021
* Release status: General Availability
### What's new in this version
## What's new in this version
* New Features:
* Added “Currently restoring backup file” in the migration progress details page of Azure SQL Migration extension when backup files location is Azure Storage blob container
* Enhancements to diagnostics in Azure SQL Migration extension
@@ -222,14 +178,14 @@
* Release date: Nov 4, 2021
* Release status: General Availability
### Hotfix release
## Hotfix release
- Fix for [#16535 Unable to See Saved Connections in Restricted Mode](https://github.com/microsoft/azuredatastudio/issues/17535)
- Fix for [#17579 Can't type in Notebook code cell after editing text cell](https://github.com/microsoft/azuredatastudio/issues/17579)
## Version 1.33.0
* Release date: October 27, 2021
* Release status: General Availability
### What's new in this version
## What's new in this version
* New Notebook Features:
* Notebook Views
* Split cell support
@@ -268,7 +224,7 @@
## Version 1.31.1
* Release date: July 29, 2021
* Release status: General Availability
### Hotfix Release
## Hotfix Release
- Fix for [#16436 Database Connection Toolbar Missing](https://github.com/microsoft/azuredatastudio/issues/16436)
## Version 1.31.0
@@ -559,7 +515,7 @@
* GA status for Big Data Cluster/SQL 2019 features [#8269](https://github.com/microsoft/azuredatastudio/issues/8269)
* Resolved [bugs and issues](https://github.com/microsoft/azuredatastudio/milestone/44?closed=1).
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
## Version 1.13.1
@@ -573,7 +529,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* General Availability release for Schema Compare and DACPAC extensions
* Resolved [bugs and issues](https://github.com/microsoft/azuredatastudio/milestone/43?closed=1).
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* aspnerd for `Use selected DB for import wizard schema list` [#7878](https://github.com/microsoft/azuredatastudio/pull/7878)
@@ -591,7 +547,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: October 2, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* Announcing the Query History panel
* Improved Query Results Grid copy selection support
* TempDB page added to Server Reports extension
@@ -602,14 +558,14 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: September 10, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* Resolved [bugs and issues](https://github.com/microsoft/azuredatastudio/milestone/41?closed=1).
## Version 1.10.0
* Release date: August 14, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* [SandDance](https://github.com/microsoft/SandDance) integration — A new way to interact with data. Download the extension [here](https://docs.microsoft.com/sql/azure-data-studio/sanddance-extension)
* Notebook improvements
* Better loading performance
@@ -625,7 +581,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: July 11, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* Release of [SentryOne Plan Explorer Extension](https://www.sentryone.com/products/sentryone-plan-explorer-extension-azure-data-studio)
* **Schema Compare**
* Schema Compare File Support (.SCMP)
@@ -651,7 +607,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: June 6, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* Initial release of the Database Admin Tool Extensions for Windows *Preview* extension
* Initial release of the Central Management Servers extension
* **Schema Compare**
@@ -672,24 +628,24 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: May 8, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* Announcing Schema Compare *Preview* extension
* Tasks Panel UX improvement
* Announcing new Welcome page
* Resolved [bugs and issues](https://github.com/microsoft/azuredatastudio/milestone/31?closed=1).
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues.
## Version 1.6.0
* Release date: April 18, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* Align with latest VS Code editor platform (currently 1.33.1)
* Resolved [bugs and issues](https://github.com/Microsoft/azuredatastudio/milestone/26?closed=1).
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* yamatoya for `fix the format (#4899)`
@@ -698,13 +654,13 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: March 18, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* Announcing T-SQL Notebooks
* Announcing PostgreSQL extension
* Announcing SQL Server Dacpac extension
* Resolved [bugs and issues](https://github.com/Microsoft/azuredatastudio/milestone/25?closed=1).
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* GeoffYoung for `Fix sqlDropColumn description #4422`
@@ -713,7 +669,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: February 13, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* Added **Admin pack for SQL Server** extension pack to make it easier to install SQL Server admin-related extensions. This includes:
* [SQL Server Agent](https://docs.microsoft.com/en-us/sql/azure-data-studio/sql-server-agent-extension?view=sql-server-2017)
* [SQL Server Profiler](https://docs.microsoft.com/en-us/sql/azure-data-studio/sql-server-profiler-extension?view=sql-server-2017)
@@ -727,7 +683,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Results streaming enabled by default for long running queries
* Resolved [bugs and issues](https://github.com/Microsoft/azuredatastudio/milestone/23?closed=1).
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* AlexFsmn for `Added context menu for DBs in explorer view to backup & restore db. #2277`
@@ -738,7 +694,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: January 9, 2019
* Release status: General Availability
### What's new in this version
## What's new in this version
* #13 Feature Request: Azure Active Directory Authentication
* #1040 Stream initial query results as they become available
* #3298 Сan't add an azure account.
@@ -748,7 +704,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Updates to [SQL Server 2019 extension](https://docs.microsoft.com/sql/azure-data-studio/sql-server-2019-extension?view=sql-server-ver15)
* **sp_executesql to SQL** and **New Database** extensions
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* Tarig0 for `Add Routine_Type to CreateStoredProc fixes #3257 (#3286)`
@@ -760,7 +716,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: November 6, 2018
* Release status: General Availability
### What's new in this version
## What's new in this version
* Update to the SQL Server 2019 Preview extension
* Introducing Paste the Plan extension
* Introducing High Color queries extension, including SSMS editor theme
@@ -769,7 +725,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Upgrade SQL Tools Service to .Net Core 2.2 Preview 3 (for eventual AAD support)
* Fix customer reported GitHub issues
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* rdaniels6813 for `Add query plan theme support #3031`
@@ -783,12 +739,12 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: October 18, 2018
* Release status: General Availability
### What's new in this version
## What's new in this version
* Introducing the Azure Resource Explorer to browse Azure SQL Databases
* Improve Object Explorer and Query Editor connectivity robustness
* SQL Server 2019 and SQL Agent extension improvements
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* philoushka for `center the icon #2760`
@@ -800,7 +756,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: September 24, 2018
* Release status: General Availability
### What's new in this version
## What's new in this version
* Announcing the SQL Server 2019 Preview extension.
* Support for SQL Server 2019 preview features including Big Data Cluster support.
* Azure Data Studio Notebooks
@@ -811,7 +767,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Accessibility improvements for screen reader, keyboard navigation and high-contrast.
* Added Connection name option to provide an alternative display name in the Servers viewlet.
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* AlexFsmn `Feature: Ability to add connection name #2332`
@@ -821,7 +777,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: August 30, 2018
* Release status: Public Preview
### What's new in this version
## What's new in this version
* Announcing the SQL Server Import Extension
* SQL Server Profiler Session management
* SQL Server Agent improvements
@@ -829,7 +785,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Quality of Life improvements: Connection strings
* Fix many customer reported GitHub issues
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* SebastianPfliegel `Added more saveAsCsv options #2099`
@@ -847,7 +803,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: July 19, 2018
* Release status: Public Preview
### What's new in this version
## What's new in this version
* SQL Server Agent for Azure Data Studio extension improvements
* Added view of Alerts, Operators, and Proxies and icons on left pane
* Added dialogs for New Job, New Job Step, New Alert, and New Operator
@@ -868,7 +824,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: June 20, 2018
* Release status: Public Preview
### What's new in this version
## What's new in this version
* **SQL Server Profiler for Azure Data Studio *Preview*** extension initial release
* The new **SQL Data Warehouse** extension includes rich customizable dashboard widgets surfacing insights to your data warehouse. This unlocks key scenarios around managing and tuning your data warehouse to ensure it is optimized for consistent performance.
* **Edit Data "Filtering and Sorting"** support
@@ -881,7 +837,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: May 7, 2018
* Release status: Public Preview
### What's new in this version
## What's new in this version
The May release is focused on stabilization and bug fixes leading up to the Build conference. This build contains the following highlights.
* Announcing **Redgate SQL Search** extension available in Extension Manager
@@ -896,7 +852,7 @@ The May release is focused on stabilization and bug fixes leading up to the Buil
* Release date: April 25, 2018
* Release status: Public Preview
### What's new in this version
## What's new in this version
The April Public Preview release contains some of the following highlights.
* Improvements to SQL Agent *Preview* extension
@@ -911,7 +867,7 @@ The April Public Preview release contains some of the following highlights.
* Release date: March 28, 2017
* Release status: Public Preview
### What's new in this version
## What's new in this version
The March Public Preview release enables some key aspects of the Azure Data Studio
extensibility story. Here are some highlights in this release.
@@ -926,14 +882,14 @@ extensibility story. Here are some highlights in this release.
* Release date: February 16, 2017
* Release status: Public Preview Hotfix 1
### What's new in this version
## What's new in this version
* Bug fix for `#717 Selecting partial query and hitting Cmd or Ctrl+C opens terminal with Error message`
## Version 0.26.6
* Release date: February 15, 2017
* Release status: Public Preview
### What's new in this version
## What's new in this version
The February release fixes several important customer reported issues, as well as various feature improvements. We've also introduced auto-update support in February which will simplify keeping updated with the lastest changes.
Here's some of the highlights in the February release.
@@ -953,7 +909,7 @@ Here's some of the highlights in the February release.
* VS Code Editor 1.19 integration
* Update JustinPealing/html-query-plan component to pick-up several Query Plan viewer improvements
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* SebastianPfliegel for `Add cursor snippet (#475)`
@@ -964,7 +920,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: January 17, 2017
* Release status: Public Preview
### What's new in this version
## What's new in this version
The January release focuses on addressing a few of the top upvoted feature suggestions, as well as fixing high-priority bugs. This release period coincides with holiday vacations, so the churn in this release is
relatively scoped.
@@ -979,7 +935,7 @@ Here's some of the highlights in the January release.
* Fix missing Azure Account branding icon
* Change "Server name" to "Server" in Connection Dialog
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* alextercete for `Fix "No extension gallery service configured" error (#427)`
@@ -989,7 +945,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Release date: December 19, 2017
* Release status: Public Preview
### What's new in this version
## What's new in this version
* Azure Integration with Create Firewall Rule
* Windows Setup, Linux DEB and Linux RPM installation packages
* Manage Dashboard visual layout editor
@@ -1013,7 +969,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Allow expanding databases not in certain non-Online states
* Connection Dialog selects most common default authentication method based on platform
### Contributions and "thank you"
## Contributions and "thank you"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* mwiedemeyer for `Fix #58: Default sort order for DB size widget (#111)`
* AlexTroshkin for `Show disconnect in context menu only when connectionProfile connected (#150)`

View File

@@ -8,53 +8,27 @@ Azure Data Studio is a data management tool that enables you to work with SQL Se
## **Download the latest Azure Data Studio release**
|Platform |Type |Download |
| --------|-----------------|-------- |
|Windows |User Installer |[64 bit][win-user] |
| |System Installer |[64 bit][win-system] |
| |.zip |[64 bit][win-zip] |
|Linux |.tar.gz |[64 bit][linux-zip] |
| |.deb |[64 bit][linux-deb] |
| |.rpm |[64 bit][linux-rpm] |
|Mac |.zip |[Universal][osx-universal]&emsp;[Intel Chip][osx-zip]&emsp;[Apple Silicon][osx-arm64]|
| Platform |
| --------------------------------------- |
| [Windows User Installer][win-user] |
| [Windows System Installer][win-system] |
| [Windows ZIP][win-zip] |
| [macOS ZIP][osx-zip] |
| [Linux TAR.GZ][linux-zip] |
| [Linux RPM][linux-rpm] |
| [Linux DEB][linux-deb] |
[win-user]: https://go.microsoft.com/fwlink/?linkid=2215273
[win-system]: https://go.microsoft.com/fwlink/?linkid=2215525
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2215526
[osx-universal]: https://go.microsoft.com/fwlink/?linkid=2215527
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2215420
[osx-arm64]: https://go.microsoft.com/fwlink/?linkid=2215346
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2215421
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2215347
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2215528
Go to our [download page](https://aka.ms/getazuredatastudio) for more specific instructions.
## Try out the latest insiders build from `main` branch:
|Platform |Type |Download - Insiders Build |
| --------|-----------------|----------------------- |
|Windows |User Installer |[64 bit][in-win-user]&emsp;[ARM][in-win-user-arm64] |
| |System Installer |[64 bit][in-win-system]&emsp;[ARM][in-win-system-arm64] |
| |.zip |[64 bit][in-win-zip]&emsp;[ARM][in-win-zip-arm64] |
|Linux |.tar.gz |[64 bit][in-linux-zip] |
| |.deb |[64 bit][in-linux-deb] |
| |.rpm |[64 bit][in-linux-rpm] |
|Mac |.zip |[Universal][in-osx-universal]&emsp;[Intel Chip][in-osx-zip]&emsp;[Apple Silicon][in-osx-arm64] |
[in-win-user]: https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-user/insider
[in-win-system]: https://azuredatastudio-update.azurewebsites.net/latest/win32-x64/insider
[in-win-zip]: https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-archive/insider
[in-win-user-arm64]: https://azuredatastudio-update.azurewebsites.net/latest/win32-arm64-user/insider
[in-win-system-arm64]: https://azuredatastudio-update.azurewebsites.net/latest/win32-arm64/insider
[in-win-zip-arm64]: https://azuredatastudio-update.azurewebsites.net/latest/win32-arm64-archive/insider
[in-linux-zip]:https://azuredatastudio-update.azurewebsites.net/latest/linux-x64/insider
[in-linux-deb]:https://azuredatastudio-update.azurewebsites.net/latest/linux-deb-x64/insider
[in-linux-rpm]:https://azuredatastudio-update.azurewebsites.net/latest/linux-rpm-x64/insider
[in-osx-universal]: https://azuredatastudio-update.azurewebsites.net/latest/darwin-universal/insider
[in-osx-zip]: https://azuredatastudio-update.azurewebsites.net/latest/darwin/insider
[in-osx-arm64]: https://azuredatastudio-update.azurewebsites.net/latest/darwin-arm64/insider
## Try out the latest insiders build from `main`:
- [Windows User Installer - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-user/insider)
- [Windows System Installer - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/win32-x64/insider)
- [Windows ZIP - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-archive/insider)
- [macOS ZIP (Universal) - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/darwin-universal/insider)
- [macOS ZIP (Intel Chip) - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/darwin/insider)
- [macOS ZIP (Apple Silicon) - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/darwin-arm64/insider)
- [Linux TAR.GZ - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/linux-x64/insider)
See the [change log](https://github.com/Microsoft/azuredatastudio/blob/main/CHANGELOG.md) for additional details of what's in this release.
Go to our [download page](https://aka.ms/getazuredatastudio) for more specific instructions.
@@ -158,3 +132,11 @@ And of course, we'd like to thank the authors of all upstream dependencies. Ple
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [Source EULA](LICENSE.txt).
[win-user]: https://go.microsoft.com/fwlink/?linkid=2204567
[win-system]: https://go.microsoft.com/fwlink/?linkid=2204568
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2204772
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2204569
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2204773
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2204774
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2204570

View File

@@ -12,7 +12,6 @@ const files = [
'.build/langpacks/**/*.vsix',
'.build/extensions/**/*.vsix',
'.build/win32-x64/**/*.{exe,zip}',
'.build/win32-arm64/**/*.{exe,zip}',
'.build/linux/sha256hashes.txt',
'.build/linux/deb/amd64/deb/*.deb',
'.build/linux/rpm/x86_64/*.rpm',

View File

@@ -13,8 +13,7 @@ import * as fs from 'fs';
const files = [
'.build/langpacks/**/*.vsix', // langpacks
'.build/extensions/**/*.vsix', // external extensions
'.build/win32-x64/**/*.{exe,zip}', // windows x64 binaries
'.build/win32-arm64/**/*.{exe,zip}', // windows arm64 binaries
'.build/win32-x64/**/*.{exe,zip}', // windows binaries
'.build/linux/sha256hashes.txt', // linux hashes
'.build/linux/deb/amd64/deb/*.deb', // linux debs
'.build/linux/rpm/x86_64/*.rpm', // linux rpms

View File

@@ -106,27 +106,17 @@ stages:
timeoutInMinutes: 90
- stage: Windows
condition: and(succeeded(), or(eq(variables['VSCODE_BUILD_WIN32'], true), eq(variables['VSCODE_BUILD_WIN32_ARM64'], true)))
condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32'], true))
pool:
vmImage: 'windows-2019'
dependsOn:
- Compile
jobs:
- job: Windows_x64
variables:
VSCODE_ARCH: x64
- job: Windows
steps:
- template: win32/sql-product-build-win32.yml
condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32'], 'true'))
timeoutInMinutes: 90
- job: Windows_ARM64
variables:
VSCODE_ARCH: arm64
condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32_ARM64'], 'true'))
steps:
- template: win32/sql-product-build-win32.yml
timeoutInMinutes: 90
# disable due to invalid machine pool (karlb 3/9/2022)
# - job: Windows_Test
# condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32'], 'true'))

View File

@@ -1,7 +1,7 @@
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
$Arch = $env:VSCODE_ARCH
$Arch = "x64"
$Repo = "$(pwd)"
$Root = "$Repo\.."

View File

@@ -48,7 +48,7 @@ steps:
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { node build/azure-pipelines/common/sql-computeNodeModulesCacheKey.js $(VSCODE_ARCH) > .build/yarnlockhash }
exec { node build/azure-pipelines/common/sql-computeNodeModulesCacheKey.js > .build/yarnlockhash }
displayName: Prepare yarn cache key
- task: Cache@2
@@ -69,7 +69,6 @@ steps:
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
$env:npm_config_arch="$(VSCODE_ARCH)"
$env:CHILD_CONCURRENCY="1"
exec { yarn --frozen-lockfile }
env:
@@ -103,8 +102,8 @@ steps:
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { yarn gulp "package-rebuild-extensions" }
exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-min-ci" }
exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-inno-updater" }
exec { yarn gulp "vscode-win32-x64-min-ci" }
exec { yarn gulp "vscode-win32-x64-inno-updater" }
displayName: Build
env:
VSCODE_MIXIN_PASSWORD: $(github-distro-mixin-password)
@@ -118,7 +117,7 @@ steps:
vstsFeed: '2191dd5f-4aec-491b-ac50-568bbc331c8a'
vstsFeedPackage: '2e355f03-a97e-499a-949b-f02d62b6160c'
vstsPackageVersion: '*'
condition: and(succeeded(), eq(variables['VSCODE_QUALITY'], 'saw'), ne(variables['VSCODE_ARCH'], 'arm64'))
condition: and(succeeded(), eq(variables['VSCODE_QUALITY'], 'saw'))
- powershell: |
# Install TSGOps specific extensions
@@ -131,7 +130,7 @@ steps:
Move-Item $tempFilePath $zipFilePath
Expand-Archive $zipFilePath -DestinationPath $adsExtensionPath
displayName: Install SAW Extensions
condition: and(succeeded(), eq(variables['VSCODE_QUALITY'], 'saw'), ne(variables['VSCODE_ARCH'], 'arm64'))
condition: and(succeeded(), eq(variables['VSCODE_QUALITY'], 'saw'))
# - powershell: | @anthonydresser unit tests timeout never existing the node process
# . build/azure-pipelines/win32/exec.ps1
@@ -159,27 +158,8 @@ steps:
displayName: 'Sign out code'
inputs:
ConnectedServiceName: 'Code Signing'
FolderPath: '$(agent.builddirectory)/azuredatastudio-win32-$(VSCODE_ARCH)'
Pattern: |
*.exe
*.node
!**/node_modules/**/*
resources/app/node_modules.asar.unpacked/*.dll
d3dcompiler_47.dll
vulkan-1.dll
libGLESv2.dll
ffmpeg.dll
libEGL.dll
Microsoft.SqlTools.Hosting.dll
Microsoft.SqlTools.ResourceProvider.Core.dll
Microsoft.SqlTools.ResourceProvider.DefaultImpl.dll
MicrosoftSqlToolsCredentials.dll
MicrosoftSqlToolsServiceLayer.dll
Newtonsoft.Json.dll,SqlSerializationService.dll
SqlToolsResourceProviderService.dll
Microsoft.SqlServer.*.dll
Microsoft.Data.Tools.Sql.BatchParser.dll
useMinimatch: true
FolderPath: '$(agent.builddirectory)/azuredatastudio-win32-x64'
Pattern: '*.exe,*.node,resources/app/node_modules.asar.unpacked/*.dll,swiftshader/*.dll,d3dcompiler_47.dll,vulkan-1.dll,libGLESv2.dll,ffmpeg.dll,libEGL.dll,Microsoft.SqlTools.Hosting.dll,Microsoft.SqlTools.ResourceProvider.Core.dll,Microsoft.SqlTools.ResourceProvider.DefaultImpl.dll,MicrosoftSqlToolsCredentials.dll,MicrosoftSqlToolsServiceLayer.dll,Newtonsoft.Json.dll,SqlSerializationService.dll,SqlToolsResourceProviderService.dll,Microsoft.SqlServer.*.dll,Microsoft.Data.Tools.Sql.BatchParser.dll'
signConfigType: inlineSignParams
inlineOperation: |
[
@@ -232,9 +212,9 @@ steps:
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-user-setup" }
exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-system-setup" }
exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-archive" }
exec { yarn gulp "vscode-win32-x64-user-setup" }
exec { yarn gulp "vscode-win32-x64-system-setup" }
exec { yarn gulp "vscode-win32-x64-archive" }
displayName: Archive & User & System setup
- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1

View File

@@ -8,40 +8,32 @@ Param(
$env:AZURE_STORAGE_ACCESS_KEY_2 = $storageKey
$env:AZURE_DOCUMENTDB_MASTERKEY = $documentDbKey
$Flavors = "x64","arm64"
$FlavorSuffixes = "","-arm64"
$ExeName = "AzureDataStudioSetup.exe"
$SystemExe = "$artifactsDir\win32-x64\system-setup\$ExeName"
$UserExe = "$artifactsDir\win32-x64\user-setup\$ExeName"
$UserExeName = "AzureDataStudioUserSetup.exe"
$ZipName = "azuredatastudio-win32-x64.zip"
$Zip = "$artifactsDir\win32-x64\archive\$ZipName"
For($i = 0; $i -lt $Flavors.Length; $i++)
{
$Flavor = $Flavors[$i]
$FlavorSuffix = $FlavorSuffixes[$i]
$ExeName = "AzureDataStudioSetup.exe"
$SystemExe = "$artifactsDir\win32-$Flavor\system-setup\$ExeName"
$UserExe = "$artifactsDir\win32-$Flavor\user-setup\$ExeName"
$UserExeName = "AzureDataStudioUserSetup.exe"
$ZipName = "azuredatastudio-win32-$Flavor.zip"
$Zip = "$artifactsDir\win32-$Flavor\archive\$ZipName"
$VersionJson = Get-Content -Raw -Path "$artifactsDir\version.json" | ConvertFrom-Json
$Version = $VersionJson.version
$Quality = $VersionJson.quality
$CommitId = $VersionJson.commit
$VersionJson = Get-Content -Raw -Path "$artifactsDir\version.json" | ConvertFrom-Json
$Version = $VersionJson.version
$Quality = $VersionJson.quality
$CommitId = $VersionJson.commit
$ZipUploadName = "azuredatastudio-windows-$Version"
$SetupUploadName = "azuredatastudio-windows-setup-$Version"
$UserUploadName = "azuredatastudio-windows-user-setup-$Version"
$ZipUploadName = "azuredatastudio-windows$FlavorSuffix-$Version"
$SetupUploadName = "azuredatastudio-windows$FlavorSuffix-setup-$Version"
$UserUploadName = "azuredatastudio-windows$FlavorSuffix-user-setup-$Version"
$assetPlatform = "win32-x64"
$assetPlatform = "win32-$Flavor"
If (-NOT ($Quality -eq "stable")) {
$ZipUploadName = "$ZipUploadName-$Quality"
$SetupUploadName = "$SetupUploadName-$Quality"
$UserUploadName = "$UserUploadName-$Quality"
}
node $sourcesDir/build/azure-pipelines/common/publish.js $Quality "$assetPlatform-archive" archive "$ZipUploadName.zip" $Version true $Zip $CommitId
node $sourcesDir/build/azure-pipelines/common/publish.js $Quality "$assetPlatform" setup "$SetupUploadName.exe" $Version true $SystemExe $CommitId
node $sourcesDir/build/azure-pipelines/common/publish.js $Quality "$assetPlatform-user" setup "$UserUploadName.exe" $Version true $UserExe $CommitId
If (-NOT ($Quality -eq "stable")) {
$ZipUploadName = "$ZipUploadName-$Quality"
$SetupUploadName = "$SetupUploadName-$Quality"
$UserUploadName = "$UserUploadName-$Quality"
}
node $sourcesDir/build/azure-pipelines/common/publish.js $Quality "$assetPlatform-archive" archive "$ZipUploadName.zip" $Version true $Zip $CommitId
node $sourcesDir/build/azure-pipelines/common/publish.js $Quality "$assetPlatform" setup "$SetupUploadName.exe" $Version true $SystemExe $CommitId
node $sourcesDir/build/azure-pipelines/common/publish.js $Quality "$assetPlatform-user" setup "$UserUploadName.exe" $Version true $UserExe $CommitId

View File

@@ -30,7 +30,8 @@ app.once('ready', () => {
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableWebSQL: false
enableWebSQL: false,
nativeWindowOpen: true
}
});
window.setMenuBarVisibility(false);

View File

@@ -78,11 +78,7 @@ async function main() {
});
await fs.writeJson(productJsonPath, productJson);
// Verify if native module architecture is correct
// {{SQL CARBON EDIT}} Some of our extensions have their own keytar so lookup
// only in core modules since this code doesn't work with multiple found modules.
// We're assuming here the intent is just to check a single file for validation and not
// needing to check any others since this currently is ignoring all other native modules.
const findOutput = await (0, cross_spawn_promise_1.spawn)('find', [outAppPath, '-name', 'keytar.node', '-regex', '.*node_modules.asar.unpacked.*',]);
const findOutput = await (0, cross_spawn_promise_1.spawn)('find', [outAppPath, '-name', 'keytar.node']);
const lipoOutput = await (0, cross_spawn_promise_1.spawn)('lipo', ['-archs', findOutput.replace(/\n$/, '')]);
if (lipoOutput.replace(/\n$/, '') !== 'x86_64 arm64') {
throw new Error(`Invalid arch, got : ${lipoOutput}`);

View File

@@ -88,11 +88,7 @@ async function main() {
await fs.writeJson(productJsonPath, productJson);
// Verify if native module architecture is correct
// {{SQL CARBON EDIT}} Some of our extensions have their own keytar so lookup
// only in core modules since this code doesn't work with multiple found modules.
// We're assuming here the intent is just to check a single file for validation and not
// needing to check any others since this currently is ignoring all other native modules.
const findOutput = await spawn('find', [outAppPath, '-name', 'keytar.node', '-regex', '.*node_modules.asar.unpacked.*',]);
const findOutput = await spawn('find', [outAppPath, '-name', 'keytar.node']);
const lipoOutput = await spawn('lipo', ['-archs', findOutput.replace(/\n$/, '')]);
if (lipoOutput.replace(/\n$/, '') !== 'x86_64 arm64') {
throw new Error(`Invalid arch, got : ${lipoOutput}`);

View File

@@ -154,7 +154,6 @@ module.exports.indentationFilter = [
'!extensions/sql-database-projects/BuildDirectory/SystemDacpacs/**',
'!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts',
'!extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts',
'!extensions/datavirtualization/scaleoutdataservice/**',
'!resources/linux/snap/electron-launch',
'!extensions/markdown-language-features/media/*.js',
'!extensions/simple-browser/media/*.js',

View File

@@ -24,6 +24,11 @@ const product = require('../product.json');
const extensionsPath = path.join(path.dirname(__dirname), 'extensions');
// {{SQL CARBON EDIT}} - TODO: Import needs to be updated to work with langpacks.
const sqlLocalizedExtensions = [
'import',
];
// {{SQL CARBON EDIT}} Not doing this for us right now
// To save 250ms for each gulp startup, we are caching the result here
const compilations = glob.sync('**/tsconfig.json', {
@@ -156,7 +161,7 @@ const tasks = compilations.map(function (tsconfigFile) {
const cleanTask = task.define(`clean-extension-${name}`, util.rimraf(out));
const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => {
const pipeline = createPipeline(false, true);
const pipeline = createPipeline(sqlLocalizedExtensions.includes(name), true); // {{SQL CARBON EDIT}}
const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts']));
const input = es.merge(nonts, pipeline.tsProjectSrc());

View File

@@ -40,8 +40,8 @@ const formatFiles = (some) => {
});
});
return gulp.src(some, {
base: '.'
})
base: '.'
})
.pipe(filter(f => !f.stat.isDirectory()))
.pipe(formatting);
@@ -102,19 +102,19 @@ gulp.task('package-external-extensions', task.series(
const extensionName = path.basename(extensionPath);
return { name: extensionName, path: extensionPath };
})
.filter(element => ext.vscodeExternalExtensions.indexOf(element.name) === -1) // VS Code external extensions are bundled into ADS so no need to create a normal VSIX for them
.map(element => {
const pkgJson = require(path.join(element.path, 'package.json'));
const vsixDirectory = path.join(root, '.build', 'extensions');
mkdirp.sync(vsixDirectory);
const packagePath = path.join(vsixDirectory, `${pkgJson.name}-${pkgJson.version}.vsix`);
console.info('Creating vsix for ' + element.path + ' result:' + packagePath);
return vsce.createVSIX({
cwd: element.path,
packagePath: packagePath,
useYarn: true
});
.filter(element => ext.vscodeExternalExtensions.indexOf(element.name) === -1) // VS Code external extensions are bundled into ADS so no need to create a normal VSIX for them
.map(element => {
const pkgJson = require(path.join(element.path, 'package.json'));
const vsixDirectory = path.join(root, '.build', 'extensions');
mkdirp.sync(vsixDirectory);
const packagePath = path.join(vsixDirectory, `${pkgJson.name}-${pkgJson.version}.vsix`);
console.info('Creating vsix for ' + element.path + ' result:' + packagePath);
return vsce.createVSIX({
cwd: element.path,
packagePath: packagePath,
useYarn: true
});
});
// Wait for all the initial VSIXes to be completed before making the VS Code ones since we'll be overwriting
// values in the package.json for those.
await Promise.all(vsixes);
@@ -133,26 +133,15 @@ gulp.task('package-external-extensions', task.series(
// And now use gulp-json-editor to modify the contents
const updateData = JSON.parse(fs.readFileSync(vscodeManifestFullPath)); // Read in the set of values to replace from package.vscode.json
Object.keys(updateData).forEach(key => {
if (key !== 'contributes') {
data[key] = updateData[key];
}
data[key] = updateData[key];
});
if (data.contributes?.menus) {
if(data.contributes?.menus){
// Remove ADS-only menus. This is a subset of the menus listed in https://github.com/microsoft/azuredatastudio/blob/main/src/vs/workbench/api/common/menusExtensionPoint.ts
// More can be added to the list as needed.
['objectExplorer/item/context', 'dataExplorer/context', 'dashboard/toolbar'].forEach(menu => {
delete data.contributes.menus[menu];
});
}
// Add any configuration properties from the package.vscode.json
// Currently only supports bringing over properties in the first config object found and doesn't support modifying the title
if (updateData.contributes?.configuration[0]?.properties) {
Object.keys(updateData.contributes.configuration[0].properties).forEach(key => {
data.contributes.configuration[0].properties[key] = updateData.contributes.configuration[0].properties[key];
});
}
return data;
}, { beautify: false }))
.pipe(gulp.dest(packageDir));

View File

@@ -104,7 +104,7 @@ function buildWin32Setup(arch, target) {
IncompatibleTargetAppId: { 'ia32': product.win32AppId, 'x64': product.win32x64AppId, 'arm64': product.win32arm64AppId }[arch],
IncompatibleArchAppId: { 'ia32': x64AppId, 'x64': ia32AppId, 'arm64': ia32AppId }[arch],
AppUserId: product.win32AppUserModelId,
ArchitecturesAllowed: { 'ia32': '', 'x64': 'x64', 'arm64': 'arm64 x64' }[arch], //{{SQL CARBON EDIT}} - we still have x64 binaries in SqlToolsService, need to allow x64 binaries for arm64 arch.
ArchitecturesAllowed: { 'ia32': '', 'x64': 'x64', 'arm64': 'arm64' }[arch],
ArchitecturesInstallIn64BitMode: { 'ia32': '', 'x64': 'x64', 'arm64': 'arm64' }[arch],
SourceDir: sourcePath,
RepoDir: repoPath,

View File

@@ -261,9 +261,9 @@ const externalExtensions = [
'azuremonitor',
'cms',
'dacpac',
'datavirtualization',
'import',
'kusto',
'liveshare',
'machine-learning',
'profiler',
'query-history',

View File

@@ -306,9 +306,9 @@ const externalExtensions = [
'azuremonitor',
'cms',
'dacpac',
'datavirtualization',
'import',
'kusto',
'liveshare',
'machine-learning',
'profiler',
'query-history',

View File

@@ -65,7 +65,6 @@ exports.referenceGeneratedDepsByArch = {
'libc.so.6(GLIBC_2.8)(64bit)',
'libc.so.6(GLIBC_2.9)(64bit)',
'libcairo.so.2()(64bit)',
'libcups.so.2()(64bit)',
'libcurl.so.4()(64bit)',
'libdbus-1.so.3()(64bit)',
'libdl.so.2()(64bit)',
@@ -148,7 +147,6 @@ exports.referenceGeneratedDepsByArch = {
'libc.so.6(GLIBC_2.8)',
'libc.so.6(GLIBC_2.9)',
'libcairo.so.2',
'libcups.so.2',
'libcurl.so.4()(64bit)',
'libdbus-1.so.3',
'libdl.so.2',
@@ -239,7 +237,6 @@ exports.referenceGeneratedDepsByArch = {
'libc.so.6()(64bit)',
'libc.so.6(GLIBC_2.17)(64bit)',
'libcairo.so.2()(64bit)',
'libcups.so.2()(64bit)',
'libcurl.so.4()(64bit)',
'libdbus-1.so.3()(64bit)',
'libdbus-1.so.3(LIBDBUS_1_3)(64bit)',

View File

@@ -65,7 +65,6 @@ export const referenceGeneratedDepsByArch = {
'libc.so.6(GLIBC_2.8)(64bit)',
'libc.so.6(GLIBC_2.9)(64bit)',
'libcairo.so.2()(64bit)',
'libcups.so.2()(64bit)',
'libcurl.so.4()(64bit)',
'libdbus-1.so.3()(64bit)',
'libdl.so.2()(64bit)',
@@ -148,7 +147,6 @@ export const referenceGeneratedDepsByArch = {
'libc.so.6(GLIBC_2.8)',
'libc.so.6(GLIBC_2.9)',
'libcairo.so.2',
'libcups.so.2',
'libcurl.so.4()(64bit)',
'libdbus-1.so.3',
'libdl.so.2',
@@ -239,7 +237,6 @@ export const referenceGeneratedDepsByArch = {
'libc.so.6()(64bit)',
'libc.so.6(GLIBC_2.17)(64bit)',
'libcairo.so.2()(64bit)',
'libcups.so.2()(64bit)',
'libcurl.so.4()(64bit)',
'libdbus-1.so.3()(64bit)',
'libdbus-1.so.3(LIBDBUS_1_3)(64bit)',

View File

@@ -22,7 +22,6 @@ exports.dirs = [
'extensions/configuration-editing',
'extensions/dacpac',
'extensions/data-workspace',
'extensions/datavirtualization',
'extensions/git',
'extensions/git-base',
'extensions/github',
@@ -33,6 +32,7 @@ exports.dirs = [
'extensions/json-language-features',
'extensions/json-language-features/server',
'extensions/kusto',
'extensions/liveshare',
'extensions/machine-learning',
'extensions/markdown-language-features',
'extensions/markdown-math',

View File

@@ -130,7 +130,7 @@ color-support@^1.1.2:
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
version "1.1.0"
@@ -339,9 +339,9 @@ make-fetch-happen@^9.1.0:
ssri "^8.0.0"
minimatch@^3.0.4:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"

View File

@@ -81,7 +81,6 @@
},
"dependencies": {},
"resolutions": {
"json-schema": "0.4.0",
"jsonwebtoken": "9.0.0"
"json-schema": "0.4.0"
}
}

View File

@@ -2199,9 +2199,9 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
json5@^2.2.1:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
jsonc-parser@^2.3.0:
version "2.3.1"
@@ -2229,15 +2229,21 @@ jsonparse@~1.2.0:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd"
integrity sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70=
jsonwebtoken@9.0.0, jsonwebtoken@^8.5.1:
version "9.0.0"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d"
integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==
jsonwebtoken@^8.5.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash "^4.17.21"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^7.3.8"
semver "^5.6.0"
jsprim@^1.2.2:
version "1.4.2"
@@ -2317,17 +2323,52 @@ linkify-it@^3.0.1:
dependencies:
uc.micro "^1.0.1"
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
lodash.mergewith@^4.6.1:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
lodash.unescape@4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=
lodash@^4.17.10, lodash@^4.17.21:
lodash@^4.17.10:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -2759,16 +2800,16 @@ punycode@^2.1.0, punycode@^2.1.1:
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@^6.9.1:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
version "6.10.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
dependencies:
side-channel "^1.0.4"
qs@~6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
queue-microtask@^1.2.2:
version "1.2.3"
@@ -2991,7 +3032,7 @@ semver-compare@^1.0.0:
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
semver@^5.1.0, semver@^5.3.0:
semver@^5.1.0, semver@^5.3.0, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -3015,13 +3056,6 @@ semver@^7.3.5, semver@^7.3.7:
dependencies:
lru-cache "^6.0.0"
semver@^7.3.8:
version "7.3.8"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
dependencies:
lru-cache "^6.0.0"
serialize-error@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"

View File

@@ -6,7 +6,7 @@
"git": {
"name": "chromium",
"repositoryUrl": "https://chromium.googlesource.com/chromium/src",
"commitHash": "16e28102fdf876ce6d136674ba66343ede07441f"
"commitHash": "e2aa76f05f3a6ccadbf43e37f5dfc195cc090b6a"
}
},
"licenseDetail": [
@@ -40,7 +40,7 @@
"SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
],
"isOnlyProductionDependency": true,
"version": "100.0.4894.0"
"version": "98.0.4758.141"
},
{
"component": {
@@ -48,11 +48,11 @@
"git": {
"name": "nodejs",
"repositoryUrl": "https://github.com/nodejs/node",
"commitHash": "acb71eab779fb56bf70e8a9e0cb2e82a089a87de"
"commitHash": "40ecd5601193c316e62e9216e8a4259130686208"
}
},
"isOnlyProductionDependency": true,
"version": "16.13.2"
"version": "16.13.0"
},
{
"component": {
@@ -60,12 +60,12 @@
"git": {
"name": "electron",
"repositoryUrl": "https://github.com/electron/electron",
"commitHash": "f887fa45dfaeeddfe20c9835ae7ca3a0823b661b"
"commitHash": "73c87bcfc6e18428c21676d68f829364e6a7b15d"
}
},
"isOnlyProductionDependency": true,
"license": "MIT",
"version": "19.1.8"
"version": "17.4.11"
},
{
"component": {

View File

@@ -107,7 +107,7 @@
]
},
"dependencies": {
"@microsoft/ads-extension-telemetry": "^1.3.2",
"@microsoft/ads-extension-telemetry": "^1.3.1",
"@microsoft/ads-service-downloader": "1.0.4",
"vscode-nls": "^4.1.2"
},

View File

@@ -200,10 +200,10 @@
"@microsoft/applicationinsights-shims" "^2.0.1"
"@microsoft/dynamicproto-js" "^1.1.6"
"@microsoft/ads-extension-telemetry@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@microsoft/ads-extension-telemetry/-/ads-extension-telemetry-1.3.2.tgz#d9cfb4bc7099df73e000b7bafa48bb748db924fe"
integrity sha512-TG1TE7FPp5rBA9zYPVjralZut8Bq/b5XCgm0kmkLyoQyn3c9ntmWXFuNQPOXmgbIemg5YY1/7DHKrfNcO/igkQ==
"@microsoft/ads-extension-telemetry@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@microsoft/ads-extension-telemetry/-/ads-extension-telemetry-1.3.1.tgz#fa757ee88eac91b21c3a68562da6441c2ad15c39"
integrity sha512-8Zd7RwwN7ZufMoWFmc1bwzmQc1RV7/jf/Ua33YL1+P0ZwHoWFOhf/b0lwvAVzi9TB/7oD5zA5yv7A/i2sSTn6Q==
dependencies:
"@vscode/extension-telemetry" "^0.6.2"
@@ -1032,9 +1032,11 @@ jsesc@^2.5.1:
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json5@^2.1.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
minimist "^1.2.5"
locate-path@^3.0.0:
version "3.0.0"

View File

@@ -931,9 +931,11 @@ jsesc@^2.5.1:
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json5@^2.1.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
minimist "^1.2.5"
locate-path@^3.0.0:
version "3.0.0"

View File

@@ -984,9 +984,11 @@ jsesc@^2.5.1:
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json5@^2.1.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
minimist "^1.2.5"
just-extend@^4.0.2:
version "4.1.0"

View File

@@ -1203,9 +1203,11 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
json5@^2.1.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
minimist "^1.2.5"
jsprim@^1.2.2:
version "1.4.2"
@@ -1533,9 +1535,9 @@ punycode@^2.1.0, punycode@^2.1.1:
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@~6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
readdirp@~3.2.0:
version "3.2.0"

View File

@@ -19,10 +19,7 @@ const externals = {
'universalify': 'commonjs universalify',
'@azure/arm-subscriptions': 'commonjs @azure/arm-subscriptions',
'@azure/arm-resourcegraph': 'commonjs @azure/arm-resourcegraph',
'@azure/storage-blob': 'commonjs @azure/storage-blob',
'@azure/msal-node': 'commonjs @azure/msal-node',
'@azure/msal-node-extensions': 'commonjs @azure/msal-node-extensions',
'msal': 'commonjs msal'
'@azure/storage-blob': 'commonjs @azure/storage-blob'
};
// conditionally add ws if we are going to be running in a node environment

View File

@@ -18,12 +18,12 @@
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"capabilities": {
"capabilities": {
"virtualWorkspaces": false,
"untrustedWorkspaces": {
"supported": true
}
},
},
"contributes": {
"resourceViewResources": [
{
@@ -124,19 +124,6 @@
"Verbose",
"All"
]
},
"azure.authenticationLibrary": {
"type": "string",
"description": "%config.authenticationLibrary%",
"default": "MSAL",
"enum": [
"ADAL",
"MSAL"
],
"enumDescriptions": [
"Azure Active Directory Authentication Library",
"Microsoft Authentication Library"
]
}
}
}
@@ -288,22 +275,22 @@
},
{
"command": "azure.resource.azureview.refresh",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer|synapseSqlPoolContainer|synapseWorkspaceContainer)$/",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
"group": "inline"
},
{
"command": "azure.resource.azureview.refresh",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer|synapseSqlPoolContainer|synapseWorkspaceContainer)$/",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
"group": "azurecore"
},
{
"command": "azure.resource.connectsqlserver",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:database|databaseServer|synapseSqlPool|synapseWorkspace|sqlInstance)$/",
"when": "viewItem == azure.resource.itemType.databaseServer || viewItem == azure.resource.itemType.database || viewItem == azure.resource.itemType.sqlInstance",
"group": "inline"
},
{
"command": "azure.resource.connectsqlserver",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:database|databaseServer|synapseSqlPool|synapseWorkspace|sqlInstance)$/",
"when": "viewItem == azure.resource.itemType.databaseServer || viewItem == azure.resource.itemType.database || viewItem == azure.resource.itemType.sqlInstance",
"group": "azurecore"
},
{
@@ -360,12 +347,8 @@
"dependencies": {
"@azure/arm-resourcegraph": "^4.0.0",
"@azure/arm-subscriptions": "^3.0.0",
"@azure/msal-node": "^1.9.0",
"@azure/storage-blob": "^12.6.0",
"axios": "^0.27.2",
"crypto": "^1.0.1",
"lockfile": "1.0.4",
"msal": "^1.4.16",
"node-fetch": "^2.6.7",
"qs": "^6.9.1",
"universalify": "^0.1.2",
@@ -373,12 +356,9 @@
"ws": "^7.4.6"
},
"devDependencies": {
"@microsoft/azdata-test": "^2.0.3",
"@microsoft/vscodetestcover": "^1.2.1",
"@types/keytar": "4.4.0",
"@types/lockfile": "^1.0.2",
"@types/mocha": "^7.0.2",
"@types/node": "^12.20.55",
"@types/node": "^12.11.7",
"@types/qs": "^6.9.1",
"@types/request": "^2.48.1",
"@types/sinon": "^9.0.4",
@@ -386,9 +366,8 @@
"mocha": "^7.1.1",
"should": "^13.2.1",
"sinon": "^9.0.2",
"typemoq": "^2.1.0"
},
"resolutions": {
"jsonwebtoken": "9.0.0"
"typemoq": "^2.1.0",
"@microsoft/vscodetestcover": "^1.2.1",
"@microsoft/azdata-test": "^2.0.3"
}
}

View File

@@ -31,7 +31,6 @@
"config.azureDeviceCodeMethod": "Device Code Method",
"config.noSystemKeychain": "Disable system keychain integration. Credentials will be stored in a flat file in the user's home directory.",
"config.piiLogging": "Should Personally Identifiable Information (PII) be logged in the Azure Accounts output channel and the output channel log file.",
"config.loggingLevel": "[Optional] The verbosity of logging for the Azure Accounts extension.",
"config.authenticationLibrary": "The library used for the AAD auth flow. Please restart ADS after changing this option."
"config.loggingLevel": "[Optional] The verbosity of logging for the Azure Accounts extension."
}

View File

@@ -5,6 +5,7 @@
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import {
@@ -14,55 +15,51 @@ import {
Resource,
Tenant
} from 'azurecore';
import { Deferred } from '../interfaces';
import * as url from 'url';
import * as Constants from '../../constants';
import { SimpleTokenCache } from '../simpleTokenCache';
import { MemoryDatabase } from '../utils/memoryDatabase';
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import { Logger } from '../../utils/Logger';
import * as qs from 'qs';
import { AzureAuthError } from './azureAuthError';
import { AccountInfo, AuthenticationResult, InteractionRequiredAuthError, PublicClientApplication } from '@azure/msal-node';
const localize = nls.loadMessageBundle();
export abstract class AzureAuth implements vscode.Disposable {
public static ACCOUNT_VERSION = '2.0';
protected readonly memdb = new MemoryDatabase<string>();
protected readonly WorkSchoolAccountType: string = 'work_school';
protected readonly MicrosoftAccountType: string = 'microsoft';
protected readonly loginEndpointUrl: string;
public readonly commonTenant: Tenant;
public readonly organizationTenant: Tenant;
protected readonly redirectUri: string;
protected readonly scopes: string[];
protected readonly scopesString: string;
protected readonly clientId: string;
protected readonly resources: Resource[];
private _authLibrary: string | undefined;
constructor(
protected readonly metadata: AzureAccountProviderMetadata,
protected readonly tokenCache: SimpleTokenCache,
protected readonly context: vscode.ExtensionContext,
protected clientApplication: PublicClientApplication,
protected readonly uriEventEmitter: vscode.EventEmitter<vscode.Uri>,
protected readonly authType: AzureAuthType,
public readonly userFriendlyName: string,
public readonly authLibrary: string
public readonly userFriendlyName: string
) {
this._authLibrary = authLibrary;
this.loginEndpointUrl = this.metadata.settings.host;
this.commonTenant = {
id: 'common',
displayName: 'common',
};
this.organizationTenant = {
id: 'organizations',
displayName: 'organizations',
};
this.redirectUri = this.metadata.settings.redirectUri;
this.clientId = this.metadata.settings.clientId;
this.resources = [
this.metadata.settings.armResource,
this.metadata.settings.graphResource,
@@ -103,39 +100,19 @@ export abstract class AzureAuth implements vscode.Disposable {
if (!this.metadata.settings.microsoftResource) {
throw new Error(localize('noMicrosoftResource', "Provider '{0}' does not have a Microsoft resource endpoint defined.", this.metadata.displayName));
}
if (this._authLibrary === Constants.AuthLibrary.MSAL) {
const result = await this.loginMsal(this.organizationTenant, this.metadata.settings.microsoftResource);
loginComplete = result.authComplete;
if (!result?.response || !result.response?.account) {
Logger.error(`Authentication failed: ${loginComplete}`);
return {
canceled: false
};
}
const token: Token = {
token: result.response.accessToken,
key: result.response.account.homeAccountId,
tokenType: result.response.tokenType
const result = await this.login(this.commonTenant, this.metadata.settings.microsoftResource);
loginComplete = result.authComplete;
if (!result?.response) {
Logger.error('Authentication failed');
return {
canceled: false
};
const tokenClaims = <TokenClaims>result.response.idTokenClaims;
const account = await this.hydrateAccount(token, tokenClaims);
loginComplete?.resolve();
return account;
} else {// fallback to ADAL as default
const result = await this.loginAdal(this.commonTenant, this.metadata.settings.microsoftResource);
loginComplete = result.authComplete;
if (!result?.response) {
Logger.error('Authentication failed - no response');
return {
canceled: false
};
}
const account = await this.hydrateAccount(result.response.accessToken, result.response.tokenClaims);
loginComplete?.resolve();
return account;
}
const account = await this.hydrateAccount(result.response.accessToken, result.response.tokenClaims);
loginComplete?.resolve();
return account;
} catch (ex) {
Logger.error(`Login failed: ${ex}`);
Logger.error('Login failed');
if (ex instanceof AzureAuthError) {
if (loginComplete) {
loginComplete.reject(ex);
@@ -156,9 +133,9 @@ export abstract class AzureAuth implements vscode.Disposable {
}
}
public async refreshAccessAdal(account: AzureAccount): Promise<AzureAccount> {
public async refreshAccess(account: AzureAccount): Promise<AzureAccount> {
// Deprecated account - delete it.
if (account.key.accountVersion !== Constants.AccountVersion) {
if (account.key.accountVersion !== AzureAuth.ACCOUNT_VERSION) {
account.delete = true;
return account;
}
@@ -167,7 +144,7 @@ export abstract class AzureAuth implements vscode.Disposable {
// We want to return the one that owns the Azure account.
// Not doing so can result in token being issued for the wrong tenant
const tenant = account.properties.owningTenant;
const tokenResult = await this.getAccountSecurityTokenAdal(account, tenant.id, azdata.AzureResource.MicrosoftResourceManagement);
const tokenResult = await this.getAccountSecurityToken(account, tenant.id, azdata.AzureResource.MicrosoftResourceManagement);
if (!tokenResult) {
account.isStale = true;
return account;
@@ -177,28 +154,23 @@ export abstract class AzureAuth implements vscode.Disposable {
} catch (ex) {
if (ex instanceof AzureAuthError) {
void vscode.window.showErrorMessage(ex.message);
Logger.error(`Error refreshing access for account ${account.displayInfo.displayName}`, ex.originalMessageAndException);
Logger.error(ex.originalMessageAndException);
} else {
Logger.error(ex);
}
Logger.error(ex);
account.isStale = true;
return account;
}
}
public async hydrateAccount(token: Token | AccessToken, tokenClaims: TokenClaims): Promise<AzureAccount> {
let account: azdata.Account;
if (this._authLibrary === Constants.AuthLibrary.MSAL) {
const tenants = await this.getTenantsMsal(token.token);
account = this.createAccount(tokenClaims, token.key, tenants);
} else { // fallback to ADAL as default
const tenants = await this.getTenantsAdal({ ...token });
account = this.createAccount(tokenClaims, token.key, tenants);
}
const tenants = await this.getTenants({ ...token });
const account = this.createAccount(tokenClaims, token.key, tenants);
return account;
}
public async getAccountSecurityTokenAdal(account: AzureAccount, tenantId: string, azureResource: azdata.AzureResource): Promise<Token | undefined> {
public async getAccountSecurityToken(account: AzureAccount, tenantId: string, azureResource: azdata.AzureResource): Promise<Token | undefined> {
if (account.isStale === true) {
Logger.error('Account was stale. No tokens being fetched.');
return undefined;
@@ -206,7 +178,8 @@ export abstract class AzureAuth implements vscode.Disposable {
const resource = this.resources.find(s => s.azureResourceId === azureResource);
if (!resource) {
Logger.error(`Unable to find Azure resource ${azureResource} for account ${account.displayInfo.userId} and tenant ${tenantId}`);
Logger.error('Invalid resource, not fetching', azureResource);
return undefined;
}
@@ -223,7 +196,7 @@ export abstract class AzureAuth implements vscode.Disposable {
throw new AzureAuthError(localize('azure.tenantNotFound', "Specified tenant with ID '{0}' not found.", tenantId), `Tenant ${tenantId} not found.`, undefined);
}
const cachedTokens = await this.getSavedTokenAdal(tenant, resource, account.key);
const cachedTokens = await this.getSavedToken(tenant, resource, account.key);
// Let's check to see if we can just use the cached tokens to return to the user
if (cachedTokens?.accessToken) {
@@ -240,7 +213,7 @@ export abstract class AzureAuth implements vscode.Disposable {
const maxTolerance = 2 * 60; // two minutes
if (remainingTime < maxTolerance) {
const result = await this.refreshTokenAdal(tenant, resource, cachedTokens.refreshToken);
const result = await this.refreshToken(tenant, resource, cachedTokens.refreshToken);
if (result) {
accessToken = result.accessToken;
expiresOn = Number(result.expiresOn);
@@ -251,7 +224,7 @@ export abstract class AzureAuth implements vscode.Disposable {
return {
...accessToken,
expiresOn: expiresOn,
tokenType: Constants.Bearer
tokenType: 'Bearer'
};
}
}
@@ -261,7 +234,7 @@ export abstract class AzureAuth implements vscode.Disposable {
if (!this.metadata.settings.microsoftResource) {
throw new Error(localize('noMicrosoftResource', "Provider '{0}' does not have a Microsoft resource endpoint defined.", this.metadata.displayName));
}
const baseTokens = await this.getSavedTokenAdal(this.commonTenant, this.metadata.settings.microsoftResource, account.key);
const baseTokens = await this.getSavedToken(this.commonTenant, this.metadata.settings.microsoftResource, account.key);
if (!baseTokens) {
Logger.error('User had no base tokens for the basic resource registered. This should not happen and indicates something went wrong with the authentication cycle');
const msg = localize('azure.noBaseToken', 'Something failed with the authentication, or your tokens have been deleted from the system. Please try adding your account to Azure Data Studio again.');
@@ -269,20 +242,20 @@ export abstract class AzureAuth implements vscode.Disposable {
throw new AzureAuthError(msg, 'No base token found', undefined);
}
// Let's try to convert the access token type, worst case we'll have to prompt the user to do an interactive authentication.
const result = await this.refreshTokenAdal(tenant, resource, baseTokens.refreshToken);
const result = await this.refreshToken(tenant, resource, baseTokens.refreshToken);
if (result?.accessToken) {
return {
...result.accessToken,
expiresOn: Number(result.expiresOn),
tokenType: Constants.Bearer
tokenType: 'Bearer'
};
}
return undefined;
}
protected abstract loginAdal(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse | undefined, authComplete: Deferred<void, Error> }>;
protected abstract loginMsal(tenant: Tenant, resource: Resource): Promise<{ response: AuthenticationResult | null, authComplete: Deferred<void, Error> }>;
protected abstract login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse | undefined, authComplete: Deferred<void, Error> }>;
/**
* Refreshes a token, if a refreshToken is passed in then we use that. If it is not passed in then we will prompt the user for consent.
@@ -292,7 +265,7 @@ export abstract class AzureAuth implements vscode.Disposable {
* @returns The oauth token response or undefined. Undefined is returned when the user wants to ignore a tenant or chooses not to start the
* re-authentication process for their tenant.
*/
public async refreshTokenAdal(tenant: Tenant, resource: Resource, refreshToken: RefreshToken | undefined): Promise<OAuthTokenResponse | undefined> {
public async refreshToken(tenant: Tenant, resource: Resource, refreshToken: RefreshToken | undefined): Promise<OAuthTokenResponse | undefined> {
Logger.pii('Refreshing token', [{ name: 'token', objOrArray: refreshToken }], []);
if (refreshToken) {
const postData: RefreshTokenPostData = {
@@ -302,105 +275,36 @@ export abstract class AzureAuth implements vscode.Disposable {
tenant: tenant.id,
resource: resource.endpoint
};
return this.getTokenAdal(tenant, resource, postData);
return this.getToken(tenant, resource, postData);
}
return this.handleInteractionRequiredAdal(tenant, resource);
return this.handleInteractionRequired(tenant, resource);
}
/**
* Gets the access token for the correct account and scope from the token cache, if the correct token doesn't exist in the token cache
* (i.e. expired token, wrong scope, etc.), sends a request for a new token using the refresh token
* @param accountId
* @param azureResource
* @returns The authentication result, including the access token
*/
public async getTokenMsal(accountId: string, azureResource: azdata.AzureResource, tenantId: string): Promise<AuthenticationResult | null> {
const resource = this.resources.find(s => s.azureResourceId === azureResource);
if (!resource) {
Logger.error(`Error: Could not fetch the azure resource ${azureResource} `);
return null;
}
// Resource endpoint must end with '/' to form a valid scope for MSAL token request.
const endpoint = resource.endpoint.endsWith('/') ? resource.endpoint : resource.endpoint + '/';
let account: AccountInfo | null = await this.getAccountFromMsalCache(accountId);
if (!account) {
Logger.error('Error: Could not fetch account when acquiring token');
return null;
}
let newScope;
if (resource.azureResourceId === azdata.AzureResource.ResourceManagement) {
newScope = [`${endpoint}user_impersonation`];
} else {
newScope = [`${endpoint}.default`];
}
// construct request
// forceRefresh needs to be set true here in order to fetch the correct token, due to this issue
// https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3687
const tokenRequest = {
account: account,
authority: `https://login.microsoftonline.com/${tenantId}`,
scopes: newScope,
forceRefresh: true
};
try {
return await this.clientApplication.acquireTokenSilent(tokenRequest);
} catch (e) {
Logger.error('Failed to acquireTokenSilent', e);
if (e instanceof InteractionRequiredAuthError) {
// build refresh token request
const tenant: Tenant = {
id: tenantId,
displayName: ''
};
return this.handleInteractionRequiredMsal(tenant, resource);
} else if (e.name === 'ClientAuthError') {
Logger.error(e.message);
}
Logger.error('Failed to silently acquire token, not InteractionRequiredAuthError');
return null;
}
}
public async getAccountFromMsalCache(accountId: string): Promise<AccountInfo | null> {
const cache = this.clientApplication.getTokenCache();
if (!cache) {
Logger.error('Error: Could not fetch token cache.');
return null;
}
let account: AccountInfo | null = null;
// if the accountId is a home ID, it will include a "." character
if (accountId.includes(".")) {
account = await cache.getAccountByHomeId(accountId);
} else {
account = await cache.getAccountByLocalId(accountId);
}
return account;
}
public async getTokenAdal(tenant: Tenant, resource: Resource, postData: AuthorizationCodePostData | TokenPostData | RefreshTokenPostData): Promise<OAuthTokenResponse | undefined> {
Logger.verbose('Fetching token for tenant {0}', tenant.id);
public async getToken(tenant: Tenant, resource: Resource, postData: AuthorizationCodePostData | TokenPostData | RefreshTokenPostData): Promise<OAuthTokenResponse | undefined> {
Logger.verbose('Fetching token');
const tokenUrl = `${this.loginEndpointUrl}${tenant.id}/oauth2/token`;
const response = await this.makePostRequest(tokenUrl, postData);
Logger.pii('Token: ', [{ name: 'access token', objOrArray: response.data }, { name: 'refresh token', objOrArray: response.data }], []);
Logger.pii('Token: ', [{ name: 'access token', objOrArray: response.data }, { name: 'refresh token', objOrArray: response.data }],
[{ name: 'access token', value: response.data.access_token }, { name: 'refresh token', value: response.data.refresh_token }]);
if (response.data.error === 'interaction_required') {
return this.handleInteractionRequiredAdal(tenant, resource);
return this.handleInteractionRequired(tenant, resource);
}
if (response.data.error) {
Logger.error(`Response returned error : ${response.data}`);
Logger.error('Response error!', response.data);
throw new AzureAuthError(localize('azure.responseError', "Token retrieval failed with an error. [Open developer tools]({0}) for more details.", 'command:workbench.action.toggleDevTools'), 'Token retrieval failed', undefined);
}
const accessTokenString = response.data.access_token;
const refreshTokenString = response.data.refresh_token;
const expiresOnString = response.data.expires_on;
return this.getTokenHelperAdal(tenant, resource, accessTokenString, refreshTokenString, expiresOnString);
return this.getTokenHelper(tenant, resource, accessTokenString, refreshTokenString, expiresOnString);
}
public async getTokenHelperAdal(tenant: Tenant, resource: Resource, accessTokenString: string, refreshTokenString: string, expiresOnString: string): Promise<OAuthTokenResponse> {
public async getTokenHelper(tenant: Tenant, resource: Resource, accessTokenString: string, refreshTokenString: string, expiresOnString: string): Promise<OAuthTokenResponse> {
if (!accessTokenString) {
const msg = localize('azure.accessTokenEmpty', 'No access token returned from Microsoft OAuth');
throw new AzureAuthError(msg, 'Access token was empty', undefined);
@@ -445,57 +349,28 @@ export abstract class AzureAuth implements vscode.Disposable {
const accountKey: azdata.AccountKey = {
providerId: this.metadata.id,
accountId: userKey,
authLibrary: this._authLibrary
accountId: userKey
};
await this.saveTokenAdal(tenant, resource, accountKey, result);
await this.saveToken(tenant, resource, accountKey, result);
return result;
}
public async getTenantsMsal(token: string): Promise<Tenant[]> {
const tenantUri = url.resolve(this.metadata.settings.armResource.endpoint, 'tenants?api-version=2019-11-01');
try {
Logger.verbose('Fetching tenants with uri {0}', tenantUri);
let tenantList: string[] = [];
const tenantResponse = await this.makeGetRequest(tenantUri, token);
const tenants: Tenant[] = tenantResponse.data.value.map((tenantInfo: TenantResponse) => {
if (tenantInfo.displayName) {
tenantList.push(tenantInfo.displayName);
} else {
tenantList.push(tenantInfo.tenantId);
Logger.info('Tenant display name found empty: {0}', tenantInfo.tenantId);
}
return {
id: tenantInfo.tenantId,
displayName: tenantInfo.displayName ? tenantInfo.displayName : tenantInfo.tenantId,
userId: token,
tenantCategory: tenantInfo.tenantCategory
} as Tenant;
});
Logger.verbose(`Tenants: ${tenantList}`);
const homeTenantIndex = tenants.findIndex(tenant => tenant.tenantCategory === Constants.HomeCategory);
// remove home tenant from list of tenants
if (homeTenantIndex >= 0) {
const homeTenant = tenants.splice(homeTenantIndex, 1);
tenants.unshift(homeTenant[0]);
}
Logger.verbose(`Filtered Tenants: ${tenantList}`);
return tenants;
} catch (ex) {
Logger.error(`Error fetching tenants :${ex}`);
throw new Error('Error retrieving tenant information');
}
}
//#region tenant calls
public async getTenantsAdal(token: AccessToken): Promise<Tenant[]> {
public async getTenants(token: AccessToken): Promise<Tenant[]> {
interface TenantResponse { // https://docs.microsoft.com/en-us/rest/api/resources/tenants/list
id: string
tenantId: string
displayName?: string
tenantCategory?: string
}
const tenantUri = url.resolve(this.metadata.settings.armResource.endpoint, 'tenants?api-version=2019-11-01');
try {
Logger.verbose('Fetching tenants with URI: {0}', tenantUri);
let tenantList: string[] = [];
Logger.verbose('Fetching tenants', tenantUri);
const tenantResponse = await this.makeGetRequest(tenantUri, token.token);
if (tenantResponse.status !== 200) {
Logger.error(`Error with tenant response, status: ${tenantResponse.status} | status text: ${tenantResponse.statusText}`);
@@ -503,22 +378,16 @@ export abstract class AzureAuth implements vscode.Disposable {
throw new Error('Error with tenant response');
}
const tenants: Tenant[] = tenantResponse.data.value.map((tenantInfo: TenantResponse) => {
if (tenantInfo.displayName) {
tenantList.push(tenantInfo.displayName);
} else {
tenantList.push(tenantInfo.tenantId);
Logger.info('Tenant display name found empty: {0}', tenantInfo.tenantId);
}
Logger.verbose(`Tenant: ${tenantInfo.displayName}`);
return {
id: tenantInfo.tenantId,
displayName: tenantInfo.displayName ? tenantInfo.displayName : tenantInfo.tenantId,
displayName: tenantInfo.displayName ? tenantInfo.displayName : localize('azureWorkAccountDisplayName', "Work or school account"),
userId: token.key,
tenantCategory: tenantInfo.tenantCategory
} as Tenant;
});
Logger.verbose(`Tenants: ${tenantList}`);
const homeTenantIndex = tenants.findIndex(tenant => tenant.tenantCategory === Constants.HomeCategory);
// remove home tenant from list of tenants
const homeTenantIndex = tenants.findIndex(tenant => tenant.tenantCategory === 'Home');
if (homeTenantIndex >= 0) {
const homeTenant = tenants.splice(homeTenantIndex, 1);
tenants.unshift(homeTenant[0]);
@@ -534,7 +403,7 @@ export abstract class AzureAuth implements vscode.Disposable {
//#endregion
//#region token management
private async saveTokenAdal(tenant: Tenant, resource: Resource, accountKey: azdata.AccountKey, { accessToken, refreshToken, expiresOn }: OAuthTokenResponse) {
private async saveToken(tenant: Tenant, resource: Resource, accountKey: azdata.AccountKey, { accessToken, refreshToken, expiresOn }: OAuthTokenResponse) {
const msg = localize('azure.cacheErrorAdd', "Error when adding your account to the cache.");
if (!tenant.id || !resource.id) {
Logger.pii('Tenant ID or resource ID was undefined', [], [], tenant, resource);
@@ -552,7 +421,7 @@ export abstract class AzureAuth implements vscode.Disposable {
}
}
public async getSavedTokenAdal(tenant: Tenant, resource: Resource, accountKey: azdata.AccountKey): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken | undefined, expiresOn: string } | undefined> {
public async getSavedToken(tenant: Tenant, resource: Resource, accountKey: azdata.AccountKey): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken | undefined, expiresOn: string } | undefined> {
const getMsg = localize('azure.cacheErrorGet', "Error when getting your account from the cache");
const parseMsg = localize('azure.cacheErrorParse', "Error when parsing your account from the cache");
@@ -595,22 +464,12 @@ export abstract class AzureAuth implements vscode.Disposable {
}
//#endregion
//#region interaction handling
public async handleInteractionRequiredMsal(tenant: Tenant, resource: Resource): Promise<AuthenticationResult | null> {
const shouldOpen = await this.askUserForInteraction(tenant, resource);
if (shouldOpen) {
const result = await this.loginMsal(tenant, resource);
result?.authComplete?.resolve();
return result?.response;
}
return null;
}
public async handleInteractionRequiredAdal(tenant: Tenant, resource: Resource): Promise<OAuthTokenResponse | undefined> {
public async handleInteractionRequired(tenant: Tenant, resource: Resource): Promise<OAuthTokenResponse | undefined> {
const shouldOpen = await this.askUserForInteraction(tenant, resource);
if (shouldOpen) {
const result = await this.loginAdal(tenant, resource);
const result = await this.login(tenant, resource);
result?.authComplete?.resolve();
return result?.response;
}
@@ -628,14 +487,13 @@ export abstract class AzureAuth implements vscode.Disposable {
}
const getTenantConfigurationSet = (): Set<string> => {
const configuration = vscode.workspace.getConfiguration(Constants.AzureTenantConfigSection);
const configuration = vscode.workspace.getConfiguration('azure.tenant.config');
let values: string[] = configuration.get('filter') ?? [];
return new Set<string>(values);
};
// The user wants to ignore this tenant.
if (getTenantConfigurationSet().has(tenant.id)) {
Logger.info(`Tenant ${tenant.id} found in the ignore list, authentication will not be attempted.`);
return false;
}
@@ -684,33 +542,28 @@ export abstract class AzureAuth implements vscode.Disposable {
//#region data modeling
public createAccount(tokenClaims: TokenClaims, key: string, tenants: Tenant[]): AzureAccount {
Logger.verbose(`Token Claims acccount: ${tokenClaims.name}, TID: ${tokenClaims.tid}`);
Logger.verbose(`Token Claims: ${tokenClaims.name}`);
tenants.forEach((tenant) => {
Logger.verbose(`Tenant ID: ${tenant.id}, Tenant Name: ${tenant.displayName}`);
Logger.verbose(
`Tenant ID: ${tenant.id}
Tenant Name: ${tenant.displayName}`);
});
// Determine if this is a microsoft account
let accountIssuer = 'unknown';
if (tokenClaims.iss === 'https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/' ||
tokenClaims.iss === 'https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/v2.0') {
accountIssuer = Constants.AccountIssuer.Corp;
if (tokenClaims.iss === 'https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/') {
accountIssuer = 'corp';
}
if (tokenClaims?.idp === 'live.com') {
accountIssuer = Constants.AccountIssuer.Msft;
accountIssuer = 'msft';
}
const name = tokenClaims.name ?? tokenClaims.email ?? tokenClaims.unique_name ?? tokenClaims.preferred_username;
const email = tokenClaims.email ?? tokenClaims.unique_name ?? tokenClaims.preferred_username;
let owningTenant: Tenant = this.commonTenant; // default to common tenant
const name = tokenClaims.name ?? tokenClaims.email ?? tokenClaims.unique_name;
const email = tokenClaims.email ?? tokenClaims.unique_name;
// Read more about tid > https://learn.microsoft.com/azure/active-directory/develop/id-tokens
if (tokenClaims.tid) {
owningTenant = tenants.find(t => t.id === tokenClaims.tid) ?? { 'id': tokenClaims.tid, 'displayName': 'Microsoft Account' };
} else {
Logger.info('Could not find tenant information from tokenClaims, falling back to common Tenant.');
}
const owningTenant = tenants.find(t => t.id === tokenClaims.tid)
?? { 'id': tokenClaims.tid, 'displayName': 'Microsoft Account' };
let displayName = name;
if (email) {
@@ -719,26 +572,25 @@ export abstract class AzureAuth implements vscode.Disposable {
let contextualDisplayName: string;
switch (accountIssuer) {
case Constants.AccountIssuer.Corp:
case 'corp':
contextualDisplayName = localize('azure.microsoftCorpAccount', "Microsoft Corp");
break;
case Constants.AccountIssuer.Msft:
case 'msft':
contextualDisplayName = localize('azure.microsoftAccountDisplayName', 'Microsoft Account');
break;
default:
contextualDisplayName = displayName;
}
let accountType = accountIssuer === Constants.AccountIssuer.Msft
? Constants.AccountType.Microsoft
: Constants.AccountType.WorkSchool;
let accountType = accountIssuer === 'msft'
? this.MicrosoftAccountType
: this.WorkSchoolAccountType;
const account = {
key: {
providerId: this.metadata.id,
accountId: key,
accountVersion: Constants.AccountVersion,
authLibrary: this._authLibrary
accountVersion: AzureAuth.ACCOUNT_VERSION,
},
name: displayName,
displayInfo: {
@@ -751,7 +603,7 @@ export abstract class AzureAuth implements vscode.Disposable {
},
properties: {
providerSettings: this.metadata,
isMsAccount: accountIssuer === Constants.AccountIssuer.Msft,
isMsAccount: accountIssuer === 'msft',
owningTenant: owningTenant,
tenants,
azureAuthType: this.authType
@@ -808,10 +660,8 @@ export abstract class AzureAuth implements vscode.Disposable {
protected toBase64UrlEncoding(base64string: string): string {
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding
}
public async deleteAllCacheMsal(): Promise<void> {
this.clientApplication.clearCache();
}
public async deleteAllCacheAdal(): Promise<void> {
public async deleteAllCache(): Promise<void> {
const results = await this.tokenCache.findCredentials('');
for (let { account } of results) {
@@ -821,34 +671,17 @@ export abstract class AzureAuth implements vscode.Disposable {
public async clearCredentials(account: azdata.AccountKey): Promise<void> {
try {
// remove account based on authLibrary field, accounts added before this field was present will default to
// ADAL method of account removal
if (account.authLibrary === Constants.AuthLibrary.MSAL) {
return await this.deleteAccountCacheMsal(account);
} else { // fallback to ADAL by default
return await this.deleteAccountCacheAdal(account);
}
return this.deleteAccountCache(account);
} catch (ex) {
// We need not prompt user for error if token could not be removed from cache.
Logger.error('Error when removing token from cache: ', ex);
const msg = localize('azure.cacheErrrorRemove', "Error when removing your account from the cache.");
void vscode.window.showErrorMessage(msg);
Logger.error('Error when removing tokens.', ex);
}
}
public async deleteAccountCacheMsal(account: azdata.AccountKey): Promise<void> {
const tokenCache = this.clientApplication.getTokenCache();
let msalAccount: AccountInfo | null = await this.getAccountFromMsalCache(account.accountId);
if (!msalAccount) {
Logger.error(`MSAL: Unable to find account ${account.accountId} for removal`);
throw Error(`Unable to find account ${account.accountId}`);
}
await tokenCache.removeAccount(msalAccount);
}
public async deleteAccountCacheAdal(account: azdata.AccountKey): Promise<void> {
public async deleteAccountCache(account: azdata.AccountKey): Promise<void> {
const results = await this.tokenCache.findCredentials(account.accountId);
if (!results) {
Logger.error('ADAL: Unable to find account for removal');
}
for (let { account } of results) {
await this.tokenCache.clearCredential(account);
}
@@ -889,13 +722,6 @@ export interface RefreshToken extends AccountKey {
key: string
}
export interface TenantResponse { // https://docs.microsoft.com/en-us/rest/api/resources/tenants/list
id: string
tenantId: string
displayName?: string
tenantCategory?: string
}
export interface MultiTenantTokenResponse {
[tenantId: string]: Token | undefined;
}

View File

@@ -12,13 +12,11 @@ import { SimpleTokenCache } from '../simpleTokenCache';
import { SimpleWebServer } from '../utils/simpleWebServer';
import { AzureAuthError } from './azureAuthError';
import { Logger } from '../../utils/Logger';
import * as Constants from '../../constants';
import * as nls from 'vscode-nls';
import * as path from 'path';
import * as http from 'http';
import * as qs from 'qs';
import { promises as fs } from 'fs';
import { PublicClientApplication, CryptoProvider, AuthorizationUrlRequest, AuthorizationCodeRequest, AuthenticationResult } from '@azure/msal-node';
const localize = nls.loadMessageBundle();
@@ -30,43 +28,33 @@ interface AuthCodeResponse {
interface CryptoValues {
nonce: string;
challengeMethod: string;
codeVerifier: string;
codeChallenge: string;
}
export class AzureAuthCodeGrant extends AzureAuth {
private static readonly USER_FRIENDLY_NAME: string = localize('azure.azureAuthCodeGrantName', 'Azure Auth Code Grant');
private cryptoProvider: CryptoProvider;
private pkceCodes: CryptoValues;
constructor(
metadata: AzureAccountProviderMetadata,
tokenCache: SimpleTokenCache,
context: vscode.ExtensionContext,
uriEventEmitter: vscode.EventEmitter<vscode.Uri>,
clientApplication: PublicClientApplication,
authLibrary: string
) {
super(metadata, tokenCache, context, clientApplication, uriEventEmitter, AzureAuthType.AuthCodeGrant, AzureAuthCodeGrant.USER_FRIENDLY_NAME, authLibrary);
this.cryptoProvider = new CryptoProvider();
this.pkceCodes = {
nonce: '',
challengeMethod: Constants.S256_CODE_CHALLENGE_METHOD, // Use SHA256 as the challenge method
codeVerifier: '', // Generate a code verifier for the Auth Code Request first
codeChallenge: '', // Generate a code challenge from the previously generated code verifier
};
super(metadata, tokenCache, context, uriEventEmitter, AzureAuthType.AuthCodeGrant, AzureAuthCodeGrant.USER_FRIENDLY_NAME);
}
protected async loginAdal(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse | undefined, authComplete: Deferred<void, Error> }> {
protected async login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse | undefined, authComplete: Deferred<void, Error> }> {
let authCompleteDeferred: Deferred<void, Error>;
let authCompletePromise = new Promise<void>((resolve, reject) => authCompleteDeferred = { resolve, reject });
let authResponse: AuthCodeResponse;
if (vscode.env.uiKind === vscode.UIKind.Web) {
authResponse = await this.loginWebAdal(tenant, resource);
authResponse = await this.loginWeb(tenant, resource);
} else {
authResponse = await this.loginDesktopAdal(tenant, resource, authCompletePromise);
authResponse = await this.loginDesktop(tenant, resource, authCompletePromise);
}
return {
@@ -75,30 +63,6 @@ export class AzureAuthCodeGrant extends AzureAuth {
};
}
protected async loginMsal(tenant: Tenant, resource: Resource): Promise<{ response: AuthenticationResult | null, authComplete: Deferred<void, Error> }> {
let authCompleteDeferred: Deferred<void, Error>;
let authCompletePromise = new Promise<void>((resolve, reject) => authCompleteDeferred = { resolve, reject });
let authCodeRequest: AuthorizationCodeRequest;
if (vscode.env.uiKind === vscode.UIKind.Web) {
authCodeRequest = await this.loginWebMsal(tenant, resource);
} else {
authCodeRequest = await this.loginDesktopMsal(tenant, resource, authCompletePromise);
}
let result = await this.clientApplication.acquireTokenByCode(authCodeRequest);
if (!result) {
Logger.error('Failed to acquireTokenByCode');
Logger.error(`Auth Code Request: ${JSON.stringify(authCodeRequest)}`)
throw Error('Failed to fetch token using auth code');
} else {
return {
response: result,
authComplete: authCompleteDeferred!
};
}
}
/**
* Requests an OAuthTokenResponse from Microsoft OAuth
*
@@ -115,47 +79,12 @@ export class AzureAuthCodeGrant extends AzureAuth {
resource: resource.endpoint
};
return this.getTokenAdal(tenant, resource, postData);
return this.getToken(tenant, resource, postData);
}
private async loginWebMsal(tenant: Tenant, resource: Resource): Promise<AuthorizationCodeRequest> {
private async loginWeb(tenant: Tenant, resource: Resource): Promise<AuthCodeResponse> {
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://microsoft.azurecore`));
await this.createCryptoValuesMsal();
const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' ? 443 : 80);
const state = `${port},${encodeURIComponent(this.pkceCodes.nonce)},${encodeURIComponent(callbackUri.query)}`;
try {
let authUrlRequest: AuthorizationUrlRequest;
authUrlRequest = {
scopes: this.scopes,
redirectUri: this.redirectUri,
codeChallenge: this.pkceCodes.codeChallenge,
codeChallengeMethod: this.pkceCodes.challengeMethod,
prompt: Constants.SELECT_ACCOUNT,
state: state
};
let authCodeRequest: AuthorizationCodeRequest;
authCodeRequest = {
scopes: this.scopes,
redirectUri: this.redirectUri,
codeVerifier: this.pkceCodes.codeVerifier,
code: ''
};
let authCodeUrl = await this.clientApplication.getAuthCodeUrl(authUrlRequest);
await vscode.env.openExternal(vscode.Uri.parse(authCodeUrl));
const authCode = await this.handleWebResponse(state);
authCodeRequest.code = authCode;
return authCodeRequest;
} catch (e) {
Logger.error('MSAL: Error requesting auth code', e);
throw new AzureAuthError('error', 'Error requesting auth code', e);
}
}
private async loginWebAdal(tenant: Tenant, resource: Resource): Promise<AuthCodeResponse> {
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://microsoft.azurecore`));
const { nonce, codeVerifier, codeChallenge } = this.createCryptoValuesAdal();
const { nonce, codeVerifier, codeChallenge } = this.createCryptoValues();
const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' ? 443 : 80);
const state = `${port},${encodeURIComponent(nonce)},${encodeURIComponent(callbackUri.query)}`;
@@ -165,8 +94,8 @@ export class AzureAuthCodeGrant extends AzureAuth {
client_id: this.clientId,
redirect_uri: this.redirectUri,
state,
prompt: Constants.SELECT_ACCOUNT,
code_challenge_method: Constants.S256_CODE_CHALLENGE_METHOD,
prompt: 'select_account',
code_challenge_method: 'S256',
code_challenge: codeChallenge,
resource: resource.id
};
@@ -212,7 +141,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
}, {});
}
private async loginDesktopMsal(tenant: Tenant, resource: Resource, authCompletePromise: Promise<void>): Promise<AuthorizationCodeRequest> {
private async loginDesktop(tenant: Tenant, resource: Resource, authCompletePromise: Promise<void>): Promise<AuthCodeResponse> {
const server = new SimpleWebServer();
let serverPort: string;
@@ -222,56 +151,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
const msg = localize('azure.serverCouldNotStart', 'Server could not start. This could be a permissions error or an incompatibility on your system. You can try enabling device code authentication from settings.');
throw new AzureAuthError(msg, 'Server could not start', ex);
}
await this.createCryptoValuesMsal();
const state = `${serverPort},${this.pkceCodes.nonce}`;
try {
let authUrlRequest: AuthorizationUrlRequest;
authUrlRequest = {
scopes: this.scopes,
redirectUri: `${this.redirectUri}:${serverPort}/redirect`,
codeChallenge: this.pkceCodes.codeChallenge,
codeChallengeMethod: this.pkceCodes.challengeMethod,
prompt: Constants.SELECT_ACCOUNT,
authority: `https://login.microsoftonline.com/${tenant.id}`,
state: state
};
let authCodeRequest: AuthorizationCodeRequest;
authCodeRequest = {
scopes: this.scopes,
redirectUri: `${this.redirectUri}:${serverPort}/redirect`,
codeVerifier: this.pkceCodes.codeVerifier,
authority: `https://login.microsoftonline.com/${tenant.id}`,
code: ''
};
let authCodeUrl = await this.clientApplication.getAuthCodeUrl(authUrlRequest);
await vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${serverPort}/signin?nonce=${encodeURIComponent(this.pkceCodes.nonce)}`));
const authCode = await this.addServerListeners(server, this.pkceCodes.nonce, authCodeUrl, authCompletePromise);
authCodeRequest.code = authCode;
return authCodeRequest;
}
catch (e) {
Logger.error('MSAL: Error requesting auth code', e);
throw new AzureAuthError('error', 'Error requesting auth code', e);
}
}
private async loginDesktopAdal(tenant: Tenant, resource: Resource, authCompletePromise: Promise<void>): Promise<AuthCodeResponse> {
const server = new SimpleWebServer();
let serverPort: string;
try {
serverPort = await server.startup();
} catch (ex) {
const msg = localize('azure.serverCouldNotStart', 'Server could not start. This could be a permissions error or an incompatibility on your system. You can try enabling device code authentication from settings.');
throw new AzureAuthError(msg, 'Server could not start', ex);
}
const { nonce, codeVerifier, codeChallenge } = this.createCryptoValuesAdal();
const { nonce, codeVerifier, codeChallenge } = this.createCryptoValues();
const state = `${serverPort},${encodeURIComponent(nonce)}`;
const loginQuery = {
response_type: 'code',
@@ -279,8 +159,8 @@ export class AzureAuthCodeGrant extends AzureAuth {
client_id: this.clientId,
redirect_uri: `${this.redirectUri}:${serverPort}/redirect`,
state,
prompt: Constants.SELECT_ACCOUNT,
code_challenge_method: Constants.S256_CODE_CHALLENGE_METHOD,
prompt: 'select_account',
code_challenge_method: 'S256',
code_challenge: codeChallenge,
resource: resource.endpoint
};
@@ -392,21 +272,13 @@ export class AzureAuthCodeGrant extends AzureAuth {
}
private createCryptoValuesAdal(): CryptoValues {
private createCryptoValues(): CryptoValues {
const nonce = crypto.randomBytes(16).toString('base64');
const codeVerifier = this.toBase64UrlEncoding(crypto.randomBytes(32).toString('base64'));
const codeChallenge = this.toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64'));
const challengeMethod = '';
return {
nonce, challengeMethod, codeVerifier, codeChallenge
nonce, codeVerifier, codeChallenge
};
}
private async createCryptoValuesMsal(): Promise<void> {
this.pkceCodes.nonce = this.cryptoProvider.createNewGuid();
const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
this.pkceCodes.codeVerifier = verifier;
this.pkceCodes.codeChallenge = challenge;
}
}

View File

@@ -13,6 +13,7 @@ import {
DeviceCodeCheckPostData,
} from './azureAuth';
import {
AzureAccountProviderMetadata,
AzureAuthType,
@@ -20,10 +21,8 @@ import {
Resource
} from 'azurecore';
import { Deferred } from '../interfaces';
import { AuthenticationResult, DeviceCodeRequest, PublicClientApplication } from '@azure/msal-node';
import { SimpleTokenCache } from '../simpleTokenCache';
import { Logger } from '../../utils/Logger';
const localize = nls.loadMessageBundle();
interface DeviceCodeLogin { // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
@@ -51,34 +50,12 @@ export class AzureDeviceCode extends AzureAuth {
tokenCache: SimpleTokenCache,
context: vscode.ExtensionContext,
uriEventEmitter: vscode.EventEmitter<vscode.Uri>,
clientApplication: PublicClientApplication,
authLibrary: string
) {
super(metadata, tokenCache, context, clientApplication, uriEventEmitter, AzureAuthType.DeviceCode, AzureDeviceCode.USER_FRIENDLY_NAME, authLibrary);
super(metadata, tokenCache, context, uriEventEmitter, AzureAuthType.DeviceCode, AzureDeviceCode.USER_FRIENDLY_NAME);
this.pageTitle = localize('addAccount', "Add {0} account", this.metadata.displayName);
}
protected async loginMsal(tenant: Tenant, resource: Resource): Promise<{ response: AuthenticationResult | null, authComplete: Deferred<void, Error> }> {
let authCompleteDeferred: Deferred<void, Error>;
let authCompletePromise = new Promise<void>((resolve, reject) => authCompleteDeferred = { resolve, reject });
const deviceCodeRequest: DeviceCodeRequest = {
scopes: this.scopes,
authority: `https://login.microsoftonline.com/${tenant.id}`,
deviceCodeCallback: async (response) => {
await azdata.accounts.beginAutoOAuthDeviceCode(this.metadata.id, this.pageTitle, response.message, response.userCode, response.verificationUri);
}
};
const authResult = await this.clientApplication.acquireTokenByDeviceCode(deviceCodeRequest);
this.closeOnceComplete(authCompletePromise).catch(Logger.error);
return {
response: authResult,
authComplete: authCompleteDeferred!
};
}
protected async loginAdal(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse, authComplete: Deferred<void, Error> }> {
protected async login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse, authComplete: Deferred<void, Error> }> {
let authCompleteDeferred: Deferred<void, Error>;
let authCompletePromise = new Promise<void>((resolve, reject) => authCompleteDeferred = { resolve, reject });
@@ -102,7 +79,7 @@ export class AzureDeviceCode extends AzureAuth {
const currentTime = new Date().getTime() / 1000;
const expiresOn = `${currentTime + finalDeviceLogin.expires_in}`;
const result = await this.getTokenHelperAdal(tenant, resource, accessTokenString, refreshTokenString, expiresOn);
const result = await this.getTokenHelper(tenant, resource, accessTokenString, refreshTokenString, expiresOn);
this.closeOnceComplete(authCompletePromise).catch(Logger.error);
return {
@@ -116,6 +93,7 @@ export class AzureDeviceCode extends AzureAuth {
azdata.accounts.endAutoOAuthDeviceCode();
}
private setupPolling(info: DeviceCodeLogin): Promise<DeviceCodeLoginResult> {
const timeoutMessage = localize('azure.timeoutDeviceCode', 'Timed out when waiting for device code login.');
const fiveMinutes = 5 * 60 * 1000;
@@ -152,15 +130,18 @@ export class AzureDeviceCode extends AzureAuth {
};
const postResult = await this.makePostRequest(uri, postData);
const result: DeviceCodeLoginResult = postResult.data;
return result;
} catch (ex) {
Logger.error('Unexpected error making Azure auth request', 'azureCore.checkForResult', JSON.stringify(ex?.response?.data, undefined, 2));
console.log(ex);
console.log('Unexpected error making Azure auth request', 'azureCore.checkForResult', JSON.stringify(ex?.response?.data, undefined, 2));
throw new Error(msg);
}
}
public override async autoOAuthCancelled(): Promise<void> {
return azdata.accounts.endAutoOAuthDeviceCode();
}

View File

@@ -13,37 +13,31 @@ import {
AzureAccount
} from 'azurecore';
import { Deferred } from './interfaces';
import { PublicClientApplication } from '@azure/msal-node';
import { SimpleTokenCache } from './simpleTokenCache';
import { Logger } from '../utils/Logger';
import { MultiTenantTokenResponse, Token, AzureAuth } from './auths/azureAuth';
import { AzureAuthCodeGrant } from './auths/azureAuthCodeGrant';
import { AzureDeviceCode } from './auths/azureDeviceCode';
import { filterAccounts } from '../azureResource/utils';
import * as Constants from '../constants';
const localize = nls.loadMessageBundle();
export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disposable {
private static readonly CONFIGURATION_SECTION = 'accounts.azure.auth';
private readonly authMappings = new Map<AzureAuthType, AzureAuth>();
private initComplete!: Deferred<void, Error>;
private initCompletePromise: Promise<void> = new Promise<void>((resolve, reject) => this.initComplete = { resolve, reject });
public clientApplication: PublicClientApplication;
constructor(
metadata: AzureAccountProviderMetadata,
tokenCache: SimpleTokenCache,
context: vscode.ExtensionContext,
clientApplication: PublicClientApplication,
uriEventHandler: vscode.EventEmitter<vscode.Uri>,
private readonly authLibrary: string,
private readonly forceDeviceCode: boolean = false
) {
this.clientApplication = clientApplication;
vscode.workspace.onDidChangeConfiguration((changeEvent) => {
const impactProvider = changeEvent.affectsConfiguration(Constants.AccountsAzureAuthSection);
if (impactProvider === true) {
const impact = changeEvent.affectsConfiguration(AzureAccountProvider.CONFIGURATION_SECTION);
if (impact === true) {
this.handleAuthMapping(metadata, tokenCache, context, uriEventHandler);
}
});
@@ -56,27 +50,22 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
}
clearTokenCache(): Thenable<void> {
return this.authLibrary === Constants.AuthLibrary.MSAL
? this.getAuthMethod().deleteAllCacheMsal()
// fallback to ADAL as default
: this.getAuthMethod().deleteAllCacheAdal();
return this.getAuthMethod().deleteAllCache();
}
private handleAuthMapping(metadata: AzureAccountProviderMetadata, tokenCache: SimpleTokenCache, context: vscode.ExtensionContext, uriEventHandler: vscode.EventEmitter<vscode.Uri>) {
this.authMappings.forEach(m => m.dispose());
this.authMappings.clear();
const configuration = vscode.workspace.getConfiguration(AzureAccountProvider.CONFIGURATION_SECTION);
const configuration = vscode.workspace.getConfiguration(Constants.AccountsAzureAuthSection);
const codeGrantMethod: boolean = configuration.get<boolean>(Constants.AuthType.CodeGrant, false);
const deviceCodeMethod: boolean = configuration.get<boolean>(Constants.AuthType.DeviceCode, false);
const codeGrantMethod: boolean = configuration.get<boolean>('codeGrant', false);
const deviceCodeMethod: boolean = configuration.get<boolean>('deviceCode', false);
if (codeGrantMethod === true && !this.forceDeviceCode) {
this.authMappings.set(AzureAuthType.AuthCodeGrant, new AzureAuthCodeGrant(metadata, tokenCache, context, uriEventHandler, this.clientApplication, this.authLibrary));
}
if (deviceCodeMethod === true || this.forceDeviceCode) {
this.authMappings.set(AzureAuthType.DeviceCode, new AzureDeviceCode(metadata, tokenCache, context, uriEventHandler, this.clientApplication, this.authLibrary));
}
if (codeGrantMethod === false && deviceCodeMethod === false && !this.forceDeviceCode) {
this.authMappings.set(AzureAuthType.AuthCodeGrant, new AzureAuthCodeGrant(metadata, tokenCache, context, uriEventHandler));
} else if (deviceCodeMethod === true || this.forceDeviceCode) {
this.authMappings.set(AzureAuthType.DeviceCode, new AzureDeviceCode(metadata, tokenCache, context, uriEventHandler));
} else {
console.error('No authentication methods selected');
}
}
@@ -106,25 +95,13 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
private async _initialize(storedAccounts: AzureAccount[]): Promise<AzureAccount[]> {
const accounts: AzureAccount[] = [];
console.log(`Initializing stored accounts ${JSON.stringify(accounts)}`);
const updatedAccounts = filterAccounts(storedAccounts, this.authLibrary);
for (let account of updatedAccounts) {
for (let account of storedAccounts) {
const azureAuth = this.getAuthMethod(account);
if (!azureAuth) {
account.isStale = true;
accounts.push(account);
} else {
account.isStale = false;
if (this.authLibrary === Constants.AuthLibrary.MSAL) {
// Check MSAL Cache before adding account, to mark it as stale if it is not present in cache
const accountInCache = await azureAuth.getAccountFromMsalCache(account.key.accountId);
if (!accountInCache) {
account.isStale = true;
}
accounts.push(account);
} else { // fallback to ADAL as default
accounts.push(await azureAuth.refreshAccessAdal(account));
}
accounts.push(await azureAuth.refreshAccess(account));
}
}
this.initComplete.resolve();
@@ -143,32 +120,8 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
private async _getAccountSecurityToken(account: AzureAccount, tenantId: string, resource: azdata.AzureResource): Promise<Token | undefined> {
await this.initCompletePromise;
const azureAuth = this.getAuthMethod(account);
if (azureAuth) {
Logger.pii(`Getting account security token for ${JSON.stringify(account.key)} (tenant ${tenantId}). Auth Method = ${azureAuth.userFriendlyName}`, [], []);
if (this.authLibrary === Constants.AuthLibrary.MSAL) {
tenantId = tenantId || account.properties.owningTenant.id;
let authResult = await azureAuth.getTokenMsal(account.key.accountId, resource, tenantId);
if (!authResult || !authResult.account || !authResult.account.idTokenClaims) {
Logger.error(`MSAL: getToken call failed`);
throw Error('Failed to get token');
} else {
const token: Token = {
key: authResult.account.homeAccountId,
token: authResult.accessToken,
tokenType: authResult.tokenType,
expiresOn: authResult.account.idTokenClaims.exp
};
return token;
}
} else { // fallback to ADAL as default
return azureAuth.getAccountSecurityTokenAdal(account, tenantId, resource);
}
} else {
account.isStale = true;
Logger.error(`_getAccountSecurityToken: Authentication method not found for account ${account.displayInfo.displayName}`);
throw Error('Failed to get authentication method, please remove and re-add the account');
}
Logger.pii(`Getting account security token for ${JSON.stringify(account.key)} (tenant ${tenantId}). Auth Method = ${azureAuth.userFriendlyName}`, [], []);
return azureAuth?.getAccountSecurityToken(account, tenantId, resource);
}
private async _getSecurityToken(account: AzureAccount, resource: azdata.AzureResource): Promise<MultiTenantTokenResponse | undefined> {
@@ -223,6 +176,7 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
return pick.azureAuth.startLogin();
}
refresh(account: AzureAccount): Thenable<AzureAccount | azdata.PromptFailedResult> {
return this._refresh(account);
}

View File

@@ -12,12 +12,7 @@ import providerSettings from './providerSettings';
import { AzureAccountProvider as AzureAccountProvider } from './azureAccountProvider';
import { AzureAccountProviderMetadata } from 'azurecore';
import { ProviderSettings } from './interfaces';
import { MsalCachePluginProvider } from './utils/msalCachePlugin';
import * as loc from '../localizedConstants';
import { Configuration, PublicClientApplication } from '@azure/msal-node';
import * as Constants from '../constants';
import { Logger } from '../utils/Logger';
import { ILoggerCallback, LogLevel as MsalLogLevel } from "@azure/msal-common";
let localize = nls.loadMessageBundle();
@@ -28,21 +23,22 @@ class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.
}
export class AzureAccountProviderService implements vscode.Disposable {
// CONSTANTS ///////////////////////////////////////////////////////////////
private static CommandClearTokenCache = 'accounts.clearTokenCache';
private static ConfigurationSection = 'accounts.azure.cloud';
private static CredentialNamespace = 'azureAccountProviderCredentials';
// MEMBER VARIABLES ////////////////////////////////////////////////////////
private _disposables: vscode.Disposable[] = [];
private _accountDisposals: { [accountProviderId: string]: vscode.Disposable } = {};
private _accountProviders: { [accountProviderId: string]: azdata.AccountProvider } = {};
private _credentialProvider: azdata.CredentialProvider | undefined = undefined;
private _cachePluginProvider: MsalCachePluginProvider | undefined = undefined;
private _configChangePromiseChain: Thenable<void> = Promise.resolve();
private _currentConfig: vscode.WorkspaceConfiguration | undefined = undefined;
private _event: events.EventEmitter = new events.EventEmitter();
private readonly _uriEventHandler: UriEventHandler = new UriEventHandler();
public clientApplication!: PublicClientApplication;
constructor(private _context: vscode.ExtensionContext,
private _userStoragePath: string,
private _authLibrary: string) {
constructor(private _context: vscode.ExtensionContext, private _userStoragePath: string) {
this._disposables.push(vscode.window.registerUriHandler(this._uriEventHandler));
}
@@ -51,16 +47,17 @@ export class AzureAccountProviderService implements vscode.Disposable {
let self = this;
// Register commands
this._context.subscriptions.push(vscode.commands.registerCommand(Constants.AccountsClearTokenCacheCommand,
() => { self._event.emit(Constants.AccountsClearTokenCacheCommand); }
this._context.subscriptions.push(vscode.commands.registerCommand(
AzureAccountProviderService.CommandClearTokenCache,
() => { self._event.emit(AzureAccountProviderService.CommandClearTokenCache); }
));
this._event.on(Constants.AccountsClearTokenCacheCommand, () => { void self.onClearTokenCache(); });
this._event.on(AzureAccountProviderService.CommandClearTokenCache, () => { void 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 azdata.credentials.getProvider(Constants.AzureAccountProviderCredentials)
return azdata.credentials.getProvider(AzureAccountProviderService.CredentialNamespace)
.then(credProvider => {
this._credentialProvider = credProvider;
@@ -106,7 +103,7 @@ export class AzureAccountProviderService implements vscode.Disposable {
// Add a new change processing onto the existing promise change
await this._configChangePromiseChain;
// Grab the stored config and the latest config
let newConfig = vscode.workspace.getConfiguration(Constants.AccountsAzureCloudSection);
let newConfig = vscode.workspace.getConfiguration(AzureAccountProviderService.ConfigurationSection);
let oldConfig = this._currentConfig;
this._currentConfig = newConfig;
@@ -141,67 +138,22 @@ export class AzureAccountProviderService implements vscode.Disposable {
}
private async registerAccountProvider(provider: ProviderSettings): Promise<void> {
const isSaw: boolean = vscode.env.appName.toLowerCase().indexOf(Constants.Saw) > 0;
const noSystemKeychain = vscode.workspace.getConfiguration(Constants.AzureSection).get<boolean>(Constants.NoSystemKeyChainSection);
const tokenCacheKey = `azureTokenCache-${provider.metadata.id}`;
const tokenCacheKeyMsal = `azureTokenCacheMsal-${provider.metadata.id}`;
try {
const noSystemKeychain = vscode.workspace.getConfiguration('azure').get<boolean>('noSystemKeychain');
let tokenCacheKey = `azureTokenCache-${provider.metadata.id}`;
if (!this._credentialProvider) {
throw new Error('Credential provider not registered');
}
// ADAL Token Cache
let simpleTokenCache = new SimpleTokenCache(tokenCacheKey, this._userStoragePath, noSystemKeychain, this._credentialProvider);
await simpleTokenCache.init();
// MSAL Cache Plugin
this._cachePluginProvider = new MsalCachePluginProvider(tokenCacheKeyMsal, this._userStoragePath);
const isSaw: boolean = vscode.env.appName.toLowerCase().indexOf('saw') > 0;
let accountProvider = new AzureAccountProvider(provider.metadata as AzureAccountProviderMetadata, simpleTokenCache, this._context, this._uriEventHandler, isSaw);
const msalConfiguration: Configuration = {
auth: {
clientId: provider.metadata.settings.clientId,
authority: 'https://login.windows.net/common'
},
system: {
loggerOptions: {
loggerCallback: this.getLoggerCallback(),
logLevel: MsalLogLevel.Trace,
piiLoggingEnabled: true,
},
},
cache: {
cachePlugin: this._cachePluginProvider?.getCachePlugin()
}
}
this.clientApplication = new PublicClientApplication(msalConfiguration);
let accountProvider = new AzureAccountProvider(provider.metadata as AzureAccountProviderMetadata,
simpleTokenCache, this._context, this.clientApplication, this._uriEventHandler, this._authLibrary, isSaw);
this._accountProviders[provider.metadata.id] = accountProvider;
this._accountDisposals[provider.metadata.id] = azdata.accounts.registerAccountProvider(provider.metadata, accountProvider);
} catch (e) {
console.error(`Failed to register account provider, isSaw: ${isSaw}: ${e}`);
}
}
private getLoggerCallback(): ILoggerCallback {
return (level: number, message: string, containsPii: boolean) => {
if (!containsPii) {
switch (level) {
case MsalLogLevel.Error:
Logger.error(message);
break;
case MsalLogLevel.Info:
Logger.info(message);
break;
case MsalLogLevel.Verbose:
default:
Logger.verbose(message);
break;
}
} else {
Logger.verbose(message);
}
console.error(`Failed to register account provider: ${e}`);
}
}

View File

@@ -39,7 +39,7 @@ const publicAzureSettings: ProviderSettings = {
},
graphResource: {
id: SettingIds.graph,
endpoint: 'https://graph.windows.net/',
endpoint: 'https://graph.windows.net',
azureResourceId: AzureResource.Graph
},
msGraphResource: {
@@ -49,7 +49,7 @@ const publicAzureSettings: ProviderSettings = {
},
armResource: {
id: SettingIds.arm,
endpoint: 'https://management.azure.com/',
endpoint: 'https://management.azure.com',
azureResourceId: AzureResource.ResourceManagement
},
sqlResource: {
@@ -59,38 +59,38 @@ const publicAzureSettings: ProviderSettings = {
},
ossRdbmsResource: {
id: SettingIds.ossrdbms,
endpoint: 'https://ossrdbms-aad.database.windows.net/',
endpoint: 'https://ossrdbms-aad.database.windows.net',
azureResourceId: AzureResource.OssRdbms
},
azureKeyVaultResource: {
id: SettingIds.vault,
endpoint: 'https://vault.azure.net/',
endpoint: 'https://vault.azure.net',
azureResourceId: AzureResource.AzureKeyVault
},
azureDevOpsResource: {
id: SettingIds.ado,
endpoint: '499b84ac-1321-427f-aa17-267ca6975798/',
endpoint: '499b84ac-1321-427f-aa17-267ca6975798',
azureResourceId: AzureResource.AzureDevOps,
},
azureLogAnalyticsResource: {
id: SettingIds.ala,
endpoint: 'https://api.loganalytics.io/',
endpoint: 'https://api.loganalytics.io',
azureResourceId: AzureResource.AzureLogAnalytics,
},
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.windows.net/',
endpointSuffix: '.core.windows.net',
azureResourceId: AzureResource.AzureStorage
},
azureKustoResource: {
id: SettingIds.kusto,
endpoint: 'https://kusto.kusto.windows.net/',
endpoint: 'https://kusto.kusto.windows.net',
azureResourceId: AzureResource.AzureKusto,
},
powerBiResource: {
id: SettingIds.powerbi,
endpoint: 'https://analysis.windows.net/powerbi/api/',
endpoint: 'https://analysis.windows.net/powerbi/api',
azureResourceId: AzureResource.PowerBi
},
redirectUri: 'http://localhost',
@@ -119,12 +119,12 @@ const usGovAzureSettings: ProviderSettings = {
},
graphResource: {
id: SettingIds.graph,
endpoint: 'https://graph.windows.net/',
endpoint: 'https://graph.windows.net',
azureResourceId: AzureResource.Graph
},
armResource: {
id: SettingIds.arm,
endpoint: 'https://management.usgovcloudapi.net/',
endpoint: 'https://management.usgovcloudapi.net',
azureResourceId: AzureResource.ResourceManagement
},
sqlResource: {
@@ -134,28 +134,28 @@ const usGovAzureSettings: ProviderSettings = {
},
ossRdbmsResource: {
id: SettingIds.ossrdbms,
endpoint: 'https://ossrdbms-aad.database.usgovcloudapi.net/',
endpoint: 'https://ossrdbms-aad.database.usgovcloudapi.net',
azureResourceId: AzureResource.OssRdbms
},
azureKeyVaultResource: {
id: SettingIds.vault,
endpoint: 'https://vault.usgovcloudapi.net/',
endpoint: 'https://vault.usgovcloudapi.net',
azureResourceId: AzureResource.AzureKeyVault
},
azureLogAnalyticsResource: {
id: SettingIds.ala,
endpoint: 'https://api.loganalytics.us/',
endpoint: 'https://api.loganalytics.us',
azureResourceId: AzureResource.AzureLogAnalytics,
},
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.usgovcloudapi.net/',
endpointSuffix: '.core.usgovcloudapi.net',
azureResourceId: AzureResource.AzureStorage
},
powerBiResource: {
id: SettingIds.powerbi,
endpoint: 'https://analysis.windows.net/powerbi/api/',
endpoint: 'https://analysis.windows.net/powerbi/api',
azureResourceId: AzureResource.PowerBi
},
redirectUri: 'http://localhost',
@@ -183,7 +183,7 @@ const usNatAzureSettings: ProviderSettings = {
},
graphResource: {
id: SettingIds.graph,
endpoint: 'https://graph.eaglex.ic.gov/',
endpoint: 'https://graph.eaglex.ic.gov',
azureResourceId: AzureResource.Graph
},
armResource: {
@@ -198,23 +198,23 @@ const usNatAzureSettings: ProviderSettings = {
},
ossRdbmsResource: {
id: SettingIds.ossrdbms,
endpoint: 'https://ossrdbms-aad.database.cloudapi.eaglex.ic.gov/',
endpoint: 'https://ossrdbms-aad.database.cloudapi.eaglex.ic.gov',
azureResourceId: AzureResource.OssRdbms
},
azureKeyVaultResource: {
id: SettingIds.vault,
endpoint: 'https://vault.cloudapi.eaglex.ic.gov/',
endpoint: 'https://vault.cloudapi.eaglex.ic.gov',
azureResourceId: AzureResource.AzureKeyVault
},
azureLogAnalyticsResource: {
id: SettingIds.ala,
endpoint: 'https://api.loganalytics.azure.eaglex.ic.gov/',
endpoint: 'https://api.loganalytics.azure.eaglex.ic.gov',
azureResourceId: AzureResource.AzureLogAnalytics,
},
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.eaglex.ic.gov/',
endpointSuffix: '.core.eaglex.ic.gov',
azureResourceId: AzureResource.AzureStorage
},
redirectUri: 'http://localhost',
@@ -238,33 +238,33 @@ const germanyAzureSettings: ProviderSettings = {
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
graphResource: {
id: SettingIds.graph,
endpoint: 'https://graph.cloudapi.de/',
endpoint: 'https://graph.cloudapi.de',
azureResourceId: AzureResource.Graph
},
msGraphResource: {
id: SettingIds.msgraph,
endpoint: 'https://graph.microsoft.de/',
endpoint: 'https://graph.microsoft.de',
azureResourceId: AzureResource.MsGraph
},
armResource: {
id: SettingIds.arm,
endpoint: 'https://management.microsoftazure.de/',
endpoint: 'https://management.microsoftazure.de',
azureResourceId: AzureResource.ResourceManagement
},
azureKeyVaultResource: {
id: SettingIds.vault,
endpoint: 'https://vault.microsoftazure.de/',
endpoint: 'https://vault.microsoftazure.de',
azureResourceId: AzureResource.AzureKeyVault
},
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.cloudapi.de/',
endpointSuffix: '.core.cloudapi.de',
azureResourceId: AzureResource.AzureStorage
},
powerBiResource: {
id: SettingIds.powerbi,
endpoint: 'https://analysis.windows.net/powerbi/api/',
endpoint: 'https://analysis.windows.net/powerbi/api',
azureResourceId: AzureResource.PowerBi
},
redirectUri: 'http://localhost',

View File

@@ -6,7 +6,7 @@ import * as keytarType from 'keytar';
import { join, parse } from 'path';
import { FileDatabase } from './utils/fileDatabase';
import * as azdata from 'azdata';
import { FileEncryptionHelper } from './utils/fileEncryptionHelper';
import * as crypto from 'crypto';
function getSystemKeytar(): Keytar | undefined {
try {
@@ -25,8 +25,42 @@ const separator = '§';
async function getFileKeytar(filePath: string, credentialService: azdata.CredentialProvider): Promise<Keytar | undefined> {
const fileName = parse(filePath).base;
const fileEncryptionHelper: FileEncryptionHelper = new FileEncryptionHelper(credentialService, fileName);
const db = new FileDatabase(filePath, fileEncryptionHelper.fileOpener, fileEncryptionHelper.fileSaver);
const iv = await credentialService.readCredential(`${fileName}-iv`);
const key = await credentialService.readCredential(`${fileName}-key`);
let ivBuffer: Buffer;
let keyBuffer: Buffer;
if (!iv?.password || !key?.password) {
ivBuffer = crypto.randomBytes(16);
keyBuffer = crypto.randomBytes(32);
try {
await credentialService.saveCredential(`${fileName}-iv`, ivBuffer.toString('hex'));
await credentialService.saveCredential(`${fileName}-key`, keyBuffer.toString('hex'));
} catch (ex) {
console.log(ex);
}
} else {
ivBuffer = Buffer.from(iv.password, 'hex');
keyBuffer = Buffer.from(key.password, 'hex');
}
const fileSaver = async (content: string): Promise<string> => {
const cipherIv = crypto.createCipheriv('aes-256-gcm', keyBuffer, ivBuffer);
return `${cipherIv.update(content, 'utf8', 'hex')}${cipherIv.final('hex')}%${cipherIv.getAuthTag().toString('hex')}`;
};
const fileOpener = async (content: string): Promise<string> => {
const decipherIv = crypto.createDecipheriv('aes-256-gcm', keyBuffer, ivBuffer);
const split = content.split('%');
if (split.length !== 2) {
throw new Error('File didn\'t contain the auth tag.');
}
decipherIv.setAuthTag(Buffer.from(split[1], 'hex'));
return `${decipherIv.update(split[0], 'hex', 'utf8')}${decipherIv.final('utf8')}`;
};
const db = new FileDatabase(filePath, fileOpener, fileSaver);
await db.initialize();
const fileKeytar: Keytar = {
@@ -60,7 +94,6 @@ async function getFileKeytar(filePath: string, credentialService: azdata.Credent
return fileKeytar;
}
export type Keytar = {
getPassword: typeof keytarType['getPassword'];
setPassword: typeof keytarType['setPassword'];
@@ -77,7 +110,9 @@ export class SimpleTokenCache {
private readonly userStoragePath: string,
private readonly forceFileStorage: boolean = false,
private readonly credentialService: azdata.CredentialProvider,
) { }
) {
}
async init(): Promise<void> {
this.serviceName = this.serviceName.replace(/-/g, '_');

View File

@@ -88,6 +88,7 @@ export class FileDatabase {
this.isDirty = true;
}
public async initialize(): Promise<void> {
this.isInitialized = true;
this.saveInterval = setInterval(() => this.save(), 20 * 1000);

View File

@@ -1,56 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as crypto from 'crypto';
export class FileEncryptionHelper {
constructor(
private _credentialService: azdata.CredentialProvider,
private _fileName: string,
) { }
private _ivBuffer: Buffer | undefined;
private _keyBuffer: Buffer | undefined;
async init(): Promise<void> {
const iv = await this._credentialService.readCredential(`${this._fileName}-iv`);
const key = await this._credentialService.readCredential(`${this._fileName}-key`);
if (!iv?.password || !key?.password) {
this._ivBuffer = crypto.randomBytes(16);
this._keyBuffer = crypto.randomBytes(32);
try {
await this._credentialService.saveCredential(`${this._fileName}-iv`, this._ivBuffer.toString('hex'));
await this._credentialService.saveCredential(`${this._fileName}-key`, this._keyBuffer.toString('hex'));
} catch (ex) {
console.log(ex);
}
} else {
this._ivBuffer = Buffer.from(iv.password, 'hex');
this._keyBuffer = Buffer.from(key.password, 'hex');
}
}
fileSaver = async (content: string): Promise<string> => {
if (!this._keyBuffer || !this._ivBuffer) {
await this.init();
}
const cipherIv = crypto.createCipheriv('aes-256-gcm', this._keyBuffer!, this._ivBuffer!);
return `${cipherIv.update(content, 'utf8', 'hex')}${cipherIv.final('hex')}%${cipherIv.getAuthTag().toString('hex')}`;
};
fileOpener = async (content: string): Promise<string> => {
if (!this._keyBuffer || !this._ivBuffer) {
await this.init();
}
const decipherIv = crypto.createDecipheriv('aes-256-gcm', this._keyBuffer!, this._ivBuffer!);
const split = content.split('%');
if (split.length !== 2) {
throw new Error('File didn\'t contain the auth tag.');
}
decipherIv.setAuthTag(Buffer.from(split[1], 'hex'));
return `${decipherIv.update(split[0], 'hex', 'utf8')}${decipherIv.final('utf8')}`;
};
}

View File

@@ -1,119 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICachePlugin, TokenCacheContext } from '@azure/msal-node';
import { promises as fsPromises } from 'fs';
import * as lockFile from 'lockfile';
import * as path from 'path';
import { AccountsClearTokenCacheCommand } from '../../constants';
import { Logger } from '../../utils/Logger';
export class MsalCachePluginProvider {
constructor(
private readonly _serviceName: string,
private readonly _msalFilePath: string,
) {
this._msalFilePath = path.join(this._msalFilePath, this._serviceName);
this._serviceName = this._serviceName.replace(/-/, '_');
Logger.verbose(`MsalCachePluginProvider: Using cache path ${_msalFilePath} and serviceName ${_serviceName}`);
}
private _lockTaken: boolean = false;
private getLockfilePath(): string {
return this._msalFilePath + '.lock';
}
public getCachePlugin(): ICachePlugin {
const lockFilePath = this.getLockfilePath();
const beforeCacheAccess = async (cacheContext: TokenCacheContext): Promise<void> => {
await this.waitAndLock(lockFilePath);
try {
const cache = await fsPromises.readFile(this._msalFilePath, { encoding: 'utf8' });
try {
cacheContext.tokenCache.deserialize(cache);
} catch (e) {
// Handle deserialization error in cache file in case file gets corrupted.
// Clearing cache here will ensure account is marked stale so re-authentication can be triggered.
Logger.verbose(`MsalCachePlugin: Error occurred when trying to read cache file, file contents will be cleared: ${e.message}`);
await fsPromises.writeFile(this._msalFilePath, '', { encoding: 'utf8' });
}
Logger.verbose(`MsalCachePlugin: Token read from cache successfully.`);
} catch (e) {
if (e.code === 'ENOENT') {
// File doesn't exist, log and continue
Logger.verbose(`MsalCachePlugin: Cache file not found on disk: ${e.code}`);
}
else {
Logger.error(`MsalCachePlugin: Failed to read from cache file: ${e}`);
throw e;
}
} finally {
lockFile.unlockSync(lockFilePath);
this._lockTaken = false;
}
}
const afterCacheAccess = async (cacheContext: TokenCacheContext): Promise<void> => {
if (cacheContext.cacheHasChanged) {
await this.waitAndLock(lockFilePath);
try {
const data = cacheContext.tokenCache.serialize();
await fsPromises.writeFile(this._msalFilePath, data, { encoding: 'utf8' });
Logger.verbose(`MsalCachePlugin: Token written to cache successfully.`);
} catch (e) {
Logger.error(`MsalCachePlugin: Failed to write to cache file. ${e}`);
throw e;
} finally {
lockFile.unlockSync(lockFilePath);
this._lockTaken = false;
}
}
};
// This is an implementation of ICachePlugin that uses the beforeCacheAccess and afterCacheAccess callbacks to read and write to a file
// Ref https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-node-migration#enable-token-caching
// In future we should use msal-node-extensions to provide a secure storage of tokens, instead of implementing our own
// However - as of now this library does not come with pre-compiled native libraries that causes runtime issues
// Ref https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3332
return {
beforeCacheAccess,
afterCacheAccess,
};
}
private async waitAndLock(lockFilePath: string): Promise<void> {
// Make 500 retry attempts with 100ms wait time between each attempt to allow enough time for the lock to be released.
const retries = 500;
const retryWait = 100;
// We cannot rely on lockfile.lockSync() to clear stale lockfile,
// so we check if the lockfile exists and if it does, calling unlockSync() will clear it.
if (lockFile.checkSync(lockFilePath) && !this._lockTaken) {
lockFile.unlockSync(lockFilePath);
Logger.verbose(`MsalCachePlugin: Stale lockfile found and has been removed.`);
}
let retryAttempt = 0;
while (retryAttempt <= retries) {
try {
// Use lockfile.lockSync() to ensure only one process is accessing the cache at a time.
// lockfile.lock() does not wait for async callback promise to resolve.
lockFile.lockSync(lockFilePath);
this._lockTaken = true;
break;
} catch (e) {
if (retryAttempt === retries) {
Logger.error(`MsalCachePlugin: Failed to acquire lock on cache file after ${retries} attempts.`);
throw new Error(`Failed to acquire lock on cache file after ${retries} attempts. Please attempt command: '${AccountsClearTokenCacheCommand}' to clear access token cache.`);
}
retryAttempt++;
Logger.verbose(`MsalCachePlugin: Failed to acquire lock on cache file. Retrying in ${retryWait} ms.`);
await new Promise(resolve => setTimeout(resolve, retryWait));
}
}
}
}

View File

@@ -16,8 +16,6 @@ import * as utils from './utils';
const typesClause = [
azureResource.AzureResourceType.sqlDatabase,
azureResource.AzureResourceType.sqlServer,
azureResource.AzureResourceType.sqlSynapseWorkspace,
azureResource.AzureResourceType.sqlSynapseSqlPool,
azureResource.AzureResourceType.sqlManagedInstance,
azureResource.AzureResourceType.postgresServer,
azureResource.AzureResourceType.azureArcService,
@@ -26,15 +24,13 @@ const typesClause = [
].map(type => `type == "${type}"`).join(' or ');
export class AzureDataGridProvider implements azdata.DataGridProvider {
constructor(private _appContext: AppContext,
private readonly authLibrary: string) { }
constructor(private _appContext: AppContext) { }
public providerId = constants.dataGridProviderId;
public title = loc.azureResourcesGridTitle;
public async getDataGridItems() {
let accounts: azdata.Account[];
accounts = azureResourceUtils.filterAccounts(await azdata.accounts.getAllAccounts(), this.authLibrary);
const accounts = await azdata.accounts.getAllAccounts();
const items: any[] = [];
await Promise.all(accounts.map(async (account) => {
await Promise.all(account.properties.tenants.map(async (tenant: { id: string; }) => {

View File

@@ -17,9 +17,9 @@ import { AzureResourceServiceNames } from './constants';
import { AzureAccount, Tenant, azureResource } from 'azurecore';
import { FlatAccountTreeNode } from './tree/flatAccountTreeNode';
import { ConnectionDialogTreeProvider } from './tree/connectionDialogTreeProvider';
import { AzureResourceErrorMessageUtil, filterAccounts } from './utils';
import { AzureResourceErrorMessageUtil } from './utils';
export function registerAzureResourceCommands(appContext: AppContext, azureViewTree: AzureResourceTreeProvider, connectionDialogTree: ConnectionDialogTreeProvider, authLibrary: string): void {
export function registerAzureResourceCommands(appContext: AppContext, azureViewTree: AzureResourceTreeProvider, connectionDialogTree: ConnectionDialogTreeProvider): void {
const trees = [azureViewTree, connectionDialogTree];
vscode.commands.registerCommand('azure.resource.startterminal', async (node?: TreeNode) => {
try {
@@ -33,7 +33,7 @@ export function registerAzureResourceCommands(appContext: AppContext, azureViewT
if (node instanceof AzureResourceAccountTreeNode) {
azureAccount = node.account;
} else {
let accounts = filterAccounts(await azdata.accounts.getAllAccounts(), authLibrary);
let accounts = await azdata.accounts.getAllAccounts();
accounts = accounts.filter(a => a.key.providerId.startsWith('azure'));
if (accounts.length === 0) {
const signin = localize('azure.signIn', "Sign in");

View File

@@ -10,10 +10,6 @@ export enum AzureResourceItemType {
database = 'azure.resource.itemType.database',
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
databaseServer = 'azure.resource.itemType.databaseServer',
synapseSqlPoolContainer = 'azure.resource.itemType.synapseSqlPoolContainer',
synapseSqlPool = 'azure.resource.itemType.synapseSqlPool',
synapseWorkspaceContainer = 'azure.resource.itemType.synapseWorkspaceContainer',
synapseWorkspace = 'azure.resource.itemType.synapseWorkspace',
azureDataExplorerContainer = 'azure.resource.itemType.azureDataExplorerContainer',
azureDataExplorer = 'azure.resource.itemType.azureDataExplorer',
sqlInstance = 'azure.resource.itemType.sqlInstance',
@@ -33,5 +29,3 @@ export enum AzureResourceServiceNames {
tenantService = 'AzureResourceTenantService',
terminalService = 'AzureTerminalService',
}
export const mssqlProvider = 'MSSQL';

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
import { logAnalyticsQuery } from '../queryStringConstants';
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
export interface AzureMonitorGraphData extends GraphData {
@@ -16,10 +15,12 @@ export interface AzureMonitorGraphData extends GraphData {
};
}
const instanceQuery = `where type == "${azureResource.AzureResourceType.logAnalytics}"`;
export class AzureMonitorResourceService extends ResourceServiceBase<AzureMonitorGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return logAnalyticsQuery;
return instanceQuery;
}
protected convertResource(resource: AzureMonitorGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -6,7 +6,6 @@
import { ResourceServiceBase, GraphData } from '../../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { cosmosMongoDbQuery } from '../../queryStringConstants';
interface DbServerGraphData extends GraphData {
@@ -16,10 +15,12 @@ interface DbServerGraphData extends GraphData {
};
}
const serversQuery = `where type == "${azureResource.AzureResourceType.cosmosDbAccount}" and kind == "MongoDB"`;
export class CosmosDbMongoService extends ResourceServiceBase<DbServerGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return cosmosMongoDbQuery;
return serversQuery;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionContext } from 'vscode';
import { azureResource } from 'azurecore';

View File

@@ -5,8 +5,8 @@
import { ServiceClientCredentials } from '@azure/ms-rest-js';
import { IAzureResourceService } from '../../interfaces';
import { DbServerGraphData } from '../databaseServer/databaseServerService';
import { sqlServerQuery, sqlDatabaseQuery } from '../queryStringConstants';
import { DbServerGraphData, SynapseWorkspaceGraphData } from '../databaseServer/databaseServerService';
import { synapseWorkspacesQuery, sqlServersQuery } from '../databaseServer/serverQueryStrings';
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { queryGraphResources, GraphData } from '../resourceTreeDataProviderBase';
import { AzureAccount, azureResource } from 'azurecore';
@@ -19,43 +19,69 @@ export class AzureResourceDatabaseService implements IAzureResourceService<azure
const databases: azureResource.AzureResourceDatabase[] = [];
const resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
// Query servers and databases in parallel (start all promises before waiting on the 1st)
let serverQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, sqlServerQuery);
let dbQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, sqlDatabaseQuery);
let servers: DbServerGraphData[] = await serverQueryPromise as DbServerGraphData[];
// Query servers, synapse workspaces, and databases in parallel (start all promises before waiting on the 1st)
let servers: DbServerGraphData[];
let synapseWorkspaces: SynapseWorkspaceGraphData[];
let combined: (DbServerGraphData | SynapseWorkspaceGraphData)[] = [];
/**
* We need to get the list of servers minus the Synapse Workspaces,
* then we need to make another query to get them.
*
* This is done because the first query provides invalid endpoints for Synapse Workspaces
* While the second one provides them as one of its properties.
*
* They have to be processed in different ways as their structure differs
* in terms of properties. (See databaseServer/databaseServerService.ts for more info)
*
* Queries must be made separately due to union not being recognized by resourceGraph resource calls
*/
let synapseQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, synapseWorkspacesQuery);
let serverQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, sqlServersQuery);
let dbQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, `where type == "${azureResource.AzureResourceType.sqlDatabase}"`);
servers = await serverQueryPromise as DbServerGraphData[];
synapseWorkspaces = await synapseQueryPromise as SynapseWorkspaceGraphData[];
let dbByGraph: DatabaseGraphData[] = await dbQueryPromise as DatabaseGraphData[];
combined = combined.concat(servers).concat(synapseWorkspaces);
// Group servers by resource group, then merge DB results with servers so we
// can get the login name and server fully qualified name to use for connections
let rgMap = new Map<string, (DbServerGraphData)[]>();
servers.forEach(s => {
let serversForRg = rgMap.get(s.resourceGroup) || [];
serversForRg.push(s);
rgMap.set(s.resourceGroup, serversForRg);
let rgMap = new Map<string, (DbServerGraphData | SynapseWorkspaceGraphData)[]>();
combined.forEach(s => {
if ((s as SynapseWorkspaceGraphData).properties.connectivityEndpoints) {
// If the resource is a Synapse Workspace, we need to use the managedResourceGroupName
// (any SQL pools inside will use this instead of the regular resource group associated with the workspace itself).
let serversForRg = rgMap.get((s as SynapseWorkspaceGraphData).properties.managedResourceGroupName) || [];
serversForRg.push(s as SynapseWorkspaceGraphData);
rgMap.set((s as SynapseWorkspaceGraphData).properties.managedResourceGroupName, serversForRg);
} else {
let serversForRg = rgMap.get(s.resourceGroup) || [];
serversForRg.push(s);
rgMap.set(s.resourceGroup, serversForRg);
}
});
// Match database ID. When calling exec [0] is full match, [1] is resource group name, [2] is server name
const svrIdRegExp = new RegExp(`\/subscriptions\/.+\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/(.+)\/databases\/.+`);
const synapseDBRegExp = new RegExp(`\/subscriptions\/.+\/resourceGroups\/(.+)\/providers\/Microsoft\.Synapse\/workspaces\/(.+)\/databases\/.+`);
dbByGraph.forEach(db => {
// Filter master DBs, and for all others find their server to get login info
let serversForRg = rgMap.get(db.resourceGroup);
if (serversForRg && !db.kind.endsWith('system') && (svrIdRegExp.test(db.id) || synapseDBRegExp.test(db.id))) {
const founds = svrIdRegExp.exec(db.id) ?? synapseDBRegExp.exec(db.id);
if (serversForRg && !db.kind.endsWith('system') && svrIdRegExp.test(db.id)) {
const founds = svrIdRegExp.exec(db.id);
if (!founds) {
console.warn(`Could not parse server name from ID ${db.id}`);
return;
}
const serverName = founds[2];
let server = servers.find(s => s.name === serverName);
let server = combined.find(s => s.name === serverName);
if (server) {
databases.push({
name: db.name,
id: db.id,
serverName: server.name,
serverFullName: server.properties.fullyQualifiedDomainName,
loginName: server.properties.administratorLogin,
// Determine if server object is for Synapse Workspace or not and get the needed property from the correct place.
serverFullName: (server as SynapseWorkspaceGraphData).properties.connectivityEndpoints?.sql ?? (server as DbServerGraphData).properties.fullyQualifiedDomainName,
loginName: (server as SynapseWorkspaceGraphData).properties.sqlAdministratorLogin ?? (server as DbServerGraphData).properties.administratorLogin,
subscription: {
id: db.subscriptionId,
name: (subscriptions.find(sub => sub.id === db.subscriptionId))?.name || ''

View File

@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType, mssqlProvider } from '../../../azureResource/constants';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { generateGuid } from '../../utils';
import { IAzureResourceService } from '../../interfaces';
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
@@ -25,7 +25,6 @@ export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProvi
) {
super(databaseService);
}
protected getTreeItemForResource(database: azureResource.AzureResourceDatabase, account: AzureAccount): TreeItem {
return {
id: `databaseServer_${database.serverFullName}.database_${database.name}`,
@@ -47,7 +46,7 @@ export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProvi
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProvider,
providerName: 'MSSQL',
saveProfile: false,
options: {},
azureAccount: account.key.accountId,
@@ -55,7 +54,7 @@ export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProvi
azureTenantId: database.tenant,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: mssqlProvider,
childProvider: 'MSSQL',
type: ExtensionNodeType.Database
};
}

View File

@@ -6,8 +6,8 @@
import { ServiceClientCredentials } from '@azure/ms-rest-js';
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { GraphData, queryGraphResources } from '../resourceTreeDataProviderBase';
import { sqlServerQuery } from '../queryStringConstants';
import { azureResource, AzureAccount } from 'azurecore';
import { sqlServersQuery, synapseWorkspacesQuery } from './serverQueryStrings';
import { IAzureResourceService } from '../../interfaces';
export interface DbServerGraphData extends GraphData {
@@ -17,14 +17,54 @@ export interface DbServerGraphData extends GraphData {
};
}
/**
* Properties returned by the Synapse query are different from the server ones and have to be treated differently.
*/
export interface SynapseWorkspaceGraphData extends GraphData {
properties: {
/**
* SQL connectivity endpoint and other endpoints are found here, instead of fullyQualifiedDomainName.
*/
connectivityEndpoints: { sql: string };
/**
* managedResourceGroupName is the resource group used by any SQL pools inside the workspace
* which is different from the resource group of the workspace itself.
*/
managedResourceGroupName: string;
/**
* administratorLogin is called sqlAdministratorLogin here.
*/
sqlAdministratorLogin: string;
};
}
export class AzureResourceDatabaseServerService implements IAzureResourceService<azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return sqlServersQuery;
}
public async getResources(subscriptions: azureResource.AzureResourceSubscription[], credential: ServiceClientCredentials, account: AzureAccount): Promise<azureResource.AzureResourceDatabaseServer[]> {
const convertedResources: azureResource.AzureResourceDatabaseServer[] = [];
const resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
let serverGraphResources: DbServerGraphData[] = await queryGraphResources<DbServerGraphData>(resourceClient, subscriptions, sqlServerQuery);
/**
* We need to get the list of servers minus the Synapse Workspaces,
* then we need to make another query to get them.
*
* This is done because the first query provides invalid endpoints for Synapse Workspaces
* While the second one provides them as one of its properties.
*
* They have to be processed in different ways by convertResource as their structure differs
* in terms of properties. (See above)
*
* Queries must be made separately due to union not being recognized by resourceGraph resource calls.
*/
let combinedGraphResources: (DbServerGraphData | SynapseWorkspaceGraphData)[] = [];
let serverGraphResources: DbServerGraphData[] = await queryGraphResources<DbServerGraphData>(resourceClient, subscriptions, this.query);
let synapseGraphResources: SynapseWorkspaceGraphData[] = await queryGraphResources<SynapseWorkspaceGraphData>(resourceClient, subscriptions, synapseWorkspacesQuery);
combinedGraphResources = combinedGraphResources.concat(serverGraphResources).concat(synapseGraphResources);
const ids = new Set<string>();
serverGraphResources.forEach((res) => {
combinedGraphResources.forEach((res) => {
if (!ids.has(res.id)) {
ids.add(res.id);
res.subscriptionName = subscriptions.find(sub => sub.id === res.subscriptionId)?.name;
@@ -36,14 +76,14 @@ export class AzureResourceDatabaseServerService implements IAzureResourceService
return convertedResources;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer {
protected convertResource(resource: DbServerGraphData | SynapseWorkspaceGraphData): azureResource.AzureResourceDatabaseServer {
return {
id: resource.id,
name: resource.name,
// Determine if resource object is for Synapse Workspace or not and get the needed property from the correct place.
fullName: resource.properties.fullyQualifiedDomainName,
loginName: resource.properties.administratorLogin,
fullName: (resource as SynapseWorkspaceGraphData).properties.connectivityEndpoints?.sql ?? (resource as DbServerGraphData).properties.fullyQualifiedDomainName,
loginName: (resource as SynapseWorkspaceGraphData).properties.sqlAdministratorLogin ?? (resource as DbServerGraphData).properties.administratorLogin,
defaultDatabaseName: 'master',
subscription: {
id: resource.subscriptionId,

View File

@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType, mssqlProvider } from '../../../azureResource/constants';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { generateGuid } from '../../utils';
import { IAzureResourceService } from '../../interfaces';
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
@@ -25,6 +25,7 @@ export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDat
super(databaseServerService);
}
protected getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
return {
id: `databaseServer_${databaseServer.id ? databaseServer.id : databaseServer.name}`,
@@ -46,7 +47,7 @@ export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDat
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProvider,
providerName: 'MSSQL',
saveProfile: false,
options: {},
azureAccount: account.key.accountId,
@@ -54,7 +55,7 @@ export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDat
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: mssqlProvider,
childProvider: 'MSSQL',
type: ExtensionNodeType.Server
};
}

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
/**
* Get list of Synapse Workspaces with information such as SQL connection endpoints.
*/
export const synapseWorkspacesQuery = `where type == "microsoft.synapse/workspaces"`;
/**
* Lists all Sql Servers except for Synapse Pool Servers
* (they have different properties and need to be handled separately,
* see databaseServerService.ts for more details)
*/
export const sqlServersQuery = `where type == "${azureResource.AzureResourceType.sqlServer}" and kind != "v12.0,analytics"`;

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
import { kustoClusterQuery } from '../queryStringConstants';
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
export interface KustoGraphData extends GraphData {
@@ -15,10 +14,12 @@ export interface KustoGraphData extends GraphData {
};
}
const instanceQuery = `where type == "${azureResource.AzureResourceType.kustoClusters}"`;
export class KustoResourceService extends ResourceServiceBase<KustoGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return kustoClusterQuery;
return instanceQuery;
}
protected convertResource(resource: KustoGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -6,7 +6,6 @@
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { mysqlFlexibleServerQuery } from '../queryStringConstants';
interface DbServerGraphData extends GraphData {
@@ -16,10 +15,12 @@ interface DbServerGraphData extends GraphData {
};
}
const serversQuery = `where type == "${azureResource.AzureResourceType.mysqlFlexibleServer}"`;
export class MysqlFlexibleServerService extends ResourceServiceBase<DbServerGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return mysqlFlexibleServerQuery;
return serversQuery;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -5,7 +5,6 @@
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { postgresArcServerQuery } from '../queryStringConstants';
export interface PostgresArcServerGraphData extends GraphData {
properties: {
@@ -13,10 +12,12 @@ export interface PostgresArcServerGraphData extends GraphData {
};
}
export const serversQuery = `where type == "${azureResource.AzureResourceType.azureArcPostgresServer}"`;
export class PostgresServerArcService extends ResourceServiceBase<PostgresArcServerGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return postgresArcServerQuery;
return serversQuery;
}
protected convertResource(resource: PostgresArcServerGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -6,7 +6,7 @@
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { postgresServerQuery } from '../queryStringConstants';
interface DbServerGraphData extends GraphData {
properties: {
@@ -15,10 +15,12 @@ interface DbServerGraphData extends GraphData {
};
}
const serversQuery = `where type == "${azureResource.AzureResourceType.postgresServer}"`;
export class PostgresServerService extends ResourceServiceBase<DbServerGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return postgresServerQuery;
return serversQuery;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -1,72 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
/**
* Lists all SQL Databases and Synapse SQL Databases
*/
export const sqlDatabaseQuery = `where type == "${azureResource.AzureResourceType.sqlDatabase}" or type == "${azureResource.AzureResourceType.sqlSynapseSqlDatabase}"`;
/**
* Lists all Synapse Workspaces with information such as SQL connection endpoints.
*/
export const synapseWorkspacesQuery = `where type == "${azureResource.AzureResourceType.sqlSynapseWorkspace}"`;
/**
* Lists all Synapse Dedicated SQL Pools
*/
export const synapseSqlPoolsQuery = `where type == "${azureResource.AzureResourceType.sqlSynapseSqlPool}"`;
/**
* Lists all Sql Servers excluding Synapse Pool Servers
* (they have different properties and need to be handled separately)
*/
export const sqlServerQuery = `where type == "${azureResource.AzureResourceType.sqlServer}" and kind != "v12.0,analytics"`;
/**
* Lists all Azure Arc SQL Managed Instances
*/
export const sqlInstanceArcQuery = `where type == "${azureResource.AzureResourceType.azureArcSqlManagedInstance}"`;
/**
* Lists all Azure SQL Managed Instances
*/
export const sqlInstanceQuery = `where type == "${azureResource.AzureResourceType.sqlManagedInstance}"`;
/**
* Lists all resource containers and resource groups
*/
export const resourceGroupQuery = `ResourceContainers | where type=="${azureResource.AzureResourceType.resourceGroup}"`;
/**
* Lists all postgreSQL servers
*/
export const postgresServerQuery = `where type == "${azureResource.AzureResourceType.postgresServer}"`;
/**
* Lists all Azure Arc PostgreSQL servers
*/
export const postgresArcServerQuery = `where type == "${azureResource.AzureResourceType.azureArcPostgresServer}"`;
/**
* Lists all MySQL Flexible servers
*/
export const mysqlFlexibleServerQuery = `where type == "${azureResource.AzureResourceType.mysqlFlexibleServer}"`;
/**
* Lists all Kusto Clusters
*/
export const kustoClusterQuery = `where type == "${azureResource.AzureResourceType.kustoClusters}"`;
/**
* Lists all Cosmos DB for MongoDB accounts
*/
export const cosmosMongoDbQuery = `where type == "${azureResource.AzureResourceType.cosmosDbAccount}" and kind == "MongoDB"`;
/**
* Lists all Log Analytics workspaces
*/
export const logAnalyticsQuery = `where type == "${azureResource.AzureResourceType.logAnalytics}"`;

View File

@@ -6,12 +6,11 @@
import { DbServerGraphData } from '../databaseServer/databaseServerService';
import { azureResource } from 'azurecore';
import { ResourceServiceBase } from '../resourceTreeDataProviderBase';
import { resourceGroupQuery } from '../queryStringConstants';
export class AzureResourceGroupService extends ResourceServiceBase<DbServerGraphData, azureResource.AzureResourceResourceGroup> {
protected get query(): string {
return resourceGroupQuery;
return `ResourceContainers | where type=="${azureResource.AzureResourceType.resourceGroup}"`;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceResourceGroup {

View File

@@ -28,7 +28,7 @@ export abstract class ResourceTreeDataProviderBase<T extends azureResource.Azure
return resources.map((resource) => <azureResource.IAzureResourceNode>{
account: element.account,
subscription: element.subscription,
tenantId: element.subscription.tenant,
tenantId: element.tenantId,
treeItem: this.getTreeItemForResource(resource, element.account)
}).sort((a, b) => (<any>a.treeItem.label).localeCompare(b.treeItem.label));
} catch (error) {
@@ -38,7 +38,7 @@ export abstract class ResourceTreeDataProviderBase<T extends azureResource.Azure
}
private async getResources(element: azureResource.IAzureResourceNode): Promise<T[]> {
const response = await azdata.accounts.getAccountSecurityToken(element.account, element.subscription.tenant!, azdata.AzureResource.ResourceManagement);
const response = await azdata.accounts.getAccountSecurityToken(element.account, element.tenantId, azdata.AzureResource.ResourceManagement);
if (!response) {
throw new Error(`Did not receive security token when getting resources for account ${element.account.displayInfo.displayName}`);
}

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
import { sqlInstanceQuery } from '../queryStringConstants';
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
interface SqlInstanceGraphData extends GraphData {
@@ -14,10 +13,12 @@ interface SqlInstanceGraphData extends GraphData {
};
}
const instanceQuery = `where type == "${azureResource.AzureResourceType.sqlManagedInstance}"`;
export class SqlInstanceResourceService extends ResourceServiceBase<SqlInstanceGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return sqlInstanceQuery;
return instanceQuery;
}
protected convertResource(resource: SqlInstanceGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -8,7 +8,7 @@ import { TreeItemCollapsibleState, ExtensionContext } from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType, mssqlProvider } from '../../constants';
import { AzureResourceItemType } from '../../constants';
import { generateGuid } from '../../utils';
import { IAzureResourceService } from '../../interfaces';
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
@@ -47,7 +47,7 @@ export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<az
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProvider,
providerName: 'MSSQL',
saveProfile: false,
options: {},
azureAccount: account.key.accountId,
@@ -55,7 +55,7 @@ export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<az
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: mssqlProvider,
childProvider: 'MSSQL',
type: ExtensionNodeType.Server
};
}

View File

@@ -5,7 +5,6 @@
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { sqlInstanceArcQuery } from '../queryStringConstants';
export interface SqlInstanceArcGraphData extends GraphData {
properties: {
@@ -14,10 +13,11 @@ export interface SqlInstanceArcGraphData extends GraphData {
};
}
const instanceQuery = `where type == "${azureResource.AzureResourceType.azureArcSqlManagedInstance}"`;
export class SqlInstanceArcResourceService extends ResourceServiceBase<SqlInstanceArcGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return sqlInstanceArcQuery;
return instanceQuery;
}
protected convertResource(resource: SqlInstanceArcGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -8,7 +8,7 @@ import { TreeItemCollapsibleState, ExtensionContext } from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType, mssqlProvider } from '../../constants';
import { AzureResourceItemType } from '../../constants';
import { generateGuid } from '../../utils';
import { IAzureResourceService } from '../../interfaces';
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
@@ -48,7 +48,7 @@ export class SqlInstanceArcTreeDataProvider extends ResourceTreeDataProviderBase
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProvider,
providerName: 'MSSQL',
saveProfile: false,
options: {},
azureAccount: account.key.accountId,
@@ -56,7 +56,7 @@ export class SqlInstanceArcTreeDataProvider extends ResourceTreeDataProviderBase
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: mssqlProvider,
childProvider: 'MSSQL',
type: ExtensionNodeType.Server
};
}

View File

@@ -1,27 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionContext } from 'vscode';
import { azureResource } from 'azurecore';
import { AzureResourceSynapseSqlPoolTreeDataProvider as AzureResourceSynapseSqlPoolTreeDataProvider } from './synapseSqlPoolTreeDataProvider';
import { IAzureResourceService } from '../../interfaces';
export class AzureResourceSynapseSqlPoolProvider implements azureResource.IAzureResourceProvider {
public constructor(
private _synapseSqlPoolService: IAzureResourceService<azureResource.AzureResourceDatabase>,
private _extensionContext: ExtensionContext
) {
}
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
return new AzureResourceSynapseSqlPoolTreeDataProvider(this._synapseSqlPoolService, this._extensionContext);
}
public get providerId(): string {
return 'azure.resource.providers.synapseSqlPool';
}
}

View File

@@ -1,72 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServiceClientCredentials } from '@azure/ms-rest-js';
import { IAzureResourceService } from '../../interfaces';
import { SynapseWorkspaceGraphData } from '../synapseWorkspace/synapseWorkspaceService';
import { synapseWorkspacesQuery, synapseSqlPoolsQuery } from '../queryStringConstants';
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { queryGraphResources, GraphData } from '../resourceTreeDataProviderBase';
import { AzureAccount, azureResource } from 'azurecore';
interface SynapseGraphData extends GraphData {
kind: string;
}
export class AzureResourceSynapseService implements IAzureResourceService<azureResource.AzureResourceDatabase> {
public async getResources(subscriptions: azureResource.AzureResourceSubscription[], credential: ServiceClientCredentials, account: AzureAccount): Promise<azureResource.AzureResourceDatabase[]> {
const databases: azureResource.AzureResourceDatabase[] = [];
const resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
// Query synapse servers, and databases in parallel (start all promises before waiting on the 1st)
let synapseQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, synapseSqlPoolsQuery);
let synapseWorkspaceQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, synapseWorkspacesQuery);
let synapse = await synapseQueryPromise as SynapseGraphData[];
let synapseWorkspaceByGraph: SynapseWorkspaceGraphData[] = await synapseWorkspaceQueryPromise as SynapseWorkspaceGraphData[];
// Group servers by resource group, then merge DB results with servers so we
// can get the login name and server fully qualified name to use for connections
let rgMap = new Map<string, SynapseWorkspaceGraphData[]>();
synapseWorkspaceByGraph.forEach(s => {
// As the resource is a Synapse Workspace, we need to use the managedResourceGroupName
// (any SQL pools inside will use this instead of the regular resource group associated with the workspace itself).
let serversForRg = rgMap.get(s.properties.managedResourceGroupName) || [];
serversForRg.push(s);
rgMap.set(s.properties.managedResourceGroupName, serversForRg);
});
// Match database ID. When calling exec [0] is full match, [1] is resource group name, [2] is server name
const svrIdRegExp = new RegExp(`\/subscriptions\/.+\/resourceGroups\/(.+)\/providers\/Microsoft\.Synapse\/workspaces\/(.+)\/sqlPools\/.+`);
synapse.forEach(db => {
// Filter master DBs, and for all others find their server to get login info
if (!db.kind.endsWith('system') && svrIdRegExp.test(db.id)) {
const founds = svrIdRegExp.exec(db.id);
if (!founds) {
console.warn(`Could not parse server name from ID ${db.id}`);
return;
}
const serverName = founds[2];
let server = synapseWorkspaceByGraph.find(s => s.name === serverName);
if (server) {
databases.push({
name: db.name,
id: db.id,
serverName: server.name,
serverFullName: server.properties.connectivityEndpoints?.sql,
loginName: server.properties.sqlAdministratorLogin,
subscription: {
id: db.subscriptionId,
name: (subscriptions.find(sub => sub.id === db.subscriptionId))?.name || ''
},
tenant: db.tenantId,
resourceGroup: db.resourceGroup
});
}
}
});
return databases;
}
}

View File

@@ -1,75 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TreeItem, ExtensionNodeType } from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType, mssqlProvider } from '../../constants';
import { generateGuid } from '../../utils';
import { IAzureResourceService } from '../../interfaces';
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
import { AzureAccount, azureResource } from 'azurecore';
export class AzureResourceSynapseSqlPoolTreeDataProvider extends ResourceTreeDataProviderBase<azureResource.AzureResourceDatabase> {
private static readonly containerId = 'azure.resource.providers.synapseSqlPool.treeDataProvider.synapseSqlPoolContainer';
private static readonly containerLabel = localize('azure.resource.providers.synapseSqlPool.treeDataProvider.synapseSqlPoolContainerLabel', "Dedicated SQL Pools");
public constructor(
synapseSqlPoolService: IAzureResourceService<azureResource.AzureResourceDatabase>,
private _extensionContext: vscode.ExtensionContext
) {
super(synapseSqlPoolService);
}
protected getTreeItemForResource(synapse: azureResource.AzureResourceDatabase, account: AzureAccount): TreeItem {
return {
id: `synapseWorkspace_${synapse.serverFullName}.synapseSqlPool_${synapse.name}`,
label: this.browseConnectionMode ? `${synapse.serverName}/${synapse.name} (${AzureResourceSynapseSqlPoolTreeDataProvider.containerLabel}, ${synapse.subscription.name})` : `${synapse.name} (${synapse.serverName})`,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg')
},
collapsibleState: this.browseConnectionMode ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.synapseSqlPool,
payload: {
id: generateGuid(),
connectionName: undefined,
serverName: synapse.serverFullName,
databaseName: synapse.name,
userName: synapse.loginName,
password: '',
authenticationType: '',
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProvider,
saveProfile: false,
options: {},
azureAccount: account.key.accountId,
azureResourceId: synapse.id,
azureTenantId: synapse.tenant,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: mssqlProvider,
type: ExtensionNodeType.Database
};
}
public async getRootChildren(): Promise<TreeItem[]> {
return [{
id: AzureResourceSynapseSqlPoolTreeDataProvider.containerId,
label: AzureResourceSynapseSqlPoolTreeDataProvider.containerLabel,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
},
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.synapseSqlPoolContainer
}];
}
}

View File

@@ -1,26 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionContext } from 'vscode';
import { azureResource } from 'azurecore';
import { AzureResourceSynapseWorkspaceTreeDataProvider } from './synapseWorkspaceTreeDataProvider';
import { IAzureResourceService } from '../../interfaces';
export class AzureResourceSynapseWorkspaceProvider implements azureResource.IAzureResourceProvider {
public constructor(
private _synapseWorkspaceService: IAzureResourceService<azureResource.AzureResourceDatabaseServer>,
private _extensionContext: ExtensionContext
) {
}
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
return new AzureResourceSynapseWorkspaceTreeDataProvider(this._synapseWorkspaceService, this._extensionContext);
}
public get providerId(): string {
return 'azure.resource.providers.synapseWorkspace';
}
}

View File

@@ -1,73 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServiceClientCredentials } from '@azure/ms-rest-js';
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { GraphData, queryGraphResources } from '../resourceTreeDataProviderBase';
import { azureResource, AzureAccount } from 'azurecore';
import { IAzureResourceService } from '../../interfaces';
import { synapseWorkspacesQuery } from '../queryStringConstants';
/**
* Properties returned by the Synapse query are different from the server ones and have to be treated differently.
*/
export interface SynapseWorkspaceGraphData extends GraphData {
properties: {
/**
* SQL connectivity endpoint and other endpoints are found here, instead of fullyQualifiedDomainName.
*/
connectivityEndpoints: { sql: string };
/**
* managedResourceGroupName is the resource group used by any SQL pools inside the workspace
* which is different from the resource group of the workspace itself.
*/
managedResourceGroupName: string;
/**
* administratorLogin is called sqlAdministratorLogin here.
*/
sqlAdministratorLogin: string;
};
}
export class AzureResourceSynapseWorkspaceService implements IAzureResourceService<azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return synapseWorkspacesQuery;
}
public async getResources(subscriptions: azureResource.AzureResourceSubscription[], credential: ServiceClientCredentials, account: AzureAccount): Promise<azureResource.AzureResourceDatabaseServer[]> {
const convertedResources: azureResource.AzureResourceDatabaseServer[] = [];
const resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
let serverGraphResources: SynapseWorkspaceGraphData[] = await queryGraphResources<SynapseWorkspaceGraphData>(resourceClient, subscriptions, this.query);
const ids = new Set<string>();
serverGraphResources.forEach((res) => {
if (!ids.has(res.id)) {
ids.add(res.id);
res.subscriptionName = subscriptions.find(sub => sub.id === res.subscriptionId)?.name;
const converted = this.convertResource(res);
convertedResources.push(converted);
}
});
return convertedResources;
}
protected convertResource(resource: SynapseWorkspaceGraphData): azureResource.AzureResourceDatabaseServer {
return {
id: resource.id,
name: resource.name,
fullName: resource.properties.connectivityEndpoints?.sql,
loginName: resource.properties.sqlAdministratorLogin,
defaultDatabaseName: 'master',
subscription: {
id: resource.subscriptionId,
name: resource.subscriptionName || ''
},
tenant: resource.tenantId,
resourceGroup: resource.resourceGroup
};
}
}

View File

@@ -1,74 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionNodeType, TreeItem } from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType, mssqlProvider } from '../../constants';
import { generateGuid } from '../../utils';
import { IAzureResourceService } from '../../interfaces';
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
import { AzureAccount, azureResource } from 'azurecore';
export class AzureResourceSynapseWorkspaceTreeDataProvider extends ResourceTreeDataProviderBase<azureResource.AzureResourceDatabaseServer> {
private static readonly containerId = 'azure.resource.providers.synapseWorkspace.treeDataProvider.synapseWorkspaceContainer';
private static readonly containerLabel = localize('azure.resource.providers.synapseWorkspace.treeDataProvider.synapseWorkspaceContainerLabel', "Azure Synapse Analytics");
public constructor(
synapseWorkspaceService: IAzureResourceService<azureResource.AzureResourceDatabaseServer>,
private _extensionContext: vscode.ExtensionContext
) {
super(synapseWorkspaceService);
}
protected getTreeItemForResource(synapseWorkspace: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
return {
id: `synapseWorkspace_${synapseWorkspace.id ?? synapseWorkspace.name}`,
label: this.browseConnectionMode ? `${synapseWorkspace.name} (${AzureResourceSynapseWorkspaceTreeDataProvider.containerLabel}, ${synapseWorkspace.subscription.name})` : synapseWorkspace.name,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
},
collapsibleState: this.browseConnectionMode ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.synapseWorkspace,
payload: {
id: generateGuid(),
connectionName: undefined,
serverName: synapseWorkspace.fullName,
databaseName: synapseWorkspace.defaultDatabaseName,
userName: synapseWorkspace.loginName,
password: '',
authenticationType: '',
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProvider,
saveProfile: false,
options: {},
azureAccount: account.key.accountId,
azureTenantId: synapseWorkspace.tenant,
azureResourceId: synapseWorkspace.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: mssqlProvider,
type: ExtensionNodeType.Server
};
}
public async getRootChildren(): Promise<TreeItem[]> {
return [{
id: AzureResourceSynapseWorkspaceTreeDataProvider.containerId,
label: AzureResourceSynapseWorkspaceTreeDataProvider.containerLabel,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
},
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.synapseWorkspaceContainer
}];
}
}

View File

@@ -32,7 +32,7 @@ export class AzureResourceService {
this._areResourceProvidersLoaded = false;
}
public async getRootChildren(resourceProviderId: string, account: AzureAccount, subscription: azureResource.AzureResourceSubscription): Promise<IAzureResourceNodeWithProviderId[]> {
public async getRootChildren(resourceProviderId: string, account: AzureAccount, subscription: azureResource.AzureResourceSubscription, tenantId: string): Promise<IAzureResourceNodeWithProviderId[]> {
await this.ensureResourceProvidersRegistered();
if (!(resourceProviderId in this._resourceProviders)) {
@@ -48,7 +48,7 @@ export class AzureResourceService {
resourceNode: {
account,
subscription,
tenantId: subscription.tenant!,
tenantId,
treeItem: rootChild
}
};

View File

@@ -28,6 +28,7 @@ export class AzureResourceSubscriptionService implements IAzureResourceSubscript
const subscriptions: azureResource.AzureResourceSubscription[] = [];
let gotSubscriptions = false;
const errors: Error[] = [];
for (const tenantId of tenantIds ?? account.properties.tenants.map(t => t.id)) {
try {
const token = await azdata.accounts.getAccountSecurityToken(account, tenantId, azdata.AzureResource.ResourceManagement);
@@ -41,7 +42,6 @@ export class AzureResourceSubscriptionService implements IAzureResourceSubscript
tenant: tenantId
};
}));
Logger.verbose(`AzureResourceSubscriptionService.getSubscriptions: Retrieved ${newSubs.length} subscriptions for tenant ${tenantId} / account ${account.displayInfo.displayName}`);
gotSubscriptions = true;
}
else if (!account.isStale) {

View File

@@ -66,25 +66,23 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
if (subscriptions.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
} else {
const authLibrary = vscode.workspace.getConfiguration('azure').get('authenticationLibrary');
if (authLibrary === 'ADAL') {
// Filter out everything that we can't authenticate to.
const hasTokenResults = await Promise.all(subscriptions.map(async s => {
let token: azdata.accounts.AccountSecurityToken | undefined = undefined;
let errMsg = '';
try {
token = await azdata.accounts.getAccountSecurityToken(this.account, s.tenant!, azdata.AzureResource.ResourceManagement);
} catch (err) {
errMsg = AzureResourceErrorMessageUtil.getErrorMessage(err);
}
if (!token) {
void vscode.window.showWarningMessage(localize('azure.unableToAccessSubscription', "Unable to access subscription {0} ({1}). Please [refresh the account](command:azure.resource.signin) to try again. {2}", s.name, s.id, errMsg));
return false;
}
return true;
}));
subscriptions = subscriptions.filter((_s, i) => hasTokenResults[i]);
}
// Filter out everything that we can't authenticate to.
const hasTokenResults = await Promise.all(subscriptions.map(async s => {
let token: azdata.accounts.AccountSecurityToken | undefined = undefined;
let errMsg = '';
try {
token = await azdata.accounts.getAccountSecurityToken(this.account, s.tenant!, azdata.AzureResource.ResourceManagement);
} catch (err) {
errMsg = AzureResourceErrorMessageUtil.getErrorMessage(err);
}
if (!token) {
void vscode.window.showWarningMessage(localize('azure.unableToAccessSubscription', "Unable to access subscription {0} ({1}). Please [refresh the account](command:azure.resource.signin) to try again. {2}", s.name, s.id, errMsg));
return false;
}
return true;
}));
subscriptions = subscriptions.filter((_s, i) => hasTokenResults[i]);
let subTreeNodes = await Promise.all(subscriptions.map(async (subscription) => {
return new AzureResourceSubscriptionTreeNode(this.account, subscription, subscription.tenant!, this.appContext, this.treeChangeHandler, this);
}));
@@ -166,9 +164,4 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
private _selectedSubscriptionCount = 0;
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', "No Subscriptions found.");
sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

View File

@@ -13,7 +13,7 @@ import { TreeNode } from '../treeNode';
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceErrorMessageUtil, equals, filterAccounts } from '../utils';
import { AzureResourceErrorMessageUtil, equals } from '../utils';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { FlatAccountTreeNode } from './flatAccountTreeNode';
import { Logger } from '../../utils/Logger';
@@ -26,11 +26,10 @@ export class ConnectionDialogTreeProvider implements vscode.TreeDataProvider<Tre
private _onDidChangeTreeData = new vscode.EventEmitter<TreeNode | undefined>();
private loadingAccountsPromise: Promise<void> | undefined;
public constructor(private readonly appContext: AppContext,
private readonly authLibrary: string) {
public constructor(private readonly appContext: AppContext) {
azdata.accounts.onDidChangeAccounts(async (e: azdata.DidChangeAccountsParams) => {
// This event sends it per provider, we need to make sure we get all the azure related accounts
let accounts = filterAccounts(await azdata.accounts.getAllAccounts(), authLibrary);
let accounts = await azdata.accounts.getAllAccounts();
accounts = accounts.filter(a => a.key.providerId.startsWith('azure'));
// the onDidChangeAccounts event will trigger in many cases where the accounts didn't actually change
// the notifyNodeChanged event triggers a refresh which triggers a getChildren which can trigger this callback
@@ -56,11 +55,10 @@ export class ConnectionDialogTreeProvider implements vscode.TreeDataProvider<Tre
}
if (this.accounts && this.accounts.length > 0) {
let accounts = filterAccounts(this.accounts, this.authLibrary);
const accountNodes: FlatAccountTreeNode[] = [];
const errorMessages: string[] = [];
// We are doing sequential account loading to avoid the Azure request throttling
for (const account of accounts) {
for (const account of this.accounts) {
try {
const accountNode = new FlatAccountTreeNode(account, this.appContext, this);
await accountNode.updateLabel();
@@ -87,7 +85,7 @@ export class ConnectionDialogTreeProvider implements vscode.TreeDataProvider<Tre
private async loadAccounts(): Promise<void> {
try {
this.accounts = filterAccounts(await azdata.accounts.getAllAccounts(), this.authLibrary);
this.accounts = await azdata.accounts.getAllAccounts();
// System has been initialized
this.setSystemInitialized();
this._onDidChangeTreeData.fire(undefined);

View File

@@ -20,7 +20,6 @@ import { AzureAccount, azureResource } from 'azurecore';
import { AzureResourceService } from '../resourceService';
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
import { AzureResourceErrorMessageUtil } from '../utils';
import { Logger } from '../../utils/Logger';
export class FlatAccountTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
@@ -179,24 +178,14 @@ class FlatAccountTreeNodeLoader {
}
}, 500);
try {
// Authenticate to tenants to filter out subscriptions that are not accessible.
let tenants = this._account.properties.tenants;
// Filter out tenants that we can't authenticate to.
tenants = tenants.filter(async tenant => {
const token = await azdata.accounts.getAccountSecurityToken(this._account, tenant.id, azdata.AzureResource.ResourceManagement);
return token !== undefined;
});
let subscriptions: azureResource.AzureResourceSubscription[] = (await getSubscriptionInfo(this._account, this._subscriptionService, this._subscriptionFilterService)).subscriptions;
if (subscriptions.length !== 0) {
// Filter out subscriptions that don't belong to the tenants we filtered above.
// Filter out everything that we can't authenticate to.
subscriptions = subscriptions.filter(async s => {
const tenant = tenants.find(t => t.id === s.tenant);
if (!tenant) {
Logger.info(`Account does not have permissions to view subscription ${JSON.stringify(s)}.`);
const token = await azdata.accounts.getAccountSecurityToken(this._account, s.tenant!, azdata.AzureResource.ResourceManagement);
if (!token) {
console.info(`Account does not have permissions to view subscription ${JSON.stringify(s)}.`);
return false;
}
return true;
@@ -206,10 +195,10 @@ class FlatAccountTreeNodeLoader {
const resourceProviderIds = await this._resourceService.listResourceProviderIds();
for (const subscription of subscriptions) {
for (const providerId of resourceProviderIds) {
const resourceTypes = await this._resourceService.getRootChildren(providerId, this._account, subscription);
const resourceTypes = await this._resourceService.getRootChildren(providerId, this._account, subscription, subscription.tenant!);
for (const resourceType of resourceTypes) {
const resources = await this._resourceService.getChildren(providerId, resourceType.resourceNode, true);
if (resources?.length > 0) {
if (resources.length > 0) {
this._nodes.push(...resources.map(dr => new AzureResourceResourceTreeNode(dr, this._accountNode, this.appContext)));
this._nodes = this.nodes.sort((a, b) => {
return a.getNodeInfo().label.localeCompare(b.getNodeInfo().label);

View File

@@ -12,12 +12,12 @@ const localize = nls.loadMessageBundle();
import { TreeNode } from '../treeNode';
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceErrorMessageUtil, filterAccounts } from '../utils';
import { AzureResourceErrorMessageUtil } from '../utils';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { IAzureResourceNodeWithProviderId, IAzureResourceSubscriptionService } from '../interfaces';
import { AzureResourceServiceNames } from '../constants';
import { AzureResourceService } from '../resourceService';
import { Logger } from '../../utils/Logger';
export class FlatAzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
public isSystemInitialized: boolean = false;
@@ -26,8 +26,7 @@ export class FlatAzureResourceTreeProvider implements vscode.TreeDataProvider<Tr
private resourceLoader: ResourceLoader | undefined;
public constructor(private readonly appContext: AppContext,
private readonly authLibrary: string) {
public constructor(private readonly appContext: AppContext) {
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
@@ -36,7 +35,7 @@ export class FlatAzureResourceTreeProvider implements vscode.TreeDataProvider<Tr
}
if (!this.resourceLoader) {
this.resourceLoader = new ResourceLoader(this.appContext, this.authLibrary);
this.resourceLoader = new ResourceLoader(this.appContext);
this.resourceLoader.onDidAddNewResource(e => this._onDidChangeTreeData.fire(e));
}
@@ -88,8 +87,7 @@ class ResourceLoader {
private readonly _onDidAddNewResource = new vscode.EventEmitter<TreeNode | undefined>();
public readonly onDidAddNewResource = this._onDidAddNewResource.event;
constructor(private readonly appContext: AppContext,
private readonly authLibrary: string) {
constructor(private readonly appContext: AppContext) {
this.subscriptionService = appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
this.resourceService = appContext.getService<AzureResourceService>(AzureResourceServiceNames.resourceService);
}
@@ -120,13 +118,13 @@ class ResourceLoader {
this._state = LoaderState.Loading;
const accounts = filterAccounts(await azdata.accounts.getAllAccounts(), this.authLibrary);
const accounts = await azdata.accounts.getAllAccounts();
for (const account of accounts) {
for (const tenant of account.properties.tenants) {
for (const subscription of await this.subscriptionService.getSubscriptions(account, [tenant.id])) {
for (const providerId of await this.resourceService.listResourceProviderIds()) {
for (const group of await this.resourceService.getRootChildren(providerId, account, subscription)) {
for (const group of await this.resourceService.getRootChildren(providerId, account, subscription, subscription.tenant!)) {
const children = await this.resourceService.getChildren(providerId, group.resourceNode);
let groupNode: AzureResourceResourceTreeNode | undefined = this.resourceGroups.get(group.resourceProviderId);
if (groupNode) {
@@ -143,7 +141,7 @@ class ResourceLoader {
}
}
Logger.verbose('finished loading all accounts and subscriptions');
console.log('finished loading');
clearInterval(interval);
@@ -210,4 +208,5 @@ class AzureResourceResourceTreeNode extends TreeNode {
public get nodePathValue(): string {
return this.resourceNodeWithProviderId.resourceNode.treeItem.id || '';
}
}

View File

@@ -42,7 +42,7 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTre
const children: IAzureResourceNodeWithProviderId[] = [];
for (const resourceProviderId of await resourceService.listResourceProviderIds()) {
children.push(...await resourceService.getRootChildren(resourceProviderId, this.account, this.subscription));
children.push(...await resourceService.getRootChildren(resourceProviderId, this.account, this.subscription, this.tenantId));
}
if (children.length === 0) {

View File

@@ -14,10 +14,11 @@ import { AzureResourceAccountTreeNode } from './accountTreeNode';
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceErrorMessageUtil, equals, filterAccounts } from '../utils';
import { AzureResourceErrorMessageUtil, equals } from '../utils';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { AzureAccount } from 'azurecore';
export class AzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
public isSystemInitialized: boolean = false;
@@ -25,11 +26,10 @@ export class AzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNo
private _onDidChangeTreeData = new vscode.EventEmitter<TreeNode | undefined>();
private loadingAccountsPromise: Promise<void> | undefined;
public constructor(private readonly appContext: AppContext,
private readonly authLibrary: string) {
public constructor(private readonly appContext: AppContext) {
azdata.accounts.onDidChangeAccounts(async (e: azdata.DidChangeAccountsParams) => {
// This event sends it per provider, we need to make sure we get all the azure related accounts
let accounts = filterAccounts(await azdata.accounts.getAllAccounts(), authLibrary);
let accounts = await azdata.accounts.getAllAccounts();
accounts = accounts.filter(a => a.key.providerId.startsWith('azure'));
// the onDidChangeAccounts event will trigger in many cases where the accounts didn't actually change
// the notifyNodeChanged event triggers a refresh which triggers a getChildren which can trigger this callback
@@ -56,7 +56,6 @@ export class AzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNo
try {
if (this.accounts && this.accounts.length > 0) {
this.accounts = filterAccounts(this.accounts, this.authLibrary);
return this.accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
} else {
return [new AzureResourceAccountNotSignedInTreeNode()];
@@ -68,7 +67,7 @@ export class AzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNo
private async loadAccounts(): Promise<void> {
try {
this.accounts = filterAccounts(await azdata.accounts.getAllAccounts(), this.authLibrary);
this.accounts = await azdata.accounts.getAllAccounts();
// System has been initialized
this.setSystemInitialized();
this._onDidChangeTreeData.fire(undefined);
@@ -97,6 +96,7 @@ export class AzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNo
node.clearCache();
}
}
this._onDidChangeTreeData.fire(node);
}

View File

@@ -17,7 +17,6 @@ import { IAzureResourceSubscriptionFilterService, IAzureResourceSubscriptionServ
import { AzureResourceGroupService } from './providers/resourceGroup/resourceGroupService';
import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob';
import providerSettings from '../account-provider/providerSettings';
import * as Constants from '../constants';
const localize = nls.loadMessageBundle();
@@ -542,18 +541,3 @@ export function getProviderMetadataForAccount(account: AzureAccount): AzureAccou
return provider.metadata;
}
// Filter accounts based on currently selected Auth Library:
// if the account key is present, filter based on current auth library
// if there is no account key (pre-MSAL account), then it is an ADAL account and
// should be displayed as long as ADAL is the currently selected auth library
export function filterAccounts(accounts: azdata.Account[], authLibrary: string): azdata.Account[] {
let filteredAccounts = accounts.filter(account => {
if (account.key.authLibrary) {
return account.key.authLibrary === authLibrary;
} else {
return authLibrary === Constants.AuthLibrary.ADAL;
}
});
return filteredAccounts;
}

View File

@@ -337,7 +337,7 @@ declare module 'azurecore' {
export namespace azureResource {
/**
* AzureCore extension supports following resource types of Azure Resource Graph.
* AzureCore core extension supports following resource types of Azure Resource Graph.
* To add more resources, please refer this guide: https://docs.microsoft.com/en-us/azure/governance/resource-graph/reference/supported-tables-resources
*/
export const enum AzureResourceType {
@@ -345,9 +345,6 @@ declare module 'azurecore' {
sqlServer = 'microsoft.sql/servers',
sqlDatabase = 'microsoft.sql/servers/databases',
sqlManagedInstance = 'microsoft.sql/managedinstances',
sqlSynapseWorkspace = 'microsoft.synapse/workspaces', // (Synapse Analytics workspace)
sqlSynapseSqlPool = 'microsoft.synapse/workspaces/sqlpools', // (Dedicated SQL pools)
sqlSynapseSqlDatabase = 'microsoft.synapse/workspaces/sqldatabases', // (Synapse SQL databases)
azureArcSqlManagedInstance = 'microsoft.azuredata/sqlmanagedinstances',
virtualMachines = 'microsoft.compute/virtualmachines',
kustoClusters = 'microsoft.kusto/clusters',
@@ -494,7 +491,6 @@ declare module 'azurecore' {
fullName: string;
defaultDatabaseName: string;
}
export interface BlobContainer extends AzureResource { }
export interface FileShare extends AzureResource { }

View File

@@ -3,104 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const Account = 'account';
export const AccountsSection = 'accounts';
export const AuthSection = 'auth';
export const AuthenticationLibrarySection = 'authenticationLibrary';
export const AzureSection = 'azure';
export const AzureAccountProviderCredentials = 'azureAccountProviderCredentials';
export const CloudSection = 'cloud';
export const ClearTokenCacheCommand = 'clearTokenCache';
export const ConfigSection = 'config';
export const AccountsClearTokenCacheCommand = AccountsSection + '.' + ClearTokenCacheCommand;
export const AccountsAzureAuthSection = AccountsSection + '.' + AzureSection + '.' + AuthSection;
export const AccountsAzureCloudSection = AccountsSection + '.' + AzureSection + '.' + CloudSection;
export const AzureAuthenticationLibrarySection = AzureSection + '.' + AuthenticationLibrarySection;
export const EnableArcFeaturesSection = 'enableArcFeatures';
export const ServiceName = 'azuredatastudio';
export const TenantSection = 'tenant';
export const AzureTenantConfigSection = AzureSection + '.' + TenantSection + '.' + ConfigSection;
export const NoSystemKeyChainSection = 'noSystemKeychain';
/** MSAL Account version */
export const AccountVersion = '2.0';
export const Bearer = 'Bearer';
/**
* Use SHA-256 algorithm
*/
export const S256_CODE_CHALLENGE_METHOD = 'S256';
export const SELECT_ACCOUNT = 'select_account';
export const Saw = 'saw';
export const extensionConfigSectionName = 'azure';
export const ViewType = 'view';
export const HomeCategory = 'Home';
export const dataGridProviderId = 'azure-resources';
export const AzureTokenFolderName = 'Azure Accounts';
export const DefaultAuthLibrary = 'ADAL';
export enum BuiltInCommands {
SetContext = 'setContext'
}
/**
* AAD Auth library as selected.
*/
export enum AuthLibrary {
MSAL = 'MSAL',
ADAL = 'ADAL'
}
/**
* Authentication type as selected.
*/
export enum AuthType {
DeviceCode = 'deviceCode',
CodeGrant = 'codeGrant'
}
/**
* Account issuer as received from access token
*/
export enum AccountIssuer {
Corp = 'corp',
Msft = 'msft',
}
/**
* Azure Account type as received from access token
*/
export enum AccountType {
WorkSchool = 'work_school',
Microsoft = 'microsoft',
}
export enum Platform {
Windows = 'win32',
Mac = 'darwin',
Linux = 'linux'
}

View File

@@ -45,15 +45,11 @@ import * as azurecore from 'azurecore';
import * as azureResourceUtils from './azureResource/utils';
import * as utils from './utils';
import * as loc from './localizedConstants';
import * as Constants from './constants';
import * as constants from './constants';
import { AzureResourceGroupService } from './azureResource/providers/resourceGroup/resourceGroupService';
import { Logger } from './utils/Logger';
import { ConnectionDialogTreeProvider } from './azureResource/tree/connectionDialogTreeProvider';
import { AzureDataGridProvider } from './azureDataGridProvider';
import { AzureResourceSynapseSqlPoolProvider } from './azureResource/providers/synapseSqlPool/synapseSqlPoolProvider';
import { AzureResourceSynapseWorkspaceProvider } from './azureResource/providers/synapseWorkspace/synapseWorkspaceProvider';
import { AzureResourceSynapseWorkspaceService } from './azureResource/providers/synapseWorkspace/synapseWorkspaceService';
import { AzureResourceSynapseService } from './azureResource/providers/synapseSqlPool/synapseSqlPoolService';
let extensionContext: vscode.ExtensionContext;
@@ -62,15 +58,15 @@ let extensionContext: vscode.ExtensionContext;
function getAppDataPath() {
let platform = process.platform;
switch (platform) {
case Constants.Platform.Windows: return process.env['APPDATA'] || path.join(process.env['USERPROFILE']!, 'AppData', 'Roaming');
case Constants.Platform.Mac: return path.join(os.homedir(), 'Library', 'Application Support');
case Constants.Platform.Linux: return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE']!, 'AppData', 'Roaming');
case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support');
case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
default: throw new Error('Platform not supported');
}
}
function getDefaultLogLocation() {
return path.join(getAppDataPath(), Constants.ServiceName);
return path.join(getAppDataPath(), 'azuredatastudio');
}
function pushDisposable(disposable: vscode.Disposable): void {
@@ -89,27 +85,24 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
}
// TODO: Since Code Grant auth doesnt work in web mode, enabling Device code auth by default for web mode. We can remove this once we have that working in web mode.
const config = vscode.workspace.getConfiguration(Constants.AccountsAzureAuthSection);
const config = vscode.workspace.getConfiguration('accounts.azure.auth');
if (vscode.env.uiKind === vscode.UIKind.Web) {
await config.update('deviceCode', true, vscode.ConfigurationTarget.Global);
}
const authLibrary: string = vscode.workspace.getConfiguration(Constants.AzureSection).get(Constants.AuthenticationLibrarySection)
?? Constants.DefaultAuthLibrary;
updatePiiLoggingLevel();
// Create the provider service and activate
initAzureAccountProvider(extensionContext, storagePath, authLibrary!).catch((err) => console.log(err));
initAzureAccountProvider(extensionContext, storagePath).catch((err) => console.log(err));
registerAzureServices(appContext);
const azureResourceTree = new AzureResourceTreeProvider(appContext, authLibrary);
const connectionDialogTree = new ConnectionDialogTreeProvider(appContext, authLibrary);
const azureResourceTree = new AzureResourceTreeProvider(appContext);
const connectionDialogTree = new ConnectionDialogTreeProvider(appContext);
pushDisposable(vscode.window.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
pushDisposable(vscode.window.registerTreeDataProvider('connectionDialog/azureResourceExplorer', connectionDialogTree));
pushDisposable(vscode.workspace.onDidChangeConfiguration(e => onDidChangeConfiguration(e)));
registerAzureResourceCommands(appContext, azureResourceTree, connectionDialogTree, authLibrary);
azdata.dataprotocol.registerDataGridProvider(new AzureDataGridProvider(appContext, authLibrary));
registerAzureResourceCommands(appContext, azureResourceTree, connectionDialogTree);
azdata.dataprotocol.registerDataGridProvider(new AzureDataGridProvider(appContext));
vscode.commands.registerCommand('azure.dataGrid.openInAzurePortal', async (item: azdata.DataGridItem) => {
const portalEndpoint = item.portalEndpoint;
const subscriptionId = item.subscriptionId;
@@ -137,14 +130,12 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
return azureResourceUtils.getLocations(appContext, account, subscription, ignoreErrors);
},
provideResources(): azurecore.azureResource.IAzureResourceProvider[] {
const arcFeaturedEnabled = vscode.workspace.getConfiguration(Constants.AzureSection).get(Constants.EnableArcFeaturesSection);
const arcFeaturedEnabled = vscode.workspace.getConfiguration(constants.extensionConfigSectionName).get('enableArcFeatures');
const providers: azurecore.azureResource.IAzureResourceProvider[] = [
new KustoProvider(new KustoResourceService(), extensionContext),
new AzureMonitorProvider(new AzureMonitorResourceService(), extensionContext),
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), extensionContext),
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), extensionContext),
new AzureResourceSynapseSqlPoolProvider(new AzureResourceSynapseService(), extensionContext),
new AzureResourceSynapseWorkspaceProvider(new AzureResourceSynapseWorkspaceService(), extensionContext),
new SqlInstanceProvider(new SqlInstanceResourceService(), extensionContext),
new PostgresServerProvider(new PostgresServerService(), extensionContext),
new CosmosDbMongoProvider(new CosmosDbMongoService(), extensionContext),
@@ -242,7 +233,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
// Create the folder for storing the token caches
async function findOrMakeStoragePath() {
let defaultLogLocation = getDefaultLogLocation();
let storagePath = path.join(defaultLogLocation, Constants.AzureTokenFolderName);
let storagePath = path.join(defaultLogLocation, constants.AzureTokenFolderName);
try {
await fs.mkdir(defaultLogLocation, { recursive: true });
@@ -267,9 +258,9 @@ async function findOrMakeStoragePath() {
return storagePath;
}
async function initAzureAccountProvider(extensionContext: vscode.ExtensionContext, storagePath: string, authLibrary: string): Promise<void> {
async function initAzureAccountProvider(extensionContext: vscode.ExtensionContext, storagePath: string): Promise<void> {
try {
const accountProviderService = new AzureAccountProviderService(extensionContext, storagePath, authLibrary);
const accountProviderService = new AzureAccountProviderService(extensionContext, storagePath);
extensionContext.subscriptions.push(accountProviderService);
await accountProviderService.activate();
} catch (err) {
@@ -290,27 +281,10 @@ async function onDidChangeConfiguration(e: vscode.ConfigurationChangeEvent): Pro
if (e.affectsConfiguration('azure.piiLogging')) {
updatePiiLoggingLevel();
}
if (e.affectsConfiguration('azure.authenticationLibrary')) {
await displayReloadAds();
}
}
function updatePiiLoggingLevel(): void {
const piiLogging: boolean = vscode.workspace.getConfiguration(Constants.AzureSection).get('piiLogging', false);
const piiLogging: boolean = vscode.workspace.getConfiguration(constants.extensionConfigSectionName).get('piiLogging', false);
Logger.piiLogging = piiLogging;
}
// Display notification with button to reload
// return true if button clicked
// return false if button not clicked
async function displayReloadAds(): Promise<boolean> {
const result = await vscode.window.showInformationMessage(loc.reloadPrompt, loc.reloadChoice);
if (result === loc.reloadChoice) {
await vscode.commands.executeCommand('workbench.action.reloadWindow');
return true;
} else {
return false;
}
}

View File

@@ -62,9 +62,6 @@ export const location = localize('azurecore.location', "Location");
export const subscription = localize('azurecore.subscription', "Subscription");
export const typeIcon = localize('azurecore.typeIcon', "Type Icon");
export const reloadPrompt = localize('azurecore.reloadPrompt', "Authentication Library has changed, please reload Azure Data Studio.");
export const reloadChoice = localize('azurecore.reloadChoice', "Reload Azure Data Studio");
// Azure Resource Types
export const sqlServer = localize('azurecore.sqlServer', "SQL server");
export const sqlDatabase = localize('azurecore.sqlDatabase', "SQL database");

View File

@@ -13,6 +13,7 @@ import providerSettings from '../../../account-provider/providerSettings';
import { AzureResource } from 'azdata';
import { AxiosResponse } from 'axios';
let azureAuthCodeGrant: TypeMoq.IMock<AzureAuthCodeGrant>;
// let azureDeviceCode: TypeMoq.IMock<AzureDeviceCode>;
@@ -51,21 +52,9 @@ describe('Azure Authentication', function () {
mockAccount = {
isStale: false,
displayInfo: {
contextualDisplayName: 'test',
accountType: 'test',
displayName: 'test',
userId: 'test'
},
key: {
providerId: 'test',
accountId: 'test'
},
properties: {
owningTenant: mockTenant,
tenants: [mockTenant],
providerSettings: provider,
isMsAccount: true
tenants: [mockTenant]
}
} as AzureAccount;
@@ -79,7 +68,7 @@ describe('Azure Authentication', function () {
it('accountHydration should yield a valid account', async function () {
azureAuthCodeGrant.setup(x => x.getTenantsAdal(mockToken)).returns((): Promise<Tenant[]> => {
azureAuthCodeGrant.setup(x => x.getTenants(mockToken)).returns((): Promise<Tenant[]> => {
return Promise.resolve([
mockTenant
]);
@@ -94,30 +83,30 @@ describe('Azure Authentication', function () {
describe('getAccountSecurityToken', function () {
it('should be undefined on stale account', async function () {
mockAccount.isStale = true;
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, TypeMoq.It.isAny(), TypeMoq.It.isAny());
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, TypeMoq.It.isAny(), TypeMoq.It.isAny());
should(securityToken).be.undefined();
});
it('dont find correct resources', async function () {
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, TypeMoq.It.isAny(), -1);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, TypeMoq.It.isAny(), -1);
should(securityToken).be.undefined();
});
it('incorrect tenant', async function () {
await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, 'invalid_tenant', AzureResource.MicrosoftResourceManagement).should.be.rejected();
await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, 'invalid_tenant', AzureResource.MicrosoftResourceManagement).should.be.rejected();
});
it('token recieved for ossRdbmns resource', async function () {
azureAuthCodeGrant.setup(x => x.getTenantsAdal(mockToken)).returns(() => {
azureAuthCodeGrant.setup(x => x.getTenants(mockToken)).returns(() => {
return Promise.resolve([
mockTenant
]);
});
azureAuthCodeGrant.setup(x => x.getTokenHelperAdal(mockTenant, provider.settings.ossRdbmsResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
azureAuthCodeGrant.setup(x => x.getTokenHelper(mockTenant, provider.settings.ossRdbmsResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
return Promise.resolve({
accessToken: mockAccessToken
} as OAuthTokenResponse);
});
azureAuthCodeGrant.setup(x => x.refreshTokenAdal(mockTenant, provider.settings.ossRdbmsResource!, mockRefreshToken)).returns((): Promise<OAuthTokenResponse> => {
azureAuthCodeGrant.setup(x => x.refreshToken(mockTenant, provider.settings.ossRdbmsResource!, mockRefreshToken)).returns((): Promise<OAuthTokenResponse> => {
const mockToken: AccessToken = JSON.parse(JSON.stringify(mockAccessToken));
delete (mockToken as any).invalidData;
return Promise.resolve({
@@ -125,7 +114,7 @@ describe('Azure Authentication', function () {
} as OAuthTokenResponse);
});
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.ossRdbmsResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.ossRdbmsResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
return Promise.resolve({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
@@ -133,21 +122,21 @@ describe('Azure Authentication', function () {
});
});
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.OssRdbms);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.OssRdbms);
should(securityToken?.token).be.equal(mockAccessToken.token, 'Token are not similar');
});
it('saved token exists and can be reused', async function () {
delete (mockAccessToken as any).tokenType;
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
return Promise.resolve({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
expiresOn: `${(new Date().getTime() / 1000) + (10 * 60)}`
});
});
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
should(securityToken?.tokenType).be.equal('Bearer', 'tokenType should be bearer on a successful getSecurityToken from cache');
});
@@ -156,47 +145,47 @@ describe('Azure Authentication', function () {
it('saved token had invalid expiration', async function () {
delete (mockAccessToken as any).tokenType;
(mockAccessToken as any).invalidData = 'this should not exist on response';
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
return Promise.resolve({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
expiresOn: 'invalid'
});
});
azureAuthCodeGrant.setup(x => x.refreshTokenAdal(mockTenant, provider.settings.microsoftResource!, mockRefreshToken)).returns((): Promise<OAuthTokenResponse> => {
azureAuthCodeGrant.setup(x => x.refreshToken(mockTenant, provider.settings.microsoftResource!, mockRefreshToken)).returns((): Promise<OAuthTokenResponse> => {
const mockToken: AccessToken = JSON.parse(JSON.stringify(mockAccessToken));
delete (mockToken as any).invalidData;
return Promise.resolve({
accessToken: mockToken
} as OAuthTokenResponse);
});
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
should((securityToken as any).invalidData).be.undefined(); // Ensure its a new one
should(securityToken?.tokenType).be.equal('Bearer', 'tokenType should be bearer on a successful getSecurityToken from cache');
azureAuthCodeGrant.verify(x => x.refreshTokenAdal(mockTenant, provider.settings.microsoftResource!, mockRefreshToken), TypeMoq.Times.once());
azureAuthCodeGrant.verify(x => x.refreshToken(mockTenant, provider.settings.microsoftResource!, mockRefreshToken), TypeMoq.Times.once());
});
describe('no saved token', function () {
it('no base token', async function () {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
return Promise.resolve(undefined);
});
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(azureAuthCodeGrant.object.commonTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
azureAuthCodeGrant.setup(x => x.getSavedToken(azureAuthCodeGrant.object.commonTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
return Promise.resolve(undefined);
});
await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement).should.be.rejected();
await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement).should.be.rejected();
});
it('base token exists', async function () {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
return Promise.resolve(undefined);
});
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(azureAuthCodeGrant.object.commonTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
azureAuthCodeGrant.setup(x => x.getSavedToken(azureAuthCodeGrant.object.commonTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
return Promise.resolve({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
@@ -205,13 +194,13 @@ describe('Azure Authentication', function () {
});
delete (mockAccessToken as any).tokenType;
azureAuthCodeGrant.setup(x => x.refreshTokenAdal(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
azureAuthCodeGrant.setup(x => x.refreshToken(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
return Promise.resolve({
accessToken: mockAccessToken
} as OAuthTokenResponse);
});
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
should(securityToken?.tokenType).be.equal('Bearer', 'tokenType should be bearer on a successful getSecurityToken from cache');
});
});
@@ -229,16 +218,16 @@ describe('Azure Authentication', function () {
} as AxiosResponse<any>);
});
azureAuthCodeGrant.setup(x => x.handleInteractionRequiredAdal(mockTenant, provider.settings.microsoftResource!)).returns(() => {
azureAuthCodeGrant.setup(x => x.handleInteractionRequired(mockTenant, provider.settings.microsoftResource!)).returns(() => {
return Promise.resolve({
accessToken: mockAccessToken
} as OAuthTokenResponse);
});
const result = await azureAuthCodeGrant.object.getTokenAdal(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData);
const result = await azureAuthCodeGrant.object.getToken(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData);
azureAuthCodeGrant.verify(x => x.handleInteractionRequiredAdal(mockTenant, provider.settings.microsoftResource!), TypeMoq.Times.once());
azureAuthCodeGrant.verify(x => x.handleInteractionRequired(mockTenant, provider.settings.microsoftResource!), TypeMoq.Times.once());
should(result?.accessToken).be.deepEqual(mockAccessToken);
});
@@ -252,7 +241,7 @@ describe('Azure Authentication', function () {
} as AxiosResponse<any>);
});
await azureAuthCodeGrant.object.getTokenAdal(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData).should.be.rejected();
await azureAuthCodeGrant.object.getToken(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData).should.be.rejected();
});
it('calls getTokenHelper', async function () {
@@ -266,16 +255,16 @@ describe('Azure Authentication', function () {
} as AxiosResponse<any>);
});
azureAuthCodeGrant.setup(x => x.getTokenHelperAdal(mockTenant, provider.settings.microsoftResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
azureAuthCodeGrant.setup(x => x.getTokenHelper(mockTenant, provider.settings.microsoftResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
return Promise.resolve({
accessToken: mockAccessToken
} as OAuthTokenResponse);
});
const result = await azureAuthCodeGrant.object.getTokenAdal(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData);
const result = await azureAuthCodeGrant.object.getToken(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData);
azureAuthCodeGrant.verify(x => x.getTokenHelperAdal(mockTenant, provider.settings.microsoftResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
azureAuthCodeGrant.verify(x => x.getTokenHelper(mockTenant, provider.settings.microsoftResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
should(result?.accessToken).be.deepEqual(mockAccessToken);
});

View File

@@ -164,9 +164,6 @@ describe('AzureResourceDatabaseTreeDataProvider.getChildren', function (): void
should(child.treeItem.label).equal(`${database.name} (${database.serverName})`);
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
should(child.treeItem.contextValue).equal(AzureResourceItemType.database);
// Authentication type should be empty string by default to support setting 'Sql: Default Authentication Type'.
should(child.treeItem.payload!.authenticationType).equal('');
}
});
});

View File

@@ -163,9 +163,6 @@ describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function ():
should(child.treeItem.label).equal(databaseServer.name);
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
should(child.treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
// Authentication type should be empty string by default to support setting 'Sql: Default Authentication Type'.
should(child.treeItem.payload!.authenticationType).equal('');
}
});
});

View File

@@ -106,7 +106,7 @@ describe('AzureResourceService.getRootChildren', function (): void {
});
it('Should be correct when provider id is correct.', async function (): Promise<void> {
const children = await resourceService.getRootChildren(mockResourceProvider1.object.providerId, mockAccount, mockSubscription);
const children = await resourceService.getRootChildren(mockResourceProvider1.object.providerId, mockAccount, mockSubscription, mockTenantId);
should(children).Array();
});
@@ -114,7 +114,7 @@ describe('AzureResourceService.getRootChildren', function (): void {
it('Should throw exceptions when provider id is incorrect.', async function (): Promise<void> {
const providerId = 'non_existent_provider_id';
try {
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription);
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
} catch (error) {
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
return;
@@ -147,7 +147,7 @@ describe('AzureResourceService.getChildren', function (): void {
it('Should throw exceptions when provider id is incorrect.', async function (): Promise<void> {
const providerId = 'non_existent_provider_id';
try {
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription);
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
} catch (error) {
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
return;
@@ -180,7 +180,7 @@ describe('AzureResourceService.getTreeItem', function (): void {
it('Should throw exceptions when provider id is incorrect.', async function (): Promise<void> {
const providerId = 'non_existent_provider_id';
try {
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription);
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
} catch (error) {
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
return;

View File

@@ -28,11 +28,9 @@ import allSettings from '../../../account-provider/providerSettings';
// Mock services
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
let mockSubscriptionServiceADAL: TypeMoq.IMock<IAzureResourceSubscriptionService>;
let mockSubscriptionServiceMSAL: TypeMoq.IMock<IAzureResourceSubscriptionService>;
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
let mockAppContextADAL: AppContext;
let mockAppContextMSAL: AppContext;
let mockAppContext: AppContext;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
// Mock test data
@@ -97,25 +95,18 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionServiceMSAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, undefined)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = [];
mockAppContextADAL = new AppContext(mockExtensionContext.object);
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContextMSAL = new AppContext(mockExtensionContext.object);
mockAppContextMSAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextMSAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceMSAL.object);
mockAppContextMSAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContext = new AppContext(mockExtensionContext.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
@@ -129,8 +120,8 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
sinon.restore();
});
it('Should be correct when created for ADAL.', async function (): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
it('Should be correct when created.', async function (): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
@@ -149,34 +140,14 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
should(nodeInfo.iconType).equal(AzureResourceItemType.account);
});
it('Should be correct when created for MSAL.', async function (): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
should(accountTreeNode.nodePathValue).equal(accountTreeNodeId);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.id).equal(accountTreeNodeId);
should(treeItem.label).equal(mockAccount.displayInfo.displayName);
should(treeItem.contextValue).equal(AzureResourceItemType.account);
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
const nodeInfo = accountTreeNode.getNodeInfo();
should(nodeInfo.label).equal(mockAccount.displayInfo.displayName);
should(nodeInfo.isLeaf).false();
should(nodeInfo.nodeType).equal(AzureResourceItemType.account);
should(nodeInfo.iconType).equal(AzureResourceItemType.account);
});
it('Should be correct when there are subscriptions listed for ADAL.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
it('Should be correct when there are subscriptions listed.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
@@ -190,34 +161,13 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should be correct when there are subscriptions listed for MSAL.', async function (): Promise<void> {
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
should(subscriptionNodes).Array();
should(subscriptionNodes.length).equal(mockSubscriptions.length);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.label).equal(accountTreeNodeLabel);
const nodeInfo = accountTreeNode.getNodeInfo();
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should only show subscriptions with valid tokens for ADAL.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
it('Should only show subscriptions with valid tokens.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').onFirstCall().resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
@@ -231,53 +181,13 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should only show subscriptions with valid tokens for MSAL.', async function (): Promise<void> {
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').onFirstCall().resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
should(subscriptionNodes).Array();
should(subscriptionNodes.length).equal(1);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.label).equal(accountTreeNodeLabel);
const nodeInfo = accountTreeNode.getNodeInfo();
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should be correct when there are subscriptions filtered for ADAL.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
it('Should be correct when there are subscriptions filtered.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
should(subscriptionNodes).Array();
should(subscriptionNodes.length).equal(mockFilteredSubscriptions.length);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.label).equal(accountTreeNodeLabel);
const nodeInfo = accountTreeNode.getNodeInfo();
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should be correct when there are subscriptions filtered for MSAL.', async function (): Promise<void> {
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
@@ -296,23 +206,17 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionServiceMSAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = [];
mockAppContextADAL = new AppContext(mockExtensionContext.object);
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContextMSAL = new AppContext(mockExtensionContext.object);
mockAppContextMSAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextMSAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceMSAL.object);
mockAppContextMSAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContext = new AppContext(mockExtensionContext.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
@@ -327,15 +231,15 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
sinon.restore();
});
it('Should load subscriptions from scratch and update cache when it is clearing cache for ADAL.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(0));
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
@@ -361,16 +265,16 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
});
it('Should load subscriptions from cache when it is not clearing cache.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
await accountTreeNode.getChildren();
const children = await accountTreeNode.getChildren();
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
@@ -382,9 +286,9 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
});
it('Should handle when there is no subscriptions.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve([]));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve([]));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
@@ -398,10 +302,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
});
it('Should honor subscription filtering.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
@@ -416,16 +320,16 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
});
it('Should handle errors.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
const mockError = 'Test error';
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => { throw new Error(mockError); });
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
@@ -442,17 +346,17 @@ describe('AzureResourceAccountTreeNode.clearCache', function (): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = [];
mockAppContextADAL = new AppContext(mockExtensionContext.object);
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContext = new AppContext(mockExtensionContext.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
sinon.stub(azdata.accounts, 'getAccountSecurityToken').returns(Promise.resolve(mockToken));
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
@@ -468,7 +372,7 @@ describe('AzureResourceAccountTreeNode.clearCache', function (): void {
});
it('Should clear cache.', async function (): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
accountTreeNode.clearCache();
should(accountTreeNode.isClearingCache).true();
});

View File

@@ -26,11 +26,10 @@ let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
// Mock test data
const mockAccountAdal1: AzureAccount = {
const mockAccount1: AzureAccount = {
key: {
accountId: 'mock_account_1',
providerId: 'mock_provider',
authLibrary: 'ADAL'
providerId: 'mock_provider'
},
displayInfo: {
displayName: 'mock_account_1@test.com',
@@ -41,7 +40,7 @@ const mockAccountAdal1: AzureAccount = {
properties: TypeMoq.Mock.ofType<AzureAccountProperties>().object,
isStale: false
};
const mockAccountAdal2: AzureAccount = {
const mockAccount2: AzureAccount = {
key: {
accountId: 'mock_account_2',
providerId: 'mock_provider'
@@ -55,39 +54,7 @@ const mockAccountAdal2: AzureAccount = {
properties: TypeMoq.Mock.ofType<AzureAccountProperties>().object,
isStale: false
};
const mockAccountsADAL = [mockAccountAdal1, mockAccountAdal2];
const mockAccountMsal1: AzureAccount = {
key: {
accountId: 'mock_account_1',
providerId: 'mock_provider',
authLibrary: 'MSAL'
},
displayInfo: {
displayName: 'mock_account_1@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test',
userId: 'test@email.com'
},
properties: TypeMoq.Mock.ofType<AzureAccountProperties>().object,
isStale: false
};
const mockAccountMsal2: AzureAccount = {
key: {
accountId: 'mock_account_2',
providerId: 'mock_provider',
authLibrary: 'MSAL'
},
displayInfo: {
displayName: 'mock_account_2@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test',
userId: 'test@email.com'
},
properties: TypeMoq.Mock.ofType<AzureAccountProperties>().object,
isStale: false
};
const mockAccountsMSAL = [mockAccountMsal1, mockAccountMsal2];
const mockAccounts = [mockAccount1, mockAccount2];
describe('AzureResourceTreeProvider.getChildren', function (): void {
beforeEach(() => {
@@ -101,69 +68,35 @@ describe('AzureResourceTreeProvider.getChildren', function (): void {
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
});
afterEach(function (): void {
afterEach(function(): void {
sinon.restore();
});
it('Should load accounts for ADAL', async function (): Promise<void> {
const getAllAccountsStub = sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve(mockAccountsADAL));
it('Should load accounts.', async function (): Promise<void> {
const getAllAccountsStub = sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve(mockAccounts));
const treeProvider = new AzureResourceTreeProvider(mockAppContext, 'ADAL');
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
await treeProvider.getChildren(undefined); // Load account promise
const children = await treeProvider.getChildren(undefined); // Actual accounts
should(getAllAccountsStub.calledOnce).be.true('getAllAccounts should have been called exactly once');
should(children).Array();
should(children.length).equal(mockAccountsADAL.length);
should(children.length).equal(mockAccounts.length);
for (let ix = 0; ix < mockAccountsADAL.length; ix++) {
for (let ix = 0; ix < mockAccounts.length; ix++) {
const child = children[ix];
const account = mockAccountsADAL[ix];
const account = mockAccounts[ix];
should(child).instanceof(AzureResourceAccountTreeNode);
should(child.nodePathValue).equal(`account_${account.key.accountId}`);
}
});
it('Should load accounts for MSAL', async function (): Promise<void> {
const getAllAccountsStub = sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve(mockAccountsMSAL));
const treeProvider = new AzureResourceTreeProvider(mockAppContext, 'MSAL');
await treeProvider.getChildren(undefined); // Load account promise
const children = await treeProvider.getChildren(undefined); // Actual accounts
should(getAllAccountsStub.calledOnce).be.true('getAllAccounts should have been called exactly once');
should(children).Array();
should(children.length).equal(mockAccountsMSAL.length);
for (let ix = 0; ix < mockAccountsMSAL.length; ix++) {
const child = children[ix];
const account = mockAccountsMSAL[ix];
should(child).instanceof(AzureResourceAccountTreeNode);
should(child.nodePathValue).equal(`account_${account.key.accountId}`);
}
});
it('Should handle when there is no accounts for ADAL', async function (): Promise<void> {
it('Should handle when there is no accounts.', async function (): Promise<void> {
sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve([]));
const treeProvider = new AzureResourceTreeProvider(mockAppContext, 'ADAL');
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);
should(children).Array();
should(children.length).equal(1);
should(children[0]).instanceof(AzureResourceAccountNotSignedInTreeNode);
});
it('Should handle when there is no accounts for MSAL', async function (): Promise<void> {
sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve([]));
const treeProvider = new AzureResourceTreeProvider(mockAppContext, 'MSAL');
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);

View File

@@ -27,6 +27,11 @@
"@azure/ms-rest-js" "^2.0.4"
tslib "^1.10.0"
"@azure/core-asynciterator-polyfill@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7"
integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg==
"@azure/core-auth@^1.1.4":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.3.0.tgz#0d55517cf0650aefe755669aca8a2f3724fcf536"
@@ -36,27 +41,27 @@
tslib "^2.0.0"
"@azure/core-auth@^1.3.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e"
integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==
version "1.3.2"
resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0"
integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA==
dependencies:
"@azure/abort-controller" "^1.0.0"
tslib "^2.2.0"
"@azure/core-http@^2.0.0":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-2.3.1.tgz#eed8a7d012ba8c576c557828f66af0fc4e52b23a"
integrity sha512-cur03BUwV0Tbv81bQBOLafFB02B6G++K6F2O3IMl8pSE2QlXm3cu11bfyBNlDUKi5U+xnB3GC63ae3athhkx6Q==
"@azure/core-http@^1.2.0":
version "1.2.6"
resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-1.2.6.tgz#9cd508418572d2062fd3175274219438772bdb65"
integrity sha512-odtH7UMKtekc5YQ86xg9GlVHNXR6pq2JgJ5FBo7/jbOjNGdBqcrIVrZx2bevXVJz/uUTSx6vUf62gzTXTfqYSQ==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-asynciterator-polyfill" "^1.0.0"
"@azure/core-auth" "^1.3.0"
"@azure/core-tracing" "1.0.0-preview.13"
"@azure/core-util" "^1.1.1"
"@azure/core-tracing" "1.0.0-preview.11"
"@azure/logger" "^1.0.0"
"@types/node-fetch" "^2.5.0"
"@types/tunnel" "^0.0.3"
form-data "^4.0.0"
node-fetch "^2.6.7"
"@types/tunnel" "^0.0.1"
form-data "^3.0.0"
node-fetch "^2.6.0"
process "^0.11.10"
tough-cookie "^4.0.0"
tslib "^2.2.0"
@@ -64,44 +69,39 @@
uuid "^8.3.0"
xml2js "^0.4.19"
"@azure/core-lro@^2.2.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.4.0.tgz#e3fdff797b045ee753aab25c3d2ecc0aa3e0a539"
integrity sha512-F65+rYkll1dpw3RGm8/SSiSj+/QkMeYDanzS/QKlM1dmuneVyXbO46C88V1MRHluLGdMP6qfD3vDRYALn0z0tQ==
"@azure/core-lro@^1.0.2":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-1.0.5.tgz#856a2cb6a9bec739ee9cde33a27cc28f81ac0522"
integrity sha512-0EFCFZxARrIoLWMIRt4vuqconRVIO2Iin7nFBfJiYCCbKp5eEmxutNk8uqudPmG0XFl5YqlVh68/al/vbE5OOg==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/logger" "^1.0.0"
tslib "^2.2.0"
"@azure/core-http" "^1.2.0"
"@azure/core-tracing" "1.0.0-preview.11"
events "^3.0.0"
tslib "^2.0.0"
"@azure/core-paging@^1.1.1":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.4.0.tgz#b04a73ad18149733a848c3089a5e5ed144592338"
integrity sha512-tabFtZTg8D9XqZKEfNUOGh63SuYeOxmvH4GDcOJN+R1bZWZ1FZskctgY9Pmuwzhn+0Xvq9rmimK9hsvtLkeBsw==
version "1.1.3"
resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.1.3.tgz#3587c9898a0530cacb64bab216d7318468aa5efc"
integrity sha512-his7Ah40ThEYORSpIAwuh6B8wkGwO/zG7gqVtmSE4WAJ46e36zUDXTKReUCLBDc6HmjjApQQxxcRFy5FruG79A==
dependencies:
tslib "^2.2.0"
"@azure/core-asynciterator-polyfill" "^1.0.0"
"@azure/core-tracing@1.0.0-preview.13":
version "1.0.0-preview.13"
resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644"
integrity sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==
"@azure/core-tracing@1.0.0-preview.11":
version "1.0.0-preview.11"
resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.11.tgz#bdfb2ba73cd6c39b7d6c207b9522eb98e08b4ddd"
integrity sha512-frF0pJc9HTmKncVokhBxCqipjbql02DThQ1ZJ9wLi7SDMLdPAFyDI5xZNzX5guLz+/DtPkY+SGK2li9FIXqshQ==
dependencies:
"@opentelemetry/api" "^1.0.1"
tslib "^2.2.0"
"@azure/core-util@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.1.1.tgz#8f87b3dd468795df0f0849d9f096c3e7b29452c1"
integrity sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==
dependencies:
"@azure/abort-controller" "^1.0.0"
tslib "^2.2.0"
"@opencensus/web-types" "0.0.7"
"@opentelemetry/api" "1.0.0-rc.0"
tslib "^2.0.0"
"@azure/logger@^1.0.0":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.3.tgz#6e36704aa51be7d4a1bae24731ea580836293c96"
integrity sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==
version "1.0.2"
resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.2.tgz#ad2d06478eeda7835f53def7e4566981b47d9787"
integrity sha512-YZNjNV0vL3nN2nedmcjQBcpCTo3oqceXmgiQtEm6fLpucjRZyQKAQruhCmCpRlB1iykqKJJ/Y8CDmT5rIE6IJw==
dependencies:
tslib "^2.2.0"
tslib "^2.0.0"
"@azure/ms-rest-azure-js@^2.0.1":
version "2.1.0"
@@ -127,33 +127,19 @@
uuid "^3.3.2"
xml2js "^0.4.19"
"@azure/msal-common@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-7.6.0.tgz#b52e97ef540275f72611cff57937dfa0b34cdcca"
integrity sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q==
"@azure/msal-node@^1.9.0":
version "1.14.2"
resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-1.14.2.tgz#8f236a19efa506133d6c715047393146af182e3a"
integrity sha512-t3whVhhLdZVVeDEtUPD2Wqfa8BDi3EDMnpWp8dbuRW0GhUpikBfs4AQU0Fe6P9zS87n9LpmUTLrIcPEEuzkvfA==
dependencies:
"@azure/msal-common" "^7.6.0"
jsonwebtoken "^8.5.1"
uuid "^8.3.0"
"@azure/storage-blob@^12.6.0":
version "12.12.0"
resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.12.0.tgz#25e277c885692d5adcd8c2a949789b2837a74c59"
integrity sha512-o/Mf6lkyYG/eBW4/hXB9864RxVNmAkcKHjsGR6Inlp5hupa3exjSyH2KjO3tLO//YGA+tS+17hM2bxRl9Sn16g==
version "12.6.0"
resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.6.0.tgz#9905d80e5f908a573cc65e1cb8302abc32818844"
integrity sha512-cAzsae+5ZdhugQfIT7o5SlVyF2Sc+HygZdPO41ZYdXklfGUyEt+5K4PyM5HQDc0MTVt6x7+waXcaAXT2eF9E6A==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-http" "^2.0.0"
"@azure/core-lro" "^2.2.0"
"@azure/core-http" "^1.2.0"
"@azure/core-lro" "^1.0.2"
"@azure/core-paging" "^1.1.1"
"@azure/core-tracing" "1.0.0-preview.13"
"@azure/core-tracing" "1.0.0-preview.11"
"@azure/logger" "^1.0.0"
events "^3.0.0"
tslib "^2.2.0"
tslib "^2.0.0"
"@babel/code-frame@^7.8.3":
version "7.8.3"
@@ -362,10 +348,15 @@
istanbul-reports "^3.0.0"
mocha "^7.1.1"
"@opentelemetry/api@^1.0.1":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.3.0.tgz#27c6f776ac3c1c616651e506a89f438a0ed6a055"
integrity sha512-YveTnGNsFFixTKJz09Oi4zYkiLT5af3WpZDu4aIUM7xX+2bHAkOJayFTVQd6zB8kkWPpbua4Ha6Ql00grdLlJQ==
"@opencensus/web-types@0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@opencensus/web-types/-/web-types-0.0.7.tgz#4426de1fe5aa8f624db395d2152b902874f0570a"
integrity sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g==
"@opentelemetry/api@1.0.0-rc.0":
version "1.0.0-rc.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.0.0-rc.0.tgz#0c7c3f5e1285f99cedb563d74ad1adb9822b5144"
integrity sha512-iXKByCMfrlO5S6Oh97BuM56tM2cIBB0XsL/vWF/AtJrJEKx4MC/Xdu0xDsGXMGcNWpqF7ujMsjjnp0+UHBwnDQ==
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2":
version "1.8.0"
@@ -413,20 +404,15 @@
resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.0.tgz#ca24e6ee6d0df10c003aafe26e93113b8faf0d8e"
integrity sha512-cq/NkUUy6rpWD8n7PweNQQBpw2o0cf5v6fbkUVEpOB9VzzIvyPvSEId1/goIj+MciW2v1Lw5mRimKO01XgE9EA==
"@types/lockfile@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/lockfile/-/lockfile-1.0.2.tgz#3f77e84171a2b7e3198bd5717c7547a54393baf8"
integrity sha512-jD5VbvhfMhaYN4M3qPJuhMVUg3Dfc4tvPvLEAXn6GXbs/ajDFtCQahX37GIE65ipTI3I+hEvNaXS3MYAn9Ce3Q==
"@types/mocha@^7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce"
integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==
"@types/node-fetch@^2.5.0":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==
version "2.5.11"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.11.tgz#ce22a2e65fc8999f4dbdb7ddbbcf187d755169e4"
integrity sha512-2upCKaqVZETDRb8A2VTaRymqFBEgH8u6yr96b/u3+1uQEPDRo3mJLEiPk7vdXBHRtjwkjqzFYMJXrt0Z9QsYjQ==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
@@ -436,15 +422,15 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.5.tgz#59738bf30b31aea1faa2df7f4a5f55613750cf00"
integrity sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw==
"@types/node@^12.20.55":
version "12.20.55"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
"@types/node@^12.11.7":
version "12.12.32"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.32.tgz#0ccc836d273e8a3cddf568daf22729cfa57c1925"
integrity sha512-44/reuCrwiQEsXud3I5X3sqI5jIXAmHB5xoiyKUw965olNHF3IWKjBLKK3F9LOSUZmK+oDt8jmyO637iX+hMgA==
"@types/qs@^6.9.1":
version "6.9.7"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
version "6.9.1"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.1.tgz#937fab3194766256ee09fcd40b781740758617e7"
integrity sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==
"@types/request@^2.48.1":
version "2.48.4"
@@ -473,10 +459,10 @@
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5"
integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ==
"@types/tunnel@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9"
integrity sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==
"@types/tunnel@^0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c"
integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==
dependencies:
"@types/node" "*"
@@ -588,11 +574,6 @@ browser-stdout@1.3.1:
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@@ -690,11 +671,6 @@ crypt@~0.0.1:
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
crypto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
@@ -765,13 +741,6 @@ diff@^4.0.2:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@@ -1293,42 +1262,17 @@ jsesc@^2.5.1:
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json5@^2.1.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonwebtoken@9.0.0, jsonwebtoken@^8.5.1:
version "9.0.0"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d"
integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
jws "^3.2.2"
lodash "^4.17.21"
ms "^2.1.1"
semver "^7.3.8"
minimist "^1.2.5"
just-extend@^4.0.2:
version "4.1.0"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4"
integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -1337,19 +1281,12 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
lockfile@1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609"
integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==
dependencies:
signal-exit "^3.0.2"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
lodash@^4.16.4, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4:
lodash@^4.16.4, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.4:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -1361,13 +1298,6 @@ log-symbols@3.0.0:
dependencies:
chalk "^2.4.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@@ -1487,13 +1417,6 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
msal@^1.4.16:
version "1.4.17"
resolved "https://registry.yarnpkg.com/msal/-/msal-1.4.17.tgz#b78171c0471ede506eeaabc86343f8f4e2d01634"
integrity sha512-RjHwP2cCIWQ9iUIk1SziUMb9+jj5mC4OqG2w16E5yig8jySi/TwiFvKlwcjNrPsndph0HtgCtbENnk5julf3yQ==
dependencies:
tslib "^1.9.3"
nise@^4.0.1:
version "4.0.4"
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd"
@@ -1567,7 +1490,7 @@ object.getownpropertydescriptors@^2.0.3:
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
@@ -1630,29 +1553,22 @@ postinstall-build@^5.0.1:
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
psl@^1.1.28:
psl@^1.1.28, psl@^1.1.33:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@^6.9.1:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
dependencies:
side-channel "^1.0.4"
version "6.9.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e"
integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==
readdirp@~3.2.0:
version "3.2.0"
@@ -1694,11 +1610,6 @@ rimraf@^2.6.3:
dependencies:
glob "^7.1.3"
safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@@ -1719,13 +1630,6 @@ semver@^6.0.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.8:
version "7.3.8"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
dependencies:
lru-cache "^6.0.0"
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -1784,11 +1688,6 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
signal-exit@^3.0.2:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
sinon@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d"
@@ -1874,7 +1773,7 @@ strip-bom@^4.0.0:
strip-json-comments@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
supports-color@6.0.0:
version "6.0.0"
@@ -1932,7 +1831,7 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
tslib@^1.10.0, tslib@^1.9.3:
tslib@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -1943,9 +1842,9 @@ tslib@^2.0.0:
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
tslib@^2.2.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
tunnel@0.0.6, tunnel@^0.0.6:
version "0.0.6"
@@ -2081,11 +1980,6 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@13.1.2, yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"

View File

@@ -1,7 +1,7 @@
{
"name": "azuremonitor",
"description": "%azuremonitor.description%",
"version": "0.1.10",
"version": "0.1.9",
"publisher": "Microsoft",
"aiKey": "29a207bb14f84905966a8f22524cb730-25407f35-11b6-4d4e-8114-ab9e843cb52f-7380",
"activationEvents": [
@@ -213,7 +213,7 @@
"figures": "^2.0.0",
"find-remove": "1.2.1",
"@microsoft/ads-service-downloader": "1.0.4",
"@microsoft/ads-extension-telemetry": "^1.3.2",
"@microsoft/ads-extension-telemetry": "^1.3.1",
"vscode-languageclient": "5.2.1",
"vscode-nls": "^4.0.0"
},

View File

@@ -20,10 +20,10 @@
"@microsoft/applicationinsights-shims" "^2.0.2"
"@microsoft/dynamicproto-js" "^1.1.7"
"@microsoft/ads-extension-telemetry@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@microsoft/ads-extension-telemetry/-/ads-extension-telemetry-1.3.2.tgz#d9cfb4bc7099df73e000b7bafa48bb748db924fe"
integrity sha512-TG1TE7FPp5rBA9zYPVjralZut8Bq/b5XCgm0kmkLyoQyn3c9ntmWXFuNQPOXmgbIemg5YY1/7DHKrfNcO/igkQ==
"@microsoft/ads-extension-telemetry@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@microsoft/ads-extension-telemetry/-/ads-extension-telemetry-1.3.1.tgz#fa757ee88eac91b21c3a68562da6441c2ad15c39"
integrity sha512-8Zd7RwwN7ZufMoWFmc1bwzmQc1RV7/jf/Ua33YL1+P0ZwHoWFOhf/b0lwvAVzi9TB/7oD5zA5yv7A/i2sSTn6Q==
dependencies:
"@vscode/extension-telemetry" "^0.6.2"

View File

@@ -134,7 +134,7 @@
{
"command": "bigDataClusters.command.connectController",
"title": "%command.connectController.title%",
"icon": "$(plug)"
"icon": "$(disconnect)"
},
{
"command": "bigDataClusters.command.removeController",

View File

@@ -275,9 +275,9 @@ punycode@^2.1.0:
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@~6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
request@^2.88.0:
version "2.88.0"

View File

@@ -2,7 +2,7 @@
"name": "cms",
"displayName": "%cms.displayName%",
"description": "%cms.description%",
"version": "0.9.3",
"version": "0.9.1",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",

View File

@@ -919,9 +919,11 @@ jsesc@^2.5.1:
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json5@^2.1.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
minimist "^1.2.5"
locate-path@^3.0.0:
version "3.0.0"

View File

@@ -2,7 +2,7 @@
"name": "dacpac",
"displayName": "SQL Server Dacpac",
"description": "SQL Server Dacpac for Azure Data Studio.",
"version": "1.12.0",
"version": "1.11.0",
"publisher": "Microsoft",
"preview": false,
"engines": {
@@ -92,7 +92,7 @@
}
},
"dependencies": {
"@microsoft/ads-extension-telemetry": "^1.3.2",
"@microsoft/ads-extension-telemetry": "^1.3.1",
"htmlparser2": "^3.10.1",
"vscode-nls": "^4.0.0"
},

Some files were not shown because too many files have changed in this diff Show More