From 6491490af6f79620234bc6b8df7972a82cedd0b8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 2 Nov 2018 16:27:05 +1300 Subject: [PATCH 1/3] Update static files sample to use AuthorizationMiddleware --- build/dependencies.props | 3 +- build/sources.props | 1 + .../Authorization/MinimumAgePolicyProvider.cs | 2 + samples/StaticFilesAuth/Startup.cs | 168 +++++++++--------- .../StaticFilesAuth/StaticFilesAuth.csproj | 1 + 5 files changed, 89 insertions(+), 86 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 978f4b2..5042353 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -10,7 +10,8 @@ 3.0.0-alpha1-10670 3.0.0-alpha1-10670 3.0.0-alpha1-10670 - 3.0.0-alpha1-10670 + 3.0.0-alpha1-t000 + 3.0.0-alpha1-t000 3.0.0-alpha1-10670 3.0.0-alpha1-10670 3.0.0-alpha1-10670 diff --git a/build/sources.props b/build/sources.props index 9215df9..3ea4fa9 100644 --- a/build/sources.props +++ b/build/sources.props @@ -5,6 +5,7 @@ $(DotNetRestoreSources) $(RestoreSources); + C:\Development\Source\Security\artifacts\build; https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; diff --git a/samples/CustomPolicyProvider/Authorization/MinimumAgePolicyProvider.cs b/samples/CustomPolicyProvider/Authorization/MinimumAgePolicyProvider.cs index 8fc1109..78852b5 100644 --- a/samples/CustomPolicyProvider/Authorization/MinimumAgePolicyProvider.cs +++ b/samples/CustomPolicyProvider/Authorization/MinimumAgePolicyProvider.cs @@ -27,6 +27,8 @@ public MinimumAgePolicyProvider(IOptions options) public Task GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync(); + public Task GetRequiredPolicyAsync() => FallbackPolicyProvider.GetRequiredPolicyAsync(); + // Policies are looked up by string name, so expect 'parameters' (like age) // to be embedded in the policy names. This is abstracted away from developers // by the more strongly-typed attributes derived from AuthorizeAttribute diff --git a/samples/StaticFilesAuth/Startup.cs b/samples/StaticFilesAuth/Startup.cs index 9077cd9..4881e81 100644 --- a/samples/StaticFilesAuth/Startup.cs +++ b/samples/StaticFilesAuth/Startup.cs @@ -1,12 +1,15 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -49,27 +52,36 @@ public void ConfigureServices(IServiceCollection services) { return false; } - var userPath = Path.Combine(usersPath, userName); - if (context.Resource is IFileInfo file) + if (context.Resource is Endpoint endpoint) { - var path = Path.GetDirectoryName(file.PhysicalPath); - return string.Equals(path, basePath, StringComparison.OrdinalIgnoreCase) - || string.Equals(path, usersPath, StringComparison.OrdinalIgnoreCase) - || string.Equals(path, userPath, StringComparison.OrdinalIgnoreCase) - || path.StartsWith(userPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); - } - else if (context.Resource is IDirectoryContents dir) - { - // https://github.com/aspnet/Home/issues/3073 - // This won't work right if the directory is empty - var path = Path.GetDirectoryName(dir.First().PhysicalPath); - return string.Equals(path, basePath, StringComparison.OrdinalIgnoreCase) - || string.Equals(path, usersPath, StringComparison.OrdinalIgnoreCase) - || string.Equals(path, userPath, StringComparison.OrdinalIgnoreCase) - || path.StartsWith(userPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); + var userPath = Path.Combine(usersPath, userName); + + var file = endpoint.Metadata.GetMetadata(); + if (file != null) + { + var path = Path.GetDirectoryName(file.PhysicalPath); + return string.Equals(path, basePath, StringComparison.OrdinalIgnoreCase) + || string.Equals(path, usersPath, StringComparison.OrdinalIgnoreCase) + || string.Equals(path, userPath, StringComparison.OrdinalIgnoreCase) + || path.StartsWith(userPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); + } + + var dir = endpoint.Metadata.GetMetadata(); + if (dir != null) + { + // https://github.com/aspnet/Home/issues/3073 + // This won't work right if the directory is empty + var path = Path.GetDirectoryName(dir.First().PhysicalPath); + return string.Equals(path, basePath, StringComparison.OrdinalIgnoreCase) + || string.Equals(path, usersPath, StringComparison.OrdinalIgnoreCase) + || string.Equals(path, userPath, StringComparison.OrdinalIgnoreCase) + || path.StartsWith(userPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); + } + + throw new InvalidOperationException($"Missing file system metadata."); } - throw new NotImplementedException($"Unknown resource type '{context.Resource.GetType()}'"); + throw new InvalidOperationException($"Unknown resource type '{context.Resource.GetType()}'"); }); }); }); @@ -98,12 +110,45 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthori app.Map("/MapAuthenticatedFiles", branch => { - MapAuthenticatedFiles(branch, files); + // Blanket authorization, any authenticated user is allowed access to these resources. + branch.UseAuthorization(new AuthorizationOptions + { + RequiredPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build() + }); + + branch.UseFileServer(new FileServerOptions() + { + EnableDirectoryBrowsing = true, + FileProvider = files + }); }); app.Map("/MapImperativeFiles", branch => { - MapImperativeFiles(authorizationService, branch, files); + branch.Use(async (context, next) => + { + var fileSystemInfo = GetFileSystemInfo(files, context.Request.Path); + if (fileSystemInfo != null) + { + var endpoint = new Endpoint( + c => Task.CompletedTask, + new EndpointMetadataCollection(fileSystemInfo, new AuthorizeAttribute("files")), + context.Request.Path); + + context.Features.Set(new EndpointFeature(endpoint)); + } + + await next(); + }); + + // Policy based authorization, requests must meet the policy criteria to be get access to the resources. + branch.UseAuthorization(); + + branch.UseFileServer(new FileServerOptions() + { + EnableDirectoryBrowsing = true, + FileProvider = files + }); }); app.UseMvc(routes => @@ -114,81 +159,34 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthori }); } - // Blanket authorization, any authenticated user is allowed access to these resources. - private static void MapAuthenticatedFiles(IApplicationBuilder branch, PhysicalFileProvider files) + private static object GetFileSystemInfo(PhysicalFileProvider files, string path) { - branch.Use(async (context, next) => + var fileInfo = files.GetFileInfo(path); + if (fileInfo.Exists) { - if (!context.User.Identity.IsAuthenticated) + return fileInfo; + } + else + { + // https://github.com/aspnet/Home/issues/2537 + var dir = files.GetDirectoryContents(path); + if (dir.Exists) { - await context.ChallengeAsync(new AuthenticationProperties() - { - // https://github.com/aspnet/Security/issues/1730 - // Return here after authenticating - RedirectUri = context.Request.PathBase + context.Request.Path + context.Request.QueryString - }); - return; + return dir; } + } - await next(); - }); - branch.UseFileServer(new FileServerOptions() - { - EnableDirectoryBrowsing = true, - FileProvider = files - }); + return null; } - // Policy based authorization, requests must meet the policy criteria to be get access to the resources. - private static void MapImperativeFiles(IAuthorizationService authorizationService, IApplicationBuilder branch, PhysicalFileProvider files) + private class EndpointFeature : IEndpointFeature { - branch.Use(async (context, next) => - { - var fileInfo = files.GetFileInfo(context.Request.Path); - AuthorizationResult result = null; - if (fileInfo.Exists) - { - result = await authorizationService.AuthorizeAsync(context.User, fileInfo, "files"); - } - else - { - // https://github.com/aspnet/Home/issues/2537 - var dir = files.GetDirectoryContents(context.Request.Path); - if (dir.Exists) - { - result = await authorizationService.AuthorizeAsync(context.User, dir, "files"); - } - else - { - context.Response.StatusCode = StatusCodes.Status404NotFound; - return; - } - } + public Endpoint Endpoint { get; set; } - if (!result.Succeeded) - { - if (!context.User.Identity.IsAuthenticated) - { - await context.ChallengeAsync(new AuthenticationProperties() - { - // https://github.com/aspnet/Security/issues/1730 - // Return here after authenticating - RedirectUri = context.Request.PathBase + context.Request.Path + context.Request.QueryString - }); - return; - } - // Authenticated but not authorized - await context.ForbidAsync(); - return; - } - - await next(); - }); - branch.UseFileServer(new FileServerOptions() + public EndpointFeature(Endpoint endpoint) { - EnableDirectoryBrowsing = true, - FileProvider = files - }); + Endpoint = endpoint; + } } } } diff --git a/samples/StaticFilesAuth/StaticFilesAuth.csproj b/samples/StaticFilesAuth/StaticFilesAuth.csproj index 0b50e27..e904d7e 100644 --- a/samples/StaticFilesAuth/StaticFilesAuth.csproj +++ b/samples/StaticFilesAuth/StaticFilesAuth.csproj @@ -36,6 +36,7 @@ + From 220a2933a408038a2b4c759e40f677a4474a9f79 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 5 Nov 2018 11:52:46 +1300 Subject: [PATCH 2/3] Improve sample --- samples/StaticFilesAuth/Startup.cs | 78 ++++++++++++++++-------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/samples/StaticFilesAuth/Startup.cs b/samples/StaticFilesAuth/Startup.cs index 4881e81..9af1a80 100644 --- a/samples/StaticFilesAuth/Startup.cs +++ b/samples/StaticFilesAuth/Startup.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -108,48 +109,26 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthori var files = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "PrivateFiles")); - app.Map("/MapAuthenticatedFiles", branch => + app.Use(async (context, next) => { - // Blanket authorization, any authenticated user is allowed access to these resources. - branch.UseAuthorization(new AuthorizationOptions + // Set an endpoint for files inside PrivateFiles to run authorization + PathString remaining; + if (context.Request.Path.StartsWithSegments("/MapImperativeFiles", out remaining)) { - RequiredPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build() - }); - - branch.UseFileServer(new FileServerOptions() - { - EnableDirectoryBrowsing = true, - FileProvider = files - }); - }); - - app.Map("/MapImperativeFiles", branch => - { - branch.Use(async (context, next) => + SetFileEndpoint(context, files, remaining, "files"); + } + else if (context.Request.Path.StartsWithSegments("/MapAuthenticatedFiles", out remaining)) { - var fileSystemInfo = GetFileSystemInfo(files, context.Request.Path); - if (fileSystemInfo != null) - { - var endpoint = new Endpoint( - c => Task.CompletedTask, - new EndpointMetadataCollection(fileSystemInfo, new AuthorizeAttribute("files")), - context.Request.Path); - - context.Features.Set(new EndpointFeature(endpoint)); - } + SetFileEndpoint(context, files, remaining, null); + } - await next(); - }); + await next(); + }); - // Policy based authorization, requests must meet the policy criteria to be get access to the resources. - branch.UseAuthorization(); + app.UseAuthorization(); - branch.UseFileServer(new FileServerOptions() - { - EnableDirectoryBrowsing = true, - FileProvider = files - }); - }); + app.Map("/MapAuthenticatedFiles", branch => SetupFileServer(branch, files)); + app.Map("/MapImperativeFiles", branch => SetupFileServer(branch, files)); app.UseMvc(routes => { @@ -159,6 +138,33 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthori }); } + private void SetupFileServer(IApplicationBuilder builder, IFileProvider files) + { + builder.UseFileServer(new FileServerOptions() + { + EnableDirectoryBrowsing = true, + FileProvider = files + }); + } + + private static void SetFileEndpoint(HttpContext context, PhysicalFileProvider files, PathString filePath, string policy) + { + var fileSystemInfo = GetFileSystemInfo(files, filePath); + if (fileSystemInfo != null) + { + var metadata = new List(); + metadata.Add(fileSystemInfo); + metadata.Add(new AuthorizeAttribute(policy)); + + var endpoint = new Endpoint( + c => Task.CompletedTask, + new EndpointMetadataCollection(metadata), + context.Request.Path); + + context.Features.Set(new EndpointFeature(endpoint)); + } + } + private static object GetFileSystemInfo(PhysicalFileProvider files, string path) { var fileInfo = files.GetFileInfo(path); From 230cce0ec342bf9ff80da807a3e86bf564cefe60 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 5 Nov 2018 12:18:55 +1300 Subject: [PATCH 3/3] Fix build --- build/dependencies.props | 4 ++-- build/sources.props | 1 - samples/CustomPolicyProvider/CustomPolicyProvider.csproj | 1 + samples/DynamicSchemes/DynamicSchemes.csproj | 1 + .../Identity.ExternalClaims/Identity.ExternalClaims.csproj | 1 + samples/StaticFilesAuth/StaticFilesAuth.csproj | 2 +- .../AuthSamples.FunctionalTests.csproj | 2 ++ 7 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 5042353..e4eda0a 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -10,8 +10,8 @@ 3.0.0-alpha1-10670 3.0.0-alpha1-10670 3.0.0-alpha1-10670 - 3.0.0-alpha1-t000 - 3.0.0-alpha1-t000 + 3.0.0-a-alpha1-authz-middleware-16948 + 3.0.0-a-alpha1-authz-middleware-16948 3.0.0-alpha1-10670 3.0.0-alpha1-10670 3.0.0-alpha1-10670 diff --git a/build/sources.props b/build/sources.props index 3ea4fa9..9215df9 100644 --- a/build/sources.props +++ b/build/sources.props @@ -5,7 +5,6 @@ $(DotNetRestoreSources) $(RestoreSources); - C:\Development\Source\Security\artifacts\build; https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; diff --git a/samples/CustomPolicyProvider/CustomPolicyProvider.csproj b/samples/CustomPolicyProvider/CustomPolicyProvider.csproj index f6b155c..9d1f16b 100644 --- a/samples/CustomPolicyProvider/CustomPolicyProvider.csproj +++ b/samples/CustomPolicyProvider/CustomPolicyProvider.csproj @@ -2,6 +2,7 @@ netcoreapp3.0 + NU1605 diff --git a/samples/DynamicSchemes/DynamicSchemes.csproj b/samples/DynamicSchemes/DynamicSchemes.csproj index e34c19c..7a782ac 100644 --- a/samples/DynamicSchemes/DynamicSchemes.csproj +++ b/samples/DynamicSchemes/DynamicSchemes.csproj @@ -1,6 +1,7 @@  netcoreapp3.0;net461 + NU1605 diff --git a/samples/Identity.ExternalClaims/Identity.ExternalClaims.csproj b/samples/Identity.ExternalClaims/Identity.ExternalClaims.csproj index 41d617a..62f0de3 100644 --- a/samples/Identity.ExternalClaims/Identity.ExternalClaims.csproj +++ b/samples/Identity.ExternalClaims/Identity.ExternalClaims.csproj @@ -2,6 +2,7 @@ netcoreapp3.0;net461 aspnet-Identity.ExternalClaims-E95BE154-CB1B-4633-A2E0-B2DF12FE8BD3 + NU1605 diff --git a/samples/StaticFilesAuth/StaticFilesAuth.csproj b/samples/StaticFilesAuth/StaticFilesAuth.csproj index e904d7e..c6507ca 100644 --- a/samples/StaticFilesAuth/StaticFilesAuth.csproj +++ b/samples/StaticFilesAuth/StaticFilesAuth.csproj @@ -3,6 +3,7 @@ netcoreapp3.0;net461 aspnet-StaticFilesAuth-AFE2BD9D-1575-4C3E-BE32-3F15C5BC9947 + NU1605 @@ -35,7 +36,6 @@ - diff --git a/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj b/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj index 7fe45b5..a480622 100644 --- a/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj +++ b/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj @@ -2,6 +2,7 @@ netcoreapp3.0 + NU1605 @@ -21,6 +22,7 @@ +