From 4ecabacc7642431bc7d1529e46e07354cf6a870f Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 21 Nov 2017 14:16:39 -0800 Subject: [PATCH] Create an initial implementation of the secret manager for VS (#355) --- DotNetTools.sln | 60 ++++- build/VSIX.props | 9 + build/VSIX.targets | 86 +++++++ build/dependencies.props | 13 +- build/repo.targets | 10 + build/sources.props | 3 +- korebuild-lock.txt | 4 +- korebuild.json | 14 +- .../Key.snk | Bin 0 -> 596 bytes ...lStudio.SecretManager.TestExtension.csproj | 149 ++++++++++++ .../NotifyPropertyChanged.cs | 18 ++ .../ProjectViewModel.cs | 20 ++ .../Properties/AssemblyInfo.cs | 33 +++ .../RelayCommand.cs | 44 ++++ .../Resources/SecretManagerTestCommand.png | Bin 0 -> 1172 bytes .../Resources/SecretManagerTestPackage.ico | Bin 0 -> 428446 bytes .../SecretManagerTestCommand.cs | 100 ++++++++ .../SecretManagerTestControl.xaml | 87 +++++++ .../SecretManagerTestControl.xaml.cs | 37 +++ .../SecretManagerTestPackage.cs | 27 +++ .../SecretManagerTestPackage.vsct | 83 +++++++ .../SecretManagerTestWindow.cs | 31 +++ .../SecretManagerViewModel.cs | 214 ++++++++++++++++++ .../VSPackage.resx | 140 ++++++++++++ .../app.config | 27 +++ .../source.extension.vsixmanifest | 21 ++ ...icrosoft.VisualStudio.SecretManager.csproj | 106 +++++++++ .../ProjectLocalSecretsManager.cs | 148 ++++++++++++ .../Properties/AssemblyInfo.cs | 16 ++ .../Resources.Designer.cs | 140 ++++++++++++ .../Resources.resx | 146 ++++++++++++ .../SecretManagerFactory.cs | 37 +++ .../SecretStore.cs | 169 ++++++++++++++ .../Sources/ConfigurationPath.cs | 79 +++++++ .../Sources/JsonConfigurationFileParser.cs | 120 ++++++++++ .../Sources/PathHelper.cs | 57 +++++ .../source.extension.vsixmanifest | 21 ++ version.props | 3 + 38 files changed, 2260 insertions(+), 12 deletions(-) create mode 100644 build/VSIX.props create mode 100644 build/VSIX.targets create mode 100644 build/repo.targets create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Key.snk create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Microsoft.VisualStudio.SecretManager.TestExtension.csproj create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/NotifyPropertyChanged.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/ProjectViewModel.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Properties/AssemblyInfo.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/RelayCommand.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Resources/SecretManagerTestCommand.png create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Resources/SecretManagerTestPackage.ico create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestCommand.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.vsct create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestWindow.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerViewModel.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/VSPackage.resx create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/app.config create mode 100644 tooling/Microsoft.VisualStudio.SecretManager.TestExtension/source.extension.vsixmanifest create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/Microsoft.VisualStudio.SecretManager.csproj create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/ProjectLocalSecretsManager.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/Properties/AssemblyInfo.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/Resources.Designer.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/Resources.resx create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/SecretManagerFactory.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/SecretStore.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/Sources/ConfigurationPath.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/Sources/JsonConfigurationFileParser.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/Sources/PathHelper.cs create mode 100644 tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest diff --git a/DotNetTools.sln b/DotNetTools.sln index 95b7b67009..302f0e2f5d 100644 --- a/DotNetTools.sln +++ b/DotNetTools.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27110.0 +VisualStudioVersion = 15.0.27120.0 MinimumVisualStudioVersion = 15.0.26730.03 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{66517987-2A5A-4330-B130-207039378FD4}" ProjectSection(SolutionItems) = preProject @@ -20,12 +20,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution build.ps1 = build.ps1 build.sh = build.sh CONTRIBUTING.md = CONTRIBUTING.md + build\dependencies.props = build\dependencies.props Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets LICENSE.txt = LICENSE.txt NuGet.config = NuGet.config NuGetPackageVerifier.json = NuGetPackageVerifier.json README.md = README.md + build\sources.props = build\sources.props version.props = version.props EndProjectSection EndProject @@ -46,46 +48,98 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Cachin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DeveloperCertificates.Tools", "src\Microsoft.AspNetCore.DeveloperCertificates.Tools\Microsoft.AspNetCore.DeveloperCertificates.Tools.csproj", "{4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DeveloperCertificates.XPlat", "src\Microsoft.AspNetCore.DeveloperCertificates.XPlat\Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj", "{96E71881-1465-44F5-B4B7-DF9B370FFD02}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.SecretManager", "tooling\Microsoft.VisualStudio.SecretManager\Microsoft.VisualStudio.SecretManager.csproj", "{5E117F2E-7152-447F-BF47-59F759EEF3A7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tooling", "tooling", "{62826851-7D74-4F1E-B7D1-12553B789CD8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.SecretManager.TestExtension", "tooling\Microsoft.VisualStudio.SecretManager.TestExtension\Microsoft.VisualStudio.SecretManager.TestExtension.csproj", "{965F8820-F809-4081-9090-1AEC903F291B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DeveloperCertificates.XPlat", "src\Microsoft.AspNetCore.DeveloperCertificates.XPlat\Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj", "{96E71881-1465-44F5-B4B7-DF9B370FFD02}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + DebugNoVSIX|Any CPU = DebugNoVSIX|Any CPU Release|Any CPU = Release|Any CPU + ReleaseNoVSIX|Any CPU = ReleaseNoVSIX|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Release|Any CPU.Build.0 = Release|Any CPU + {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16BADE2F-1184-4518-8A70-B68A19D0805B}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {16BADE2F-1184-4518-8A70-B68A19D0805B}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.Release|Any CPU.ActiveCfg = Release|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.Release|Any CPU.Build.0 = Release|Any CPU + {16BADE2F-1184-4518-8A70-B68A19D0805B}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {16BADE2F-1184-4518-8A70-B68A19D0805B}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Release|Any CPU.Build.0 = Release|Any CPU + {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B331122-83B1-4F08-A119-DC846959844C}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {7B331122-83B1-4F08-A119-DC846959844C}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.Release|Any CPU.Build.0 = Release|Any CPU + {7B331122-83B1-4F08-A119-DC846959844C}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {7B331122-83B1-4F08-A119-DC846959844C}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.Release|Any CPU.Build.0 = Release|Any CPU + {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Release|Any CPU.Build.0 = Release|Any CPU + {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Release|Any CPU.Build.0 = Release|Any CPU + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU + {5E117F2E-7152-447F-BF47-59F759EEF3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E117F2E-7152-447F-BF47-59F759EEF3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E117F2E-7152-447F-BF47-59F759EEF3A7}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {5E117F2E-7152-447F-BF47-59F759EEF3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E117F2E-7152-447F-BF47-59F759EEF3A7}.Release|Any CPU.Build.0 = Release|Any CPU + {5E117F2E-7152-447F-BF47-59F759EEF3A7}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {965F8820-F809-4081-9090-1AEC903F291B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {965F8820-F809-4081-9090-1AEC903F291B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {965F8820-F809-4081-9090-1AEC903F291B}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {965F8820-F809-4081-9090-1AEC903F291B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {965F8820-F809-4081-9090-1AEC903F291B}.Release|Any CPU.Build.0 = Release|Any CPU + {965F8820-F809-4081-9090-1AEC903F291B}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96E71881-1465-44F5-B4B7-DF9B370FFD02}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {96E71881-1465-44F5-B4B7-DF9B370FFD02}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.Release|Any CPU.ActiveCfg = Release|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.Release|Any CPU.Build.0 = Release|Any CPU + {96E71881-1465-44F5-B4B7-DF9B370FFD02}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {96E71881-1465-44F5-B4B7-DF9B370FFD02}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -98,6 +152,8 @@ Global {8A2E6961-6B12-4A8E-8215-3E7301D52EAC} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} {53F3B53D-303A-4DAA-9C38-4F55195FA5B9} = {66517987-2A5A-4330-B130-207039378FD4} {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8} = {66517987-2A5A-4330-B130-207039378FD4} + {5E117F2E-7152-447F-BF47-59F759EEF3A7} = {62826851-7D74-4F1E-B7D1-12553B789CD8} + {965F8820-F809-4081-9090-1AEC903F291B} = {62826851-7D74-4F1E-B7D1-12553B789CD8} {96E71881-1465-44F5-B4B7-DF9B370FFD02} = {66517987-2A5A-4330-B130-207039378FD4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/build/VSIX.props b/build/VSIX.props new file mode 100644 index 0000000000..9640955a99 --- /dev/null +++ b/build/VSIX.props @@ -0,0 +1,9 @@ + + + + + + diff --git a/build/VSIX.targets b/build/VSIX.targets new file mode 100644 index 0000000000..e679e98e42 --- /dev/null +++ b/build/VSIX.targets @@ -0,0 +1,86 @@ + + + true + $(RestoreDependsOn);RestoreVSIX + $(PackageDependsOn);PackageVSIX + Microsoft.VisualStudio.SecretManager + $(RepositoryRoot)tooling\$(VSIXName)\$(VSIXName).csproj + $(BuildDir)$(VSIXName).vsix + $(ArtifactsDir)msbuild\ + + + + + + + + + $(MSBuildArtifactsDir)vsix-restore.rsp + + + + + + + + + + + + + + + + + + + + + + $(MSBuildArtifactsDir)vsix.log + $(MSBuildArtifactsDir)vsix-build.rsp + + + + + + + + + + + + + + + diff --git a/build/dependencies.props b/build/dependencies.props index 72cc8468e4..87f9d17fce 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,17 +3,18 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.1.0-preview1-15549 - 2.1.0-preview1-27595 - 2.1.0-preview1-27595 - 2.1.0-preview1-27595 - 2.1.0-preview1-27595 - 2.1.0-preview1-27595 + 2.1.0-preview1-15573 + 2.1.0-preview1-27644 + 2.1.0-preview1-27644 + 2.1.0-preview1-27644 + 2.1.0-preview1-27644 + 2.1.0-preview1-27644 2.0.0 2.1.0-preview1-25907-02 15.3.0 4.4.0 4.4.0 + 9.0.1 2.3.0 2.3.0 diff --git a/build/repo.targets b/build/repo.targets new file mode 100644 index 0000000000..1183428580 --- /dev/null +++ b/build/repo.targets @@ -0,0 +1,10 @@ + + + + + + + Configuration=$(Configuration)NoVSIX + + + diff --git a/build/sources.props b/build/sources.props index c03f3ddb60..77203a6622 100644 --- a/build/sources.props +++ b/build/sources.props @@ -1,4 +1,4 @@ - + @@ -7,6 +7,7 @@ $(RestoreSources); https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; + https://vside.myget.org/F/vssdk/api/v3/index.json; $(RestoreSources); diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 45463cc71e..e255811551 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-preview1-15549 -commithash:f570e08585fec510dd60cd4bfe8795388b757a95 +version:2.1.0-preview1-15573 +commithash:82cb53a8578610baf96ef33142797c68bae68c81 diff --git a/korebuild.json b/korebuild.json index bd5d51a51b..0a2e713c08 100644 --- a/korebuild.json +++ b/korebuild.json @@ -1,4 +1,16 @@ { "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", - "channel": "dev" + "channel": "dev", + "toolsets": { + "visualstudio": { + "required": [ + "windows" + ], + "includePrerelease": true, + "minVersion": "15.4", + "requiredWorkloads": [ + "Microsoft.VisualStudio.Component.VSSDK" + ] + } + } } diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Key.snk b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Key.snk new file mode 100644 index 0000000000000000000000000000000000000000..2181a1627bf841f91b220de8258de4591629d169 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50098eLAD9Hsv)-@LYruqYXoq*$CbcyqhF9_ zJNINE4KwxCc=zqW)Jwkn`UV}^Y(IMBUm{xgCv_L2E)Wkc$U2dxKX7}>;DDK-YyBR0 zv=7PjBU$b1ek&UiLnaZH0)Abxi^KRk>d3t73-A?lyM6?EoG?NRqitAMj2X3Z%u&|E zrwKH+r+S(9p=O{cvKd2`xM2&?bvfd7`75k&O%+r%^Y9Y((vA%FwT9CHDc1YIm z?bKQ;eA=^ehw|6kYCr#+^U@%^KVmW;jWR>>>52Qp(#(u)JQMZntn)LxTf^YaEopg% zSwCw!kXHvf`Yi5*iHA|n^xZiW`Vs!)erHg)_=wmuq`=R*E1(2vKuV7Aqfi;Z_JV}) zocdzFRoQ|X~Wz-;7of>%3oHo1{Tr@ar%xdF&hy6Cdrd z#Vr5nWd2vx^_sX>r<(q0{qfHrRw{Oq(R~1V%Y)_)X+3Kn8cf$)`f?|$rnz~wob0mw z!!?6FA|Ksdn;(hg$rFCTIa5M7;@1K!FJEr&5h+UyX% i)<83a5xbDB8Z + + + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + False + False + False + True + True + ..\..\build\Key.snk + True + + + + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {965F8820-F809-4081-9090-1AEC903F291B} + Library + Properties + Microsoft.VisualStudio.SecretManager.TestExtension + Microsoft.VisualStudio.SecretManager.TestExtension + v4.6.1 + true + true + true + true + true + false + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + SecretManagerTestControl.xaml + + + + + + + Designer + + + + + + + + + Designer + MSBuild:Compile + + + + + + + + False + + + + + + + + + + + + + + + + + + + 7.10.6071 + + + + + + 11.0.61030 + + + 12.0.30110 + + + 15.0.26606 + + + 8.0.50727 + + + 9.0.30729 + + + + 4.4.0 + + + + + + + Menus.ctmenu + + + + + true + VSPackage + + + + + + diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/NotifyPropertyChanged.cs b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/NotifyPropertyChanged.cs new file mode 100644 index 0000000000..720593d469 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/NotifyPropertyChanged.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Microsoft.VisualStudio.SecretManager.TestExtension +{ + public abstract class NotifyPropertyChanged : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/ProjectViewModel.cs b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/ProjectViewModel.cs new file mode 100644 index 0000000000..f667db5267 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/ProjectViewModel.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using Microsoft.VisualStudio.ProjectSystem; + +namespace Microsoft.VisualStudio.SecretManager.TestExtension +{ + public class ProjectViewModel : NotifyPropertyChanged + { + public ProjectViewModel(UnconfiguredProject project) + { + Project = project; + } + + internal UnconfiguredProject Project { get; } + + public string ProjectName => Path.GetFileNameWithoutExtension(Project.FullPath); + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Properties/AssemblyInfo.cs b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..9999a9ba7e --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.VisualStudio.SecretManager.TestExtension")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.VisualStudio.SecretManager.TestExtension")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/RelayCommand.cs b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/RelayCommand.cs new file mode 100644 index 0000000000..e12b2bd622 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/RelayCommand.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Windows.Input; + +namespace Microsoft.VisualStudio.SecretManager.TestExtension +{ + public class RelayCommand : ICommand + { + readonly Action _execute = null; + readonly Predicate _canExecute = null; + + public RelayCommand(Action execute) + : this(execute, null) + { + } + + public RelayCommand(Action execute, Predicate canExecute) + { + if (execute == null) + throw new ArgumentNullException("execute"); + + _execute = execute; + _canExecute = canExecute; + } + + public bool CanExecute(object parameter) + { + return _canExecute == null ? true : _canExecute((T)parameter); + } + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public void Execute(object parameter) + { + _execute((T)parameter); + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Resources/SecretManagerTestCommand.png b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Resources/SecretManagerTestCommand.png new file mode 100644 index 0000000000000000000000000000000000000000..b22d975cbf00eda60e37587614e0677d0bfb525d GIT binary patch literal 1172 zcmV;F1Z(?=P)AHIP00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGqB>(^xB>_oNB=7(L1Sv^GK~z{r&6rO} zR8bVhMO+m@n`n^>S1nuxLD3=*TeOfsmlen?+6F={#2~abZOoueAZTJ(h!8>v{t3;> za?E)|5DA$%%#4KTQ&4A9oX&T-XSj3Ub>17D7(e)!JLjHr)!%vdzW1y+tlHh(J<9H6 zC)uRE&VI~3rPB^9A}Xs7r5JEE`&%NBp!)jyJP!>G(f0N>8gX8yD`m6Unw_1U4t9(0 z`^R-SPl)q;AA1VsnwSfU>5DfL{NbJ}g%6z=Y!y2`Ha4b!&wZSu_228{Z}{|MX*t&j zZo(SpokR_TtlRpvN}qckDl^<=y9pd?++5B0pB~($m6=KGCJ#9-DB`AjX=;A-hyG_1 zN-BV`t*0W3!CXzHQVMc9m8MO9D`43PoZE#OT-5;IfBKm6Z09Y^y}%qG^SC?X#c8;% zmPW2zAT)v-cn0$>D6qi4bZ1++_WElMyK_xfRaTMM6*J>-K>W+fd8@3$k!07x#Wsa& zh6e@)sHdmL2_OEsyh5>94C^nU#%=(NtDNIFl9sXrroHe%Qf zR#RHk57Kf$F@4Ym6oL;2B#=yiGL}!B8j$3hx#l>m9^&~A$eWpY>x2()toyQ7R+pg( zF;^30j9tL+`*eCvE-0oC(tt4duu~>1a~uXx^37ax999qU{L_hv32JL=bHaz`K$0hxe2L3grw7Dy*V%vN z@pF89A^<;=&QMcR6D=$(V6A3ds#K@}eDzQ1v^=?>zyKfhChP(p#i(@(kW64QmQS4) zkXbK z3DUMSq1eYz6R-(fVy2zqKPCsu9LuLp3rKRrQR_M8gpZvtIXRj8-rL(t$z&3146={t za`hC1ryCPE8iCz_=YDAha^&$-WCO9ipLYNow%GtP4m$-%g{;#8a^T^2W)0ic`s=u3>eS(!53jy;Ap}TwK(!j{X4>h0jPj$#%z}oK71%7O)%j{Z2*9CjSurx-l4;Lit}qY m*JGpEo(&`nZYkjtQN=$$-bTw6(Xv|r0000RpVtU*8~r_ZFd4k7cV&(R$~0mE^Iv%p}VGO8-7vf^b_R#j%ytLpdl?(59T$c&8m z;`{Q|D|Yu3#iqDf6vb`VUM>Fc--_b@xT|l!t*@^Y#ee+oMR9ssUH{vaqWB-L-R#@d z^*5WM_=o@L7PzlX@mcZBzuOf5`+w2;>iQqQDvE#p^Tw^OuRkk_=l{HM>#OTO{H`ee z{l7XV<@(d&o71bsfBk=(;{JWIyXyx<@%F~G+TUsMkAG7X|Mx##tNpoa=jR{(^R?RF zY4L}vMe!g0%eCU0%3)Escqy;)Yg;w@-<^SM+ilgj@2=}!+v5EE+!=P||BKy@dhX@^ zM?>_@`*PQ_e0}ux^t-cc?l!^xKI&~B_|Jm?_}6mI@6Cfg^bh{|tZVz=UxmPTMqi9? zjDJSke~0}m1b~05dB4Z}0RLI$w$J#4Fk>6H{v7J_xNQA-Yy(8M+kIUAd$2Q>KV(#}CpAcqj~A*v73thx$A&TYnzg zfPag*-(!42n6Zspe-8C|T(S-Mz(SfY3FNy9*<%Fw&s41 z@d;tZHg5en)aP;8`t#TZ{C(8hKI0R@jBVWdbEwbbvi0Y&4fwa1`#r`dgc;ko_2*EZ z$7So!V;k`IQE&Thrj4{dsHy{w?NykMRj%#x`#KIn?KI+4}R?2K;^0+dktH!i;U)`g5qyxb^2ypT}kE&tn_#_fc>Aj86zNwsGsvp+1kx)}O~V;NN2I_ZXiLW^Ciu zpF@2fm#sgKZNT40z3nqTAX^?6*j{yeq; ze;@U>&-nP5j>fKMmdfyYJ5Aj@|1?`~Nz8`|rMc(cMs7P1=9V_WH-+GLk9#4=#Qc zXDSE2S$VEq1Lv#0rjbl&CY{YMtRidLt<*!@3M4#$2!^HT0~ zNA5qN_*JMH1xD^aHt%+RC+qfN-2Rh_U&WwMVATGf+H}VE&f28C-kAL-7rzQNNP#i? zkI%Wg!b`j9YWvfj$gO5p*-0pV6^Ece-`3@gPIIL-IHw{bNf(BIE?e}ug5kM587yKBFHC$=9@{OUgY)BbjQBSR?A zjd6(A;rq)^1c3^4w;xhfzQx+hHZ<^}>whl*fquxv-dK(5kVksH z?FAMZ=%)b3HO6Ceha1{%f2@v{cj{Dt2dj2i@momu_X>{a6cFeKSMLut34PkP1xLDB zwiFQC?}k;sePbJlmta8qYrx&O+|{sz+GW9P<)DDyt*$B1`e}MYGCFH!4~lnx`#J!L ze&se^gHiiS?5j;m3WWEM=hUVje-8>J@77loR04?9pp#Jqt={b&3;a~Pey`UvZ(=`K zok+b^-U*~Y(5C~>k?Tsbd)Kk#%P^DxdTKCq|3TZ2ufbV&e+{Vt*mD^a2o%pEEd?ks zi(_t&Uf!HqC4jJNcEzn%@U}Z@dlvya6Ky~TXJSf70VA+y7!(NhYcOE{vB{S)C;{x% zK&HR1z)9N=tw8|VZjk~L%8(i8o>qaNQ-eMD#e4SD+T(uB4L!tc>u;*CXteTyu!! z>aMr&Xm?KVN))m?=d5(w9rLeXCxCoJx_#gSe87*cCj2Yt1NwkId_{Eo&7Kp$OA_*c-!WzffN>({jHZzwtX zYKOYL%DVflJKA2|t0w%~j&yr>EBi%vw7t4s+0WaNZttYBcL)8e(*9R}q}xBE32Jyn zahL2p|Dsj?YuI_$r+-xjxb;W6eL}b=yrMYU>FAQHd%fY6|73d6-LiY-5Z-o2x>qj1 z?tr}N56=bIgD&~H8?*PqLjax2ko;>`fZh%6#_{jlpsa7t2F`u&24`f~H>mloZg96; zt~2PYvTGaE?p26t_nizj+ZJAS?`PoI8~6SOC+E%j#=XCRXK&qm8(hEO^`3iggIkSS zJ@?)Qt=)IuZqV?t7eaNlgecHzF+fb}Td^9Hqj&wagt z)AN=9+;d-V;MSd!ecyd$sOxa;bxBR<43O&w=DK=#Wt z?xX4Fm1`V$(!EF*&FMHhG@BnqFMr^<(b1=Ni!(Qi$2-5i@S+`WXH%~lcXF(qpM9$A zZVTtgojld$cQ2Zb(>1BLzUJS7a2r$$DuY{II;3HDHk8K!jhvM~OI^la4p%?m_G|fD zo#hWwm(f=yx77Khy=ouz3OZV<1EzA#!=*3UsqsoM6=C;rv%N*_Zzt#XsNMbjKT(P3edg4??z?A0%^RDZYQjLwhp^r+677tVpJms?NYs&j`1{z%vLrQ-t?mdfh7)z>Y$ zcD~)$g=6a+uXCq{t)nvSRo;?qw!7WG{BcJ-zwKV9?!`Rcx_Q(Xj_3Przn>WSVw{MGg@7@*f-@h*|E-s1>A3hX6|Mao=<(FUBGg=@31Xh4R zdA=!e-nndFN}k&AWlR5#mZ&n0{4W^-N}aXgOP2i|@L#KRcpUY=WC(=Itd3r??4f_P z(qRMYf5{LCmsuUXWZ6UiYNf*l)c=wp5H7PiDzo3L>`TU%*D4(zm-umgWSgsA>^GM`89(YD zuCq2O@#Ff)HdnpaZ!Ui_e$-#9ba-6i$MunIu6nWGT>fPIsDHT5+Ni{j>m%D-^GJe!wt8{o=;>Y!oZLWH;-(3D={HTAp&f2KNkLx4bT=inVx%|oa zQGcz{;ccxI@`IGUZ{^2@nqY^)^k8E?*i~Z*EC*w!`wMvJ_C4O8V+2*Pj z`_1J~#*g}k>#U7R{J1`{%~dbUl>L0GNHY)Ms z`p7m{z1VLqe=>g5U#oO@T;j*|k!`MevEN+&Wc;XqxX#+B#E2bflMQk@2JcN}-iuiQh7-tVbo|NBxyTE5i~$4e4C}Uc>%# z`IGTm)jwCf$iG4$7oMoUKtR#CHY`@F|NfDyUf@6YPYC4VljJYXe@*G(afu)6Q?|M4 z#eQ@7lkube;W}%h5GJe!w zt8{o=;>Y!oZLWH;-(3D={HTAp&f2KNkLx4bT=inVx%|oaQGcz{;ccxI@ z`IGUZ{^2@nqY^)^k8E?*i~Z*EC*w!`wMvJ_C4O8V+2*Pj`_1J~#*g}k>#U7R{J1`{ z%~dbUl>L0GNHY)Ms`p7m{z1VLqe=>g5U#oO@ zT;j*|k!`MevEN+&Wc;XqxX#+B#EhwH43O8mG!vdvX5 z_M6L}j34#aDjgn|_;G#K(#rU)>c3PxkiS46OmlTqtX6;i9ra%(0LE^W} zD(g|n_)-60nbkpwpN{l0Ei!)8UoCaeAn{vfmG!7({HTAh%<7=TPe*#078yV4ua-J! zkoYaL%6e2Xe$+o$W_3{Frz5>gi;N%jS4$lK+QC47?5*2W!~aXqd+)Kb+k<`-kZDllPI8O8aBkU z<{5`5{sany(^N+}yJ&lu;*Y0*nrYY&&AXa#xZ;ncKse3#XfF`S#Xm`XP!Rc07|$;< zqc8mo#lLx@s$;_U{ttzb^djcI?C&HOf3vxE=g#jEQm>ZUQJ^pV48`AExpn8xt>pYS z8ygDrWuK$?b=;q40KkF*ed*^Y{$_JhZgD&H2rTi-O@Y4b^Avxxxmj(JegqN%c`4AB zexBl&hhMc#`Vp9!0)5%%Dt>wRRU73v0&`NJFa2D_?;P4Wj=*db=*vD^@%iwpHp_Da z=Al4e`uU3A9Ddbyd5*vg6zI!7WAVMiui7!w5tvGWzVvez-#h%OEpr`#NfhYIK5Owi zhhMd8wj*#l1^Uv@TKw+eS8bc`2%JcPzU=SZx&38Eg^I(k+BxG9=uVd}`_tL~X^_Z# zXTJ4$mK${{kNeoW|CJ5klP}!I%Gg);gMVyxZNGW_O3ALoZ}hQu|0~ZCc;))g9~AKr z*bhb<$)W@0vs#8DFv@#BuNDA*G}=$rLG`hBf1lJNa1igkZQJ~d!W$mYP18aBv3Gx; zlp}CB@9Ta2?92XsJUt@`ItU(q)mD>_z)`$+d+jA(4fG_uZ;TO*Tj&}qep7%)*v^oeKewAy# z^mU()z=iNW34%%o;lob@pgaPX#QP)!NC)A=Ps{(#=gZ@L5(KvcBySz=8$n6TvXFuTRl4ipG+U1Ni zLH2h-0r#(Htf8fg8fzl$>4XCAnckQ}>lQfH#H)0|AuE%c;qamvQ=7r? zngx$Gk?(c1p!X&=1KNs(k2mpmI(pEZY0ZT`fBBXs39q9^?M-UB`s|fhnoR0WU)W>j z_W2oZ+wFGxQ$A7bajZX2GlA@a~y*jyBqb@!WQzgJE8RkPo%>=&B- zdSyS?>?f67GuHwBRkxp!12w#&xJ&kr`ubaBKiBPLfLq;82=|0n9^fjv3GdqNP!*c=lpi7?b#_YZD5J2aB0lEV8Zg4k_f8PdWeS0==?t3>l zBfGvq&2M#syXA77L1&d++n{!@LR7o&WU$$`aI7F;J?R)O)4V<301mK?gdIPuaob3DVD?@D$xUV*F=gEaz9&lf6P}@W9%MILl zK73d_dGe%q`t)h>?Af#8`Sa(+%a<>U*RNj}Z{EBq-oAZXynFYqc>n%=adB}`eE9I8 z`1z-g#V^17QvCX>D&=YedPpFY1seDLkEFXy{!_x0J{ z-zB{AS!HqGJLf-*-n=Tk()HDw^$IzE-+8s1x9HCAI=_C+->TYz=YLK6b)8@N>Z?vB z)fQiuE6V7;@ZGsTqv{-VN4_Yhxdo;7+qb{^%=@dASIOvIpM3UJIsJCEL%rli^H&~k zkoC)YgDcm|%iHzjGNyXP?Jvq-SifF5D^vbm6-Z~czr@9L#clqz^YGOb5l1=C`^Kp_ z_eJ5p)rG2p_U5%+j*CXBp!}%Yd0vl}lpdRoXDo{HlgnMSV{y136t5S>IkhK6v8C31 zp8QDdt)eKO7u<6X(zb5cJ#5>3+-z@A``gL+J!*G<|4(%LNuN_s-Rr)4HJ?+jx(oga zd`^Az=+WM1)mN`xsh?HxIhDag3k24OK>azjKJWugl~TY^53#-bmdaOrCvp2Wk96Bn z_Uf*f95lVo`et;Y_HOFsRVV&b`_l7|#h03Y?|P!Ef1k;>uFPL&7Pp1f-Ed=X7B~O> zwbDgt?<~H-cYm7=xo{KQ_xg6vg?R1u?dDGvl{b9kfo`Cx+fKT}`-(C-`J8Svn;W+B z_RQU8mQj6n<$8HT*I3Ia$|FKB@P5LhV!)qTnh?~4-g z{^;F}>iWws-RVo;23|Ms8TEX5o#^kz4SJT~9s}H{{L_d2YwpAT@4l$6Z@B*F?r%3< zt*(n3H?Cc~dGp$}YPw75CiQOIxJlWayU&7CUKj4Ocj)>b z2&*CM@9y6{Kx%2_`hcUAA9W3KkomJT?5CQKlBg%A2a`tJ$L9I`agCJ9CQBAKlFdh z{6F^Gp?~QA*fns>`9uHE|1tCb*mH;eq5osoz%l0!{X_r9%>QH09r}m|JZYf{-OV4*T6C7 z5B)>`$ISm@&mH=Q{*PS)$DBX(5B(oA|BpR)=zrPz|M{nnyEVTKt*

N3mPu2mM3; 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 + /// Command ID. + /// + public const int CommandId = 0x0100; + + ///

+ /// Command menu group (command set GUID). + /// + public static readonly Guid CommandSet = new Guid("e415a3f4-f2a8-4834-b7f7-f89844b2505c"); + + /// + /// VS Package that provides this command, not null. + /// + private readonly Package package; + + /// + /// Initializes a new instance of the class. + /// Adds our command handlers for menu (commands must exist in the command table file) + /// + /// Owner package, not null. + private SecretManagerTestCommand(Package package) + { + if (package == null) + { + throw new ArgumentNullException("package"); + } + + this.package = package; + + OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService; + if (commandService != null) + { + var menuCommandID = new CommandID(CommandSet, CommandId); + var menuItem = new MenuCommand(this.ShowToolWindow, menuCommandID); + commandService.AddCommand(menuItem); + } + } + + /// + /// Gets the instance of the command. + /// + public static SecretManagerTestCommand Instance + { + get; + private set; + } + + /// + /// Gets the service provider from the owner package. + /// + private IServiceProvider ServiceProvider + { + get + { + return this.package; + } + } + + /// + /// Initializes the singleton instance of the command. + /// + /// Owner package, not null. + public static void Initialize(Package package) + { + Instance = new SecretManagerTestCommand(package); + } + + /// + /// Shows the tool window when the menu item is clicked. + /// + /// The event sender. + /// The event args. + private void ShowToolWindow(object sender, EventArgs e) + { + // Get the instance number 0 of this tool window. This window is single instance so this instance + // is actually the only one. + // The last flag is set to true so that if the tool window does not exists it will be created. + ToolWindowPane window = this.package.FindToolWindow(typeof(SecretManagerTestWindow), 0, true); + if ((null == window) || (null == window.Frame)) + { + throw new NotSupportedException("Cannot create tool window"); + } + + IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; + Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml new file mode 100644 index 0000000000..201144b7e1 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml.cs b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml.cs new file mode 100644 index 0000000000..b39c208ddf --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.SecretManager.TestExtension +{ + using System.Diagnostics.CodeAnalysis; + using System.Windows; + using System.Windows.Controls; + + /// + /// Interaction logic for SecretManagerTestControl. + /// + public partial class SecretManagerTestControl : UserControl + { + /// + /// Initializes a new instance of the class. + /// + public SecretManagerTestControl() + { + this.InitializeComponent(); + } + + /// + /// Handles click on the button by displaying a message box. + /// + /// The event sender. + /// The event args. + [SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Justification = "Sample code")] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Default event handler naming pattern")] + private void button1_Click(object sender, RoutedEventArgs e) + { + MessageBox.Show( + string.Format(System.Globalization.CultureInfo.CurrentUICulture, "Invoked '{0}'", this.ToString()), + "SecretManagerTest"); + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.cs b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.cs new file mode 100644 index 0000000000..ada842d9cb --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.SecretManager.TestExtension +{ + [PackageRegistration(UseManagedResourcesOnly = true)] + [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About + [ProvideMenuResource("Menus.ctmenu", 1)] + [ProvideToolWindow(typeof(SecretManagerTestWindow))] + [Guid(SecretManagerTestPackage.PackageGuidString)] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] + public sealed class SecretManagerTestPackage : Package + { + public const string PackageGuidString = "7b771e3e-f599-4fde-95a9-e35019e705f7"; + + protected override void Initialize() + { + SecretManagerTestCommand.Initialize(this); + base.Initialize(); + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.vsct b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.vsct new file mode 100644 index 0000000000..554ae60164 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.vsct @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestWindow.cs b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestWindow.cs new file mode 100644 index 0000000000..240a8fb935 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestWindow.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.SecretManager.TestExtension +{ + [Guid("6afffd63-17b6-4ef2-b515-fee22d767631")] + public class SecretManagerTestWindow : ToolWindowPane + { + public SecretManagerTestWindow() + : base(null) + { + this.Caption = "SecretManager Test Window"; + this.Content = new SecretManagerTestControl(); + } + + protected override void Initialize() + { + base.Initialize(); + + var component = (IComponentModel)GetService(typeof(SComponentModel)); + var projectService = component.GetService().GetProjectService(); + ((SecretManagerTestControl)Content).DataContext = new SecretManagerViewModel(projectService); + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerViewModel.cs b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerViewModel.cs new file mode 100644 index 0000000000..3a4d5cbf61 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerViewModel.cs @@ -0,0 +1,214 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Windows; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.VisualStudio.SecretManager.TestExtension +{ + public class SecretManagerViewModel : NotifyPropertyChanged + { + private readonly IProjectService _projectService; + private readonly Random _rand; + private string _error; + private bool _isLoaded; + private ProjectViewModel _selectedProject; + + public SecretManagerViewModel(IProjectService projectService) + { + _projectService = projectService; + + RefreshCommand = new RelayCommand(Refresh, RefreshIsEnabled); + AddCommand = new RelayCommand(Add, IsProjectLoaded); + SaveCommand = new RelayCommand(Save, IsProjectLoaded); + Refresh(null); + _rand = new Random(); + } + + public RelayCommand RefreshCommand { get; } + + public RelayCommand AddCommand { get; } + public RelayCommand SaveCommand { get; } + + public ObservableCollection Projects { get; } = new ObservableCollection(); + + public ProjectViewModel SelectedProject + { + get => _selectedProject; + set + { + if (value == _selectedProject) + { + return; + } + + _selectedProject = value; + OnSelectedProjectChanged(); + OnPropertyChanged(); + } + } + + public bool IsLoaded + { + get => _isLoaded; + set + { + if (value == _isLoaded) + { + return; + } + + _isLoaded = value; + OnPropertyChanged(); + } + } + + public string Error + { + get => _error; + set + { + if (value == _error) + { + return; + } + + _error = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ErrorVisibility)); + } + } + + public Visibility ErrorVisibility => Error == null ? Visibility.Collapsed : Visibility.Visible; + + public ObservableCollection> Secrets { get; } = new ObservableCollection>(); + + private bool RefreshIsEnabled(object obj) => IsLoaded || SelectedProject == null; + + private void Refresh(object obj) + { + Projects.Clear(); + + foreach (var project in _projectService.LoadedUnconfiguredProjects) + { + Projects.Add(new ProjectViewModel(project)); + } + } + + private bool IsProjectLoaded(object obj) => IsLoaded && SelectedProject != null; + + private void Add(object obj) + { + Secrets.Add(new KeyValuePair("NewKey" + _rand.Next(10_000), "My new totally random and secret test value")); + } + + private async void Save(object obj) + { + Exception exception; + + try + { + IOleServiceProvider oleServices; + var project = (IVsProject)_selectedProject.Project.Services.HostObject; + Marshal.ThrowExceptionForHR(project.GetItemContext((uint)VSConstants.VSITEMID.Root, out oleServices)); + var services = new ServiceProvider(oleServices); + + var projectSecrets = (IVsProjectSecrets)services.GetService(typeof(SVsProjectLocalSecrets)); + await TaskScheduler.Default; + + if (projectSecrets == null) + { + exception = null; + } + else + { + foreach (var secret in Secrets) + { + await projectSecrets.SetSecretAsync(secret.Key, secret.Value).ConfigureAwait(false); + } + + exception = null; + } + } + catch (Exception ex) + { + exception = ex; + } + + if (exception != null) + { + Error = exception.ToString(); + } + } + + private async void OnSelectedProjectChanged() + { + Secrets.Clear(); + IsLoaded = false; + + if (_selectedProject == null) + { + return; + } + + KeyValuePair[] results; + Exception exception; + + try + { + IOleServiceProvider oleServices; + var project = (IVsProject)_selectedProject.Project.Services.HostObject; + Marshal.ThrowExceptionForHR(project.GetItemContext((uint)VSConstants.VSITEMID.Root, out oleServices)); + var services = new ServiceProvider(oleServices); + + var projectSecrets = (IVsProjectSecrets)services.GetService(typeof(SVsProjectLocalSecrets)); + await TaskScheduler.Default; + + if (projectSecrets == null) + { + results = null; + exception = null; + } + else + { + var secrets = await projectSecrets.GetSecretsAsync().ConfigureAwait(false); + + results = secrets.ToArray(); + exception = null; + } + } + catch (Exception ex) + { + results = null; + exception = ex; + } + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + if (exception != null) + { + Error = exception.ToString(); + } + else if (results != null) + { + for (var i = 0; i < results.Length; i++) + { + Secrets.Add(results[i]); + } + } + + IsLoaded = true; + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/VSPackage.resx b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/VSPackage.resx new file mode 100644 index 0000000000..ca1c752309 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/VSPackage.resx @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + SecretManagerTest Extension + + + SecretManagerTest Visual Studio Extension Detailed Info + + + Resources\SecretManagerTestPackage.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/app.config b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/app.config new file mode 100644 index 0000000000..4bac29887b --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/app.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/source.extension.vsixmanifest b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/source.extension.vsixmanifest new file mode 100644 index 0000000000..3b8e971651 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager.TestExtension/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + Microsoft.VisualStudio.SecretManager.TestExtension + A test extension for Microsoft.VisualStudio.TestExtension + + + + + + + + + + + + + + + diff --git a/tooling/Microsoft.VisualStudio.SecretManager/Microsoft.VisualStudio.SecretManager.csproj b/tooling/Microsoft.VisualStudio.SecretManager/Microsoft.VisualStudio.SecretManager.csproj new file mode 100644 index 0000000000..bfd99e46b7 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/Microsoft.VisualStudio.SecretManager.csproj @@ -0,0 +1,106 @@ + + + + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + False + False + False + True + True + ..\..\build\Key.snk + True + + + + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {5E117F2E-7152-447F-BF47-59F759EEF3A7} + Library + Properties + Microsoft.VisualStudio.SecretManager + Microsoft.VisualStudio.SecretManager + v4.6.1 + true + true + true + true + true + false + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + TRACE;DEBUG;EXTENSION_DEVELOPER_MODE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + diff --git a/tooling/Microsoft.VisualStudio.SecretManager/ProjectLocalSecretsManager.cs b/tooling/Microsoft.VisualStudio.SecretManager/ProjectLocalSecretsManager.cs new file mode 100644 index 0000000000..e516a85ffb --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/ProjectLocalSecretsManager.cs @@ -0,0 +1,148 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.ProjectSystem.Properties; +using Microsoft.VisualStudio.Threading; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.VisualStudio.SecretManager +{ + /// + /// Provides an thread-safe access the secrets.json file based on the UserSecretsId property in a configured project. + /// + internal class ProjectLocalSecretsManager : Shell.IVsProjectSecrets, Shell.SVsProjectLocalSecrets + { + private const string UserSecretsPropertyName = "UserSecretsId"; + + private readonly AsyncSemaphore _semaphore; + private readonly IProjectPropertiesProvider _propertiesProvider; + private readonly Lazy _services; + + public ProjectLocalSecretsManager(IProjectPropertiesProvider propertiesProvider, Lazy serviceProvider) + { + _propertiesProvider = propertiesProvider ?? throw new ArgumentNullException(nameof(propertiesProvider)); + _services = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _semaphore = new AsyncSemaphore(1); + } + + public string SanitizeName(string name) => name; + + public IReadOnlyCollection GetInvalidCharactersFrom(string name) => Array.Empty(); + + public async Task AddSecretAsync(string name, string value, CancellationToken cancellationToken = default) + { + EnsureKeyNameIsValid(name); + await TaskScheduler.Default; + + using (await _semaphore.EnterAsync(cancellationToken)) + using (var store = await GetOrCreateStoreAsync(cancellationToken)) + { + if (store.ContainsKey(name)) + { + throw new ArgumentException(Resources.Error_SecretAlreadyExists, nameof(name)); + } + + store.Set(name, value); + await store.SaveAsync(cancellationToken); + } + } + + public async Task SetSecretAsync(string name, string value, CancellationToken cancellationToken = default) + { + EnsureKeyNameIsValid(name); + await TaskScheduler.Default; + + using (await _semaphore.EnterAsync(cancellationToken)) + using (var store = await GetOrCreateStoreAsync(cancellationToken)) + { + store.Set(name, value); + await store.SaveAsync(cancellationToken); + } + } + + public async Task GetSecretAsync(string name, CancellationToken cancellationToken = default) + { + EnsureKeyNameIsValid(name); + await TaskScheduler.Default; + + using (await _semaphore.EnterAsync(cancellationToken)) + using (var store = await GetOrCreateStoreAsync(cancellationToken)) + { + return store.Get(name); + } + } + + public async Task> GetSecretNamesAsync(CancellationToken cancellationToken = default) + { + await TaskScheduler.Default; + + using (await _semaphore.EnterAsync(cancellationToken)) + using (var store = await GetOrCreateStoreAsync(cancellationToken)) + { + return store.ReadOnlyKeys; + } + } + + + public async Task> GetSecretsAsync(CancellationToken cancellationToken = default) + { + await TaskScheduler.Default; + + using (await _semaphore.EnterAsync(cancellationToken)) + using (var store = await GetOrCreateStoreAsync(cancellationToken)) + { + return store.Values; + } + } + + public async Task RemoveSecretAsync(string name, CancellationToken cancellationToken = default) + { + EnsureKeyNameIsValid(name); + await TaskScheduler.Default; + + using (await _semaphore.EnterAsync(cancellationToken)) + using (var store = await GetOrCreateStoreAsync(cancellationToken)) + { + if (store.Remove(name)) + { + await store.SaveAsync(cancellationToken); + return true; + } + + return false; + } + } + + private void EnsureKeyNameIsValid(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (name.Length == 0) + { + throw new ArgumentException(nameof(name)); + } + } + + private async Task GetOrCreateStoreAsync(CancellationToken cancellationToken) + { + var userSecretsId = await _propertiesProvider.GetCommonProperties().GetEvaluatedPropertyValueAsync(UserSecretsPropertyName); + + if (string.IsNullOrEmpty(userSecretsId)) + { + userSecretsId = Guid.NewGuid().ToString(); + await _propertiesProvider.GetCommonProperties().SetPropertyValueAsync(UserSecretsPropertyName, userSecretsId); + } + + var store = new SecretStore(userSecretsId); + await store.LoadAsync(cancellationToken); + return store; + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager/Properties/AssemblyInfo.cs b/tooling/Microsoft.VisualStudio.SecretManager/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..56a9841165 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using Microsoft.VisualStudio.Shell; + +// required for VS to generate the pkgdef +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: ProvideCodeBase(CodeBase = @"$PackageFolder$\Microsoft.VisualStudio.SecretManager.dll")] +[assembly: ProvideBindingRedirection( + AssemblyName = "Microsoft.VisualStudio.SecretManager", + GenerateCodeBase = true, + PublicKeyToken = "adb9793829ddae60", + OldVersionLowerBound = "0.0.0.0", + OldVersionUpperBound = "1.0.0.0", + NewVersion = "1.0.0.0")] diff --git a/tooling/Microsoft.VisualStudio.SecretManager/Resources.Designer.cs b/tooling/Microsoft.VisualStudio.SecretManager/Resources.Designer.cs new file mode 100644 index 0000000000..bd79af4b8c --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/Resources.Designer.cs @@ -0,0 +1,140 @@ +// +namespace Microsoft.VisualStudio.SecretManager +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.VisualStudio.SecretManager.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// A secret with this name already exists. + /// + internal static string Error_SecretAlreadyExists + { + get => GetString("Error_SecretAlreadyExists"); + } + + /// + /// Value cannot be null or an empty string. + /// + internal static string Common_StringNullOrEmpty + { + get => GetString("Common_StringNullOrEmpty"); + } + + /// + /// Value cannot be null or an empty string. + /// + internal static string FormatCommon_StringNullOrEmpty() + => GetString("Common_StringNullOrEmpty"); + + /// + /// Invalid character '{0}' found in the user secrets ID at index '{1}'. + /// + internal static string Error_Invalid_Character_In_UserSecrets_Id + { + get => GetString("Error_Invalid_Character_In_UserSecrets_Id"); + } + + /// + /// Invalid character '{0}' found in the user secrets ID at index '{1}'. + /// + internal static string FormatError_Invalid_Character_In_UserSecrets_Id(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("Error_Invalid_Character_In_UserSecrets_Id"), p0, p1); + + /// + /// Could not find 'UserSecretsIdAttribute' on assembly '{0}'. + /// Check that the project for '{0}' has set the 'UserSecretsId' build property. + /// If the 'UserSecretsId' property is already set then add a reference to the Microsoft.Extensions.Configuration.UserSecrets package. + /// + internal static string Error_Missing_UserSecretsIdAttribute + { + get => GetString("Error_Missing_UserSecretsIdAttribute"); + } + + /// + /// Could not find 'UserSecretsIdAttribute' on assembly '{0}'. + /// Check that the project for '{0}' has set the 'UserSecretsId' build property. + /// If the 'UserSecretsId' property is already set then add a reference to the Microsoft.Extensions.Configuration.UserSecrets package. + /// + internal static string FormatError_Missing_UserSecretsIdAttribute(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("Error_Missing_UserSecretsIdAttribute"), p0); + + /// + /// File path must be a non-empty string. + /// + internal static string Error_InvalidFilePath + { + get => GetString("Error_InvalidFilePath"); + } + + /// + /// File path must be a non-empty string. + /// + internal static string FormatError_InvalidFilePath() + => GetString("Error_InvalidFilePath"); + + /// + /// Could not parse the JSON file. Error on line number '{0}': '{1}'. + /// + internal static string Error_JSONParseError + { + get => GetString("Error_JSONParseError"); + } + + /// + /// Could not parse the JSON file. Error on line number '{0}': '{1}'. + /// + internal static string FormatError_JSONParseError(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("Error_JSONParseError"), p0, p1); + + /// + /// A duplicate key '{0}' was found. + /// + internal static string Error_KeyIsDuplicated + { + get => GetString("Error_KeyIsDuplicated"); + } + + /// + /// A duplicate key '{0}' was found. + /// + internal static string FormatError_KeyIsDuplicated(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("Error_KeyIsDuplicated"), p0); + + /// + /// Unsupported JSON token '{0}' was found. Path '{1}', line {2} position {3}. + /// + internal static string Error_UnsupportedJSONToken + { + get => GetString("Error_UnsupportedJSONToken"); + } + + /// + /// Unsupported JSON token '{0}' was found. Path '{1}', line {2} position {3}. + /// + internal static string FormatError_UnsupportedJSONToken(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("Error_UnsupportedJSONToken"), p0, p1, p2, p3); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager/Resources.resx b/tooling/Microsoft.VisualStudio.SecretManager/Resources.resx new file mode 100644 index 0000000000..1057cd7926 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/Resources.resx @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + File path must be a non-empty string. + + + Could not parse the JSON file. Error on line number '{0}': '{1}'. + + + A duplicate key '{0}' was found. + + + Unsupported JSON token '{0}' was found. Path '{1}', line {2} position {3}. + + + Value cannot be null or an empty string. + + + Invalid character '{0}' found in the user secrets ID at index '{1}'. + + + Could not find 'UserSecretsIdAttribute' on assembly '{0}'. +Check that the project for '{0}' has set the 'UserSecretsId' build property. +If the 'UserSecretsId' property is already set then add a reference to the Microsoft.Extensions.Configuration.UserSecrets package. + + + A secret with this name already exists. + + \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.SecretManager/SecretManagerFactory.cs b/tooling/Microsoft.VisualStudio.SecretManager/SecretManagerFactory.cs new file mode 100644 index 0000000000..b37212e23b --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/SecretManagerFactory.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.SecretManager +{ + internal class SecretManagerFactory + { + // This is capability is set in Microsoft.Extensions.Configuration.UserSecrets + private const string CapabilityName = "LocalUserSecrets"; + + private readonly Lazy _secretManager; + private readonly UnconfiguredProject _project; + + [ImportingConstructor] + public SecretManagerFactory(UnconfiguredProject project, SVsServiceProvider vsServiceProvider) + { + _project = project; + + var serviceProvider = new Lazy(() => vsServiceProvider); + + _secretManager = new Lazy(() => + { + var propertiesProvider = _project.Services.ActiveConfiguredProjectProvider.ActiveConfiguredProject.Services.ProjectPropertiesProvider; + return new ProjectLocalSecretsManager(propertiesProvider, serviceProvider); + }); + } + + [ExportVsProfferedProjectService(typeof(SVsProjectLocalSecrets))] + [AppliesTo(CapabilityName)] + public SVsProjectLocalSecrets ProjectLocalSecretsManager => _secretManager.Value; + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager/SecretStore.cs b/tooling/Microsoft.VisualStudio.SecretManager/SecretStore.cs new file mode 100644 index 0000000000..ee10d5069c --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/SecretStore.cs @@ -0,0 +1,169 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.Configuration.UserSecrets; +using Microsoft.VisualStudio.Threading; +using Newtonsoft.Json.Linq; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.VisualStudio.SecretManager +{ + /// + /// Provides read and write access to the secrets.json file for local user secrets. + /// This is not thread-safe. + /// This object is meant to have a short lifetime. + /// When calling , this will overwrite the secrets.json file. It does not check for concurrency issues if another process has edited this file. + /// + internal class SecretStore : IDisposable + { + private Dictionary _secrets; + private string _fileDir; + private string _filePath; + private bool _isDirty; + private volatile bool _disposed; + + public SecretStore(string userSecretsId) + { + _filePath = PathHelper.GetSecretsPathFromSecretsId(userSecretsId); + _fileDir = Path.GetDirectoryName(_filePath); + } + + public IReadOnlyCollection ReadOnlyKeys + { + get + { + EnsureNotDisposed(); + return _secrets.Keys; + } + } + + public IReadOnlyDictionary Values + { + get + { + EnsureNotDisposed(); + + return _secrets; + } + } + + public bool ContainsKey(string key) + { + EnsureNotDisposed(); + + return _secrets.ContainsKey(key); + } + + public string Get(string name) + { + EnsureNotDisposed(); + + return _secrets[name]; + } + + public void Set(string key, string value) + { + EnsureNotDisposed(); + + _isDirty = true; + _secrets[key] = value; + } + + public bool Remove(string key) + { + EnsureNotDisposed(); + _isDirty = true; + return _secrets.Remove(key); + } + + public async Task LoadAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + await TaskScheduler.Default; + + EnsureNotDisposed(); + + string text = null; + + if (File.Exists(_filePath)) + { + text = File.ReadAllText(_filePath); + } + + _secrets = DeserializeJson(text); + } + + public async Task SaveAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + await TaskScheduler.Default; + + EnsureNotDisposed(); + + if (!_isDirty) + { + return; + } + + Directory.CreateDirectory(_fileDir); + File.WriteAllText(_filePath, Stringify(_secrets), Encoding.UTF8); + + _isDirty = false; + } + + private void EnsureNotDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(SecretStore)); + } + } + + private static string Stringify(Dictionary secrets) + { + var contents = new JObject(); + if (secrets != null) + { + foreach (var secret in secrets) + { + contents[secret.Key] = secret.Value; + } + } + + return contents.ToString(); + } + + private static Dictionary DeserializeJson(string text) + { + if (string.IsNullOrEmpty(text)) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + using (var stream = new MemoryStream()) + { + var bytes = Encoding.UTF8.GetBytes(text); + stream.Write(bytes, 0, bytes.Length); + stream.Position = 0; + + // might throw FormatException if JSON is malformed. + var data = JsonConfigurationFileParser.Parse(stream); + + return new Dictionary(data, StringComparer.OrdinalIgnoreCase); + } + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager/Sources/ConfigurationPath.cs b/tooling/Microsoft.VisualStudio.SecretManager/Sources/ConfigurationPath.cs new file mode 100644 index 0000000000..d4f277e0d1 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/Sources/ConfigurationPath.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Configuration +{ + /// + /// Utility methods and constants for manipulating Configuration paths + /// + internal static class ConfigurationPath + { + /// + /// The delimiter ":" used to separate individual keys in a path. + /// + public static readonly string KeyDelimiter = ":"; + + /// + /// Combines path segments into one path. + /// + /// The path segments to combine. + /// The combined path. + public static string Combine(params string[] pathSegments) + { + if (pathSegments == null) + { + throw new ArgumentNullException(nameof(pathSegments)); + } + return string.Join(KeyDelimiter, pathSegments); + } + + /// + /// Combines path segments into one path. + /// + /// The path segments to combine. + /// The combined path. + public static string Combine(IEnumerable pathSegments) + { + if (pathSegments == null) + { + throw new ArgumentNullException(nameof(pathSegments)); + } + return string.Join(KeyDelimiter, pathSegments); + } + + /// + /// Extracts the last path segment from the path. + /// + /// The path. + /// The last path segment of the path. + public static string GetSectionKey(string path) + { + if (string.IsNullOrEmpty(path)) + { + return path; + } + + var lastDelimiterIndex = path.LastIndexOf(KeyDelimiter, StringComparison.OrdinalIgnoreCase); + return lastDelimiterIndex == -1 ? path : path.Substring(lastDelimiterIndex + 1); + } + + /// + /// Extracts the path corresponding to the parent node for a given path. + /// + /// The path. + /// The original path minus the last individual segment found in it. Null if the original path corresponds to a top level node. + public static string GetParentPath(string path) + { + if (string.IsNullOrEmpty(path)) + { + return null; + } + + var lastDelimiterIndex = path.LastIndexOf(KeyDelimiter, StringComparison.OrdinalIgnoreCase); + return lastDelimiterIndex == -1 ? null : path.Substring(0, lastDelimiterIndex); + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager/Sources/JsonConfigurationFileParser.cs b/tooling/Microsoft.VisualStudio.SecretManager/Sources/JsonConfigurationFileParser.cs new file mode 100644 index 0000000000..1cc65407c5 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/Sources/JsonConfigurationFileParser.cs @@ -0,0 +1,120 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using Microsoft.VisualStudio.SecretManager; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Extensions.Configuration.Json +{ + internal class JsonConfigurationFileParser + { + private JsonConfigurationFileParser() { } + + private readonly IDictionary _data = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Stack _context = new Stack(); + private string _currentPath; + + private JsonTextReader _reader; + + public static IDictionary Parse(Stream input) + => new JsonConfigurationFileParser().ParseStream(input); + + private IDictionary ParseStream(Stream input) + { + _data.Clear(); + _reader = new JsonTextReader(new StreamReader(input)); + _reader.DateParseHandling = DateParseHandling.None; + + var jsonConfig = JObject.Load(_reader); + + VisitJObject(jsonConfig); + + return _data; + } + + private void VisitJObject(JObject jObject) + { + foreach (var property in jObject.Properties()) + { + EnterContext(property.Name); + VisitProperty(property); + ExitContext(); + } + } + + private void VisitProperty(JProperty property) + { + VisitToken(property.Value); + } + + private void VisitToken(JToken token) + { + switch (token.Type) + { + case JTokenType.Object: + VisitJObject(token.Value()); + break; + + case JTokenType.Array: + VisitArray(token.Value()); + break; + + case JTokenType.Integer: + case JTokenType.Float: + case JTokenType.String: + case JTokenType.Boolean: + case JTokenType.Bytes: + case JTokenType.Raw: + case JTokenType.Null: + VisitPrimitive(token.Value()); + break; + + default: + throw new FormatException(Resources.FormatError_UnsupportedJSONToken( + _reader.TokenType, + _reader.Path, + _reader.LineNumber, + _reader.LinePosition)); + } + } + + private void VisitArray(JArray array) + { + for (int index = 0; index < array.Count; index++) + { + EnterContext(index.ToString()); + VisitToken(array[index]); + ExitContext(); + } + } + + private void VisitPrimitive(JValue data) + { + var key = _currentPath; + + if (_data.ContainsKey(key)) + { + throw new FormatException(Resources.FormatError_KeyIsDuplicated(key)); + } + _data[key] = data.ToString(CultureInfo.InvariantCulture); + } + + private void EnterContext(string context) + { + _context.Push(context); + _currentPath = ConfigurationPath.Combine(_context.Reverse()); + } + + private void ExitContext() + { + _context.Pop(); + _currentPath = ConfigurationPath.Combine(_context.Reverse()); + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager/Sources/PathHelper.cs b/tooling/Microsoft.VisualStudio.SecretManager/Sources/PathHelper.cs new file mode 100644 index 0000000000..ae135cb5c4 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/Sources/PathHelper.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.VisualStudio.SecretManager; + +namespace Microsoft.Extensions.Configuration.UserSecrets +{ + /// + /// Provides paths for user secrets configuration files. + /// + internal class PathHelper + { + internal const string SecretsFileName = "secrets.json"; + + /// + /// + /// Returns the path to the JSON file that stores user secrets. + /// + /// + /// This uses the current user profile to locate the secrets file on disk in a location outside of source control. + /// + /// + /// The user secret ID. + /// The full path to the secret file. + public static string GetSecretsPathFromSecretsId(string userSecretsId) + { + if (string.IsNullOrEmpty(userSecretsId)) + { + throw new ArgumentException(Resources.Common_StringNullOrEmpty, nameof(userSecretsId)); + } + + var badCharIndex = userSecretsId.IndexOfAny(Path.GetInvalidFileNameChars()); + if (badCharIndex != -1) + { + throw new InvalidOperationException( + string.Format( + Resources.Error_Invalid_Character_In_UserSecrets_Id, + userSecretsId[badCharIndex], + badCharIndex)); + } + + var root = Environment.GetEnvironmentVariable("APPDATA") ?? // On Windows it goes to %APPDATA%\Microsoft\UserSecrets\ + Environment.GetEnvironmentVariable("HOME"); // On Mac/Linux it goes to ~/.microsoft/usersecrets/ + + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPDATA"))) + { + return Path.Combine(root, "Microsoft", "UserSecrets", userSecretsId, SecretsFileName); + } + else + { + return Path.Combine(root, ".microsoft", "usersecrets", userSecretsId, SecretsFileName); + } + } + } +} diff --git a/tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest b/tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest new file mode 100644 index 0000000000..643914b606 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + Microsoft.VisualStudio.SecretManager + Enables IVsProjectSecrets for ASP.NET Core projects that use Microsoft.Extensions.Configuration.UserSecrets. + + + + + + + + + + + + + + + diff --git a/version.props b/version.props index 5c4a7c32d1..4884e87b9e 100644 --- a/version.props +++ b/version.props @@ -1,9 +1,12 @@ 2.1.0 + 15.6 preview1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final + $(VsixVersion).$(BuildNumber) + $(VsixVersion).999999 t000 $(VersionSuffix)-$(BuildNumber)