Skip to content

Commit af70e57

Browse files
authored
Adds the ability to specify an appsettings file for user-jwts (#58605)
* Adds the ability to specify an appsettings file for user-jwts Addresses #56169 * Move the validation logic into a central location * Adds copilot suggestions to improve error messages * Fix tests after changing error message when appsettings not found
1 parent 089c3d5 commit af70e57

File tree

7 files changed

+215
-21
lines changed

7 files changed

+215
-21
lines changed

src/Tools/dotnet-user-jwts/src/Commands/ClearCommand.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,32 @@ public static void Register(ProjectCommandLineApplication app)
1919
Resources.ClearCommand_ForceOption_Description,
2020
CommandOptionType.NoValue);
2121

22+
var appsettingsFileOption = cmd.Option(
23+
"--appsettings-file",
24+
Resources.CreateCommand_appsettingsFileOption_Description,
25+
CommandOptionType.SingleValue);
26+
2227
cmd.HelpOption("-h|--help");
2328

2429
cmd.OnExecute(() =>
2530
{
26-
return Execute(cmd.Reporter, cmd.ProjectOption.Value(), forceOption.HasValue());
31+
if (!DevJwtCliHelpers.GetProjectAndSecretsId(cmd.ProjectOption.Value(), cmd.Reporter, out var project, out var userSecretsId))
32+
{
33+
return 1;
34+
}
35+
36+
if (!DevJwtCliHelpers.GetAppSettingsFile(project, appsettingsFileOption.Value(), cmd.Reporter, out var appsettingsFile))
37+
{
38+
return 1;
39+
}
40+
41+
return Execute(cmd.Reporter, project, userSecretsId, forceOption.HasValue(), appsettingsFile);
2742
});
2843
});
2944
}
3045

31-
private static int Execute(IReporter reporter, string projectPath, bool force)
46+
private static int Execute(IReporter reporter, string project, string userSecretsId, bool force, string appsettingsFile)
3247
{
33-
if (!DevJwtCliHelpers.GetProjectAndSecretsId(projectPath, reporter, out var project, out var userSecretsId))
34-
{
35-
return 1;
36-
}
3748
var jwtStore = new JwtStore(userSecretsId);
3849
var count = jwtStore.Jwts.Count;
3950

@@ -54,7 +65,7 @@ private static int Execute(IReporter reporter, string projectPath, bool force)
5465
}
5566
}
5667

57-
var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
68+
var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), appsettingsFile);
5869
foreach (var jwt in jwtStore.Jwts)
5970
{
6071
JwtAuthenticationSchemeSettings.RemoveScheme(appsettingsFilePath, jwt.Value.Scheme);

src/Tools/dotnet-user-jwts/src/Commands/CreateCommand.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,29 @@ public static void Register(ProjectCommandLineApplication app, Program program)
7777
Resources.CreateCommand_ValidForOption_Description,
7878
CommandOptionType.SingleValue);
7979

80+
var appsettingsFileOption = cmd.Option(
81+
"--appsettings-file",
82+
Resources.CreateCommand_appsettingsFileOption_Description,
83+
CommandOptionType.SingleValue);
84+
8085
cmd.HelpOption("-h|--help");
8186

8287
cmd.OnExecute(() =>
8388
{
84-
var (options, isValid, optionsString) = ValidateArguments(
85-
cmd.Reporter, cmd.ProjectOption, schemeNameOption, nameOption, audienceOption, issuerOption, notBeforeOption, expiresOnOption, validForOption, rolesOption, scopesOption, claimsOption);
89+
var (options, isValid, optionsString, appsettingsFile) = ValidateArguments(
90+
cmd.Reporter, cmd.ProjectOption, schemeNameOption, nameOption, audienceOption, issuerOption, notBeforeOption, expiresOnOption, validForOption, rolesOption, scopesOption, claimsOption, appsettingsFileOption);
8691

8792
if (!isValid)
8893
{
8994
return 1;
9095
}
9196

92-
return Execute(cmd.Reporter, cmd.ProjectOption.Value(), options, optionsString, cmd.OutputOption.Value(), program);
97+
return Execute(cmd.Reporter, cmd.ProjectOption.Value(), options, optionsString, cmd.OutputOption.Value(), appsettingsFile, program);
9398
});
9499
});
95100
}
96101

97-
private static (JwtCreatorOptions, bool, string) ValidateArguments(
102+
private static (JwtCreatorOptions, bool, string, string) ValidateArguments(
98103
IReporter reporter,
99104
CommandOption projectOption,
100105
CommandOption schemeNameOption,
@@ -106,7 +111,8 @@ private static (JwtCreatorOptions, bool, string) ValidateArguments(
106111
CommandOption validForOption,
107112
CommandOption rolesOption,
108113
CommandOption scopesOption,
109-
CommandOption claimsOption)
114+
CommandOption claimsOption,
115+
CommandOption appsettingsFileOption)
110116
{
111117
var isValid = true;
112118
var finder = new MsBuildProjectFinder(Directory.GetCurrentDirectory());
@@ -121,6 +127,7 @@ private static (JwtCreatorOptions, bool, string) ValidateArguments(
121127
return (
122128
null,
123129
isValid,
130+
string.Empty,
124131
string.Empty
125132
);
126133
}
@@ -209,10 +216,19 @@ private static (JwtCreatorOptions, bool, string) ValidateArguments(
209216
optionsString += $"{Resources.JwtPrint_CustomClaims}: [{string.Join(", ", claims.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]{Environment.NewLine}";
210217
}
211218

219+
var appsettingsFile = DevJwtCliHelpers.DefaultAppSettingsFile;
220+
if (appsettingsFileOption.HasValue())
221+
{
222+
isValid = DevJwtCliHelpers.GetAppSettingsFile(project, appsettingsFileOption.Value(), reporter, out appsettingsFile);
223+
224+
optionsString += appsettingsFileOption.HasValue() ? $"{Resources.JwtPrint_appsettingsFile}: {appsettingsFile}{Environment.NewLine}" : string.Empty;
225+
}
226+
212227
return (
213228
new JwtCreatorOptions(scheme, name, audience, issuer, notBefore, expiresOn, roles, scopes, claims),
214229
isValid,
215-
optionsString);
230+
optionsString,
231+
appsettingsFile);
216232

217233
static bool ParseDate(string datetime, out DateTime parsedDateTime) =>
218234
DateTime.TryParseExact(datetime, _dateTimeFormats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsedDateTime);
@@ -224,6 +240,7 @@ private static int Execute(
224240
JwtCreatorOptions options,
225241
string optionsString,
226242
string outputFormat,
243+
string appsettingsFile,
227244
Program program)
228245
{
229246
if (!DevJwtCliHelpers.GetProjectAndSecretsId(projectPath, reporter, out var project, out var userSecretsId))
@@ -244,7 +261,7 @@ private static int Execute(
244261
jwtStore.Jwts.Add(jwtToken.Id, jwt);
245262
jwtStore.Save();
246263

247-
var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
264+
var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), appsettingsFile);
248265
var settingsToWrite = new JwtAuthenticationSchemeSettings(options.Scheme, options.Audiences, options.Issuer);
249266
settingsToWrite.Save(appsettingsFilePath);
250267

src/Tools/dotnet-user-jwts/src/Commands/RemoveCommand.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ public static void Register(ProjectCommandLineApplication app)
1515
cmd.Description = Resources.RemoveCommand_Description;
1616

1717
var idArgument = cmd.Argument("[id]", Resources.RemoveCommand_IdArgument_Description);
18+
19+
var appsettingsFileOption = cmd.Option(
20+
"--appsettings-file",
21+
Resources.CreateCommand_appsettingsFileOption_Description,
22+
CommandOptionType.SingleValue);
23+
1824
cmd.HelpOption("-h|--help");
1925

2026
cmd.OnExecute(() =>
@@ -24,17 +30,24 @@ public static void Register(ProjectCommandLineApplication app)
2430
cmd.ShowHelp();
2531
return 0;
2632
}
27-
return Execute(cmd.Reporter, cmd.ProjectOption.Value(), idArgument.Value);
33+
34+
if (!DevJwtCliHelpers.GetProjectAndSecretsId(cmd.ProjectOption.Value(), cmd.Reporter, out var project, out var userSecretsId))
35+
{
36+
return 1;
37+
}
38+
39+
if (!DevJwtCliHelpers.GetAppSettingsFile(project, appsettingsFileOption.Value(), cmd.Reporter, out var appsettingsFile))
40+
{
41+
return 1;
42+
}
43+
44+
return Execute(cmd.Reporter, project, userSecretsId, idArgument.Value, appsettingsFile);
2845
});
2946
});
3047
}
3148

32-
private static int Execute(IReporter reporter, string projectPath, string id)
49+
private static int Execute(IReporter reporter, string project, string userSecretsId, string id, string appsettingsFile)
3350
{
34-
if (!DevJwtCliHelpers.GetProjectAndSecretsId(projectPath, reporter, out var project, out var userSecretsId))
35-
{
36-
return 1;
37-
}
3851
var jwtStore = new JwtStore(userSecretsId);
3952

4053
if (!jwtStore.Jwts.TryGetValue(id, out var jwt))
@@ -43,7 +56,7 @@ private static int Execute(IReporter reporter, string projectPath, string id)
4356
return 1;
4457
}
4558

46-
var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
59+
var appsettingsFilePath = Path.Combine(Path.GetDirectoryName(project), appsettingsFile);
4760
JwtAuthenticationSchemeSettings.RemoveScheme(appsettingsFilePath, jwt.Scheme);
4861
jwtStore.Jwts.Remove(id);
4962
jwtStore.Save();

src/Tools/dotnet-user-jwts/src/Helpers/DevJwtCliHelpers.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer.Tools;
1111

1212
internal static class DevJwtCliHelpers
1313
{
14+
public const string DefaultAppSettingsFile = "appsettings.Development.json";
15+
1416
public static string GetOrSetUserSecretsId(string projectFilePath)
1517
{
1618
var resolver = new ProjectIdResolver(NullReporter.Singleton, projectFilePath);
@@ -42,6 +44,27 @@ public static bool GetProjectAndSecretsId(string projectPath, IReporter reporter
4244
return true;
4345
}
4446

47+
public static bool GetAppSettingsFile(string projectPath, string appsettingsFileOption, IReporter reporter, out string appsettingsFile)
48+
{
49+
appsettingsFile = DevJwtCliHelpers.DefaultAppSettingsFile;
50+
if (appsettingsFileOption is not null)
51+
{
52+
appsettingsFile = appsettingsFileOption;
53+
if (!appsettingsFile.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
54+
{
55+
reporter.Error(Resources.RemoveCommand_InvalidAppsettingsFile_Error);
56+
return false;
57+
}
58+
else if (!File.Exists(Path.Combine(Path.GetDirectoryName(projectPath), appsettingsFile)))
59+
{
60+
reporter.Error(Resources.FormatRemoveCommand_AppsettingsFileNotFound_Error(Path.Combine(Path.GetDirectoryName(projectPath), appsettingsFile)));
61+
return false;
62+
}
63+
}
64+
65+
return true;
66+
}
67+
4568
public static List<string> GetAudienceCandidatesFromLaunchSettings(string project)
4669
{
4770
if (string.IsNullOrEmpty(project))
@@ -213,4 +236,5 @@ public static bool IsNullOrEmpty<T>(this IEnumerable<T> enumerable)
213236
{
214237
return enumerable == null || !enumerable.Any();
215238
}
239+
216240
}

src/Tools/dotnet-user-jwts/src/Resources.resx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@
150150
<data name="CreateCommand_ExpiresOnOption_Description" xml:space="preserve">
151151
<value>The UTC date &amp; 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.</value>
152152
</data>
153+
<data name="CreateCommand_InvalidAppsettingsFile_Error" xml:space="preserve">
154+
<value>Invalid Appsettings file extension. Ensure file extension is .json.</value>
155+
</data>
156+
<data name="CreateCommand_AppsettingsFileNotFound_Error" xml:space="preserve">
157+
<value>Could not find Appsettings file in '{0}'. Check the filename and that the file exists.</value>
158+
</data>
153159
<data name="CreateCommand_InvalidClaims_Error" xml:space="preserve">
154160
<value>Malformed claims supplied. Ensure each claim is in the format "name=value".</value>
155161
</data>
@@ -189,6 +195,9 @@
189195
<data name="CreateCommand_ValidForOption_Description" xml:space="preserve">
190196
<value>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.</value>
191197
</data>
198+
<data name="CreateCommand_appsettingsFileOption_Description" xml:space="preserve">
199+
<value>The appSettings configuration file to add the test scheme to.</value>
200+
</data>
192201
<data name="JwtPrint_Audiences" xml:space="preserve">
193202
<value>Audience(s)</value>
194203
</data>
@@ -225,6 +234,9 @@
225234
<data name="JwtPrint_Scopes" xml:space="preserve">
226235
<value>Scopes</value>
227236
</data>
237+
<data name="JwtPrint_appsettingsFile" xml:space="preserve">
238+
<value>Appsettings File</value>
239+
</data>
228240
<data name="JwtPrint_Token" xml:space="preserve">
229241
<value>Token</value>
230242
</data>
@@ -312,6 +324,12 @@
312324
<data name="RemoveCommand_NoJwtFound" xml:space="preserve">
313325
<value>No JWT with ID '{0}' found.</value>
314326
</data>
327+
<data name="RemoveCommand_InvalidAppsettingsFile_Error" xml:space="preserve">
328+
<value>The specified appsettings file is invalid. Please provide a valid JSON file.</value>
329+
</data>
330+
<data name="RemoveCommand_AppsettingsFileNotFound_Error" xml:space="preserve">
331+
<value>Could not find Appsettings file '{0}'. Check the filename and that the file exists.</value>
332+
</data>
315333
<data name="KeyCommand_IssuerOption_Description" xml:space="preserve">
316334
<value>The issuer associated with the signing key to be reset or displayed. Defaults to 'dotnet-user-jwts'.</value>
317335
</data>

src/Tools/dotnet-user-jwts/test/UserJwtsTestFixture.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ public string CreateProject(bool hasSecret = true)
8181
Path.Combine(projectPath.FullName, "appsettings.Development.json"),
8282
"{}");
8383

84+
File.WriteAllText(
85+
Path.Combine(projectPath.FullName, "appsettings.Local.json"),
86+
"{}");
87+
8488
if (hasSecret)
8589
{
8690
_disposables.Push(() =>

0 commit comments

Comments
 (0)