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 |
|||
{ |
|||
public class AbpFeaturesTestBase : AbpIntegratedTest<AbpFeaturesTestModule> |
|||
public class FeatureTestBase : AbpIntegratedTest<AbpFeaturesTestModule> |
|||
{ |
|||
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