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..3133799e95d3 100644
--- a/src/Tools/dotnet-user-secrets/src/Internal/MsBuildProjectFinder.cs
+++ b/src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs
@@ -1,13 +1,10 @@
// 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.AspNetCore.Tools;
using Microsoft.Extensions.Tools.Internal;
-namespace Microsoft.Extensions.SecretManager.Tools.Internal;
-
internal sealed class MsBuildProjectFinder
{
private readonly string _directory;
@@ -36,12 +33,12 @@ public string FindMsBuildProject(string project)
if (projects.Count > 1)
{
- throw new FileNotFoundException(Resources.FormatError_MultipleProjectsFound(projectPath));
+ throw new FileNotFoundException(SecretsHelpersResources.FormatError_MultipleProjectsFound(projectPath));
}
if (projects.Count == 0)
{
- throw new FileNotFoundException(Resources.FormatError_NoProjectsFound(projectPath));
+ throw new FileNotFoundException(SecretsHelpersResources.FormatError_NoProjectsFound(projectPath));
}
return projects[0];
@@ -49,7 +46,7 @@ public string FindMsBuildProject(string project)
if (!File.Exists(projectPath))
{
- throw new FileNotFoundException(Resources.FormatError_ProjectPath_NotFound(projectPath));
+ throw new FileNotFoundException(SecretsHelpersResources.FormatError_ProjectPath_NotFound(projectPath));
}
return projectPath;
diff --git a/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs b/src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs
similarity index 85%
rename from src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs
rename to src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs
index 95657b8c0ba8..d9c794586ae4 100644
--- a/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs
+++ b/src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs
@@ -6,16 +6,15 @@
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.
///
-public class ProjectIdResolver
+internal sealed class ProjectIdResolver
{
private const string DefaultConfig = "Debug";
private readonly IReporter _reporter;
@@ -32,9 +31,18 @@ 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(SecretsHelpersResources.FormatMessage_Project_File_Path(projectFile));
configuration = !string.IsNullOrEmpty(configuration)
? configuration
@@ -98,18 +106,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(SecretsHelpersResources.FormatError_ProjectFailedToLoad(projectFile));
+ return null;
}
if (!File.Exists(outputFile))
{
- throw new InvalidOperationException(Resources.FormatError_ProjectMissingId(projectFile));
+ _reporter.Error(SecretsHelpersResources.FormatError_ProjectMissingId(projectFile));
+ return null;
}
var id = File.ReadAllText(outputFile)?.Trim();
if (string.IsNullOrEmpty(id))
{
- throw new InvalidOperationException(Resources.FormatError_ProjectMissingId(projectFile));
+ _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..bdc7cd88d5e9
--- /dev/null
+++ b/src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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 project file '{0}' does not exist.
+
+
+ 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
new file mode 100644
index 000000000000..19c2a6e5c1ee
--- /dev/null
+++ b/src/Tools/Shared/SecretsHelpers/UserSecretsCreator.cs
@@ -0,0 +1,84 @@
+// 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.AspNetCore.Tools;
+using Microsoft.Extensions.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(newSecretsId.Contains))
+ {
+ throw new ArgumentException(SecretsHelpersResources.FormatError_InvalidSecretsId(newSecretsId));
+ }
+
+ var existingUserSecretsId = projectDocument.XPathSelectElements("//UserSecretsId").FirstOrDefault();
+
+ // Check if a UserSecretsId is already set
+ if (existingUserSecretsId is not null)
+ {
+ // Only set the UserSecretsId if the user specified an explicit value
+ if (string.IsNullOrWhiteSpace(overrideId))
+ {
+ reporter.Output(SecretsHelpersResources.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(SecretsHelpersResources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, 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 e34a017553c3..155c581cbf6a 100644
--- a/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs
+++ b/src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs
@@ -4,8 +4,6 @@
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.Tools.Internal;
@@ -14,17 +12,15 @@ 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);
+ var id = resolver.Resolve(projectFilePath, configuration: null);
+ if (string.IsNullOrEmpty(id))
{
- return null;
+ return UserSecretsCreator.CreateUserSecretsId(reporter, projectFilePath, projectFilePath);
}
-
- return existingUserSecretsId.Value;
+ return id;
}
public static string GetProject(string projectPath = null)
@@ -54,7 +50,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 +81,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/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 93b34c52c64b..53cc0514d7e4 100644
--- a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj
+++ b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj
@@ -14,6 +14,15 @@
+
+
+
+
+
+
+ Microsoft.AspNetCore.Tools.SecretsHelpersResources
+
+
diff --git a/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs b/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs
index 413b8503987e..e0db638ae63d 100644
--- a/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs
+++ b/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs
@@ -45,17 +45,21 @@ 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]
- 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("Project does not contain a user secrets ID.", _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 166847392504..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,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 System.Xml;
-using System.Xml.Linq;
-using System.Xml.XPath;
using Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.Extensions.SecretManager.Tools.Internal;
@@ -73,72 +67,6 @@ public void Execute(CommandContext context, string workingDirectory)
public void Execute(CommandContext context)
{
- var projectPath = ResolveProjectPath(ProjectPath, 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))
- {
- context.Reporter.Output(Resources.FormatMessage_ProjectAlreadyInitialized(projectPath));
- return;
- }
-
- 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);
-
- context.Reporter.Output(Resources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, projectPath));
- }
-
- 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..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)
@@ -16,7 +16,15 @@
-
+
+
+
+
+
+
+ Microsoft.AspNetCore.Tools.SecretsHelpersResources
+
+
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..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
@@ -1,4 +1,4 @@
-
+
$(DefaultNetCoreTargetFramework)
@@ -7,7 +7,7 @@
-
+