Skip to content

Throw when UseAuthorization is incorrectly configured #14401

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

Merged
merged 1 commit into from
Oct 1, 2019
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
12 changes: 11 additions & 1 deletion src/Analyzers/Analyzers/src/StartupAnalyzer.Diagnostics.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
Expand All @@ -19,6 +19,7 @@ static Diagnostics()
{
// ASP
BuildServiceProviderShouldNotCalledInConfigureServicesMethod,
IncorrectlyConfiguredAuthorizationMiddleware,

// MVC
UnsupportedUseMvcWithEndpointRouting,
Expand All @@ -42,6 +43,15 @@ static Diagnostics()
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: "https://aka.ms/YJggeFn");

internal readonly static DiagnosticDescriptor IncorrectlyConfiguredAuthorizationMiddleware = new DiagnosticDescriptor(
"ASP0001",
"Authorization middleware is incorrectly configured.",
"The call to UseAuthorization should appear between app.UseRouting() and app.UseEndpoints(..) for authorization to be correctly evaluated.",
"Usage",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: "https://aka.ms/AA64fv1");
}
}
}
3 changes: 2 additions & 1 deletion src/Analyzers/Analyzers/src/StartupAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
Expand Down Expand Up @@ -82,6 +82,7 @@ private void OnCompilationStart(CompilationStartAnalysisContext context)
var analysis = builder.Build();
new UseMvcAnalyzer(analysis).AnalyzeSymbol(context);
new BuildServiceProviderValidator(analysis).AnalyzeSymbol(context);
new UseAuthorizationAnalyzer(analysis).AnalyzeSymbol(context);
});

}, SymbolKind.NamedType);
Expand Down
82 changes: 82 additions & 0 deletions src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.AspNetCore.Analyzers
{
internal class UseAuthorizationAnalyzer
{
private readonly StartupAnalysis _context;

public UseAuthorizationAnalyzer(StartupAnalysis context)
{
_context = context;
}

public void AnalyzeSymbol(SymbolAnalysisContext context)
{
Debug.Assert(context.Symbol.Kind == SymbolKind.NamedType);
Debug.Assert(StartupFacts.IsStartupClass(_context.StartupSymbols, (INamedTypeSymbol)context.Symbol));

var type = (INamedTypeSymbol)context.Symbol;

foreach (var middlewareAnalysis in _context.GetRelatedAnalyses<MiddlewareAnalysis>(type))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YSK: this doesn't do control-flow analysis - ie, it's trivial to cause false positives or false negatives when .Map is involved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. We can always recommend turning the analyzer for users where this becomes problematic. Doing flow analysis for this sounds like a lot more effort

{
MiddlewareItem? useAuthorizationItem = default;
MiddlewareItem? useRoutingItem = default;

var length = middlewareAnalysis.Middleware.Length;
for (var i = length - 1; i >= 0; i-- )
{
var middlewareItem = middlewareAnalysis.Middleware[i];
var middleware = middlewareItem.UseMethod.Name;

if (middleware == "UseAuthorization")
{
if (useRoutingItem != null && useAuthorizationItem == null)
{
// This looks like
//
// app.UseAuthorization();
// ...
// app.UseRouting();
// app.UseEndpoints(...);

context.ReportDiagnostic(Diagnostic.Create(
StartupAnalyzer.Diagnostics.IncorrectlyConfiguredAuthorizationMiddleware,
middlewareItem.Operation.Syntax.GetLocation(),
middlewareItem.UseMethod.Name));
}

useAuthorizationItem = middlewareItem;
}
else if (middleware == "UseEndpoints")
{
if (useAuthorizationItem != null)
{
// This configuration looks like
//
// app.UseRouting();
// app.UseEndpoints(...);
// ...
// app.UseAuthorization();
//

context.ReportDiagnostic(Diagnostic.Create(
StartupAnalyzer.Diagnostics.IncorrectlyConfiguredAuthorizationMiddleware,
useAuthorizationItem.Operation.Syntax.GetLocation(),
middlewareItem.UseMethod.Name));
}
}
else if (middleware == "UseRouting")
{
useRoutingItem = middlewareItem;
}
}
}
}
}
}
87 changes: 86 additions & 1 deletion src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public async Task StartupAnalyzer_MvcOptionsAnalysis_MultipleMiddleware()

Assert.Collection(
middlewareAnalysis.Middleware,
item => Assert.Equal("UseAuthorization", item.UseMethod.Name),
item => Assert.Equal("UseStaticFiles", item.UseMethod.Name),
item => Assert.Equal("UseMiddleware", item.UseMethod.Name),
item => Assert.Equal("UseMvc", item.UseMethod.Name),
item => Assert.Equal("UseRouting", item.UseMethod.Name),
Expand Down Expand Up @@ -228,5 +228,90 @@ public async Task StartupAnalyzer_ServicesAnalysis_CallBuildServiceProvider()
AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location);
});
}

[Fact]
public async Task StartupAnalyzer_UseAuthorizationConfiguredCorrectly_ReportsNoDiagnostics()
{
// Arrange
var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthConfiguredCorrectly));

// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);

// Assert
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
Assert.NotEmpty(middlewareAnalysis.Middleware);
Assert.Empty(diagnostics);
}

[Fact]
public async Task StartupAnalyzer_UseAuthorizationInvokedMultipleTimesInEndpointRoutingBlock_ReportsNoDiagnostics()
{
// Arrange
var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthMultipleTimes));

// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);

// Assert
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
Assert.NotEmpty(middlewareAnalysis.Middleware);
Assert.Empty(diagnostics);
}

[Fact]
public async Task StartupAnalyzer_UseAuthorizationConfiguredBeforeUseRouting_ReportsDiagnostics()
{
// Arrange
var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthBeforeUseRouting));

// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);

// Assert
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
Assert.NotEmpty(middlewareAnalysis.Middleware);
Assert.Collection(diagnostics,
diagnostic =>
{
Assert.Same(StartupAnalyzer.Diagnostics.IncorrectlyConfiguredAuthorizationMiddleware, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location);
});
}

[Fact]
public async Task StartupAnalyzer_UseAuthorizationConfiguredAfterUseEndpoints_ReportsDiagnostics()
{
// Arrange
var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthAfterUseEndpoints));

// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);

// Assert
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
Assert.NotEmpty(middlewareAnalysis.Middleware);
Assert.Collection(diagnostics,
diagnostic =>
{
Assert.Same(StartupAnalyzer.Diagnostics.IncorrectlyConfiguredAuthorizationMiddleware, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location);
});
}

[Fact]
public async Task StartupAnalyzer_MultipleUseAuthorization_ReportsNoDiagnostics()
{
// Arrange
var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthFallbackPolicy));

// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);

// Assert
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
Assert.NotEmpty(middlewareAnalysis.Middleware);
Assert.Empty(diagnostics);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Builder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Authorization;
Expand All @@ -18,7 +18,7 @@ public void Configure(IApplicationBuilder app)
{
/*MM1*/app.UseMvcWithDefaultRoute();

app.UseAuthorization();
app.UseStaticFiles();
app.UseMiddleware<AuthorizationMiddleware>();

/*MM2*/app.UseMvc();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Authorization;
Expand All @@ -16,7 +16,7 @@ public void ConfigureServices(IServiceCollection services)

public void Configure(IApplicationBuilder app)
{
app.UseAuthorization();
app.UseStaticFiles();
app.UseMiddleware<AuthorizationMiddleware>();

/*MM*/app.UseMvc();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Builder;

namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class UseAuthAfterUseEndpoints
{
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(r => { });
/*MM*/app.UseAuthorization();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Builder;

namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class UseAuthBeforeUseRouting
{
public void Configure(IApplicationBuilder app)
{
app.UseFileServer();
/*MM*/app.UseAuthorization();
app.UseRouting();
app.UseEndpoints(r => { });
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Builder;

namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class UseAuthConfiguredCorrectly
{
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(r => { });
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Builder;

namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class UseAuthFallbackPolicy
{
public void Configure(IApplicationBuilder app)
{
// This sort of setup would be useful if the user wants to use Auth for non-endpoint content to be handled using the Fallback policy, while
// using the second instance for regular endpoint routing based auth. We do not want to produce a warning in this case.
app.UseAuthorization();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't super get this - it only makes sense if there's a terminal middleware between here and routing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll toss in a Static files to appear after this.

app.UseStaticFiles();

app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(r => { });
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Builder;

namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class UseAuthMultipleTimes
{
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseAuthorization();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File is called UseAuthMultipleTimes - yet it uses auth a single time. How can this be possible?

app.UseAuthorization();
app.UseEndpoints(r => { });
}
}
}
Loading