Skip to content

Commit a0244b2

Browse files
authored
Add support for JSON output to more commands (#45383)
* Add support for JSON output to more commands * Tweak test
1 parent 792e021 commit a0244b2

File tree

6 files changed

+151
-46
lines changed

6 files changed

+151
-46
lines changed

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

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

80-
var outputOption = cmd.Option(
81-
"-o|--output",
82-
Resources.CreateCommand_OutputOption_Description,
83-
CommandOptionType.SingleValue);
84-
8580
cmd.HelpOption("-h|--help");
8681

8782
cmd.OnExecute(() =>
@@ -94,7 +89,7 @@ public static void Register(ProjectCommandLineApplication app, Program program)
9489
return 1;
9590
}
9691

97-
return Execute(cmd.Reporter, cmd.ProjectOption.Value(), options, optionsString, outputOption.Value(), program);
92+
return Execute(cmd.Reporter, cmd.ProjectOption.Value(), options, optionsString, cmd.OutputOption.Value(), program);
9893
});
9994
});
10095
}

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Microsoft.Extensions.CommandLineUtils;
55
using Microsoft.Extensions.Tools.Internal;
6+
using System.Text.Json;
67

78
namespace Microsoft.AspNetCore.Authentication.JwtBearer.Tools;
89

@@ -23,19 +24,46 @@ public static void Register(ProjectCommandLineApplication app)
2324

2425
cmd.OnExecute(() =>
2526
{
26-
return Execute(cmd.Reporter, cmd.ProjectOption.Value(), showTokensOption.HasValue());
27+
return Execute(cmd.Reporter, cmd.ProjectOption.Value(), showTokensOption.HasValue(), cmd.OutputOption.Value());
2728
});
2829
});
2930
}
3031

31-
private static int Execute(IReporter reporter, string projectPath, bool showTokens)
32+
private static int Execute(IReporter reporter, string projectPath, bool showTokens, string outputFormat)
3233
{
3334
if (!DevJwtCliHelpers.GetProjectAndSecretsId(projectPath, reporter, out var project, out var userSecretsId))
3435
{
3536
return 1;
3637
}
3738
var jwtStore = new JwtStore(userSecretsId);
3839

40+
switch (outputFormat)
41+
{
42+
case "json":
43+
PrintJwtsJson(reporter, jwtStore);
44+
break;
45+
default:
46+
PrintJwtsDefault(reporter, project, userSecretsId, jwtStore, showTokens);
47+
break;
48+
}
49+
50+
return 0;
51+
}
52+
53+
private static void PrintJwtsJson(IReporter reporter, JwtStore jwtStore)
54+
{
55+
if (jwtStore.Jwts is { Count: > 0 } jwts)
56+
{
57+
reporter.Output(JsonSerializer.Serialize(jwtStore.Jwts, new JsonSerializerOptions { WriteIndented = true }));
58+
}
59+
else
60+
{
61+
reporter.Output(JsonSerializer.Serialize(Array.Empty<Jwt>(), new JsonSerializerOptions { WriteIndented = true }));
62+
}
63+
}
64+
65+
private static void PrintJwtsDefault(IReporter reporter, string project, string userSecretsId, JwtStore jwtStore, bool showTokens)
66+
{
3967
reporter.Output(Resources.FormatListCommand_Project(project));
4068
reporter.Output(Resources.FormatListCommand_UserSecretsId(userSecretsId));
4169

@@ -69,6 +97,5 @@ private static int Execute(IReporter reporter, string projectPath, bool showToke
6997
reporter.Output(Resources.ListCommand_NoJwts);
7098
}
7199

72-
return 0;
73100
}
74101
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ public static void Register(ProjectCommandLineApplication app)
3030
cmd.Reporter,
3131
cmd.ProjectOption.Value(),
3232
idArgument.Value,
33-
showAllOption.HasValue());
33+
showAllOption.HasValue(),
34+
cmd.OutputOption.Value());
3435
});
3536
});
3637
}
3738

38-
private static int Execute(IReporter reporter, string projectPath, string id, bool showAll)
39+
private static int Execute(IReporter reporter, string projectPath, string id, bool showAll, string outputFormat)
3940
{
4041
if (!DevJwtCliHelpers.GetProjectAndSecretsId(projectPath, reporter, out var _, out var userSecretsId))
4142
{
@@ -49,9 +50,8 @@ private static int Execute(IReporter reporter, string projectPath, string id, bo
4950
return 1;
5051
}
5152

52-
reporter.Output(Resources.FormatPrintCommand_Confirmed(id));
5353
JwtSecurityToken fullToken = JwtIssuer.Extract(jwt.Token);
54-
DevJwtCliHelpers.PrintJwt(reporter, jwt, showAll, fullToken);
54+
DevJwtCliHelpers.PrintJwt(reporter, jwt, showAll, outputFormat, fullToken);
5555

5656
return 0;
5757
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ internal sealed class ProjectCommandLineApplication : CommandLineApplication
1010
{
1111
public CommandOption ProjectOption { get; private set; }
1212

13+
public CommandOption OutputOption { get; private set; }
14+
1315
public IReporter Reporter { get; private set; }
1416

1517
public ProjectCommandLineApplication(IReporter reporter, bool throwOnUnexpectedArg = true, bool continueAfterUnexpectedArg = false, bool treatUnmatchedOptionsAsArguments = false)
@@ -19,6 +21,12 @@ public ProjectCommandLineApplication(IReporter reporter, bool throwOnUnexpectedA
1921
"-p|--project",
2022
Resources.ProjectOption_Description,
2123
CommandOptionType.SingleValue);
24+
25+
OutputOption = Option(
26+
"-o|--output",
27+
Resources.CreateCommand_OutputOption_Description,
28+
CommandOptionType.SingleValue);
29+
2230
Reporter = reporter;
2331
}
2432

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

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -143,48 +143,70 @@ static string[] ExtractKestrelUrlsFromProfile(JsonProperty profile)
143143
}
144144
}
145145

146-
public static void PrintJwt(IReporter reporter, Jwt jwt, bool showAll, JwtSecurityToken fullToken = null)
146+
public static void PrintJwt(IReporter reporter, Jwt jwt, bool showAll, string outputFormat, JwtSecurityToken fullToken = null)
147147
{
148-
reporter.Output($"{Resources.JwtPrint_Id}: {jwt.Id}");
149-
reporter.Output($"{Resources.JwtPrint_Name}: {jwt.Name}");
150-
reporter.Output($"{Resources.JwtPrint_Scheme}: {jwt.Scheme}");
151-
reporter.Output($"{Resources.JwtPrint_Audiences}: {jwt.Audience}");
152-
reporter.Output($"{Resources.JwtPrint_NotBefore}: {jwt.NotBefore:O}");
153-
reporter.Output($"{Resources.JwtPrint_ExpiresOn}: {jwt.Expires:O}");
154-
reporter.Output($"{Resources.JwtPrint_IssuedOn}: {jwt.Issued:O}");
155-
156-
if (!jwt.Scopes.IsNullOrEmpty() || showAll)
148+
switch (outputFormat)
157149
{
158-
var scopesValue = jwt.Scopes.IsNullOrEmpty()
159-
? "none"
160-
: string.Join(", ", jwt.Scopes);
161-
reporter.Output($"{Resources.JwtPrint_Scopes}: {scopesValue}");
150+
case "token":
151+
reporter.Output(fullToken is not null ? jwt.Token : string.Empty);
152+
break;
153+
case "json":
154+
PrintJwtJson(reporter, jwt, showAll, fullToken);
155+
break;
156+
default:
157+
PrintJwtDefault(reporter, jwt, showAll, fullToken);
158+
break;
162159
}
163160

164-
if (!jwt.Roles.IsNullOrEmpty() || showAll)
161+
static void PrintJwtJson(IReporter reporter, Jwt jwt, bool showAll, JwtSecurityToken fullToken)
165162
{
166-
var rolesValue = jwt.Roles.IsNullOrEmpty()
167-
? "none"
168-
: string.Join(", ", jwt.Roles);
169-
reporter.Output($"{Resources.JwtPrint_Roles}: [{rolesValue}]");
163+
reporter.Output(JsonSerializer.Serialize(jwt, new JsonSerializerOptions { WriteIndented = true }));
170164
}
171165

172-
if (!jwt.CustomClaims.IsNullOrEmpty() || showAll)
166+
static void PrintJwtDefault(IReporter reporter, Jwt jwt, bool showAll, JwtSecurityToken fullToken)
173167
{
174-
var customClaimsValue = jwt.CustomClaims.IsNullOrEmpty()
175-
? "none"
176-
: string.Join(", ", jwt.CustomClaims.Select(kvp => $"{kvp.Key}={kvp.Value}"));
177-
reporter.Output($"{Resources.JwtPrint_CustomClaims}: [{customClaimsValue}]");
178-
}
168+
reporter.Output(Resources.FormatPrintCommand_Confirmed(jwt.Id));
169+
reporter.Output($"{Resources.JwtPrint_Id}: {jwt.Id}");
170+
reporter.Output($"{Resources.JwtPrint_Name}: {jwt.Name}");
171+
reporter.Output($"{Resources.JwtPrint_Scheme}: {jwt.Scheme}");
172+
reporter.Output($"{Resources.JwtPrint_Audiences}: {jwt.Audience}");
173+
reporter.Output($"{Resources.JwtPrint_NotBefore}: {jwt.NotBefore:O}");
174+
reporter.Output($"{Resources.JwtPrint_ExpiresOn}: {jwt.Expires:O}");
175+
reporter.Output($"{Resources.JwtPrint_IssuedOn}: {jwt.Issued:O}");
176+
177+
if (!jwt.Scopes.IsNullOrEmpty() || showAll)
178+
{
179+
var scopesValue = jwt.Scopes.IsNullOrEmpty()
180+
? "none"
181+
: string.Join(", ", jwt.Scopes);
182+
reporter.Output($"{Resources.JwtPrint_Scopes}: {scopesValue}");
183+
}
179184

180-
if (showAll)
181-
{
182-
reporter.Output($"{Resources.JwtPrint_TokenHeader}: {fullToken.Header.SerializeToJson()}");
183-
reporter.Output($"{Resources.JwtPrint_TokenPayload}: {fullToken.Payload.SerializeToJson()}");
184-
}
185+
if (!jwt.Roles.IsNullOrEmpty() || showAll)
186+
{
187+
var rolesValue = jwt.Roles.IsNullOrEmpty()
188+
? "none"
189+
: string.Join(", ", jwt.Roles);
190+
reporter.Output($"{Resources.JwtPrint_Roles}: [{rolesValue}]");
191+
}
185192

186-
var tokenValueFieldName = showAll ? Resources.JwtPrint_CompactToken : Resources.JwtPrint_Token;
187-
reporter.Output($"{tokenValueFieldName}: {jwt.Token}");
193+
if (!jwt.CustomClaims.IsNullOrEmpty() || showAll)
194+
{
195+
var customClaimsValue = jwt.CustomClaims.IsNullOrEmpty()
196+
? "none"
197+
: string.Join(", ", jwt.CustomClaims.Select(kvp => $"{kvp.Key}={kvp.Value}"));
198+
reporter.Output($"{Resources.JwtPrint_CustomClaims}: [{customClaimsValue}]");
199+
}
200+
201+
if (showAll)
202+
{
203+
reporter.Output($"{Resources.JwtPrint_TokenHeader}: {fullToken.Header.SerializeToJson()}");
204+
reporter.Output($"{Resources.JwtPrint_TokenPayload}: {fullToken.Payload.SerializeToJson()}");
205+
}
206+
207+
var tokenValueFieldName = showAll ? Resources.JwtPrint_CompactToken : Resources.JwtPrint_Token;
208+
reporter.Output($"{tokenValueFieldName}: {jwt.Token}");
209+
}
188210
}
189211

190212
public static bool TryParseClaims(List<string> input, out Dictionary<string, string> claims)

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

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ public void Print_ReturnsNothingForMissingToken()
9494
public void List_ReturnsIdForGeneratedToken()
9595
{
9696
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
97-
var appsettings = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
9897
var app = new Program(_console);
9998

10099
app.Run(new[] { "create", "--project", project, "--scheme", "MyCustomScheme" });
@@ -104,6 +103,40 @@ public void List_ReturnsIdForGeneratedToken()
104103
Assert.Contains("MyCustomScheme", _console.GetOutput());
105104
}
106105

106+
[Fact]
107+
public void List_ReturnsIdForGeneratedToken_WithJsonFormat()
108+
{
109+
var schemeName = "MyCustomScheme";
110+
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
111+
var app = new Program(_console);
112+
113+
app.Run(new[] { "create", "--project", project, "--scheme", schemeName });
114+
var matches = Regex.Matches(_console.GetOutput(), "New JWT saved with ID '(.*?)'");
115+
var id = matches.SingleOrDefault().Groups[1].Value;
116+
_console.ClearOutput();
117+
118+
app.Run(new[] { "list", "--project", project, "--output", "json" });
119+
var output = _console.GetOutput();
120+
var deserialized = JsonSerializer.Deserialize<Dictionary<string, Jwt>>(output);
121+
122+
var jwt = deserialized[id];
123+
124+
Assert.NotNull(deserialized);
125+
Assert.Equal(schemeName, jwt.Scheme);
126+
}
127+
128+
[Fact]
129+
public void List_ReturnsEmptyListWhenNoTokens_WithJsonFormat()
130+
{
131+
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
132+
var app = new Program(_console);
133+
134+
app.Run(new[] { "list", "--project", project, "--output", "json" });
135+
var output = _console.GetOutput();
136+
137+
Assert.Equal("[]", output.Trim());
138+
}
139+
107140
[Fact]
108141
public void Remove_RemovesGeneratedToken()
109142
{
@@ -269,6 +302,26 @@ public void PrintCommand_ShowsBasicOptions()
269302
Assert.Contains($"Audience(s): http://localhost:23528, https://localhost:44395, https://localhost:5001, http://localhost:5000", output);
270303
}
271304

305+
[Fact]
306+
public void PrintCommand_ShowsBasicOptions_WithJsonFormat()
307+
{
308+
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
309+
var app = new Program(_console);
310+
311+
app.Run(new[] { "create", "--project", project });
312+
var matches = Regex.Matches(_console.GetOutput(), "New JWT saved with ID '(.*?)'");
313+
var id = matches.SingleOrDefault().Groups[1].Value;
314+
_console.ClearOutput();
315+
316+
app.Run(new[] { "print", id, "--project", project, "--output", "json" });
317+
var output = _console.GetOutput();
318+
var deserialized = JsonSerializer.Deserialize<Jwt>(output);
319+
320+
Assert.Equal(Environment.UserName, deserialized.Name);
321+
Assert.Equal(DevJwtsDefaults.Scheme, deserialized.Scheme);
322+
Assert.Equal($"http://localhost:23528, https://localhost:44395, https://localhost:5001, http://localhost:5000", deserialized.Audience);
323+
}
324+
272325
[Fact]
273326
public void PrintCommand_ShowsCustomizedOptions()
274327
{

0 commit comments

Comments
 (0)