Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add analyzer to recommend using dotnet user-jwts #19

Merged
merged 5 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This repository contains a collection of analyzers developed during MSHack 2022,
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| MH001 | Just an easter egg analyzer. Finds instances where an integer variable is assigned to 42 and recommends renaming the identifier to `meaningOfLife`. |
| MH002 | Emits a warning when a Razor component writes to one of its parameter properties directly. |
| MH003 | Recommends that the user leverage the `dotnet user-jwts` command line tool when they are using JWT-bearer based auth. |
| MH005 | Finds 'out', 'in', or 'ref' modifiers on arguments in request delegates. |

## Development Instructions
Expand Down
3 changes: 3 additions & 0 deletions samples/WebSample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication().AddJwtBearer();

var app = builder.Build();

app.MapGet("/mh001", () =>
Expand Down
4 changes: 4 additions & 0 deletions samples/WebSample/WebSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@
ReferenceOutputAssembly="false"
OutputItemType="Analyzer" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" />
</ItemGroup>
</Project>
8 changes: 8 additions & 0 deletions src/Analyzers/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ public static class DiagnosticDescriptors
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor UseDotnetUserJwtsTool = new(
"MH003",
"Recommend using dotnet user-jwts tool",
"It looks like you're using JWT-bearer based authentication in your application. Consider using the `dotnet user-jwts` tool to generate tokens for local development.",
"Usage",
DiagnosticSeverity.Info,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor BadArgumentModifier = new(
"MH005",
"Found an invalid modifier on an argument",
Expand Down
43 changes: 43 additions & 0 deletions src/Analyzers/MinimalAuthenticationAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;

namespace MSHack2022.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public partial class MinimalNet7Analyzers : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
DiagnosticDescriptors.UseDotnetUserJwtsTool);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterOperationAction(static context =>
{
var compilation = context.Compilation;
if (!WellKnownTypes.TryCreate(compilation, out var wellKnownTypes))
{
Debug.Fail("One or more types could not be found. This usually means you are bad at spelling C# type names.");
return;
}

var invocation = (IInvocationOperation)context.Operation;
var invocationTarget = invocation.TargetMethod;
if (invocationTarget is not null
&& invocationTarget.Name == "AddJwtBearer"
&& SymbolEqualityComparer.Default.Equals(wellKnownTypes!.JwtBearerExtensions, invocationTarget.ContainingType))
{
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.UseDotnetUserJwtsTool, invocation.Syntax.GetLocation()));
}

}, OperationKind.Invocation);
}
}

8 changes: 8 additions & 0 deletions src/Analyzers/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,18 @@ public static bool TryCreate(Compilation compilation, out WellKnownTypes? wellKn
return false;
}

const string JwtBearerExtensions = "Microsoft.Extensions.DependencyInjection.JwtBearerExtensions";
if (compilation.GetTypeByMetadataName(JwtBearerExtensions) is not { } jwtBearerExtensions)
{
return false;
}

wellKnownTypes = new WellKnownTypes
{
EndpointRouteBuilderExtensions = endpointRouteBuilderExtensions,
Delegate = @delegate,
IResult = iResult,
JwtBearerExtensions = jwtBearerExtensions
};

return true;
Expand All @@ -38,5 +45,6 @@ public static bool TryCreate(Compilation compilation, out WellKnownTypes? wellKn
public INamedTypeSymbol EndpointRouteBuilderExtensions { get; private set; } = null!;
public INamedTypeSymbol Delegate { get; private set; } = null!;
public INamedTypeSymbol IResult { get; private set; } = null!;
public INamedTypeSymbol JwtBearerExtensions { get; private set; } = null!;
}
}
2 changes: 1 addition & 1 deletion tests/EasterEggAnalyzerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace MSHack2022.Tests;

public partial class EasterEggAnalyzerTest
public class EasterEggAnalyzerTest
{
[Fact]
public async Task TriggersOnIntWith42()
Expand Down
1 change: 1 addition & 0 deletions tests/MSHack2022.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.4.0-1.final" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4-beta1.22403.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.1" />
Expand Down
141 changes: 141 additions & 0 deletions tests/MinimalNet7AnalyzerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System.Threading.Tasks;
using Xunit;
using VerifyCS = MSHack2022.Tests.CSharpAnalyzerVerifier<MSHack2022.Analyzers.MinimalNet7Analyzers>;

namespace MSHack2022.Tests;

public class MinimalNet7AnalyzerTests
{
[Fact]
public async Task TriggersOnAddJwtBearerCall()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;

class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

[|builder.Services.AddAuthentication().AddJwtBearer()|];

var app = builder.Build();

app.Run();
}
}");
}

[Fact]
public async Task TriggersOnAddJwtBearerCall_TopLevelStatements()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

[|builder.Services.AddAuthentication().AddJwtBearer()|];

var app = builder.Build();

app.Run();
");
}

[Fact]
public async Task DoesNotTriggerOIncorrectCall_NonExtensionMethodInvocation()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

AddJwtBearer(builder.Services);

var app = builder.Build();

app.Run();

static IServiceCollection AddJwtBearer(IServiceCollection services)
{
return services;
}
");
}

[Fact]
public async Task DoesNotTriggerOnIncorrectCall_ExtensionMethodInvocation()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;

public static class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddJwtBearer();

var app = builder.Build();

app.Run();
}
}

public static class TestExtensionMethods
{
public static IServiceCollection AddJwtBearer(this IServiceCollection services)
{
return services;
}
}
");
}

[Fact]
public async Task TriggersOnAddJwtBearerCall_InAnotherMethod()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;

public static class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddJwtBearer();

var app = builder.Build();

app.Run();
}
}

public static class TestExtensionMethods
{
public static IServiceCollection AddJwtBearer(this IServiceCollection services)
{
[|services.AddAuthentication().AddJwtBearer()|];
return services;
}
}
");
}
}

4 changes: 3 additions & 1 deletion tests/Verifiers/CSharpAnalyzerVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ internal static ReferenceAssemblies GetReferenceAssemblies()
TrimAssemblyExtension(typeof(Microsoft.Extensions.Configuration.ConfigurationManager).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.Extensions.Configuration.JsonConfigurationExtensions).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.Extensions.Configuration.IConfigurationBuilder).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.Extensions.Configuration.EnvironmentVariablesExtensions).Assembly.Location)));
TrimAssemblyExtension(typeof(Microsoft.Extensions.Configuration.EnvironmentVariablesExtensions).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.Extensions.DependencyInjection.JwtBearerExtensions).Assembly.Location),
TrimAssemblyExtension(typeof(Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions).Assembly.Location)));
}

static string TrimAssemblyExtension(string fullPath) => fullPath.Replace(".dll", string.Empty);
Expand Down