diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs index 2448688edb..fe6b98006b 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs @@ -36,6 +36,52 @@ namespace Microsoft.Extensions.DependencyInjection return options; } + /// + /// Adds a to the page with the specified path. + /// + /// The to configure. + /// The path of the Razor Page. + /// The . + public static RazorPagesOptions AllowAnonymousToPage(this RazorPagesOptions options, string path) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path)); + } + + var anonymousFilter = new AllowAnonymousFilter(); + options.Conventions.Add(new PageConvention(path, model => model.Filters.Add(anonymousFilter))); + return options; + } + + /// + /// Adds a to all pages under the specified path. + /// + /// The to configure. + /// The folder path. + /// The . + public static RazorPagesOptions AllowAnonymousToFolder(this RazorPagesOptions options, string folderPath) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + var anonymousFilter = new AllowAnonymousFilter(); + options.Conventions.Add(new FolderConvention(folderPath, model => model.Filters.Add(anonymousFilter))); + return options; + } + /// /// Adds a with the specified policy to the page with the specified path. /// diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs index 1046b880b7..2dc590cfd0 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs @@ -399,6 +399,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("/Login?ReturnUrl=%2FHelloWorldWithAuth", response.Headers.Location.PathAndQuery); } + [Fact] + public async Task AuthorizePage_AllowAnonymousForSpecificPages() + { + // Arrange + var url = "/Pages/Admin/Login"; + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("Login Page", content); + } + [Fact] public async Task PageStart_IsDiscoveredWhenRootDirectoryIsNotSpecified() diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs index c1b88b5442..fb09882688 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs @@ -37,6 +37,79 @@ namespace Microsoft.Extensions.DependencyInjection model => Assert.Same(filter, Assert.Single(model.Filters))); } + [Fact] + public void AuthorizePage_AddsAllowAnonymousFilterToSpecificPage() + { + // Arrange + var options = new RazorPagesOptions(); + var models = new[] + { + new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), + new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), + new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + }; + + // Act + options.AuthorizeFolder("/Users"); + options.AllowAnonymousToPage("/Users/Contact.cshtml"); + ApplyConventions(options, models); + + // Assert + Assert.Collection(models, + model => Assert.Empty(model.Filters), + model => + { + Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath); + Assert.IsType(Assert.Single(model.Filters)); + }, + model => + { + Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath); + Assert.IsType(model.Filters[0]); + Assert.IsType(model.Filters[1]); + }); + } + + [Theory] + [InlineData("/Users")] + [InlineData("/Users/")] + public void AuthorizePage_AddsAllowAnonymousFilterToPagesUnderFolder(string folderName) + { + // Arrange + var options = new RazorPagesOptions(); + var models = new[] + { + new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), + new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), + new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + }; + + // Act + options.AuthorizeFolder("/"); + options.AllowAnonymousToFolder("/Users"); + ApplyConventions(options, models); + + // Assert + Assert.Collection(models, + model => + { + Assert.Equal("/Index.cshtml", model.ViewEnginePath); + Assert.IsType(Assert.Single(model.Filters)); + }, + model => + { + Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath); + Assert.IsType(model.Filters[0]); + Assert.IsType(model.Filters[1]); + }, + model => + { + Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath); + Assert.IsType(model.Filters[0]); + Assert.IsType(model.Filters[1]); + }); + } + [Fact] public void AuthorizePage_AddsAuthorizeFilterWithPolicyToSpecificPage() { diff --git a/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml b/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml new file mode 100644 index 0000000000..44c9bb85ab --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml @@ -0,0 +1,2 @@ +@page +Login Page \ No newline at end of file diff --git a/test/WebSites/RazorPagesWebSite/Startup.cs b/test/WebSites/RazorPagesWebSite/Startup.cs index 369d7452a2..4a39e2f302 100644 --- a/test/WebSites/RazorPagesWebSite/Startup.cs +++ b/test/WebSites/RazorPagesWebSite/Startup.cs @@ -16,6 +16,8 @@ namespace RazorPagesWebSite .AddRazorPagesOptions(options => { options.AuthorizePage("/HelloWorldWithAuth"); + options.AuthorizeFolder("/Pages/Admin"); + options.AllowAnonymousToPage("/Pages/Admin/Login"); }); }