Create Global Feature Action & Page filters to prevent usage of a controller/page if the feature was disabled

Resolve #5074
pull/5082/head
maliming 5 years ago
parent 92bedb38b4
commit 6e01e79001

@ -4,6 +4,7 @@ using Volo.Abp.AspNetCore.Mvc.Auditing;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Features;
using Volo.Abp.AspNetCore.Mvc.GlobalFeatures;
using Volo.Abp.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.AspNetCore.Mvc.Response;
using Volo.Abp.AspNetCore.Mvc.Uow;
@ -29,6 +30,7 @@ namespace Volo.Abp.AspNetCore.Mvc
private static void AddActionFilters(MvcOptions options)
{
options.Filters.AddService(typeof(GlobalFeatureActionFilter));
options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpNoContentActionFilter));
options.Filters.AddService(typeof(AbpFeatureActionFilter));
@ -39,6 +41,7 @@ namespace Volo.Abp.AspNetCore.Mvc
private static void AddPageFilters(MvcOptions options)
{
options.Filters.AddService(typeof(GlobalFeaturePageFilter));
options.Filters.AddService(typeof(AbpExceptionPageFilter));
options.Filters.AddService(typeof(AbpAuditPageFilter));
options.Filters.AddService(typeof(AbpFeaturePageFilter));
@ -58,4 +61,4 @@ namespace Volo.Abp.AspNetCore.Mvc
);
}
}
}
}

@ -0,0 +1,47 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
public class GlobalFeatureActionFilter : IAsyncActionFilter, ITransientDependency
{
public ILogger<GlobalFeatureActionFilter> Logger { get; set; }
public GlobalFeatureActionFilter()
{
Logger = NullLogger<GlobalFeatureActionFilter>.Instance;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ActionDescriptor.IsControllerAction())
{
await next();
return;
}
if (!IsGlobalFeatureEnabled(context.Controller.GetType(), out var attribute))
{
Logger.LogWarning($"The '{context.Controller.GetType().FullName}' controller needs to enable '{attribute.Name}' feature.");
context.Result = new NotFoundResult();
return;
}
await next();
}
protected virtual bool IsGlobalFeatureEnabled(Type controllerType, out RequiresGlobalFeatureAttribute attribute)
{
attribute = ReflectionHelper.GetSingleAttributeOrDefault<RequiresGlobalFeatureAttribute>(controllerType);
return attribute == null || GlobalFeatureManager.Instance.IsEnabled(attribute.GetFeatureName());
}
}
}

@ -0,0 +1,52 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
public class GlobalFeaturePageFilter: IAsyncPageFilter, ITransientDependency
{
public ILogger<GlobalFeaturePageFilter> Logger { get; set; }
public GlobalFeaturePageFilter()
{
Logger = NullLogger<GlobalFeaturePageFilter>.Instance;
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
return Task.CompletedTask;
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context.HandlerInstance == null || !context.ActionDescriptor.IsPageAction())
{
await next();
return;
}
if (!IsGlobalFeatureEnabled(context.HandlerInstance.GetType(), out var attribute))
{
Logger.LogWarning($"The '{context.HandlerInstance.GetType().FullName}' page needs to enable '{attribute.Name}' feature.");
context.Result = new NotFoundResult();
return;
}
await next();
}
protected virtual bool IsGlobalFeatureEnabled(Type controllerType, out RequiresGlobalFeatureAttribute attribute)
{
attribute = ReflectionHelper.GetSingleAttributeOrDefault<RequiresGlobalFeatureAttribute>(controllerType);
return attribute == null || GlobalFeatureManager.Instance.IsEnabled(attribute.GetFeatureName());
}
}
}

@ -61,6 +61,14 @@
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\GlobalFeature\DisabledGlobalFeatureTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\GlobalFeature\EnabledGlobalFeatureTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<!-- https://github.com/NuGet/Home/issues/4412. -->

@ -1,19 +1,23 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Authorization;
using Volo.Abp.AspNetCore.Mvc.GlobalFeatures;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.Localization.Resource;
using Volo.Abp.AspNetCore.Security.Claims;
using Volo.Abp.AspNetCore.TestBase;
using Volo.Abp.Autofac;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Localization;
using Volo.Abp.MemoryDb;
using Volo.Abp.Modularity;
using Volo.Abp.TestApp;
using Volo.Abp.Threading;
using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem;
@ -27,6 +31,8 @@ namespace Volo.Abp.AspNetCore.Mvc
)]
public class AbpAspNetCoreMvcTestModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
@ -40,6 +46,13 @@ namespace Volo.Abp.AspNetCore.Mvc
public override void ConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
GlobalFeatureManager.Instance.Modules.GetOrAdd(AbpAspNetCoreMvcTestFeatures.ModuleName,
() => new AbpAspNetCoreMvcTestFeatures(GlobalFeatureManager.Instance))
.EnableAll();
});
context.Services.AddAuthorization(options =>
{
options.AddPolicy("MyClaimTestPolicy", policy =>

@ -0,0 +1,30 @@
using JetBrains.Annotations;
using Volo.Abp.GlobalFeatures;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
[GlobalFeatureName(Name)]
public class AbpAspNetCoreMvcTestFeature1 : Abp.GlobalFeatures.GlobalFeature
{
public const string Name = "AbpAspNetCoreMvcTest.Feature1";
internal AbpAspNetCoreMvcTestFeature1([NotNull] AbpAspNetCoreMvcTestFeatures abpAspNetCoreMvcTestFeatures)
: base(abpAspNetCoreMvcTestFeatures)
{
}
}
public class AbpAspNetCoreMvcTestFeatures : GlobalModuleFeatures
{
public const string ModuleName = "AbpAspNetCoreMvcTest";
public AbpAspNetCoreMvcTestFeature1 Reactions => GetFeature<AbpAspNetCoreMvcTestFeature1>();
public AbpAspNetCoreMvcTestFeatures([NotNull] GlobalFeatureManager featureManager)
: base(featureManager)
{
AddFeature(new AbpAspNetCoreMvcTestFeature1(this));
}
}
}

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.GlobalFeatures;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
[RequiresGlobalFeature("Not-Enabled-Feature")]
[Route("api/DisabledGlobalFeatureTestController-Test")]
public class DisabledGlobalFeatureTestController : AbpController
{
[HttpGet]
[Route("TestMethod")]
public string TestMethod()
{
return "TestMethod";
}
}
}

@ -0,0 +1,19 @@
@page
@model Volo.Abp.AspNetCore.Mvc.GlobalFeatures.DisabledGlobalFeatureTestPage
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
</div>
</body>
</html>

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.GlobalFeatures;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
[RequiresGlobalFeature("Not-Enabled-Feature")]
public class DisabledGlobalFeatureTestPage : PageModel
{
public void OnGet()
{
}
}
}

@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.GlobalFeatures;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
[RequiresGlobalFeature(AbpAspNetCoreMvcTestFeature1.Name)]
[Route("api/EnabledGlobalFeatureTestController-Test")]
public class EnabledGlobalFeatureTestController : AbpController
{
[HttpGet]
[Route("TestMethod")]
public string TestMethod()
{
return "TestMethod";
}
}
}

@ -0,0 +1,19 @@
@page
@model Volo.Abp.AspNetCore.Mvc.GlobalFeatures.EnabledGlobalFeatureTestPage
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
</div>
</body>
</html>

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.GlobalFeatures;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
[RequiresGlobalFeature(AbpAspNetCoreMvcTestFeature1.Name)]
public class EnabledGlobalFeatureTestPage : PageModel
{
public void OnGet()
{
}
}
}

@ -0,0 +1,24 @@
using System.Net;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
public class RequiresGlobalFeatureTestController_Tests: AspNetCoreMvcTestBase
{
[Fact]
public async Task Should_404_If_Feature_Disabled()
{
var result = await GetResponseAsync("/api/DisabledGlobalFeatureTestController-Test/TestMethod", HttpStatusCode.NotFound);
result.StatusCode.ShouldBe(HttpStatusCode.NotFound);
}
[Fact]
public async Task Should_Pass_Check_If_Feature_Enabled()
{
var result = await GetResponseAsync("/api/EnabledGlobalFeatureTestController-Test/TestMethod", HttpStatusCode.OK);
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
}
}

@ -0,0 +1,24 @@
using System.Net;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
public class RequiresGlobalFeatureTestPage_Tests : AspNetCoreMvcTestBase
{
[Fact]
public async Task Should_404_If_Feature_Disabled()
{
var result = await GetResponseAsync("/GlobalFeatures/DisabledGlobalFeatureTestPage", HttpStatusCode.NotFound);
result.StatusCode.ShouldBe(HttpStatusCode.NotFound);
}
[Fact]
public async Task Should_Pass_Check_If_Feature_Enabled()
{
var result = await GetResponseAsync("/GlobalFeatures/EnabledGlobalFeatureTestPage", HttpStatusCode.OK);
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
}
}
Loading…
Cancel
Save