From baf11a1275bbddc19765135902417d808740e8ed Mon Sep 17 00:00:00 2001
From: Chris <362037+ChrisAnn@users.noreply.github.com>
Date: Thu, 24 Oct 2024 09:42:20 +0100
Subject: [PATCH] Adds the ability to specify an appsettings file for user-jwts
Addresses https://github.com/dotnet/aspnetcore/issues/56169
---
.../src/Commands/ClearCommand.cs | 36 ++++--
.../src/Commands/CreateCommand.cs | 43 ++++++--
.../src/Commands/RemoveCommand.cs | 38 +++++--
src/Tools/dotnet-user-jwts/src/Resources.resx | 18 +++
.../test/UserJwtsTestFixture.cs | 4 +
.../dotnet-user-jwts/test/UserJwtsTests.cs | 104 ++++++++++++++++++
6 files changed, 222 insertions(+), 21 deletions(-)
diff --git a/src/Tools/dotnet-user-jwts/src/Commands/ClearCommand.cs b/src/Tools/dotnet-user-jwts/src/Commands/ClearCommand.cs
index 2b977e3f6313..97d91cf74a4f 100644
--- a/src/Tools/dotnet-user-jwts/src/Commands/ClearCommand.cs
+++ b/src/Tools/dotnet-user-jwts/src/Commands/ClearCommand.cs
@@ -19,21 +19,43 @@ public static void Register(ProjectCommandLineApplication app)
Resources.ClearCommand_ForceOption_Description,
CommandOptionType.NoValue);
+ var appsettingsFileOption = cmd.Option(
+ "--appsettings-file",
+ Resources.CreateCommand_appsettingsFileOption_Description,
+ CommandOptionType.SingleValue);
+
cmd.HelpOption("-h|--help");
cmd.OnExecute(() =>
{
- return Execute(cmd.Reporter, cmd.ProjectOption.Value(), forceOption.HasValue());
+ if (!DevJwtCliHelpers.GetProjectAndSecretsId(cmd.ProjectOption.Value(), cmd.Reporter, out var project, out var userSecretsId))
+ {
+ return 1;
+ }
+
+ var appsettingsFile = "appsettings.Development.json";
+ if (appsettingsFileOption.HasValue())
+ {
+ appsettingsFile = appsettingsFileOption.Value();
+ if (!appsettingsFile.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
+ {
+ cmd.Reporter.Error(Resources.RemoveCommand_InvalidAppsettingsFile_Error);
+ return 1;
+ }
+ else if (!File.Exists(Path.Combine(Path.GetDirectoryName(project), appsettingsFile)))
+ {
+ cmd.Reporter.Error(Resources.FormatRemoveCommand_AppsettingsFileNotFound_Error(Path.GetDirectoryName(project)));
+ return 1;
+ }
+ }
+
+ return Execute(cmd.Reporter, project, userSecretsId, forceOption.HasValue(), appsettingsFile);
});
});
}
- private static int Execute(IReporter reporter, string projectPath, bool force)
+ private static int Execute(IReporter reporter, string project, string userSecretsId, bool force, string appsettingsFile)
{
- if (!DevJwtCliHelpers.GetProjectAndSecretsId(projectPath, reporter, out var project, out var userSecretsId))
- {
- return 1;
- }
var jwtStore = new JwtStore(userSecretsId);
var count = jwtStore.Jwts.Count;
@@ -54,7 +76,7 @@ private static int Execute(IReporter reporter, string projectPath, bool force)
}
}
- var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
+ var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), appsettingsFile);
foreach (var jwt in jwtStore.Jwts)
{
JwtAuthenticationSchemeSettings.RemoveScheme(appsettingsFilePath, jwt.Value.Scheme);
diff --git a/src/Tools/dotnet-user-jwts/src/Commands/CreateCommand.cs b/src/Tools/dotnet-user-jwts/src/Commands/CreateCommand.cs
index ef3a3b833655..1baac76fa888 100644
--- a/src/Tools/dotnet-user-jwts/src/Commands/CreateCommand.cs
+++ b/src/Tools/dotnet-user-jwts/src/Commands/CreateCommand.cs
@@ -77,24 +77,29 @@ public static void Register(ProjectCommandLineApplication app, Program program)
Resources.CreateCommand_ValidForOption_Description,
CommandOptionType.SingleValue);
+ var appsettingsFileOption = cmd.Option(
+ "--appsettings-file",
+ Resources.CreateCommand_appsettingsFileOption_Description,
+ CommandOptionType.SingleValue);
+
cmd.HelpOption("-h|--help");
cmd.OnExecute(() =>
{
- var (options, isValid, optionsString) = ValidateArguments(
- cmd.Reporter, cmd.ProjectOption, schemeNameOption, nameOption, audienceOption, issuerOption, notBeforeOption, expiresOnOption, validForOption, rolesOption, scopesOption, claimsOption);
+ var (options, isValid, optionsString, appsettingsFile) = ValidateArguments(
+ cmd.Reporter, cmd.ProjectOption, schemeNameOption, nameOption, audienceOption, issuerOption, notBeforeOption, expiresOnOption, validForOption, rolesOption, scopesOption, claimsOption, appsettingsFileOption);
if (!isValid)
{
return 1;
}
- return Execute(cmd.Reporter, cmd.ProjectOption.Value(), options, optionsString, cmd.OutputOption.Value(), program);
+ return Execute(cmd.Reporter, cmd.ProjectOption.Value(), options, optionsString, cmd.OutputOption.Value(), appsettingsFile, program);
});
});
}
- private static (JwtCreatorOptions, bool, string) ValidateArguments(
+ private static (JwtCreatorOptions, bool, string, string) ValidateArguments(
IReporter reporter,
CommandOption projectOption,
CommandOption schemeNameOption,
@@ -106,7 +111,8 @@ private static (JwtCreatorOptions, bool, string) ValidateArguments(
CommandOption validForOption,
CommandOption rolesOption,
CommandOption scopesOption,
- CommandOption claimsOption)
+ CommandOption claimsOption,
+ CommandOption appsettingsFileOption)
{
var isValid = true;
var finder = new MsBuildProjectFinder(Directory.GetCurrentDirectory());
@@ -121,6 +127,7 @@ private static (JwtCreatorOptions, bool, string) ValidateArguments(
return (
null,
isValid,
+ string.Empty,
string.Empty
);
}
@@ -209,10 +216,31 @@ private static (JwtCreatorOptions, bool, string) ValidateArguments(
optionsString += $"{Resources.JwtPrint_CustomClaims}: [{string.Join(", ", claims.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]{Environment.NewLine}";
}
+ var appsettingsFile = "appsettings.Development.json";
+ if (appsettingsFileOption.HasValue())
+ {
+ appsettingsFile = appsettingsFileOption.Value();
+ if (!appsettingsFile.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
+ {
+ reporter.Error(Resources.CreateCommand_InvalidAppsettingsFile_Error);
+ isValid = false;
+ }
+ else if (!File.Exists(Path.Combine(Path.GetDirectoryName(project), appsettingsFile)))
+ {
+ reporter.Error(Resources.FormatCreateCommand_AppsettingsFileNotFound_Error(Path.GetDirectoryName(project)));
+ isValid = false;
+ }
+ else
+ {
+ optionsString += appsettingsFileOption.HasValue() ? $"{Resources.JwtPrint_appsettingsFile}: {appsettingsFile}{Environment.NewLine}" : string.Empty;
+ }
+ }
+
return (
new JwtCreatorOptions(scheme, name, audience, issuer, notBefore, expiresOn, roles, scopes, claims),
isValid,
- optionsString);
+ optionsString,
+ appsettingsFile);
static bool ParseDate(string datetime, out DateTime parsedDateTime) =>
DateTime.TryParseExact(datetime, _dateTimeFormats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsedDateTime);
@@ -224,6 +252,7 @@ private static int Execute(
JwtCreatorOptions options,
string optionsString,
string outputFormat,
+ string appsettingsFile,
Program program)
{
if (!DevJwtCliHelpers.GetProjectAndSecretsId(projectPath, reporter, out var project, out var userSecretsId))
@@ -244,7 +273,7 @@ private static int Execute(
jwtStore.Jwts.Add(jwtToken.Id, jwt);
jwtStore.Save();
- var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
+ var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), appsettingsFile);
var settingsToWrite = new JwtAuthenticationSchemeSettings(options.Scheme, options.Audiences, options.Issuer);
settingsToWrite.Save(appsettingsFilePath);
diff --git a/src/Tools/dotnet-user-jwts/src/Commands/RemoveCommand.cs b/src/Tools/dotnet-user-jwts/src/Commands/RemoveCommand.cs
index 0c3a69eaf40e..27c2fdad1a42 100644
--- a/src/Tools/dotnet-user-jwts/src/Commands/RemoveCommand.cs
+++ b/src/Tools/dotnet-user-jwts/src/Commands/RemoveCommand.cs
@@ -15,6 +15,12 @@ public static void Register(ProjectCommandLineApplication app)
cmd.Description = Resources.RemoveCommand_Description;
var idArgument = cmd.Argument("[id]", Resources.RemoveCommand_IdArgument_Description);
+
+ var appsettingsFileOption = cmd.Option(
+ "--appsettings-file",
+ Resources.CreateCommand_appsettingsFileOption_Description,
+ CommandOptionType.SingleValue);
+
cmd.HelpOption("-h|--help");
cmd.OnExecute(() =>
@@ -24,17 +30,35 @@ public static void Register(ProjectCommandLineApplication app)
cmd.ShowHelp();
return 0;
}
- return Execute(cmd.Reporter, cmd.ProjectOption.Value(), idArgument.Value);
+
+ if (!DevJwtCliHelpers.GetProjectAndSecretsId(cmd.ProjectOption.Value(), cmd.Reporter, out var project, out var userSecretsId))
+ {
+ return 1;
+ }
+
+ var appsettingsFile = "appsettings.Development.json";
+ if (appsettingsFileOption.HasValue())
+ {
+ appsettingsFile = appsettingsFileOption.Value();
+ if (!appsettingsFile.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
+ {
+ cmd.Reporter.Error(Resources.RemoveCommand_InvalidAppsettingsFile_Error);
+ return 1;
+ }
+ else if (!File.Exists(Path.Combine(Path.GetDirectoryName(project), appsettingsFile)))
+ {
+ cmd.Reporter.Error(Resources.FormatRemoveCommand_AppsettingsFileNotFound_Error(Path.GetDirectoryName(project)));
+ return 1;
+ }
+ }
+
+ return Execute(cmd.Reporter, project, userSecretsId, idArgument.Value, appsettingsFile);
});
});
}
- private static int Execute(IReporter reporter, string projectPath, string id)
+ private static int Execute(IReporter reporter, string project, string userSecretsId, string id, string appsettingsFile)
{
- if (!DevJwtCliHelpers.GetProjectAndSecretsId(projectPath, reporter, out var project, out var userSecretsId))
- {
- return 1;
- }
var jwtStore = new JwtStore(userSecretsId);
if (!jwtStore.Jwts.TryGetValue(id, out var jwt))
@@ -43,7 +67,7 @@ private static int Execute(IReporter reporter, string projectPath, string id)
return 1;
}
- var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
+ var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), appsettingsFile);
JwtAuthenticationSchemeSettings.RemoveScheme(appsettingsFilePath, jwt.Scheme);
jwtStore.Jwts.Remove(id);
jwtStore.Save();
diff --git a/src/Tools/dotnet-user-jwts/src/Resources.resx b/src/Tools/dotnet-user-jwts/src/Resources.resx
index 094a066418d9..64b475f88309 100644
--- a/src/Tools/dotnet-user-jwts/src/Resources.resx
+++ b/src/Tools/dotnet-user-jwts/src/Resources.resx
@@ -150,6 +150,12 @@
The UTC date & time the JWT should expire in the format 'yyyy-MM-dd [[[[HH:mm]]:ss]]'. Defaults to 3 months after the --not-before date. Do not use this option in conjunction with the --valid-for option.
+
+ Invalid Appsettings file extension. Ensure file extension is .json.
+
+
+ Could not find Appsettings file in '{0}'. Check the filename and that the file exists.
+
Malformed claims supplied. Ensure each claim is in the format "name=value".
@@ -189,6 +195,9 @@
The period the JWT should expire after. Specify using a number followed by a duration type like 'd' for days, 'h' for hours, 'm' for minutes, and 's' for seconds, e.g. '365d'. Do not use this option in conjunction with the --expires-on option.
+
+ The appSettings configuration file to add the test scheme to.
+
Audience(s)
@@ -225,6 +234,9 @@
Scopes
+
+ Appsettings File
+
Token
@@ -312,6 +324,12 @@
No JWT with ID '{0}' found.
+
+ Invalid Appsettings file extension. Ensure file extension is .json.
+
+
+ Could not find Appsettings file in '{0}'. Check the filename and that the file exists.
+
The issuer associated with the signing key to be reset or displayed. Defaults to 'dotnet-user-jwts'.
diff --git a/src/Tools/dotnet-user-jwts/test/UserJwtsTestFixture.cs b/src/Tools/dotnet-user-jwts/test/UserJwtsTestFixture.cs
index 946530df7c6a..b812a017255c 100644
--- a/src/Tools/dotnet-user-jwts/test/UserJwtsTestFixture.cs
+++ b/src/Tools/dotnet-user-jwts/test/UserJwtsTestFixture.cs
@@ -81,6 +81,10 @@ public string CreateProject(bool hasSecret = true)
Path.Combine(projectPath.FullName, "appsettings.Development.json"),
"{}");
+ File.WriteAllText(
+ Path.Combine(projectPath.FullName, "appsettings.Local.json"),
+ "{}");
+
if (hasSecret)
{
_disposables.Push(() =>
diff --git a/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs b/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs
index 71cc2cdb7d10..b37355d79408 100644
--- a/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs
+++ b/src/Tools/dotnet-user-jwts/test/UserJwtsTests.cs
@@ -79,6 +79,23 @@ public void Create_CanModifyExistingScheme()
Assert.Equal("new-issuer", appSettings["Authentication"]["Schemes"]["Bearer"]["ValidIssuer"].GetValue());
}
+ [Fact]
+ public void Create_CanModifyExistingSchemeInGivenAppSettings()
+ {
+ var project = Path.Combine(fixture.CreateProject(), "TestProject.csproj");
+ var appsettings = Path.Combine(Path.GetDirectoryName(project), "appsettings.Local.json");
+ var app = new Program(_console);
+
+ app.Run(new[] { "create", "--project", project, "--appsettings-file", "appsettings.Local.json" });
+ Assert.Contains("New JWT saved", _console.GetOutput());
+
+ var appSettings = JsonSerializer.Deserialize(File.ReadAllText(appsettings));
+ Assert.Equal("dotnet-user-jwts", appSettings["Authentication"]["Schemes"]["Bearer"]["ValidIssuer"].GetValue());
+ app.Run(["create", "--project", project, "--issuer", "new-issuer", "--appsettings-file", "appsettings.Local.json"]);
+ appSettings = JsonSerializer.Deserialize(File.ReadAllText(appsettings));
+ Assert.Equal("new-issuer", appSettings["Authentication"]["Schemes"]["Bearer"]["ValidIssuer"].GetValue());
+ }
+
[Fact]
public void Print_ReturnsNothingForMissingToken()
{
@@ -154,6 +171,24 @@ public void Remove_RemovesGeneratedToken()
Assert.Contains("Scheme2", appsettingsContent);
}
+ [Fact]
+ public void Remove_RemovesGeneratedTokenInGivenAppsettings()
+ {
+ var project = Path.Combine(fixture.CreateProject(), "TestProject.csproj");
+ var appsettings = Path.Combine(Path.GetDirectoryName(project), "appsettings.Local.json");
+ var app = new Program(_console);
+
+ app.Run(new[] { "create", "--project", project, "--appsettings-file", "appsettings.Local.json" });
+ var matches = Regex.Matches(_console.GetOutput(), "New JWT saved with ID '(.*?)'");
+ var id = matches.SingleOrDefault().Groups[1].Value;
+ app.Run(new[] { "create", "--project", project, "--appsettings-file", "appsettings.Local.json", "--scheme", "Scheme2" });
+
+ app.Run(new[] { "remove", id, "--project", project, "--appsettings-file", "appsettings.Local.json" });
+ var appsettingsContent = File.ReadAllText(appsettings);
+ Assert.DoesNotContain(DevJwtsDefaults.Scheme, appsettingsContent);
+ Assert.Contains("Scheme2", appsettingsContent);
+ }
+
[Fact]
public void Clear_RemovesGeneratedTokens()
{
@@ -172,6 +207,24 @@ public void Clear_RemovesGeneratedTokens()
Assert.DoesNotContain("Scheme2", appsettingsContent);
}
+ [Fact]
+ public void Clear_RemovesGeneratedTokensInGivenAppsettings()
+ {
+ var project = Path.Combine(fixture.CreateProject(), "TestProject.csproj");
+ var appsettings = Path.Combine(Path.GetDirectoryName(project), "appsettings.Local.json");
+ var app = new Program(_console);
+
+ app.Run(new[] { "create", "--project", project, "--appsettings-file", "appsettings.Local.json" });
+ app.Run(new[] { "create", "--project", project, "--appsettings-file", "appsettings.Local.json", "--scheme", "Scheme2" });
+
+ Assert.Contains("New JWT saved", _console.GetOutput());
+
+ app.Run(new[] { "clear", "--project", project, "--appsettings-file", "appsettings.Local.json", "--force" });
+ var appsettingsContent = File.ReadAllText(appsettings);
+ Assert.DoesNotContain(DevJwtsDefaults.Scheme, appsettingsContent);
+ Assert.DoesNotContain("Scheme2", appsettingsContent);
+ }
+
[Fact]
public void Key_CanResetSigningKey()
{
@@ -625,6 +678,18 @@ public void Create_CanHandleNoProjectOptionProvided_WithNoProjects()
Assert.DoesNotContain(Resources.CreateCommand_NoAudience_Error, _console.GetOutput());
}
+ [Fact]
+ public void Create_CanHandleAppsettingsOption_WithNoFile()
+ {
+ var projectPath = fixture.CreateProject();
+ Directory.SetCurrentDirectory(projectPath);
+
+ var app = new Program(_console);
+ app.Run(["create", "--appsettings-file", "appsettings.DoesNotExist.json"]);
+
+ Assert.Contains($"Could not find Appsettings file in '{Directory.GetCurrentDirectory()}'. Check the filename and that the file exists.", _console.GetOutput());
+ }
+
[Fact]
public void Delete_CanHandleNoProjectOptionProvided_WithNoProjects()
{
@@ -637,6 +702,18 @@ public void Delete_CanHandleNoProjectOptionProvided_WithNoProjects()
Assert.Contains($"Could not find a MSBuild project file in '{Directory.GetCurrentDirectory()}'. Specify which project to use with the --project option.", _console.GetOutput());
}
+ [Fact]
+ public void Delete_CanHandleAppsettingsOption_WithNoFile()
+ {
+ var projectPath = fixture.CreateProject();
+ Directory.SetCurrentDirectory(projectPath);
+
+ var app = new Program(_console);
+ app.Run(["remove", "some-id", "--appsettings-file", "appsettings.DoesNotExist.json"]);
+
+ Assert.Contains($"Could not find Appsettings file in '{Directory.GetCurrentDirectory()}'. Check the filename and that the file exists.", _console.GetOutput());
+ }
+
[Fact]
public void Clear_CanHandleNoProjectOptionProvided_WithNoProjects()
{
@@ -649,6 +726,18 @@ public void Clear_CanHandleNoProjectOptionProvided_WithNoProjects()
Assert.Contains($"Could not find a MSBuild project file in '{Directory.GetCurrentDirectory()}'. Specify which project to use with the --project option.", _console.GetOutput());
}
+ [Fact]
+ public void Clear_CanHandleAppsettingsOption_WithNoFile()
+ {
+ var projectPath = fixture.CreateProject();
+ Directory.SetCurrentDirectory(projectPath);
+
+ var app = new Program(_console);
+ app.Run(["clear", "--appsettings-file", "appsettings.DoesNotExist.json"]);
+
+ Assert.Contains($"Could not find Appsettings file in '{Directory.GetCurrentDirectory()}'. Check the filename and that the file exists.", _console.GetOutput());
+ }
+
[Fact]
public void List_CanHandleNoProjectOptionProvided_WithNoProjects()
{
@@ -704,6 +793,21 @@ public void Create_CanHandleRelativePathAsOption()
Assert.Contains("New JWT saved", _console.GetOutput());
}
+ [Fact]
+ public void Create_CanHandleRelativePathAsOptionForAppsettingsOption()
+ {
+ var projectPath = fixture.CreateProject();
+ var tempPath = Path.GetTempPath();
+ var targetPath = Path.GetRelativePath(tempPath, projectPath);
+ Directory.SetCurrentDirectory(tempPath);
+
+ var app = new Program(_console);
+ app.Run(new[] { "create", "--project", targetPath, "--appsettings-file", "appsettings.Local.json" });
+
+ Assert.DoesNotContain($"Could not find Appsettings file in '{projectPath}'. Check the filename and that the file exists.", _console.GetOutput());
+ Assert.Contains("New JWT saved", _console.GetOutput());
+ }
+
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "UnixFileMode is not supported on Windows.")]
public void Create_CreatesFileWithUserOnlyUnixFileMode()