From 81a96f0273690b48ede8b48b7213a7b97de48f82 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 2 Jun 2022 17:57:38 -0700 Subject: [PATCH 1/4] [user-jwts] Read secrets ID from SecretsManager and generate missing secrets --- eng/ProjectReferences.props | 1 + .../src/Helpers/DevJwtCliHelpers.cs | 22 +++++++++--------- .../src/dotnet-user-jwts.csproj | 7 ++++-- .../dotnet-user-jwts/test/UserJwtsTests.cs | 6 +++-- .../src/Internal/InitCommand.cs | 23 +++++++++++-------- .../src/Internal/ProjectIdResolver.cs | 4 ++-- .../test/dotnet-user-secrets.Tests.csproj | 7 ++---- 7 files changed, 39 insertions(+), 31 deletions(-) diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index c0842d685c55..5785bcd08a59 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -69,6 +69,7 @@ + diff --git a/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs b/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs index e34a017553c3..53a425065158 100644 --- a/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs +++ b/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs @@ -4,27 +4,26 @@ using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Text.Json; -using System.Xml.Linq; -using System.Xml.XPath; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.UserSecrets; +using Microsoft.Extensions.SecretManager.Tools.Internal; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.AspNetCore.Authentication.JwtBearer.Tools; internal static class DevJwtCliHelpers { - public static string GetUserSecretsId(string projectFilePath) + public static string GetOrSetUserSecretsId(IReporter reporter, string projectFilePath) { - var projectDocument = XDocument.Load(projectFilePath, LoadOptions.PreserveWhitespace); - var existingUserSecretsId = projectDocument.XPathSelectElements("//UserSecretsId").FirstOrDefault(); - - if (existingUserSecretsId == null) + var resolver = new ProjectIdResolver(reporter, projectFilePath); + try { - return null; + return resolver.Resolve(projectFilePath, configuration: null); + } + catch (NullReferenceException) + { + return InitCommand.CreateUserSecretsId(reporter, projectFilePath, projectFilePath); } - - return existingUserSecretsId.Value; } public static string GetProject(string projectPath = null) @@ -54,7 +53,7 @@ public static bool GetProjectAndSecretsId(string projectPath, IReporter reporter return false; } - userSecretsId = GetUserSecretsId(project); + userSecretsId = GetOrSetUserSecretsId(reporter, project); if (userSecretsId == null) { reporter.Error($"Project does not contain a user secrets ID."); @@ -85,6 +84,7 @@ public static byte[] CreateSigningKeyMaterial(string userSecretsId, bool reset = // Create signing material and save to user secrets var newKeyMaterial = System.Security.Cryptography.RandomNumberGenerator.GetBytes(DevJwtsDefaults.SigningKeyLength); var secretsFilePath = PathHelper.GetSecretsPathFromSecretsId(userSecretsId); + Directory.CreateDirectory(Path.GetDirectoryName(secretsFilePath)); IDictionary secrets = null; if (File.Exists(secretsFilePath)) diff --git a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj index 93b34c52c64b..d96f3c0a9ee2 100644 --- a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj +++ b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) exe @@ -13,7 +13,9 @@ - + @@ -21,5 +23,6 @@ + \ No newline at end of file diff --git a/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs b/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs index 413b8503987e..ef20a86cefba 100644 --- a/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs +++ b/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs @@ -45,7 +45,8 @@ public void List_HandlesNoSecretsInProject() var app = new Program(_console); app.Run(new[] { "list", "--project", project }); - Assert.Contains("Project does not contain a user secrets ID.", _console.GetOutput()); + Assert.Contains("Set UserSecretsId to ", _console.GetOutput()); + Assert.Contains("No JWTs created yet!", _console.GetOutput()); } [Fact] @@ -55,7 +56,8 @@ public void Create_WarnsOnNoSecretInproject() var app = new Program(_console); app.Run(new[] { "create", "--project", project }); - Assert.Contains("Project does not contain a user secrets ID.", _console.GetOutput()); + Assert.Contains("Set UserSecretsId to ", _console.GetOutput()); + Assert.Contains("New JWT saved", _console.GetOutput()); } [Fact] diff --git a/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs b/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs index 166847392504..5acf359a344f 100644 --- a/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs +++ b/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs @@ -1,13 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.Tools.Internal; namespace Microsoft.Extensions.SecretManager.Tools.Internal; @@ -73,15 +72,20 @@ public void Execute(CommandContext context, string workingDirectory) public void Execute(CommandContext context) { - var projectPath = ResolveProjectPath(ProjectPath, WorkingDirectory); + CreateUserSecretsId(context.Reporter, ProjectPath, WorkingDirectory, OverrideId); + } + + public static string CreateUserSecretsId(IReporter reporter, string project, string workingDirectory, string overrideId = null) + { + var projectPath = ResolveProjectPath(project, workingDirectory); // Load the project file as XML var projectDocument = XDocument.Load(projectPath, LoadOptions.PreserveWhitespace); // Accept the `--id` CLI option to the main app - string newSecretsId = string.IsNullOrWhiteSpace(OverrideId) + string newSecretsId = string.IsNullOrWhiteSpace(overrideId) ? Guid.NewGuid().ToString() - : OverrideId; + : overrideId; // Confirm secret ID does not contain invalid characters if (Path.GetInvalidPathChars().Any(invalidChar => newSecretsId.Contains(invalidChar))) @@ -95,10 +99,10 @@ public void Execute(CommandContext context) if (existingUserSecretsId is object) { // Only set the UserSecretsId if the user specified an explicit value - if (string.IsNullOrWhiteSpace(OverrideId)) + if (string.IsNullOrWhiteSpace(overrideId)) { - context.Reporter.Output(Resources.FormatMessage_ProjectAlreadyInitialized(projectPath)); - return; + reporter.Output(Resources.FormatMessage_ProjectAlreadyInitialized(projectPath)); + return existingUserSecretsId.Value; } existingUserSecretsId.SetValue(newSecretsId); @@ -133,7 +137,8 @@ public void Execute(CommandContext context) using var xw = XmlWriter.Create(projectPath, settings); projectDocument.Save(xw); - context.Reporter.Output(Resources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, projectPath)); + reporter.Output(Resources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, projectPath)); + return newSecretsId; } private static string ResolveProjectPath(string name, string path) diff --git a/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs b/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs index 95657b8c0ba8..14c6acd1386e 100644 --- a/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs +++ b/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs @@ -103,13 +103,13 @@ public string Resolve(string project, string configuration) if (!File.Exists(outputFile)) { - throw new InvalidOperationException(Resources.FormatError_ProjectMissingId(projectFile)); + throw new FileNotFoundException(Resources.FormatError_ProjectMissingId(projectFile)); } var id = File.ReadAllText(outputFile)?.Trim(); if (string.IsNullOrEmpty(id)) { - throw new InvalidOperationException(Resources.FormatError_ProjectMissingId(projectFile)); + throw new NullReferenceException(Resources.FormatError_ProjectMissingId(projectFile)); } return id; diff --git a/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj b/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj index 94aa9103bdbc..8a067be65017 100644 --- a/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj +++ b/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -10,12 +10,9 @@ - - - - + From 1ee882ba63caeba877664da714f393afdb2ebe76 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 3 Jun 2022 14:47:45 -0700 Subject: [PATCH 2/4] Address feedback from review --- eng/ProjectReferences.props | 1 - .../SecretsHelpers}/MsBuildProjectFinder.cs | 8 +- .../SecretsHelpers}/ProjectIdResolver.cs | 24 ++++-- .../SecretsHelpers/UserSecretsCreator.cs | 85 +++++++++++++++++++ .../assets/SecretManager.targets | 0 .../src/Helpers/DevJwtCliHelpers.cs | 10 +-- .../src/dotnet-user-jwts.csproj | 7 +- .../dotnet-user-jwts/test/UserJwtsTests.cs | 8 +- .../test/dotnet-user-jwts.Tests.csproj | 3 +- .../src/Internal/InitCommand.cs | 79 +---------------- src/Tools/dotnet-user-secrets/src/Program.cs | 10 +-- .../src/dotnet-user-secrets.csproj | 3 +- .../test/dotnet-user-secrets.Tests.csproj | 7 +- 13 files changed, 131 insertions(+), 114 deletions(-) rename src/Tools/{dotnet-user-secrets/src/Internal => Shared/SecretsHelpers}/MsBuildProjectFinder.cs (75%) rename src/Tools/{dotnet-user-secrets/src/Internal => Shared/SecretsHelpers}/ProjectIdResolver.cs (83%) create mode 100644 src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs rename src/Tools/{dotnet-user-secrets/src => Shared/SecretsHelpers}/assets/SecretManager.targets (100%) diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 5785bcd08a59..c0842d685c55 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -69,7 +69,6 @@ - diff --git a/src/Tools/dotnet-user-secrets/src/Internal/MsBuildProjectFinder.cs b/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs similarity index 75% rename from src/Tools/dotnet-user-secrets/src/Internal/MsBuildProjectFinder.cs rename to src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs index 9bb6bef60c5c..3c45443fac73 100644 --- a/src/Tools/dotnet-user-secrets/src/Internal/MsBuildProjectFinder.cs +++ b/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.IO; using System.Linq; using Microsoft.Extensions.Tools.Internal; @@ -36,12 +34,12 @@ public string FindMsBuildProject(string project) if (projects.Count > 1) { - throw new FileNotFoundException(Resources.FormatError_MultipleProjectsFound(projectPath)); + throw new FileNotFoundException($"Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option."); } if (projects.Count == 0) { - throw new FileNotFoundException(Resources.FormatError_NoProjectsFound(projectPath)); + throw new FileNotFoundException($"Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option."); } return projects[0]; @@ -49,7 +47,7 @@ public string FindMsBuildProject(string project) if (!File.Exists(projectPath)) { - throw new FileNotFoundException(Resources.FormatError_ProjectPath_NotFound(projectPath)); + throw new FileNotFoundException($"The project file '{projectPath}' does not exist."); } return projectPath; diff --git a/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs b/src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs similarity index 83% rename from src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs rename to src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs index 14c6acd1386e..f8305e6a83b4 100644 --- a/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs +++ b/src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal; /// This API supports infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// -public class ProjectIdResolver +internal sealed class ProjectIdResolver { private const string DefaultConfig = "Debug"; private readonly IReporter _reporter; @@ -32,9 +32,19 @@ public ProjectIdResolver(IReporter reporter, string workingDirectory) public string Resolve(string project, string configuration) { var finder = new MsBuildProjectFinder(_workingDirectory); - var projectFile = finder.FindMsBuildProject(project); + string projectFile; + try + { + projectFile = finder.FindMsBuildProject(project); + } + catch (Exception ex) + { + _reporter.Error(ex.Message); + return null; + } + - _reporter.Verbose(Resources.FormatMessage_Project_File_Path(projectFile)); + _reporter.Verbose($"Project file path {projectFile}."); configuration = !string.IsNullOrEmpty(configuration) ? configuration @@ -98,18 +108,20 @@ public string Resolve(string project, string configuration) _reporter.Verbose(outputBuilder.ToString()); _reporter.Verbose(errorBuilder.ToString()); _reporter.Error($"Exit code: {process.ExitCode}"); - throw new InvalidOperationException(Resources.FormatError_ProjectFailedToLoad(projectFile)); + _reporter.Error($"Could not load the MSBuild project '{projectFile}'."); + return null; } if (!File.Exists(outputFile)) { - throw new FileNotFoundException(Resources.FormatError_ProjectMissingId(projectFile)); + _reporter.Error($"Could not find the global property 'UserSecretsId' in MSBuild project '{projectFile}'. Ensure this property is set in the project or use the '--id' command line option."); + return null; } var id = File.ReadAllText(outputFile)?.Trim(); if (string.IsNullOrEmpty(id)) { - throw new NullReferenceException(Resources.FormatError_ProjectMissingId(projectFile)); + _reporter.Error($"Could not find the global property 'UserSecretsId' in MSBuild project '{projectFile}'. Ensure this property is set in the project or use the '--id' command line option."); } return id; diff --git a/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs b/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs new file mode 100644 index 000000000000..236c97bcf132 --- /dev/null +++ b/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using Microsoft.Extensions.Tools.Internal; + +namespace Microsoft.Extensions.SecretManager.Tools.Internal; + +internal static class UserSecretsCreator +{ + public static string CreateUserSecretsId(IReporter reporter, string project, string workingDirectory, string overrideId = null) + { + var projectPath = ResolveProjectPath(project, workingDirectory); + + // Load the project file as XML + var projectDocument = XDocument.Load(projectPath, LoadOptions.PreserveWhitespace); + + // Accept the `--id` CLI option to the main app + string newSecretsId = string.IsNullOrWhiteSpace(overrideId) + ? Guid.NewGuid().ToString() + : overrideId; + + // Confirm secret ID does not contain invalid characters + if (Path.GetInvalidPathChars().Any(invalidChar => newSecretsId.Contains(invalidChar))) + { + throw new ArgumentException($"The UserSecretsId '{newSecretsId}' cannot contain any characters that cannot be used in a file path."); + } + + var existingUserSecretsId = projectDocument.XPathSelectElements("//UserSecretsId").FirstOrDefault(); + + // Check if a UserSecretsId is already set + if (existingUserSecretsId is object) + { + // Only set the UserSecretsId if the user specified an explicit value + if (string.IsNullOrWhiteSpace(overrideId)) + { + reporter.Output($"The MSBuild project '{projectPath}' has already been initialized with a UserSecretsId."); + return existingUserSecretsId.Value; + } + + existingUserSecretsId.SetValue(newSecretsId); + } + else + { + // Find the first non-conditional PropertyGroup + var propertyGroup = projectDocument.Root.DescendantNodes() + .FirstOrDefault(node => node is XElement el + && el.Name == "PropertyGroup" + && el.Attributes().All(attr => + attr.Name != "Condition")) as XElement; + + // No valid property group, create a new one + if (propertyGroup == null) + { + propertyGroup = new XElement("PropertyGroup"); + projectDocument.Root.AddFirst(propertyGroup); + } + + // Add UserSecretsId element + propertyGroup.Add(" "); + propertyGroup.Add(new XElement("UserSecretsId", newSecretsId)); + propertyGroup.Add($"{Environment.NewLine} "); + } + + var settings = new XmlWriterSettings + { + OmitXmlDeclaration = true, + }; + + using var xw = XmlWriter.Create(projectPath, settings); + projectDocument.Save(xw); + + reporter.Output($"Set UserSecretsId to '{newSecretsId}' for MSBuild project '{projectPath}'."); + return newSecretsId; + } + + private static string ResolveProjectPath(string name, string path) + { + var finder = new MsBuildProjectFinder(path); + return finder.FindMsBuildProject(name); + } +} diff --git a/src/Tools/dotnet-user-secrets/src/assets/SecretManager.targets b/src/Tools/Shared/SecretsHelpers/assets/SecretManager.targets similarity index 100% rename from src/Tools/dotnet-user-secrets/src/assets/SecretManager.targets rename to src/Tools/Shared/SecretsHelpers/assets/SecretManager.targets diff --git a/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs b/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs index 53a425065158..27e2b321cf3e 100644 --- a/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs +++ b/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs @@ -16,14 +16,12 @@ internal static class DevJwtCliHelpers public static string GetOrSetUserSecretsId(IReporter reporter, string projectFilePath) { var resolver = new ProjectIdResolver(reporter, projectFilePath); - try + var id = resolver.Resolve(projectFilePath, configuration: null); + if (string.IsNullOrEmpty(id)) { - return resolver.Resolve(projectFilePath, configuration: null); - } - catch (NullReferenceException) - { - return InitCommand.CreateUserSecretsId(reporter, projectFilePath, projectFilePath); + return UserSecretsCreator.CreateUserSecretsId(reporter, projectFilePath, projectFilePath); } + return id; } public static string GetProject(string projectPath = null) diff --git a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj index d96f3c0a9ee2..d32891d9c5e4 100644 --- a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj +++ b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj @@ -13,9 +13,9 @@ - + + + @@ -23,6 +23,5 @@ - \ No newline at end of file diff --git a/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs b/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs index ef20a86cefba..e0db638ae63d 100644 --- a/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs +++ b/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs @@ -50,14 +50,16 @@ public void List_HandlesNoSecretsInProject() } [Fact] - public void Create_WarnsOnNoSecretInproject() + public void Create_CreatesSecretOnNoSecretInproject() { var project = Path.Combine(_fixture.CreateProject(false), "TestProject.csproj"); var app = new Program(_console); app.Run(new[] { "create", "--project", project }); - Assert.Contains("Set UserSecretsId to ", _console.GetOutput()); - Assert.Contains("New JWT saved", _console.GetOutput()); + var output = _console.GetOutput(); + Assert.DoesNotContain("could not find SecretManager.targets", output); + Assert.Contains("Set UserSecretsId to ", output); + Assert.Contains("New JWT saved", output); } [Fact] diff --git a/src/Tools/dotnet-user-jwts/test/dotnet-user-jwts.Tests.csproj b/src/Tools/dotnet-user-jwts/test/dotnet-user-jwts.Tests.csproj index 84d7ec58c998..5ad17868a98a 100644 --- a/src/Tools/dotnet-user-jwts/test/dotnet-user-jwts.Tests.csproj +++ b/src/Tools/dotnet-user-jwts/test/dotnet-user-jwts.Tests.csproj @@ -7,10 +7,11 @@ + - \ No newline at end of file + diff --git a/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs b/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs index 5acf359a344f..d6aa7edd15ab 100644 --- a/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs +++ b/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs @@ -1,12 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Linq; -using System.Xml; -using System.Xml.Linq; -using System.Xml.XPath; using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.Tools.Internal; namespace Microsoft.Extensions.SecretManager.Tools.Internal; @@ -72,78 +67,6 @@ public void Execute(CommandContext context, string workingDirectory) public void Execute(CommandContext context) { - CreateUserSecretsId(context.Reporter, ProjectPath, WorkingDirectory, OverrideId); - } - - public static string CreateUserSecretsId(IReporter reporter, string project, string workingDirectory, string overrideId = null) - { - var projectPath = ResolveProjectPath(project, workingDirectory); - - // Load the project file as XML - var projectDocument = XDocument.Load(projectPath, LoadOptions.PreserveWhitespace); - - // Accept the `--id` CLI option to the main app - string newSecretsId = string.IsNullOrWhiteSpace(overrideId) - ? Guid.NewGuid().ToString() - : overrideId; - - // Confirm secret ID does not contain invalid characters - if (Path.GetInvalidPathChars().Any(invalidChar => newSecretsId.Contains(invalidChar))) - { - throw new ArgumentException(Resources.FormatError_InvalidSecretsId(newSecretsId)); - } - - var existingUserSecretsId = projectDocument.XPathSelectElements("//UserSecretsId").FirstOrDefault(); - - // Check if a UserSecretsId is already set - if (existingUserSecretsId is object) - { - // Only set the UserSecretsId if the user specified an explicit value - if (string.IsNullOrWhiteSpace(overrideId)) - { - reporter.Output(Resources.FormatMessage_ProjectAlreadyInitialized(projectPath)); - return existingUserSecretsId.Value; - } - - existingUserSecretsId.SetValue(newSecretsId); - } - else - { - // Find the first non-conditional PropertyGroup - var propertyGroup = projectDocument.Root.DescendantNodes() - .FirstOrDefault(node => node is XElement el - && el.Name == "PropertyGroup" - && el.Attributes().All(attr => - attr.Name != "Condition")) as XElement; - - // No valid property group, create a new one - if (propertyGroup == null) - { - propertyGroup = new XElement("PropertyGroup"); - projectDocument.Root.AddFirst(propertyGroup); - } - - // Add UserSecretsId element - propertyGroup.Add(" "); - propertyGroup.Add(new XElement("UserSecretsId", newSecretsId)); - propertyGroup.Add($"{Environment.NewLine} "); - } - - var settings = new XmlWriterSettings - { - OmitXmlDeclaration = true, - }; - - using var xw = XmlWriter.Create(projectPath, settings); - projectDocument.Save(xw); - - reporter.Output(Resources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, projectPath)); - return newSecretsId; - } - - private static string ResolveProjectPath(string name, string path) - { - var finder = new MsBuildProjectFinder(path); - return finder.FindMsBuildProject(name); + UserSecretsCreator.CreateUserSecretsId(context.Reporter, ProjectPath, WorkingDirectory, OverrideId); } } diff --git a/src/Tools/dotnet-user-secrets/src/Program.cs b/src/Tools/dotnet-user-secrets/src/Program.cs index 362a0701aef4..d6102f51631d 100644 --- a/src/Tools/dotnet-user-secrets/src/Program.cs +++ b/src/Tools/dotnet-user-secrets/src/Program.cs @@ -75,14 +75,10 @@ internal int RunInternal(params string[] args) return 0; } - string userSecretsId; - try - { - userSecretsId = ResolveId(options, reporter); - } - catch (Exception ex) when (ex is InvalidOperationException || ex is FileNotFoundException) + var userSecretsId = ResolveId(options, reporter); + + if (string.IsNullOrEmpty(userSecretsId)) { - reporter.Error(ex.Message); return 1; } diff --git a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj index 1ef774e1c5e7..ba9b4082d50b 100644 --- a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj +++ b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj @@ -16,7 +16,8 @@ - + + diff --git a/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj b/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj index 8a067be65017..87d33c99a122 100644 --- a/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj +++ b/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj @@ -7,12 +7,15 @@ - + + + + + - From 08c54b4465d5a413aa79a13a8e0a0b510db6efb2 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 8 Jun 2022 14:57:14 -0700 Subject: [PATCH 3/4] Add shared localizations strings for secrets --- .../SecretsHelpers/MsBuildProjectFinder.cs | 9 +- .../SecretsHelpers/ProjectIdResolver.cs | 12 +- .../SecretsHelpersResources.resx | 144 ++++++++++++++++++ .../SecretsHelpers/UserSecretsCreator.cs | 11 +- .../src/Helpers/DevJwtCliHelpers.cs | 1 - src/Tools/dotnet-user-jwts/src/Program.cs | 9 +- .../src/dotnet-user-jwts.csproj | 9 +- .../src/dotnet-user-secrets.csproj | 9 +- 8 files changed, 182 insertions(+), 22 deletions(-) create mode 100644 src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx diff --git a/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs b/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs index 3c45443fac73..11aa7bba8534 100644 --- a/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs +++ b/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq; +using Microsoft.AspNetCore.Tools; using Microsoft.Extensions.Tools.Internal; -namespace Microsoft.Extensions.SecretManager.Tools.Internal; - internal sealed class MsBuildProjectFinder { private readonly string _directory; @@ -34,12 +33,12 @@ public string FindMsBuildProject(string project) if (projects.Count > 1) { - throw new FileNotFoundException($"Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option."); + throw new FileNotFoundException(SecretsHelpersResources.FormatError_MultipleProjectsFound(projectPath)); } if (projects.Count == 0) { - throw new FileNotFoundException($"Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option."); + throw new FileNotFoundException(SecretsHelpersResources.FormatError_NoProjectsFound(projectPath)); } return projects[0]; @@ -47,7 +46,7 @@ public string FindMsBuildProject(string project) if (!File.Exists(projectPath)) { - throw new FileNotFoundException($"The project file '{projectPath}' does not exist."); + throw new FileNotFoundException(SecretsHelpersResources.FormatError_NoProjectsFound(projectPath)); } return projectPath; diff --git a/src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs b/src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs index f8305e6a83b4..d9c794586ae4 100644 --- a/src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs +++ b/src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs @@ -6,11 +6,10 @@ using System.IO; using System.Linq; using System.Text; +using Microsoft.AspNetCore.Tools; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; -namespace Microsoft.Extensions.SecretManager.Tools.Internal; - /// /// This API supports infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -42,9 +41,8 @@ public string Resolve(string project, string configuration) _reporter.Error(ex.Message); return null; } - - _reporter.Verbose($"Project file path {projectFile}."); + _reporter.Verbose(SecretsHelpersResources.FormatMessage_Project_File_Path(projectFile)); configuration = !string.IsNullOrEmpty(configuration) ? configuration @@ -108,20 +106,20 @@ public string Resolve(string project, string configuration) _reporter.Verbose(outputBuilder.ToString()); _reporter.Verbose(errorBuilder.ToString()); _reporter.Error($"Exit code: {process.ExitCode}"); - _reporter.Error($"Could not load the MSBuild project '{projectFile}'."); + _reporter.Error(SecretsHelpersResources.FormatError_ProjectFailedToLoad(projectFile)); return null; } if (!File.Exists(outputFile)) { - _reporter.Error($"Could not find the global property 'UserSecretsId' in MSBuild project '{projectFile}'. Ensure this property is set in the project or use the '--id' command line option."); + _reporter.Error(SecretsHelpersResources.FormatError_ProjectMissingId(projectFile)); return null; } var id = File.ReadAllText(outputFile)?.Trim(); if (string.IsNullOrEmpty(id)) { - _reporter.Error($"Could not find the global property 'UserSecretsId' in MSBuild project '{projectFile}'. Ensure this property is set in the project or use the '--id' command line option."); + _reporter.Error(SecretsHelpersResources.FormatError_ProjectMissingId(projectFile)); } return id; diff --git a/src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx b/src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx new file mode 100644 index 000000000000..f53d929e4fa0 --- /dev/null +++ b/src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + The UserSecretsId '{userSecretsId}' cannot contain any characters that cannot be used in a file path. + + + Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option. + + + Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. + + + Could not load the MSBuild project '{project}'. + + + Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option. + + + The MSBuild project '{project}' has already been initialized with a UserSecretsId. + + + Project file path {project}. + + + Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'. + + \ No newline at end of file diff --git a/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs b/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs index 236c97bcf132..10ac7bcf7d39 100644 --- a/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs +++ b/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs @@ -5,10 +5,9 @@ using System.Xml; using System.Xml.Linq; using System.Xml.XPath; +using Microsoft.AspNetCore.Tools; using Microsoft.Extensions.Tools.Internal; -namespace Microsoft.Extensions.SecretManager.Tools.Internal; - internal static class UserSecretsCreator { public static string CreateUserSecretsId(IReporter reporter, string project, string workingDirectory, string overrideId = null) @@ -24,9 +23,9 @@ public static string CreateUserSecretsId(IReporter reporter, string project, str : overrideId; // Confirm secret ID does not contain invalid characters - if (Path.GetInvalidPathChars().Any(invalidChar => newSecretsId.Contains(invalidChar))) + if (Path.GetInvalidPathChars().Any(newSecretsId.Contains)) { - throw new ArgumentException($"The UserSecretsId '{newSecretsId}' cannot contain any characters that cannot be used in a file path."); + throw new ArgumentException(SecretsHelpersResources.FormatError_InvalidSecretsId(newSecretsId)); } var existingUserSecretsId = projectDocument.XPathSelectElements("//UserSecretsId").FirstOrDefault(); @@ -37,7 +36,7 @@ public static string CreateUserSecretsId(IReporter reporter, string project, str // Only set the UserSecretsId if the user specified an explicit value if (string.IsNullOrWhiteSpace(overrideId)) { - reporter.Output($"The MSBuild project '{projectPath}' has already been initialized with a UserSecretsId."); + reporter.Output(SecretsHelpersResources.FormatMessage_ProjectAlreadyInitialized(projectPath)); return existingUserSecretsId.Value; } @@ -73,7 +72,7 @@ public static string CreateUserSecretsId(IReporter reporter, string project, str using var xw = XmlWriter.Create(projectPath, settings); projectDocument.Save(xw); - reporter.Output($"Set UserSecretsId to '{newSecretsId}' for MSBuild project '{projectPath}'."); + reporter.Output(SecretsHelpersResources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, projectPath)); return newSecretsId; } diff --git a/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs b/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs index 27e2b321cf3e..155c581cbf6a 100644 --- a/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs +++ b/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs @@ -6,7 +6,6 @@ using System.Text.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.UserSecrets; -using Microsoft.Extensions.SecretManager.Tools.Internal; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.AspNetCore.Authentication.JwtBearer.Tools; diff --git a/src/Tools/dotnet-user-jwts/src/Program.cs b/src/Tools/dotnet-user-jwts/src/Program.cs index 5d64717ccdb6..8967727ac24e 100644 --- a/src/Tools/dotnet-user-jwts/src/Program.cs +++ b/src/Tools/dotnet-user-jwts/src/Program.cs @@ -47,6 +47,13 @@ public void Run(string[] args) // Show help information if no subcommand/option was specified. userJwts.OnExecute(() => userJwts.ShowHelp()); - userJwts.Execute(args); + try + { + userJwts.Execute(args); + } + catch (Exception ex) + { + _reporter.Error(ex.Message); + } } } diff --git a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj index d32891d9c5e4..53cc0514d7e4 100644 --- a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj +++ b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) exe @@ -18,6 +18,13 @@ + + + Microsoft.AspNetCore.Tools.SecretsHelpersResources + + + + diff --git a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj index ba9b4082d50b..61586af02eff 100644 --- a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj +++ b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -20,6 +20,13 @@ + + + Microsoft.AspNetCore.Tools.SecretsHelpersResources + + + + From 195e5a502fc50f7965906826c4b3de188700bf5f Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 9 Jun 2022 08:55:12 -0700 Subject: [PATCH 4/4] Update localization and address feedback --- src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs | 2 +- src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx | 3 +++ src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs b/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs index 11aa7bba8534..3133799e95d3 100644 --- a/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs +++ b/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs @@ -46,7 +46,7 @@ public string FindMsBuildProject(string project) if (!File.Exists(projectPath)) { - throw new FileNotFoundException(SecretsHelpersResources.FormatError_NoProjectsFound(projectPath)); + throw new FileNotFoundException(SecretsHelpersResources.FormatError_ProjectPath_NotFound(projectPath)); } return projectPath; diff --git a/src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx b/src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx index f53d929e4fa0..bdc7cd88d5e9 100644 --- a/src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx +++ b/src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx @@ -132,6 +132,9 @@ Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option. + + The project file '{0}' does not exist. + The MSBuild project '{project}' has already been initialized with a UserSecretsId. diff --git a/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs b/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs index 10ac7bcf7d39..19c2a6e5c1ee 100644 --- a/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs +++ b/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs @@ -31,7 +31,7 @@ public static string CreateUserSecretsId(IReporter reporter, string project, str var existingUserSecretsId = projectDocument.XPathSelectElements("//UserSecretsId").FirstOrDefault(); // Check if a UserSecretsId is already set - if (existingUserSecretsId is object) + if (existingUserSecretsId is not null) { // Only set the UserSecretsId if the user specified an explicit value if (string.IsNullOrWhiteSpace(overrideId))