mirror of https://github.com/abpframework/abp.git
23 changed files with 461 additions and 8 deletions
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
[AttributeUsage(AttributeTargets.Method)] |
||||
|
public class DisableFeatureCheckAttribute : Attribute |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Aspects; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DynamicProxy; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
public class FeatureInterceptor : AbpInterceptor, ITransientDependency |
||||
|
{ |
||||
|
private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService; |
||||
|
|
||||
|
public FeatureInterceptor( |
||||
|
IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService) |
||||
|
{ |
||||
|
_methodInvocationAuthorizationService = methodInvocationAuthorizationService; |
||||
|
} |
||||
|
|
||||
|
public override void Intercept(IAbpMethodInvocation invocation) |
||||
|
{ |
||||
|
if (AbpCrossCuttingConcerns.IsApplied( |
||||
|
invocation.TargetObject, |
||||
|
AbpCrossCuttingConcerns.FeatureChecking)) |
||||
|
{ |
||||
|
invocation.Proceed(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
AsyncHelper.RunSync(() => CheckFeaturesAsync(invocation)); |
||||
|
invocation.Proceed(); |
||||
|
} |
||||
|
|
||||
|
public override async Task InterceptAsync(IAbpMethodInvocation invocation) |
||||
|
{ |
||||
|
if (AbpCrossCuttingConcerns.IsApplied( |
||||
|
invocation.TargetObject, |
||||
|
AbpCrossCuttingConcerns.FeatureChecking)) |
||||
|
{ |
||||
|
await invocation.ProceedAsync(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
AsyncHelper.RunSync(() => CheckFeaturesAsync(invocation)); |
||||
|
await invocation.ProceedAsync(); |
||||
|
} |
||||
|
|
||||
|
protected virtual Task CheckFeaturesAsync(IAbpMethodInvocation invocation) |
||||
|
{ |
||||
|
return _methodInvocationAuthorizationService.CheckAsync( |
||||
|
new MethodInvocationFeatureCheckerContext( |
||||
|
invocation.Method |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
public static class FeatureInterceptorRegistrar |
||||
|
{ |
||||
|
public static void RegisterIfNeeded(IOnServiceRegistredContext context) |
||||
|
{ |
||||
|
if (ShouldIntercept(context.ImplementationType)) |
||||
|
{ |
||||
|
context.Interceptors.TryAdd<FeatureInterceptor>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool ShouldIntercept(Type type) |
||||
|
{ |
||||
|
return type.IsDefined(typeof(RequiresFeatureAttribute), true) || |
||||
|
AnyMethodRequiresFeatureAttribute(type); |
||||
|
} |
||||
|
|
||||
|
private static bool AnyMethodRequiresFeatureAttribute(Type implementationType) |
||||
|
{ |
||||
|
return implementationType |
||||
|
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) |
||||
|
.Any(HasRequiresFeatureAttribute); |
||||
|
} |
||||
|
|
||||
|
private static bool HasRequiresFeatureAttribute(MemberInfo methodInfo) |
||||
|
{ |
||||
|
return methodInfo.IsDefined(typeof(RequiresFeatureAttribute), true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
public interface IMethodInvocationFeatureCheckerService |
||||
|
{ |
||||
|
Task CheckAsync( |
||||
|
MethodInvocationFeatureCheckerContext context |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
public class MethodInvocationFeatureCheckerContext |
||||
|
{ |
||||
|
public MethodInfo Method { get; } |
||||
|
|
||||
|
public MethodInvocationFeatureCheckerContext(MethodInfo method) |
||||
|
{ |
||||
|
Method = method; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
public class MethodInvocationFeatureCheckerService : IMethodInvocationFeatureCheckerService, ITransientDependency |
||||
|
{ |
||||
|
private readonly IFeatureChecker _featureChecker; |
||||
|
|
||||
|
public MethodInvocationFeatureCheckerService( |
||||
|
IFeatureChecker featureChecker) |
||||
|
{ |
||||
|
_featureChecker = featureChecker; |
||||
|
} |
||||
|
|
||||
|
public async Task CheckAsync(MethodInvocationFeatureCheckerContext context) |
||||
|
{ |
||||
|
if (IsFeatureCheckDisabled(context)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var requiresFeatureAttribute in GetRequiredFeatureAttributes(context)) |
||||
|
{ |
||||
|
await _featureChecker.CheckEnabledAsync(requiresFeatureAttribute.RequiresAll, requiresFeatureAttribute.Features); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual bool IsFeatureCheckDisabled(MethodInvocationFeatureCheckerContext context) |
||||
|
{ |
||||
|
return context.Method |
||||
|
.GetCustomAttributes(true) |
||||
|
.OfType<DisableFeatureCheckAttribute>() |
||||
|
.Any(); |
||||
|
} |
||||
|
|
||||
|
protected virtual RequiresFeatureAttribute[] GetRequiredFeatureAttributes(MethodInvocationFeatureCheckerContext context) |
||||
|
{ |
||||
|
var classAttributes = context.Method.DeclaringType |
||||
|
.GetCustomAttributes(true) |
||||
|
.OfType<RequiresFeatureAttribute>(); |
||||
|
|
||||
|
var methodAttributes = context.Method |
||||
|
.GetCustomAttributes(true) |
||||
|
.OfType<RequiresFeatureAttribute>(); |
||||
|
|
||||
|
return classAttributes.Union(methodAttributes).ToArray(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This attribute can be used on a class/method to declare that given class/method is available
|
||||
|
/// only if required feature(s) are enabled.
|
||||
|
/// </summary>
|
||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] |
||||
|
public class RequiresFeatureAttribute : Attribute |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A list of features to be checked if they are enabled.
|
||||
|
/// </summary>
|
||||
|
public string[] Features { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// If this property is set to true, all of the <see cref="Features"/> must be enabled.
|
||||
|
/// If it's false, at least one of the <see cref="Features"/> must be enabled.
|
||||
|
/// Default: false.
|
||||
|
/// </summary>
|
||||
|
public bool RequiresAll { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of <see cref="RequiresFeatureAttribute"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="features">A list of features to be checked if they are enabled</param>
|
||||
|
public RequiresFeatureAttribute(params string[] features) |
||||
|
{ |
||||
|
Features = features ?? Array.Empty<string>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
[RequiresFeature("BooleanTestFeature1")] |
||||
|
public class ClassFeatureTestService : ITransientDependency |
||||
|
{ |
||||
|
/* Since this class is used with the class reference, |
||||
|
* need to virtual keywords, otherwise dynamic proxy can not work. |
||||
|
*/ |
||||
|
|
||||
|
[RequiresFeature("BooleanTestFeature2")] |
||||
|
public virtual int Feature2() |
||||
|
{ |
||||
|
return 42; |
||||
|
} |
||||
|
|
||||
|
public virtual void NoAdditionalFeature() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
using Shouldly; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Authorization; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
public class FeatureInterceptor_Tests : FeatureTestBase |
||||
|
{ |
||||
|
private readonly ClassFeatureTestService _classFeatureTestService; |
||||
|
private readonly IMethodFeatureTestService _methodFeatureTestService; |
||||
|
private readonly ICurrentTenant _currentTenant; |
||||
|
|
||||
|
public FeatureInterceptor_Tests() |
||||
|
{ |
||||
|
_classFeatureTestService = GetRequiredService<ClassFeatureTestService>(); |
||||
|
_methodFeatureTestService = GetRequiredService<IMethodFeatureTestService>(); |
||||
|
_currentTenant = GetRequiredService<ICurrentTenant>(); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(null)] //Features were not enabled for null tenantid
|
||||
|
[InlineData(TestFeatureStore.Tenant2IdValue)] //Features were not enabled for Tenant 2
|
||||
|
public async Task Should_Not_Allow_To_Method_Calls_If_Related_Features_Were_Not_Enabled(string tenantIdValue) |
||||
|
{ |
||||
|
using (_currentTenant.Change(ParseNullableGuid(tenantIdValue))) |
||||
|
{ |
||||
|
Assert.Throws<AbpAuthorizationException>(() => |
||||
|
{ |
||||
|
_classFeatureTestService.NoAdditionalFeature(); |
||||
|
}); |
||||
|
|
||||
|
Assert.Throws<AbpAuthorizationException>(() => |
||||
|
{ |
||||
|
_classFeatureTestService.Feature2(); |
||||
|
}); |
||||
|
|
||||
|
await Assert.ThrowsAsync<AbpAuthorizationException>(async () => |
||||
|
{ |
||||
|
await _methodFeatureTestService.Feature1Async(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Allow_To_Method_Calls_If_Related_Features_Were_Enabled() |
||||
|
{ |
||||
|
//Features were enabled for Tenant 1
|
||||
|
using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) |
||||
|
{ |
||||
|
_classFeatureTestService.NoAdditionalFeature(); |
||||
|
_classFeatureTestService.Feature2().ShouldBe(42); |
||||
|
(await _methodFeatureTestService.Feature1Async()).ShouldBe(42); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(null)] |
||||
|
[InlineData(TestFeatureStore.Tenant1IdValue)] |
||||
|
[InlineData(TestFeatureStore.Tenant2IdValue)] |
||||
|
public async Task Should_Allow_To_Method_Calls_For_Those_Have_No_RequiresFeature_Attributes(string tenantIdValue) |
||||
|
{ |
||||
|
using (_currentTenant.Change(ParseNullableGuid(tenantIdValue))) |
||||
|
{ |
||||
|
await _methodFeatureTestService.NonFeatureAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Guid? ParseNullableGuid(string tenantIdValue) |
||||
|
{ |
||||
|
return tenantIdValue == null ? (Guid?)null : new Guid(tenantIdValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,6 @@ |
|||||
namespace Volo.Abp.Features |
namespace Volo.Abp.Features |
||||
{ |
{ |
||||
public class AbpFeaturesTestBase : AbpIntegratedTest<AbpFeaturesTestModule> |
public class FeatureTestBase : AbpIntegratedTest<AbpFeaturesTestModule> |
||||
{ |
{ |
||||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
{ |
{ |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
public interface IMethodFeatureTestService |
||||
|
{ |
||||
|
Task<int> Feature1Async(); |
||||
|
|
||||
|
Task NonFeatureAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Features |
||||
|
{ |
||||
|
public class MethodFeatureTestService : ITransientDependency, IMethodFeatureTestService |
||||
|
{ |
||||
|
/* Since this class is used over an interface (IMethodFeatureTestService), |
||||
|
* no need to virtual keywords, dynamic proxy can work. |
||||
|
*/ |
||||
|
|
||||
|
[RequiresFeature("BooleanTestFeature1")] |
||||
|
public Task<int> Feature1Async() |
||||
|
{ |
||||
|
return Task.FromResult(42); |
||||
|
} |
||||
|
|
||||
|
public Task NonFeatureAsync() |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue