From 09e019841c2ff70c14a0ceb2958b1fc4e6bd0c8a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 1 Feb 2019 11:14:51 +1300 Subject: [PATCH] Validate required services in UseAuthorization (#6915) --- .../src/AuthorizationAppBuilderExtensions.cs | 19 ++- .../src/AuthorizationPolicyMarkerService.cs | 9 ++ .../src/PolicyServiceCollectionExtensions.cs | 4 +- .../src/Properties/Resources.Designer.cs | 44 +++++++ .../Authorization/Policy/src/Resources.resx | 123 ++++++++++++++++++ .../AuthorizationAppBuilderExtensionsTests.cs | 27 +++- 6 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 src/Security/Authorization/Policy/src/AuthorizationPolicyMarkerService.cs create mode 100644 src/Security/Authorization/Policy/src/Properties/Resources.Designer.cs create mode 100644 src/Security/Authorization/Policy/src/Resources.resx diff --git a/src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs b/src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs index 66f0eb6f1e..08c4538f5a 100644 --- a/src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs +++ b/src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs @@ -1,8 +1,10 @@ -// 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; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Builder { @@ -23,7 +25,22 @@ namespace Microsoft.AspNetCore.Builder throw new ArgumentNullException(nameof(app)); } + VerifyServicesRegistered(app); + return app.UseMiddleware(); } + + private static void VerifyServicesRegistered(IApplicationBuilder app) + { + // Verify that AddAuthorizationPolicy was called before calling UseAuthorization + // We use the AuthorizationPolicyMarkerService to ensure all the services were added. + if (app.ApplicationServices.GetService(typeof(AuthorizationPolicyMarkerService)) == null) + { + throw new InvalidOperationException(Resources.FormatException_UnableToFindServices( + nameof(IServiceCollection), + nameof(PolicyServiceCollectionExtensions.AddAuthorizationPolicyEvaluator), + "ConfigureServices(...)")); + } + } } } diff --git a/src/Security/Authorization/Policy/src/AuthorizationPolicyMarkerService.cs b/src/Security/Authorization/Policy/src/AuthorizationPolicyMarkerService.cs new file mode 100644 index 0000000000..9061891e43 --- /dev/null +++ b/src/Security/Authorization/Policy/src/AuthorizationPolicyMarkerService.cs @@ -0,0 +1,9 @@ +// 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. + +namespace Microsoft.AspNetCore.Authorization.Policy +{ + internal class AuthorizationPolicyMarkerService + { + } +} diff --git a/src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs b/src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs index 9b72a5cab4..dc1056e696 100644 --- a/src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs +++ b/src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs @@ -23,7 +23,9 @@ namespace Microsoft.Extensions.DependencyInjection { throw new ArgumentNullException(nameof(services)); } - + + services.AddAuthorization(); + services.TryAddSingleton(); services.TryAdd(ServiceDescriptor.Transient()); return services; } diff --git a/src/Security/Authorization/Policy/src/Properties/Resources.Designer.cs b/src/Security/Authorization/Policy/src/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..d4e50a8631 --- /dev/null +++ b/src/Security/Authorization/Policy/src/Properties/Resources.Designer.cs @@ -0,0 +1,44 @@ +// +namespace Microsoft.AspNetCore.Authorization.Policy +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Authorization.Policy.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + /// + internal static string Exception_UnableToFindServices + { + get => GetString("Exception_UnableToFindServices"); + } + + /// + /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + /// + internal static string FormatException_UnableToFindServices(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UnableToFindServices"), p0, p1, p2); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Security/Authorization/Policy/src/Resources.resx b/src/Security/Authorization/Policy/src/Resources.resx new file mode 100644 index 0000000000..15d6f7d53c --- /dev/null +++ b/src/Security/Authorization/Policy/src/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + + \ No newline at end of file diff --git a/src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs b/src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs index c6fb596f18..ffb3ddeb1b 100644 --- a/src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs +++ b/src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs @@ -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; @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Authorization.Test public class AuthorizationAppBuilderExtensionsTests { [Fact] - public async Task UseAuthorization_RegistersMiddleware() + public async Task UseAuthorization_HasRequiredSevices_RegistersMiddleware() { // Arrange var authenticationService = new TestAuthenticationService(); @@ -48,11 +48,32 @@ namespace Microsoft.AspNetCore.Authorization.Test Assert.True(authenticationService.ChallengeCalled); } + [Fact] + public void UseAuthorization_MissingRequiredSevices_FriendlyErrorMessage() + { + // Arrange + var authenticationService = new TestAuthenticationService(); + + var app = new ApplicationBuilder(new ServiceCollection().BuildServiceProvider()); + + // Act + var ex = Assert.Throws(() => + { + app.UseAuthorization(); + }); + + // Assert + Assert.Equal( + "Unable to find the required services. Please add all the required services by calling " + + "'IServiceCollection.AddAuthorizationPolicyEvaluator' inside the call to 'ConfigureServices(...)' " + + "in the application startup code.", + ex.Message); + } + private IServiceProvider CreateServices(IAuthenticationService authenticationService) { var services = new ServiceCollection(); - services.AddAuthorization(options => { }); services.AddAuthorizationPolicyEvaluator(); services.AddLogging(); services.AddSingleton(authenticationService);